diff --git a/system/autoload/Admin.php b/system/autoload/Admin.php
new file mode 100644
index 0000000..c39ee81
--- /dev/null
+++ b/system/autoload/Admin.php
@@ -0,0 +1,61 @@
+find_one($id);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/system/autoload/App.php b/system/autoload/App.php
new file mode 100644
index 0000000..71f1614
--- /dev/null
+++ b/system/autoload/App.php
@@ -0,0 +1,29 @@
+where('id', $id_customer)->find_one();
+ $c->balance = $amount + $c['balance'];
+ $c->save();
+ }
+
+ public static function transfer($id_customer, $phoneTarget, $amount)
+ {
+ global $config;
+ if (Balance::min($id_customer, $amount)) {
+ return Balance::plusByPhone($phoneTarget, $amount);
+ } else {
+ return false;
+ }
+ }
+
+ public static function min($id_customer, $amount)
+ {
+ $c = ORM::for_table('tbl_customers')->where('id', $id_customer)->find_one();
+ if ($c && $c['balance'] >= $amount) {
+ $c->balance = $c['balance'] - $amount;
+ $c->save();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public static function plusByPhone($phone_customer, $amount)
+ {
+ $c = ORM::for_table('tbl_customers')->where('username', $phone_customer)->find_one();
+ if ($c) {
+ $c->balance = $amount + $c['balance'];
+ $c->save();
+ return true;
+ }
+ return false;
+ }
+
+ public static function minByPhone($phone_customer, $amount)
+ {
+ $c = ORM::for_table('tbl_customers')->where('username', $phone_customer)->find_one();
+ if ($c && $c['balance'] >= $amount) {
+ $c->balance = $c['balance'] - $amount;
+ $c->save();
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/system/autoload/CronLog.php b/system/autoload/CronLog.php
new file mode 100644
index 0000000..2b5e414
--- /dev/null
+++ b/system/autoload/CronLog.php
@@ -0,0 +1,283 @@
+create();
+ $log->cron_type = $cronType;
+ $log->started_at = date('Y-m-d H:i:s');
+ $log->status = 'running';
+ $log->save();
+
+ self::$logId = $log->id();
+ return self::$logId;
+ }
+
+ /**
+ * Update log with current statistics
+ * @param array $stats Statistics array
+ */
+ public static function updateStats($stats = [])
+ {
+ if (!self::$logId) return;
+
+ $log = ORM::for_table('tbl_cron_logs')->find_one(self::$logId);
+ if (!$log) return;
+
+ foreach ($stats as $key => $value) {
+ if (property_exists($log, $key)) {
+ $log->$key = $value;
+ }
+ }
+
+ $log->save();
+ }
+
+ /**
+ * Increment a counter
+ * @param string $field Field to increment
+ * @param int $amount Amount to increment by
+ */
+ public static function increment($field, $amount = 1)
+ {
+ if (!self::$logId) return;
+
+ $log = ORM::for_table('tbl_cron_logs')->find_one(self::$logId);
+ if (!$log) return;
+
+ if (property_exists($log, $field)) {
+ $log->$field = $log->$field + $amount;
+ $log->save();
+ }
+ }
+
+ /**
+ * Complete the cron job successfully
+ * @param array $finalStats Final statistics
+ */
+ public static function complete($finalStats = [])
+ {
+ if (!self::$logId) return;
+
+ $log = ORM::for_table('tbl_cron_logs')->find_one(self::$logId);
+ if (!$log) return;
+
+ $log->finished_at = date('Y-m-d H:i:s');
+ $log->status = 'completed';
+
+ // Calculate execution time
+ if (self::$startTime) {
+ $log->execution_time = round(microtime(true) - self::$startTime, 3);
+ }
+
+ // Calculate memory usage
+ if (self::$startMemory) {
+ $currentMemory = memory_get_usage();
+ $peakMemory = memory_get_peak_usage();
+ $log->memory_usage = self::formatBytes($peakMemory - self::$startMemory);
+ }
+
+ // Update final stats
+ foreach ($finalStats as $key => $value) {
+ if (property_exists($log, $key)) {
+ $log->$key = $value;
+ }
+ }
+
+ $log->save();
+
+ // Clean up old logs (keep last 1000 records)
+ self::cleanup();
+ }
+
+ /**
+ * Mark cron job as failed
+ * @param string $errorMessage Error message
+ * @param array $finalStats Final statistics
+ */
+ public static function fail($errorMessage, $finalStats = [])
+ {
+ if (!self::$logId) return;
+
+ $log = ORM::for_table('tbl_cron_logs')->find_one(self::$logId);
+ if (!$log) return;
+
+ $log->finished_at = date('Y-m-d H:i:s');
+ $log->status = 'failed';
+ $log->error_message = $errorMessage;
+
+ // Calculate execution time
+ if (self::$startTime) {
+ $log->execution_time = round(microtime(true) - self::$startTime, 3);
+ }
+
+ // Update final stats
+ foreach ($finalStats as $key => $value) {
+ if (property_exists($log, $key)) {
+ $log->$key = $value;
+ }
+ }
+
+ $log->save();
+ }
+
+ /**
+ * Get recent cron logs
+ * @param int $limit Number of records to return
+ * @param string $cronType Filter by cron type
+ * @return array
+ */
+ public static function getRecent($limit = 50, $cronType = null)
+ {
+ $query = ORM::for_table('tbl_cron_logs');
+
+ if ($cronType) {
+ $query->where('cron_type', $cronType);
+ }
+
+ return $query->order_by_desc('started_at')
+ ->limit($limit)
+ ->find_many();
+ }
+
+ /**
+ * Get cron job statistics
+ * @param int $days Number of days to look back
+ * @return array
+ */
+ public static function getStats($days = 7)
+ {
+ $since = date('Y-m-d H:i:s', strtotime("-$days days"));
+
+ $stats = ORM::for_table('tbl_cron_logs')
+ ->where_gte('started_at', $since)
+ ->find_many();
+
+ $result = [
+ 'total_runs' => count($stats),
+ 'successful_runs' => 0,
+ 'failed_runs' => 0,
+ 'total_expired_users' => 0,
+ 'total_notifications_sent' => 0,
+ 'total_auto_renewals' => 0,
+ 'avg_execution_time' => 0,
+ 'last_run' => null,
+ 'last_success' => null,
+ 'last_failure' => null
+ ];
+
+ $totalExecutionTime = 0;
+ $executionCount = 0;
+
+ foreach ($stats as $log) {
+ if ($log->status == 'completed') {
+ $result['successful_runs']++;
+ $result['last_success'] = $log->finished_at;
+ } elseif ($log->status == 'failed') {
+ $result['failed_runs']++;
+ $result['last_failure'] = $log->finished_at;
+ }
+
+ $result['total_expired_users'] += $log->expired_users_processed;
+ $result['total_notifications_sent'] += $log->notifications_sent;
+ $result['total_auto_renewals'] += $log->auto_renewals_successful;
+
+ if ($log->execution_time) {
+ $totalExecutionTime += $log->execution_time;
+ $executionCount++;
+ }
+
+ if (!$result['last_run'] || $log->started_at > $result['last_run']) {
+ $result['last_run'] = $log->started_at;
+ }
+ }
+
+ if ($executionCount > 0) {
+ $result['avg_execution_time'] = round($totalExecutionTime / $executionCount, 3);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Clean up old logs (keep last 1000 records)
+ */
+ private static function cleanup()
+ {
+ $count = ORM::for_table('tbl_cron_logs')->count();
+
+ if ($count > 1000) {
+ $logsToDelete = ORM::for_table('tbl_cron_logs')
+ ->order_by_asc('started_at')
+ ->limit($count - 1000)
+ ->find_many();
+
+ foreach ($logsToDelete as $log) {
+ $log->delete();
+ }
+ }
+ }
+
+ /**
+ * Format bytes to human readable format
+ * @param int $bytes
+ * @return string
+ */
+ private static function formatBytes($bytes)
+ {
+ $units = ['B', 'KB', 'MB', 'GB'];
+ $bytes = max($bytes, 0);
+ $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
+ $pow = min($pow, count($units) - 1);
+
+ $bytes /= pow(1024, $pow);
+
+ return round($bytes, 2) . ' ' . $units[$pow];
+ }
+
+ /**
+ * Check if cron is running (has a running log in last 5 minutes)
+ * @return bool
+ */
+ public static function isRunning()
+ {
+ $fiveMinutesAgo = date('Y-m-d H:i:s', strtotime('-5 minutes'));
+
+ $runningLog = ORM::for_table('tbl_cron_logs')
+ ->where('status', 'running')
+ ->where_gte('started_at', $fiveMinutesAgo)
+ ->find_one();
+
+ return $runningLog ? true : false;
+ }
+
+ /**
+ * Get the last successful cron run
+ * @return object|null
+ */
+ public static function getLastSuccess()
+ {
+ return ORM::for_table('tbl_cron_logs')
+ ->where('status', 'completed')
+ ->order_by_desc('finished_at')
+ ->find_one();
+ }
+}
diff --git a/system/autoload/Csrf.php b/system/autoload/Csrf.php
new file mode 100644
index 0000000..57752a0
--- /dev/null
+++ b/system/autoload/Csrf.php
@@ -0,0 +1,55 @@
+ self::$tokenExpiration) {
+ self::clearToken();
+ return false;
+ }
+
+ return self::validateToken($token, $storedToken);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ public static function generateAndStoreToken()
+ {
+ $token = self::generateToken();
+ $_SESSION['csrf_token'] = $token;
+ $_SESSION['csrf_token_time'] = time();
+ return $token;
+ }
+
+ public static function clearToken()
+ {
+ unset($_SESSION['csrf_token'], $_SESSION['csrf_token_time']);
+ }
+}
diff --git a/system/autoload/File.php b/system/autoload/File.php
new file mode 100644
index 0000000..156d7be
--- /dev/null
+++ b/system/autoload/File.php
@@ -0,0 +1,108 @@
+ $name,
+ "admin" => $admin,
+ "position" => $position,
+ "icon" => $icon,
+ "function" => $function,
+ "label" => $label,
+ "color" => $color,
+ "auth" => $auth
+ ];
+}
+
+$hook_registered = array();
+
+function register_hook($action, $function){
+ global $hook_registered;
+ $hook_registered[] = [
+ 'action' => $action,
+ 'function' => $function
+ ];
+}
+
+function run_hook($action){
+ global $hook_registered;
+ foreach($hook_registered as $hook){
+ if($hook['action'] == $action){
+ if(function_exists($hook['function'])){
+ call_user_func($hook['function']);
+ }
+ }
+ }
+}
diff --git a/system/autoload/Http.php b/system/autoload/Http.php
new file mode 100644
index 0000000..7728075
--- /dev/null
+++ b/system/autoload/Http.php
@@ -0,0 +1,117 @@
+0){
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+ }
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ if (!empty($http_proxy)) {
+ curl_setopt($ch, CURLOPT_PROXY, $http_proxy);
+ if (!empty($http_proxyauth)) {
+ curl_setopt($ch, CURLOPT_PROXYUSERPWD, $http_proxyauth);
+ }
+ }
+ $server_output = curl_exec($ch);
+ if (curl_errno($ch)) {
+ $error_msg = curl_error($ch);
+ }
+ curl_close($ch);
+ if($admin && $error_msg){
+ r2(U . 'dashboard', 'd', $error_msg);
+ }
+ return ($server_output) ? $server_output : $error_msg;
+ }
+
+ public static function postJsonData($url, $array_post, $headers = [], $basic = null)
+ {
+ global $http_proxy, $http_proxyauth, $admin;
+ $headers[] = 'Content-Type: application/json';
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_POST, 1);
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 15);
+ curl_setopt($ch, CURLOPT_VERBOSE, false);
+ curl_setopt($ch, CURLINFO_HEADER_OUT, false);
+ if (!empty($http_proxy)) {
+ curl_setopt($ch, CURLOPT_PROXY, $http_proxy);
+ if (!empty($http_proxyauth)) {
+ curl_setopt($ch, CURLOPT_PROXYUSERPWD, $http_proxyauth);
+ }
+ }
+ curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($array_post));
+ if(is_array($headers) && count($headers)>0){
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+ }
+ if (!empty($basic)) {
+ curl_setopt($ch, CURLOPT_USERPWD, $basic);
+ }
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ $server_output = curl_exec($ch);
+ if (curl_errno($ch)) {
+ $error_msg = curl_error($ch);
+ }
+ curl_close($ch);
+ if($admin && $error_msg){
+ r2(U . 'dashboard', 'd', $error_msg);
+ }
+ return ($server_output) ? $server_output : $error_msg;
+ }
+
+
+ public static function postData($url, $array_post, $headers = [], $basic = null)
+ {
+ global $http_proxy, $http_proxyauth, $admin;
+ $headers[] = 'Content-Type: application/x-www-form-urlencoded';
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_POST, 1);
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 15);
+ curl_setopt($ch, CURLOPT_VERBOSE, false);
+ curl_setopt($ch, CURLINFO_HEADER_OUT, false);
+ if (!empty($http_proxy)) {
+ curl_setopt($ch, CURLOPT_PROXY, $http_proxy);
+ if (!empty($http_proxyauth)) {
+ curl_setopt($ch, CURLOPT_PROXYUSERPWD, $http_proxyauth);
+ }
+ }
+ curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($array_post));
+ if(is_array($headers) && count($headers)>0){
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+ }
+ if (!empty($basic)) {
+ curl_setopt($ch, CURLOPT_USERPWD, $basic);
+ }
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ $server_output = curl_exec($ch);
+ if (curl_errno($ch)) {
+ $error_msg = curl_error($ch);
+ }
+ curl_close($ch);
+ if($admin && $error_msg){
+ r2(U . 'dashboard', 'd', $error_msg);
+ }
+ return ($server_output) ? $server_output : $error_msg;
+ }
+}
diff --git a/system/autoload/Lang.php b/system/autoload/Lang.php
new file mode 100644
index 0000000..9a5cc18
--- /dev/null
+++ b/system/autoload/Lang.php
@@ -0,0 +1,262 @@
+diff($ago);
+
+ $diff->w = floor($diff->d / 7);
+ $diff->d -= $diff->w * 7;
+
+ $string = array(
+ 'y' => Lang::T('year'),
+ 'm' => Lang::T('month'),
+ 'w' => Lang::T('week'),
+ 'd' => Lang::T('day'),
+ 'h' => Lang::T('hour'),
+ 'i' => Lang::T('minute'),
+ 's' => Lang::T('second'),
+ );
+ foreach ($string as $k => &$v) {
+ if ($diff->$k) {
+ $v = $diff->$k . ' ' . $v . ($diff->$k > 1 ? 's' : '');
+ } else {
+ unset($string[$k]);
+ }
+ }
+
+ if (!$full)
+ $string = array_slice($string, 0, 1);
+ return $string ? implode(', ', $string) .' '. Lang::T('ago') : Lang::T('just now');
+ }
+
+ public static function nl2br($text)
+ {
+ return nl2br($text);
+ }
+
+ public static function arrayCount($arr)
+ {
+ if (is_array($arr)) {
+ return count($arr);
+ } else if (is_object($arr)) {
+ // Handle IdiormResultSet and other countable objects
+ if (method_exists($arr, 'count')) {
+ return $arr->count();
+ } elseif (method_exists($arr, 'toArray')) {
+ return count($arr->toArray());
+ } elseif (is_countable($arr)) {
+ return count($arr);
+ } else {
+ // Try to iterate and count
+ $count = 0;
+ foreach ($arr as $item) {
+ $count++;
+ }
+ return $count;
+ }
+ } else {
+ return 0;
+ }
+ }
+
+ public static function getNotifText($key)
+ {
+ global $_notifmsg, $_notifmsg_default;
+ if (isset($_notifmsg[$key])) {
+ return $_notifmsg[$key];
+ } else {
+ return $_notifmsg_default[$key];
+ }
+ }
+
+ public static function ucWords($text)
+ {
+ return ucwords(str_replace('_', ' ', $text));
+ }
+
+ public static function randomUpLowCase($text)
+ {
+ $jml = strlen($text);
+ $result = '';
+ for ($i = 0; $i < $jml; $i++) {
+ if (rand(0, 99) % 2) {
+ $result .= strtolower(substr($text, $i, 1));
+ } else {
+ $result .= substr($text, $i, 1);
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * $pad_type
+ * 0 Left
+ * 1 right
+ * 2 center
+ * */
+ public static function pad($text, $pad_string = ' ', $pad_type = 0)
+ {
+ global $config;
+ $cols = 37;
+ if ($config['printer_cols']) {
+ $cols = $config['printer_cols'];
+ }
+ $text = trim($text);
+ $texts = explode("\n", $text);
+ if (count($texts) > 1) {
+ $text = '';
+ foreach ($texts as $t) {
+ $text .= self::pad(trim($t), $pad_string, $pad_type) . "\n";
+ }
+ return $text;
+ } else {
+ return str_pad(trim($text), $cols, $pad_string, $pad_type);
+ }
+ }
+
+ public static function pads($textLeft, $textRight, $pad_string = ' ')
+ {
+ global $config;
+ $cols = 37;
+ if ($config['printer_cols']) {
+ $cols = $config['printer_cols'];
+ }
+ return $textLeft . str_pad($textRight, $cols - strlen($textLeft), $pad_string, 0);
+ }
+
+ public static function translate($txt, $to = 'id')
+ {
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, "https://translate.google.com/m?hl=en&sl=en&tl=$to&ie=UTF-8&prev=_m&q=" . urlencode($txt));
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
+ curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (iPhone; CPU OS 13_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/28.1 Mobile/15E148 Safari/605.1.15");
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 5);
+ curl_setopt($ch, CURLOPT_HEADER, 0);
+ $hasil = curl_exec($ch);
+ curl_close($ch);
+ $temp = explode('
', $hasil);
+ if (count($temp) > 0) {
+ $temp = explode("
create();
+ $d->date = date('Y-m-d H:i:s');
+ $d->type = $type;
+ $d->description = $description;
+ $d->userid = $userid;
+ $d->ip = (empty($username)) ? $_SERVER["REMOTE_ADDR"] : $username;
+ $d->save();
+ }
+
+ public static function arrayToText($array, $start = '', $result = '')
+ {
+ foreach ($array as $k => $v) {
+ if (is_array($v)) {
+ $result = Log::arrayToText($v, "$start$k.", $result);
+ } else {
+ $result .= $start.$k ." : ". strval($v) ."\n";
+ }
+ }
+ return $result;
+ }
+}
\ No newline at end of file
diff --git a/system/autoload/Message.php b/system/autoload/Message.php
new file mode 100644
index 0000000..1252366
--- /dev/null
+++ b/system/autoload/Message.php
@@ -0,0 +1,333 @@
+ 4 && substr($config['sms_url'], 0, 4) != "http") {
+ if (strlen($txt) > 160) {
+ $txts = str_split($txt, 160);
+ try {
+ $mikrotik = Mikrotik::info($config['sms_url']);
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ foreach ($txts as $txt) {
+ Mikrotik::sendSMS($client, $phone, $txt);
+ }
+ } catch (Exception $e) {
+ // ignore, add to logs
+ _log("Failed to send SMS using Mikrotik.\n" . $e->getMessage(), 'SMS', 0);
+ }
+ } else {
+ try {
+ $mikrotik = Mikrotik::info($config['sms_url']);
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ Mikrotik::sendSMS($client, $phone, $txt);
+ } catch (Exception $e) {
+ // ignore, add to logs
+ _log("Failed to send SMS using Mikrotik.\n" . $e->getMessage(), 'SMS', 0);
+ }
+ }
+ } else {
+ $smsurl = str_replace('[number]', urlencode($phone), $config['sms_url']);
+ $smsurl = str_replace('[text]', urlencode($txt), $smsurl);
+ return Http::getData($smsurl);
+ }
+ }
+ }
+
+ public static function sendWhatsapp($phone, $txt)
+ {
+ global $config;
+ if(empty($txt)){
+ return "";
+ }
+ run_hook('send_whatsapp'); #HOOK
+ if (!empty($config['wa_url'])) {
+ $waurl = str_replace('[number]', urlencode(Lang::phoneFormat($phone)), $config['wa_url']);
+ $waurl = str_replace('[text]', urlencode($txt), $waurl);
+ return Http::getData($waurl);
+ }
+ }
+
+ public static function sendEmail($to, $subject, $body)
+ {
+ global $config;
+ if(empty($body)){
+ return "";
+ }
+ run_hook('send_email'); #HOOK
+ if (empty($config['smtp_host'])) {
+ $attr = "";
+ if (!empty($config['mail_from'])) {
+ $attr .= "From: " . $config['mail_from'] . "\r\n";
+ }
+ if (!empty($config['mail_reply_to'])) {
+ $attr .= "Reply-To: " . $config['mail_reply_to'] . "\r\n";
+ }
+ mail($to, $subject, $body, $attr);
+ } else {
+ $mail = new PHPMailer();
+ $mail->isSMTP();
+ $mail->SMTPDebug = SMTP::DEBUG_SERVER;
+ $mail->Host = $config['smtp_host'];
+ $mail->SMTPAuth = true;
+ $mail->Username = $config['smtp_user'];
+ $mail->Password = $config['smtp_pass'];
+ $mail->SMTPSecure = $config['smtp_ssltls'];
+ $mail->Port = $config['smtp_port'];
+ if (!empty($config['mail_from'])) {
+ $mail->setFrom($config['mail_from']);
+ }
+ if (!empty($config['mail_reply_to'])) {
+ $mail->addReplyTo($config['mail_reply_to']);
+ }
+ $mail->isHTML(false);
+ $mail->addAddress($to);
+ $mail->Subject = $subject;
+ $mail->Body = $body;
+ $mail->send();
+ }
+ }
+
+ public static function sendPackageNotification($customer, $package, $price, $message, $via, $plan_type = null)
+ {
+ global $ds;
+ if(empty($message)){
+ return "";
+ }
+
+ // Get plan-specific template if plan_type is provided
+ $template = self::getPlanSpecificTemplate($message, $plan_type);
+
+ $msg = str_replace('[[name]]', $customer['fullname'], $template);
+ $msg = str_replace('[[username]]', $customer['username'], $msg);
+ $msg = str_replace('[[plan]]', $package, $msg);
+ $msg = str_replace('[[package]]', $package, $msg);
+ $msg = str_replace('[[price]]', Lang::moneyFormat($price), $msg);
+ $msg = str_replace('[[plan_type]]', $plan_type ? ucfirst(strtolower($plan_type)) : 'Internet', $msg);
+
+ // Add service-specific variables
+ global $config;
+ $msg = str_replace('[[service_portal]]', !empty($config['hotspot_url']) ? $config['hotspot_url'] : APP_URL, $msg);
+ $msg = str_replace('[[support_contact]]', !empty($config['phone']) ? $config['phone'] : 'our support team', $msg);
+
+ list($bills, $add_cost) = User::getBills($customer['id']);
+ if($add_cost>0){
+ $note = "";
+ foreach ($bills as $k => $v) {
+ $note .= $k . " : " . Lang::moneyFormat($v) . "\n";
+ }
+ $note .= "Total : " . Lang::moneyFormat($add_cost+$price) . "\n";
+ $msg = str_replace('[[bills]]', $note, $msg);
+ }else{
+ $msg = str_replace('[[bills]]', '', $msg);
+ }
+ if ($ds) {
+ $msg = str_replace('[[expired_date]]', Lang::dateAndTimeFormat($ds['expiration'], $ds['time']), $msg);
+ }else{
+ $msg = str_replace('[[expired_date]]', "", $msg);
+ }
+ if (
+ !empty($customer['phonenumber']) && strlen($customer['phonenumber']) > 5
+ && !empty($template) && in_array($via, ['sms', 'wa'])
+ ) {
+ if ($via == 'sms') {
+ echo Message::sendSMS($customer['phonenumber'], $msg);
+ } else if ($via == 'wa') {
+ echo Message::sendWhatsapp($customer['phonenumber'], $msg);
+ }
+ }
+ return "$via: $msg";
+ }
+
+ public static function sendBalanceNotification($phone, $name, $balance, $balance_now, $message, $via)
+ {
+ $msg = str_replace('[[name]]', $name, $message);
+ $msg = str_replace('[[current_balance]]', Lang::moneyFormat($balance_now), $msg);
+ $msg = str_replace('[[balance]]', Lang::moneyFormat($balance), $msg);
+ if (
+ !empty($phone) && strlen($phone) > 5
+ && !empty($message) && in_array($via, ['sms', 'wa'])
+ ) {
+ if ($via == 'sms') {
+ Message::sendSMS($phone, $msg);
+ } else if ($via == 'wa') {
+ Message::sendWhatsapp($phone, $msg);
+ }
+ }
+ return "$via: $msg";
+ }
+
+ public static function sendInvoice($cust, $trx)
+ {
+ global $config;
+ $textInvoice = Lang::getNotifText('invoice_paid');
+ $textInvoice = str_replace('[[company_name]]', $config['CompanyName'], $textInvoice);
+ $textInvoice = str_replace('[[address]]', $config['address'], $textInvoice);
+ $textInvoice = str_replace('[[phone]]', $config['phone'], $textInvoice);
+ $textInvoice = str_replace('[[invoice]]', $trx['invoice'], $textInvoice);
+ $textInvoice = str_replace('[[date]]', Lang::dateAndTimeFormat($trx['recharged_on'], $trx['recharged_time']), $textInvoice);
+ if (!empty($trx['note'])) {
+ $textInvoice = str_replace('[[note]]', $trx['note'], $textInvoice);
+ }
+ $gc = explode("-", $trx['method']);
+ $textInvoice = str_replace('[[payment_gateway]]', trim($gc[0]), $textInvoice);
+ $textInvoice = str_replace('[[payment_channel]]', trim($gc[1]), $textInvoice);
+ $textInvoice = str_replace('[[type]]', $trx['type'], $textInvoice);
+ $textInvoice = str_replace('[[plan_name]]', $trx['plan_name'], $textInvoice);
+ $textInvoice = str_replace('[[plan_price]]', Lang::moneyFormat($trx['price']), $textInvoice);
+ $textInvoice = str_replace('[[name]]', $cust['fullname'], $textInvoice);
+ $textInvoice = str_replace('[[note]]', $cust['note'], $textInvoice);
+ $textInvoice = str_replace('[[user_name]]', $trx['username'], $textInvoice);
+ $textInvoice = str_replace('[[user_password]]', $cust['password'], $textInvoice);
+ $textInvoice = str_replace('[[username]]', $trx['username'], $textInvoice);
+ $textInvoice = str_replace('[[password]]', $cust['password'], $textInvoice);
+ $textInvoice = str_replace('[[expired_date]]', Lang::dateAndTimeFormat($trx['expiration'], $trx['time']), $textInvoice);
+ $textInvoice = str_replace('[[footer]]', $config['note'], $textInvoice);
+
+ if ($config['user_notification_payment'] == 'sms') {
+ Message::sendSMS($cust['phonenumber'], $textInvoice);
+ } else if ($config['user_notification_payment'] == 'wa') {
+ Message::sendWhatsapp($cust['phonenumber'], $textInvoice);
+ }
+ }
+
+ public static function sendRegistrationNotification($customer)
+ {
+ global $config;
+
+ if(empty($customer['phonenumber']) || strlen($customer['phonenumber']) < 5){
+ return "";
+ }
+
+ $textRegistration = Lang::getNotifText('user_registration');
+ if(empty($textRegistration)) {
+ return "";
+ }
+
+ $textRegistration = str_replace('[[company_name]]', $config['CompanyName'], $textRegistration);
+ $textRegistration = str_replace('[[name]]', $customer['fullname'], $textRegistration);
+ $textRegistration = str_replace('[[user_name]]', $customer['username'], $textRegistration);
+ $textRegistration = str_replace('[[username]]', $customer['username'], $textRegistration);
+ $textRegistration = str_replace('[[password]]', $customer['password'], $textRegistration);
+ $textRegistration = str_replace('[[service_type]]', $customer['service_type'], $textRegistration);
+ $textRegistration = str_replace('[[footer]]', $config['note'], $textRegistration);
+
+ // Use the same notification setting as recharge process
+ if ($config['user_notification_payment'] == 'sms') {
+ Message::sendSMS($customer['phonenumber'], $textRegistration);
+ } else if ($config['user_notification_payment'] == 'wa') {
+ Message::sendWhatsapp($customer['phonenumber'], $textRegistration);
+ }
+
+ return $textRegistration;
+ }
+
+ /**
+ * Get plan-specific template based on plan type
+ * @param string $message_key The message key (e.g., 'expired', 'reminder_7_day')
+ * @param string $plan_type The plan type ('Hotspot', 'PPPOE', etc.)
+ * @return string The appropriate template
+ */
+ public static function getPlanSpecificTemplate($message_key, $plan_type = null)
+ {
+ global $config;
+
+ // Load notification templates
+ $notifications = self::loadNotificationTemplates();
+
+ // If no plan type provided or template doesn't exist, return original message
+ if (empty($plan_type) || !isset($notifications[$message_key])) {
+ return $message_key;
+ }
+
+ // Check if it's a new structure with plan-specific templates
+ if (is_array($notifications[$message_key])) {
+ $plan_type_lower = strtolower($plan_type);
+
+ // Try to get plan-specific template
+ if (isset($notifications[$message_key][$plan_type_lower])) {
+ return $notifications[$message_key][$plan_type_lower];
+ }
+
+ // Fallback to default template
+ if (isset($notifications[$message_key]['default'])) {
+ return $notifications[$message_key]['default'];
+ }
+ }
+
+ // Fallback to original message for backward compatibility
+ return $notifications[$message_key];
+ }
+
+ /**
+ * Load notification templates from file
+ * @return array Notification templates
+ */
+ public static function loadNotificationTemplates()
+ {
+ global $root_path;
+ $notifications_file = $root_path . 'system/uploads/notifications.default.json';
+
+ if (file_exists($notifications_file)) {
+ $content = file_get_contents($notifications_file);
+ $notifications = json_decode($content, true);
+ return $notifications ?: [];
+ }
+
+ return [];
+ }
+
+ /**
+ * Detect plan type from customer and package information
+ * @param array $customer Customer information
+ * @param string $package Package name
+ * @return string|null Plan type
+ */
+ public static function detectPlanType($customer, $package)
+ {
+ // Try to get plan type from customer's service_type
+ if (!empty($customer['service_type']) && in_array($customer['service_type'], ['Hotspot', 'PPPoE'])) {
+ return $customer['service_type'];
+ }
+
+ // Try to detect from package name (basic detection)
+ $package_lower = strtolower($package);
+ if (strpos($package_lower, 'hotspot') !== false) {
+ return 'Hotspot';
+ } elseif (strpos($package_lower, 'pppoe') !== false) {
+ return 'PPPOE';
+ }
+
+ return null;
+ }
+}
diff --git a/system/autoload/Meta.php b/system/autoload/Meta.php
new file mode 100644
index 0000000..5846529
--- /dev/null
+++ b/system/autoload/Meta.php
@@ -0,0 +1,118 @@
+set(1, 'point', '24');
+ * it means tbl_plans with id 1 have point value 24, customer will get 24 point for loyalty if buy plan with id 1
+ * You need to create the logic for that, Meta only hold the data
+ *
+ * Example to get data
+ * $point = Meta::for("tbl_plans")->get(1, 'point');
+ * this will return the point value of plan with id 1
+ *
+ * to get all key value
+ * $metas = Meta::for("tbl_plans")->getAll(1);
+ *
+ * to delete 1 data
+ * Meta::for("tbl_plans")->delete(1, 'point');
+ *
+ * to delete all data
+ * Meta::for("tbl_plans")->deleteAll(1);
+ **/
+
+
+class Meta
+{
+ protected $table = '';
+
+ protected function __construct($table)
+ {
+ $this->table = $table;
+ }
+
+ public static function for($table)
+ {
+ return new self($table);
+ }
+
+ public function get($id, $key)
+ {
+ // get the Value
+ return ORM::for_table('tbl_meta')
+ ->select('value')
+ ->where('tbl', $this->table)
+ ->where('tbl_id', $id)
+ ->where('name', $key)
+ ->find_one()['value'];
+ }
+
+ public function getAll($id)
+ {
+ //get all key Value
+ $metas = [];
+ $result = ORM::for_table('tbl_meta')
+ ->select('name')
+ ->select('value')
+ ->where('tbl', $this->table)
+ ->where('tbl_id', $id)
+ ->find_array();
+ foreach ($result as $value) {
+ $metas[$value['name']] = $value['value'];
+ }
+ return $metas;
+ }
+
+ public function set($id, $key, $value = '')
+ {
+ $meta = ORM::for_table('tbl_meta')
+ ->where('tbl', $this->table)
+ ->where('tbl_id', $id)
+ ->where('name', $key)
+ ->find_one();
+ if (!$meta) {
+ $meta = ORM::for_table('tbl_meta')->create();
+ $meta->tbl = $this->table;
+ $meta->tbl_id = $id;
+ $meta->name = $key;
+ $meta->value = $value;
+ $meta->save();
+ $result = $meta->id();
+ if ($result) {
+ return $result;
+ }
+ } else {
+ $meta->value = $value;
+ $meta->save();
+ return $meta['id'];
+ }
+ }
+
+ public function delete($id, $key = '')
+ {
+ // get the Value
+ return ORM::for_table('tbl_meta')
+ ->select('value')
+ ->where('tbl', $this->table)
+ ->where('tbl_id', $id)
+ ->where('name', $key)
+ ->delete();
+ }
+
+ public function deleteAll($id)
+ {
+ //get all key Value
+ return ORM::for_table('tbl_meta')
+ ->select('value')
+ ->where('tbl', $this->table)
+ ->where('tbl_id', $id)
+ ->delete_many();
+ }
+}
diff --git a/system/autoload/Mikrotik.php b/system/autoload/Mikrotik.php
new file mode 100644
index 0000000..274e1dc
--- /dev/null
+++ b/system/autoload/Mikrotik.php
@@ -0,0 +1,562 @@
+where('name', $name)->find_one();
+ }
+
+ public static function getClient($ip, $user, $pass, $timeout = 10)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $iport = explode(":", $ip);
+ return new RouterOS\Client($iport[0], $user, $pass, ($iport[1]) ? $iport[1] : null, false, $timeout);
+ }
+
+ public static function isUserLogin($client, $username)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $printRequest = new RouterOS\Request(
+ '/ip hotspot active print',
+ RouterOS\Query::where('user', $username)
+ );
+ return $client->sendSync($printRequest)->getProperty('.id');
+ }
+
+ public static function logMeIn($client, $user, $pass, $ip, $mac)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $addRequest = new RouterOS\Request('/ip/hotspot/active/login');
+ $client->sendSync(
+ $addRequest
+ ->setArgument('user', $user)
+ ->setArgument('password', $pass)
+ ->setArgument('ip', $ip)
+ ->setArgument('mac-address', $mac)
+ );
+ }
+
+ public static function logMeOut($client, $user)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $printRequest = new RouterOS\Request(
+ '/ip hotspot active print',
+ RouterOS\Query::where('user', $user)
+ );
+ $id = $client->sendSync($printRequest)->getProperty('.id');
+ $removeRequest = new RouterOS\Request('/ip/hotspot/active/remove');
+ $client->sendSync(
+ $removeRequest
+ ->setArgument('numbers', $id)
+ );
+ }
+
+ public static function addHotspotPlan($client, $name, $sharedusers, $rate)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $addRequest = new RouterOS\Request('/ip/hotspot/user/profile/add');
+ $client->sendSync(
+ $addRequest
+ ->setArgument('name', $name)
+ ->setArgument('shared-users', $sharedusers)
+ ->setArgument('rate-limit', $rate)
+ );
+ }
+
+ public static function setHotspotPlan($client, $name, $sharedusers, $rate)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $printRequest = new RouterOS\Request(
+ '/ip hotspot user profile print .proplist=.id',
+ RouterOS\Query::where('name', $name)
+ );
+ $profileID = $client->sendSync($printRequest)->getProperty('.id');
+ if (empty($profileID)) {
+ Mikrotik::addHotspotPlan($client, $name, $sharedusers, $rate);
+ } else {
+ $setRequest = new RouterOS\Request('/ip/hotspot/user/profile/set');
+ $client->sendSync(
+ $setRequest
+ ->setArgument('numbers', $profileID)
+ ->setArgument('shared-users', $sharedusers)
+ ->setArgument('rate-limit', $rate)
+ );
+ }
+ }
+
+ public static function setHotspotExpiredPlan($client, $name, $pool)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $printRequest = new RouterOS\Request(
+ '/ip hotspot user profile print .proplist=.id',
+ RouterOS\Query::where('name', $name)
+ );
+ $profileID = $client->sendSync($printRequest)->getProperty('.id');
+ if (empty($profileID)) {
+ $addRequest = new RouterOS\Request('/ip/hotspot/user/profile/add');
+ $client->sendSync(
+ $addRequest
+ ->setArgument('name', $name)
+ ->setArgument('shared-users', 3)
+ ->setArgument('address-pool', $pool)
+ ->setArgument('rate-limit', '512K/512K')
+ );
+ } else {
+ $setRequest = new RouterOS\Request('/ip/hotspot/user/profile/set');
+ $client->sendSync(
+ $setRequest
+ ->setArgument('numbers', $profileID)
+ ->setArgument('shared-users', 3)
+ ->setArgument('address-pool', $pool)
+ ->setArgument('rate-limit', '512K/512K')
+ );
+ }
+ }
+
+ public static function removeHotspotPlan($client, $name)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $printRequest = new RouterOS\Request(
+ '/ip hotspot user profile print .proplist=.id',
+ RouterOS\Query::where('name', $name)
+ );
+ $profileID = $client->sendSync($printRequest)->getProperty('.id');
+
+ $removeRequest = new RouterOS\Request('/ip/hotspot/user/profile/remove');
+ $client->sendSync(
+ $removeRequest
+ ->setArgument('numbers', $profileID)
+ );
+ }
+
+ public static function removeHotspotUser($client, $username)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $printRequest = new RouterOS\Request(
+ '/ip hotspot user print .proplist=.id',
+ RouterOS\Query::where('name', $username)
+ );
+ $userID = $client->sendSync($printRequest)->getProperty('.id');
+ $removeRequest = new RouterOS\Request('/ip/hotspot/user/remove');
+ $client->sendSync(
+ $removeRequest
+ ->setArgument('numbers', $userID)
+ );
+ }
+
+ public static function addHotspotUser($client, $plan, $customer)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $addRequest = new RouterOS\Request('/ip/hotspot/user/add');
+ if ($plan['typebp'] == "Limited") {
+ if ($plan['limit_type'] == "Time_Limit") {
+ if ($plan['time_unit'] == 'Hrs')
+ $timelimit = $plan['time_limit'] . ":00:00";
+ else
+ $timelimit = "00:" . $plan['time_limit'] . ":00";
+ $client->sendSync(
+ $addRequest
+ ->setArgument('name', $customer['username'])
+ ->setArgument('profile', $plan['name_plan'])
+ ->setArgument('password', $customer['password'])
+ ->setArgument('comment', $customer['fullname'])
+ ->setArgument('email', $customer['email'])
+ ->setArgument('limit-uptime', $timelimit)
+ );
+ } else if ($plan['limit_type'] == "Data_Limit") {
+ if ($plan['data_unit'] == 'GB')
+ $datalimit = $plan['data_limit'] . "000000000";
+ else
+ $datalimit = $plan['data_limit'] . "000000";
+ $client->sendSync(
+ $addRequest
+ ->setArgument('name', $customer['username'])
+ ->setArgument('profile', $plan['name_plan'])
+ ->setArgument('password', $customer['password'])
+ ->setArgument('comment', $customer['fullname'])
+ ->setArgument('email', $customer['email'])
+ ->setArgument('limit-bytes-total', $datalimit)
+ );
+ } else if ($plan['limit_type'] == "Both_Limit") {
+ if ($plan['time_unit'] == 'Hrs')
+ $timelimit = $plan['time_limit'] . ":00:00";
+ else
+ $timelimit = "00:" . $plan['time_limit'] . ":00";
+ if ($plan['data_unit'] == 'GB')
+ $datalimit = $plan['data_limit'] . "000000000";
+ else
+ $datalimit = $plan['data_limit'] . "000000";
+ $client->sendSync(
+ $addRequest
+ ->setArgument('name', $customer['username'])
+ ->setArgument('profile', $plan['name_plan'])
+ ->setArgument('password', $customer['password'])
+ ->setArgument('comment', $customer['fullname'])
+ ->setArgument('email', $customer['email'])
+ ->setArgument('limit-uptime', $timelimit)
+ ->setArgument('limit-bytes-total', $datalimit)
+ );
+ }
+ } else {
+ $client->sendSync(
+ $addRequest
+ ->setArgument('name', $customer['username'])
+ ->setArgument('profile', $plan['name_plan'])
+ ->setArgument('comment', $customer['fullname'])
+ ->setArgument('email', $customer['email'])
+ ->setArgument('password', $customer['password'])
+ );
+ }
+ }
+
+ public static function setHotspotUser($client, $user, $pass)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $printRequest = new RouterOS\Request('/ip/hotspot/user/print');
+ $printRequest->setArgument('.proplist', '.id');
+ $printRequest->setQuery(RouterOS\Query::where('name', $user));
+ $id = $client->sendSync($printRequest)->getProperty('.id');
+
+ $setRequest = new RouterOS\Request('/ip/hotspot/user/set');
+ $setRequest->setArgument('numbers', $id);
+ $setRequest->setArgument('password', $pass);
+ $client->sendSync($setRequest);
+ }
+
+ public static function setHotspotUserPackage($client, $user, $plan)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $printRequest = new RouterOS\Request('/ip/hotspot/user/print');
+ $printRequest->setArgument('.proplist', '.id');
+ $printRequest->setQuery(RouterOS\Query::where('name', $user));
+ $id = $client->sendSync($printRequest)->getProperty('.id');
+
+ $setRequest = new RouterOS\Request('/ip/hotspot/user/set');
+ $setRequest->setArgument('numbers', $id);
+ $setRequest->setArgument('profile', $plan);
+ $client->sendSync($setRequest);
+ }
+
+ public static function removeHotspotActiveUser($client, $username)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $onlineRequest = new RouterOS\Request('/ip/hotspot/active/print');
+ $onlineRequest->setArgument('.proplist', '.id');
+ $onlineRequest->setQuery(RouterOS\Query::where('user', $username));
+ $id = $client->sendSync($onlineRequest)->getProperty('.id');
+
+ $removeRequest = new RouterOS\Request('/ip/hotspot/active/remove');
+ $removeRequest->setArgument('numbers', $id);
+ $client->sendSync($removeRequest);
+ }
+
+ public static function removePpoeUser($client, $username)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $printRequest = new RouterOS\Request('/ppp/secret/print');
+ //$printRequest->setArgument('.proplist', '.id');
+ $printRequest->setQuery(RouterOS\Query::where('name', $username));
+ $id = $client->sendSync($printRequest)->getProperty('.id');
+ $removeRequest = new RouterOS\Request('/ppp/secret/remove');
+ $removeRequest->setArgument('numbers', $id);
+ $client->sendSync($removeRequest);
+ }
+
+ public static function addPpoeUser($client, $plan, $customer)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $addRequest = new RouterOS\Request('/ppp/secret/add');
+ if (!empty($customer['pppoe_password'])) {
+ $pass = $customer['pppoe_password'];
+ } else {
+ $pass = $customer['password'];
+ }
+ $client->sendSync(
+ $addRequest
+ ->setArgument('name', $customer['username'])
+ ->setArgument('service', 'pppoe')
+ ->setArgument('profile', $plan['name_plan'])
+ ->setArgument('comment', $customer['fullname'] . ' | ' . $customer['email'])
+ ->setArgument('password', $pass)
+ );
+ }
+
+ public static function setPpoeUser($client, $user, $pass)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $printRequest = new RouterOS\Request('/ppp/secret/print');
+ $printRequest->setArgument('.proplist', '.id');
+ $printRequest->setQuery(RouterOS\Query::where('name', $user));
+ $id = $client->sendSync($printRequest)->getProperty('.id');
+
+ $setRequest = new RouterOS\Request('/ppp/secret/set');
+ $setRequest->setArgument('numbers', $id);
+ $setRequest->setArgument('password', $pass);
+ $client->sendSync($setRequest);
+ }
+
+ public static function setPpoeUserPlan($client, $user, $plan)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $printRequest = new RouterOS\Request('/ppp/secret/print');
+ $printRequest->setArgument('.proplist', '.id');
+ $printRequest->setQuery(RouterOS\Query::where('name', $user));
+ $id = $client->sendSync($printRequest)->getProperty('.id');
+
+ $setRequest = new RouterOS\Request('/ppp/secret/set');
+ $setRequest->setArgument('numbers', $id);
+ $setRequest->setArgument('profile', $plan);
+ $client->sendSync($setRequest);
+ }
+
+ public static function removePpoeActive($client, $username)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $onlineRequest = new RouterOS\Request('/ppp/active/print');
+ $onlineRequest->setArgument('.proplist', '.id');
+ $onlineRequest->setQuery(RouterOS\Query::where('name', $username));
+ $id = $client->sendSync($onlineRequest)->getProperty('.id');
+
+ $removeRequest = new RouterOS\Request('/ppp/active/remove');
+ $removeRequest->setArgument('numbers', $id);
+ $client->sendSync($removeRequest);
+ }
+
+ public static function removePool($client, $name)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $printRequest = new RouterOS\Request(
+ '/ip pool print .proplist=.id',
+ RouterOS\Query::where('name', $name)
+ );
+ $poolID = $client->sendSync($printRequest)->getProperty('.id');
+
+ $removeRequest = new RouterOS\Request('/ip/pool/remove');
+ $client->sendSync(
+ $removeRequest
+ ->setArgument('numbers', $poolID)
+ );
+ }
+
+ public static function addPool($client, $name, $ip_address)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $addRequest = new RouterOS\Request('/ip/pool/add');
+ $client->sendSync(
+ $addRequest
+ ->setArgument('name', $name)
+ ->setArgument('ranges', $ip_address)
+ );
+ }
+
+ public static function setPool($client, $name, $ip_address)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $printRequest = new RouterOS\Request(
+ '/ip pool print .proplist=.id',
+ RouterOS\Query::where('name', $name)
+ );
+ $poolID = $client->sendSync($printRequest)->getProperty('.id');
+
+ if (empty($poolID)) {
+ self::addPool($client, $name, $ip_address);
+ } else {
+ $setRequest = new RouterOS\Request('/ip/pool/set');
+ $client->sendSync(
+ $setRequest
+ ->setArgument('numbers', $poolID)
+ ->setArgument('ranges', $ip_address)
+ );
+ }
+ }
+
+
+ public static function addPpoePlan($client, $name, $pool, $rate)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $addRequest = new RouterOS\Request('/ppp/profile/add');
+ $client->sendSync(
+ $addRequest
+ ->setArgument('name', $name)
+ ->setArgument('local-address', $pool)
+ ->setArgument('remote-address', $pool)
+ ->setArgument('rate-limit', $rate)
+ );
+ }
+
+ public static function setPpoePlan($client, $name, $pool, $rate)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $printRequest = new RouterOS\Request(
+ '/ppp profile print .proplist=.id',
+ RouterOS\Query::where('name', $name)
+ );
+ $profileID = $client->sendSync($printRequest)->getProperty('.id');
+ if (empty($profileID)) {
+ self::addPpoePlan($client, $name, $pool, $rate);
+ } else {
+ $setRequest = new RouterOS\Request('/ppp/profile/set');
+ $client->sendSync(
+ $setRequest
+ ->setArgument('numbers', $profileID)
+ ->setArgument('local-address', $pool)
+ ->setArgument('remote-address', $pool)
+ ->setArgument('rate-limit', $rate)
+ );
+ }
+ }
+
+ public static function removePpoePlan($client, $name)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $printRequest = new RouterOS\Request(
+ '/ppp profile print .proplist=.id',
+ RouterOS\Query::where('name', $name)
+ );
+ $profileID = $client->sendSync($printRequest)->getProperty('.id');
+
+ $removeRequest = new RouterOS\Request('/ppp/profile/remove');
+ $client->sendSync(
+ $removeRequest
+ ->setArgument('numbers', $profileID)
+ );
+ }
+
+ public static function sendSMS($client, $to, $message)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $smsRequest = new RouterOS\Request('/tool sms send');
+ $smsRequest
+ ->setArgument('phone-number', $to)
+ ->setArgument('message', $message);
+ $client->sendSync($smsRequest);
+ }
+
+ public static function getIpHotspotUser($client, $username){
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $printRequest = new RouterOS\Request(
+ '/ip hotspot active print',
+ RouterOS\Query::where('user', $username)
+ );
+ return $client->sendSync($printRequest)->getProperty('address');
+ }
+
+ public static function addIpToAddressList($client, $ip, $listName, $comment = '')
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $addRequest = new RouterOS\Request('/ip/firewall/address-list/add');
+ $client->sendSync(
+ $addRequest
+ ->setArgument('address', $ip)
+ ->setArgument('comment', $comment)
+ ->setArgument('list', $listName)
+ );
+ }
+
+ public static function removeIpFromAddressList($client, $ip)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $printRequest = new RouterOS\Request(
+ '/ip firewall address-list print .proplist=.id',
+ RouterOS\Query::where('address', $ip)
+ );
+ $id = $client->sendSync($printRequest)->getProperty('.id');
+ $removeRequest = new RouterOS\Request('/ip/firewall/address-list/remove');
+ $client->sendSync(
+ $removeRequest
+ ->setArgument('numbers', $id)
+ );
+ }
+}
diff --git a/system/autoload/Package.php b/system/autoload/Package.php
new file mode 100644
index 0000000..a2210d2
--- /dev/null
+++ b/system/autoload/Package.php
@@ -0,0 +1,817 @@
+where('id', $id_customer)->find_one();
+ $p = ORM::for_table('tbl_plans')->where('id', $plan_id)->find_one();
+
+ if ($c['status'] != 'Active') {
+ _alert(Lang::T('This account status') . ' : ' . Lang::T($c['status']), 'danger', "");
+ }
+
+ $add_cost = 0;
+ $bills = [];
+ // Zero cost recharge
+ if (isset($zero) && $zero == 1) {
+ $p['price'] = 0;
+ } else {
+ // Additional cost
+ list($bills, $add_cost) = User::getBills($id_customer);
+ if ($add_cost > 0 && $router_name != 'balance') {
+ foreach ($bills as $k => $v) {
+ $note .= $k . " : " . Lang::moneyFormat($v) . "\n";
+ }
+ $note .= $p['name_plan'] . " : " . Lang::moneyFormat($p['price']) . "\n";
+ }
+ }
+
+
+ if (!$p['enabled']) {
+ if (!isset($admin) || !isset($admin['id']) || empty($admin['id'])) {
+ r2(U . 'home', 'e', Lang::T('Plan Not found'));
+ }
+ if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
+ r2(U . 'dashboard', 'e', Lang::T('Plan Not found'));
+ }
+ }
+
+ if ($p['validity_unit'] == 'Period') {
+ $day_exp = User::getAttribute("Expired Date", $c['id']); //ORM::for_table('tbl_customers_fields')->where('field_name', 'Expired Date')->where('customer_id', $c['id'])->find_one();
+ if (!$day_exp) {
+ $day_exp = 20;
+ // $day_exp = date('d', strtotime($c['created_at']));
+ // if (empty($day_exp) || $day_exp > 28) {
+ // $day_exp = 1;
+ // }
+ $f = ORM::for_table('tbl_customers_fields')->create();
+ $f->customer_id = $c['id'];
+ $f->field_name = 'Expired Date';
+ $f->field_value = $day_exp;
+ $f->save();
+ }
+ }
+
+ if ($router_name == 'balance') {
+ // insert table transactions
+ $t = ORM::for_table('tbl_transactions')->create();
+ $t->invoice = $inv = "INV-" . Package::_raid();
+ $t->username = $c['username'];
+ $t->plan_name = $p['name_plan'];
+ $t->price = $p['price'];
+ $t->recharged_on = $date_only;
+ $t->recharged_time = date("H:i:s");
+ $t->expiration = $date_only;
+ $t->time = $time;
+ $t->method = "$gateway - $channel";
+ $t->routers = $router_name;
+ $t->type = "Balance";
+ if ($admin) {
+ $t->admin_id = ($admin['id']) ? $admin['id'] : '0';
+ } else {
+ $t->admin_id = '0';
+ }
+ $t->save();
+
+ $balance_before = $c['balance'];
+ Balance::plus($id_customer, $p['price']);
+ $balance = $c['balance'] + $p['price'];
+
+ $textInvoice = Lang::getNotifText('invoice_balance');
+ $textInvoice = str_replace('[[company_name]]', $config['CompanyName'], $textInvoice);
+ $textInvoice = str_replace('[[address]]', $config['address'], $textInvoice);
+ $textInvoice = str_replace('[[phone]]', $config['phone'], $textInvoice);
+ $textInvoice = str_replace('[[invoice]]', $inv, $textInvoice);
+ $textInvoice = str_replace('[[date]]', Lang::dateTimeFormat($date_now), $textInvoice);
+ $textInvoice = str_replace('[[payment_gateway]]', $gateway, $textInvoice);
+ $textInvoice = str_replace('[[payment_channel]]', $channel, $textInvoice);
+ $textInvoice = str_replace('[[type]]', 'Balance', $textInvoice);
+ $textInvoice = str_replace('[[plan_name]]', $p['name_plan'], $textInvoice);
+ $textInvoice = str_replace('[[plan_price]]', Lang::moneyFormat($p['price']), $textInvoice);
+ $textInvoice = str_replace('[[name]]', $c['fullname'], $textInvoice);
+ $textInvoice = str_replace('[[user_name]]', $c['username'], $textInvoice);
+ $textInvoice = str_replace('[[user_password]]', $c['password'], $textInvoice);
+ $textInvoice = str_replace('[[footer]]', $config['note'], $textInvoice);
+ $textInvoice = str_replace('[[balance_before]]', Lang::moneyFormat($balance_before), $textInvoice);
+ $textInvoice = str_replace('[[balance]]', Lang::moneyFormat($balance), $textInvoice);
+
+ if ($config['user_notification_payment'] == 'sms') {
+ Message::sendSMS($c['phonenumber'], $textInvoice);
+ } else if ($config['user_notification_payment'] == 'wa') {
+ Message::sendWhatsapp($c['phonenumber'], $textInvoice);
+ }
+
+ return true;
+ }
+
+ /**
+ * 1 Customer only can have 1 PPPOE and 1 Hotspot Plan, 1 prepaid and 1 postpaid
+ */
+ $b = ORM::for_table('tbl_user_recharges')
+ ->select('tbl_user_recharges.id', 'id')
+ ->select('customer_id')
+ ->select('username')
+ ->select('plan_id')
+ ->select('namebp')
+ ->select('recharged_on')
+ ->select('recharged_time')
+ ->select('expiration')
+ ->select('time')
+ ->select('status')
+ ->select('method')
+ ->select('tbl_user_recharges.routers', 'routers')
+ ->select('tbl_user_recharges.type', 'type')
+ ->select('admin_id')
+ ->select('prepaid')
+ ->where('customer_id', $id_customer)
+ ->where('tbl_user_recharges.routers', $router_name)
+ ->where('tbl_user_recharges.Type', $p['type'])
+ # PPPOE or Hotspot only can have 1 per customer prepaid or postpaid
+ # because 1 customer can have 1 PPPOE and 1 Hotspot Plan in mikrotik
+ //->where('prepaid', $p['prepaid'])
+ ->left_outer_join('tbl_plans', array('tbl_plans.id', '=', 'tbl_user_recharges.plan_id'))
+ ->find_one();
+
+ run_hook("recharge_user");
+
+
+ $mikrotik = Mikrotik::info($router_name);
+ if ($p['validity_unit'] == 'Months') {
+ $date_exp = date("Y-m-d", strtotime('+' . $p['validity'] . ' month'));
+ } else if ($p['validity_unit'] == 'Period') {
+ $date_tmp = date("Y-m-$day_exp", strtotime('+' . $p['validity'] . ' month'));
+ $dt1 = new DateTime("$date_only");
+ $dt2 = new DateTime("$date_tmp");
+ $diff = $dt2->diff($dt1);
+ $sum = $diff->format("%a"); // => 453
+ if ($sum >= 35 * $p['validity']) {
+ $date_exp = date("Y-m-$day_exp", strtotime('+0 month'));
+ } else {
+ $date_exp = date("Y-m-$day_exp", strtotime('+' . $p['validity'] . ' month'));
+ };
+ $time = date("23:59:00");
+ } else if ($p['validity_unit'] == 'Days') {
+ $datetime = explode(' ', date("Y-m-d H:i:s", strtotime('+' . $p['validity'] . ' day')));
+ $date_exp = $datetime[0];
+ $time = $datetime[1];
+ } else if ($p['validity_unit'] == 'Hrs') {
+ $datetime = explode(' ', date("Y-m-d H:i:s", strtotime('+' . $p['validity'] . ' hour')));
+ $date_exp = $datetime[0];
+ $time = $datetime[1];
+ } else if ($p['validity_unit'] == 'Mins') {
+ $datetime = explode(' ', date("Y-m-d H:i:s", strtotime('+' . $p['validity'] . ' minute')));
+ $date_exp = $datetime[0];
+ $time = $datetime[1];
+ }
+ $isChangePlan = false;
+ if ($p['type'] == 'Hotspot') {
+ if ($b) {
+ if ($plan_id != $b['plan_id']) {
+ $isChangePlan = true;
+ }
+ if ($config['extend_expiry'] === 'yes') {
+ if ($b['namebp'] == $p['name_plan'] && $b['status'] == 'on') {
+ // if it same internet plan, expired will extend
+ if ($p['validity_unit'] == 'Months') {
+ $date_exp = date("Y-m-d", strtotime($b['expiration'] . ' +' . $p['validity'] . ' months'));
+ $time = $b['time'];
+ } else if ($p['validity_unit'] == 'Period') {
+ $date_exp = date("Y-m-$day_exp", strtotime($b['expiration'] . ' +' . $p['validity'] . ' months'));
+ $time = date("23:59:00");
+ } else if ($p['validity_unit'] == 'Days') {
+ $date_exp = date("Y-m-d", strtotime($b['expiration'] . ' +' . $p['validity'] . ' days'));
+ $time = $b['time'];
+ } else if ($p['validity_unit'] == 'Hrs') {
+ $datetime = explode(' ', date("Y-m-d H:i:s", strtotime($b['expiration'] . ' ' . $b['time'] . ' +' . $p['validity'] . ' hours')));
+ $date_exp = $datetime[0];
+ $time = $datetime[1];
+ } else if ($p['validity_unit'] == 'Mins') {
+ $datetime = explode(' ', date("Y-m-d H:i:s", strtotime($b['expiration'] . ' ' . $b['time'] . ' +' . $p['validity'] . ' minutes')));
+ $date_exp = $datetime[0];
+ $time = $datetime[1];
+ }
+ }
+ }
+ if ($isChangePlan || $b['status'] == 'off') {
+ if ($p['is_radius']) {
+ Radius::customerAddPlan($c, $p, "$date_exp $time");
+ } else {
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ Mikrotik::removeHotspotUser($client, $c['username']);
+ Mikrotik::removeHotspotActiveUser($client, $c['username']);
+ Mikrotik::addHotspotUser($client, $p, $c);
+ }
+ }
+
+ $b->customer_id = $id_customer;
+ $b->username = $c['username'];
+ $b->plan_id = $plan_id;
+ $b->namebp = $p['name_plan'];
+ $b->recharged_on = $date_only;
+ $b->recharged_time = $time_only;
+ $b->expiration = $date_exp;
+ $b->time = $time;
+ $b->status = "on";
+ $b->method = "$gateway - $channel";
+ $b->routers = $router_name;
+ $b->type = "Hotspot";
+ if ($admin) {
+ $b->admin_id = ($admin['id']) ? $admin['id'] : '0';
+ } else {
+ $b->admin_id = '0';
+ }
+ $b->save();
+
+ // insert table transactions
+ $t = ORM::for_table('tbl_transactions')->create();
+ $t->invoice = $inv = "INV-" . Package::_raid();
+ $t->username = $c['username'];
+ $t->plan_name = $p['name_plan'];
+ if ($p['validity_unit'] == 'Period') {
+ // Postpaid price from field
+ $add_inv = User::getAttribute("Invoice", $id_customer);
+ if (empty($add_inv) or $add_inv == 0) {
+ $t->price = $p['price'] + $add_cost;
+ } else {
+ $t->price = $add_inv + $add_cost;
+ }
+ } else {
+ $t->price = $p['price'] + $add_cost;
+ }
+ $t->recharged_on = $date_only;
+ $t->recharged_time = $time_only;
+ $t->expiration = $date_exp;
+ $t->time = $time;
+ $t->method = "$gateway - $channel";
+ $t->routers = $router_name;
+ $t->note = $note;
+ $t->type = "Hotspot";
+ if ($admin) {
+ $t->admin_id = ($admin['id']) ? $admin['id'] : '0';
+ } else {
+ $t->admin_id = '0';
+ }
+ $t->save();
+
+ if ($p['validity_unit'] == 'Period') {
+ // insert price to fields for invoice next month
+ $fl = ORM::for_table('tbl_customers_fields')->where('field_name', 'Invoice')->where('customer_id', $c['id'])->find_one();
+ if (!$fl) {
+ $fl = ORM::for_table('tbl_customers_fields')->create();
+ $fl->customer_id = $c['id'];
+ $fl->field_name = 'Invoice';
+ $fl->field_value = $p['price'];
+ $fl->save();
+ } else {
+ $fl->customer_id = $c['id'];
+ $fl->field_value = $p['price'];
+ $fl->save();
+ }
+ }
+
+
+ Message::sendTelegram("#u$c[username] $c[fullname] #recharge #Hotspot \n" . $p['name_plan'] .
+ "\nRouter: " . $router_name .
+ "\nGateway: " . $gateway .
+ "\nChannel: " . $channel .
+ "\nPrice: " . Lang::moneyFormat($p['price'] + $add_cost) .
+ "\nNote:\n" . $note);
+ } else {
+ if ($p['is_radius']) {
+ Radius::customerAddPlan($c, $p, "$date_exp $time");
+ } else {
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ Mikrotik::removeHotspotUser($client, $c['username']);
+ Mikrotik::removeHotspotActiveUser($client, $c['username']);
+ Mikrotik::addHotspotUser($client, $p, $c);
+ }
+
+ $d = ORM::for_table('tbl_user_recharges')->create();
+ $d->customer_id = $id_customer;
+ $d->username = $c['username'];
+ $d->plan_id = $plan_id;
+ $d->namebp = $p['name_plan'];
+ $d->recharged_on = $date_only;
+ $d->recharged_time = $time_only;
+ $d->expiration = $date_exp;
+ $d->time = $time;
+ $d->status = "on";
+ $d->method = "$gateway - $channel";
+ $d->routers = $router_name;
+ $d->type = "Hotspot";
+ if ($admin) {
+ $d->admin_id = ($admin['id']) ? $admin['id'] : '0';
+ } else {
+ $d->admin_id = '0';
+ }
+ $d->save();
+
+ // insert table transactions
+ $t = ORM::for_table('tbl_transactions')->create();
+ $t->invoice = $inv = "INV-" . Package::_raid();
+ $t->username = $c['username'];
+ $t->plan_name = $p['name_plan'];
+ if ($p['validity_unit'] == 'Period') {
+ // Postpaid price always zero for first time
+ $t->price = 0 + $add_cost;
+ } else {
+ $t->price = $p['price'] + $add_cost;
+ }
+ $t->recharged_on = $date_only;
+ $t->recharged_time = $time_only;
+ $t->expiration = $date_exp;
+ $t->time = $time;
+ $t->method = "$gateway - $channel";
+ $t->routers = $router_name;
+ $t->note = $note;
+ $t->type = "Hotspot";
+ if ($admin) {
+ $t->admin_id = ($admin['id']) ? $admin['id'] : '0';
+ } else {
+ $t->admin_id = '0';
+ }
+ $t->save();
+
+ if ($p['validity_unit'] == 'Period' && $p['price'] != 0) {
+ // insert price to fields for invoice next month
+ $fl = ORM::for_table('tbl_customers_fields')->where('field_name', 'Invoice')->where('customer_id', $c['id'])->find_one();
+ if (!$fl) {
+ $fl = ORM::for_table('tbl_customers_fields')->create();
+ $fl->customer_id = $c['id'];
+ $fl->field_name = 'Invoice';
+ // Calculating Price
+ $sd = new DateTime("$date_only");
+ $ed = new DateTime("$date_exp");
+ $td = $ed->diff($sd);
+ $fd = $td->format("%a");
+ $gi = ($p['price'] / (30 * $p['validity'])) * $fd;
+ if ($gi > $p['price']) {
+ $fl->field_value = $p['price'];
+ } else {
+ $fl->field_value = $gi;
+ }
+ $fl->save();
+ } else {
+ $fl->customer_id = $c['id'];
+ $fl->field_value = $p['price'];
+ $fl->save();
+ }
+ }
+
+ Message::sendTelegram("#u$c[username] $c[fullname] #buy #Hotspot \n" . $p['name_plan'] .
+ "\nRouter: " . $router_name .
+ "\nGateway: " . $gateway .
+ "\nChannel: " . $channel .
+ "\nPrice: " . Lang::moneyFormat($p['price'] + $add_cost) .
+ "\nNote:\n" . $note);
+ }
+ } else {
+
+ if ($b) {
+ if ($plan_id != $b['plan_id']) {
+ $isChangePlan = true;
+ }
+ if ($config['extend_expiry'] === 'yes') {
+ if ($b['namebp'] == $p['name_plan'] && $b['status'] == 'on') {
+ // if it same internet plan, expired will extend
+ if ($p['validity_unit'] == 'Months') {
+ $date_exp = date("Y-m-d", strtotime($b['expiration'] . ' +' . $p['validity'] . ' months'));
+ $time = $b['time'];
+ } else if ($p['validity_unit'] == 'Period') {
+ $date_exp = date("Y-m-$day_exp", strtotime($b['expiration'] . ' +' . $p['validity'] . ' months'));
+ $time = date("23:59:00");
+ } else if ($p['validity_unit'] == 'Days') {
+ $date_exp = date("Y-m-d", strtotime($b['expiration'] . ' +' . $p['validity'] . ' days'));
+ $time = $b['time'];
+ } else if ($p['validity_unit'] == 'Hrs') {
+ $datetime = explode(' ', date("Y-m-d H:i:s", strtotime($b['expiration'] . ' ' . $b['time'] . ' +' . $p['validity'] . ' hours')));
+ $date_exp = $datetime[0];
+ $time = $datetime[1];
+ } else if ($p['validity_unit'] == 'Mins') {
+ $datetime = explode(' ', date("Y-m-d H:i:s", strtotime($b['expiration'] . ' ' . $b['time'] . ' +' . $p['validity'] . ' minutes')));
+ $date_exp = $datetime[0];
+ $time = $datetime[1];
+ }
+ }
+ }
+
+ if ($isChangePlan || $b['status'] == 'off') {
+ if ($p['is_radius']) {
+ Radius::customerAddPlan($c, $p, "$date_exp $time");
+ } else {
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ Mikrotik::removePpoeUser($client, $c['username']);
+ Mikrotik::removePpoeActive($client, $c['username']);
+ Mikrotik::addPpoeUser($client, $p, $c);
+ }
+ }
+
+ $b->customer_id = $id_customer;
+ $b->username = $c['username'];
+ $b->plan_id = $plan_id;
+ $b->namebp = $p['name_plan'];
+ $b->recharged_on = $date_only;
+ $b->recharged_time = $time_only;
+ $b->expiration = $date_exp;
+ $b->time = $time;
+ $b->status = "on";
+ $b->method = "$gateway - $channel";
+ $b->routers = $router_name;
+ $b->type = "PPPOE";
+ if ($admin) {
+ $b->admin_id = ($admin['id']) ? $admin['id'] : '0';
+ } else {
+ $b->admin_id = '0';
+ }
+ $b->save();
+
+ // insert table transactions
+ $t = ORM::for_table('tbl_transactions')->create();
+ $t->invoice = $inv = "INV-" . Package::_raid();
+ $t->username = $c['username'];
+ $t->plan_name = $p['name_plan'];
+ if ($p['validity_unit'] == 'Period') {
+ // Postpaid price from field
+ $add_inv = User::getAttribute("Invoice", $id_customer);
+ if (empty($add_inv) or $add_inv == 0) {
+ $t->price = $p['price'] + $add_cost;
+ } else {
+ $t->price = $add_inv + $add_cost;
+ }
+ } else {
+ $t->price = $p['price'] + $add_cost;
+ }
+ $t->recharged_on = $date_only;
+ $t->recharged_time = $time_only;
+ $t->expiration = $date_exp;
+ $t->time = $time;
+ $t->method = "$gateway - $channel";
+ $t->routers = $router_name;
+ $t->note = $note;
+ $t->type = "PPPOE";
+ if ($admin) {
+ $t->admin_id = ($admin['id']) ? $admin['id'] : '0';
+ } else {
+ $t->admin_id = '0';
+ }
+ $t->save();
+
+ if ($p['validity_unit'] == 'Period' && $p['price'] != 0) {
+ // insert price to fields for invoice next month
+ $fl = ORM::for_table('tbl_customers_fields')->where('field_name', 'Invoice')->where('customer_id', $c['id'])->find_one();
+ if (!$fl) {
+ $fl = ORM::for_table('tbl_customers_fields')->create();
+ $fl->customer_id = $c['id'];
+ $fl->field_name = 'Invoice';
+ $fl->field_value = $p['price'];
+ $fl->save();
+ } else {
+ $fl->customer_id = $c['id'];
+ $fl->field_value = $p['price'];
+ $fl->save();
+ }
+ }
+
+ Message::sendTelegram("#u$c[username] $c[fullname] #recharge #PPPOE \n" . $p['name_plan'] .
+ "\nRouter: " . $router_name .
+ "\nGateway: " . $gateway .
+ "\nChannel: " . $channel .
+ "\nPrice: " . Lang::moneyFormat($p['price'] + $add_cost) .
+ "\nNote:\n" . $note);
+ } else {
+ if ($p['is_radius']) {
+ Radius::customerAddPlan($c, $p, "$date_exp $time");
+ } else {
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ Mikrotik::removePpoeUser($client, $c['username']);
+ Mikrotik::removePpoeActive($client, $c['username']);
+ Mikrotik::addPpoeUser($client, $p, $c);
+ }
+
+ $d = ORM::for_table('tbl_user_recharges')->create();
+ $d->customer_id = $id_customer;
+ $d->username = $c['username'];
+ $d->plan_id = $plan_id;
+ $d->namebp = $p['name_plan'];
+ $d->recharged_on = $date_only;
+ $d->recharged_time = $time_only;
+ $d->expiration = $date_exp;
+ $d->time = $time;
+ $d->status = "on";
+ $d->method = "$gateway - $channel";
+ $d->routers = $router_name;
+ $d->type = "PPPOE";
+ if ($admin) {
+ $d->admin_id = ($admin['id']) ? $admin['id'] : '0';
+ } else {
+ $d->admin_id = '0';
+ }
+ $d->save();
+
+ // insert table transactions
+ $t = ORM::for_table('tbl_transactions')->create();
+ $t->invoice = $inv = "INV-" . Package::_raid();
+ $t->username = $c['username'];
+ $t->plan_name = $p['name_plan'];
+ if ($p['validity_unit'] == 'Period') {
+ // Postpaid price always zero for first time
+ $note = '';
+ $bills = [];
+ $t->price = 0;
+ } else {
+ $t->price = $p['price'] + $add_cost;
+ }
+ $t->recharged_on = $date_only;
+ $t->recharged_time = $time_only;
+ $t->expiration = $date_exp;
+ $t->time = $time;
+ $t->method = "$gateway - $channel";
+ $t->note = $note;
+ $t->routers = $router_name;
+ if ($admin) {
+ $t->admin_id = ($admin['id']) ? $admin['id'] : '0';
+ } else {
+ $t->admin_id = '0';
+ }
+ $t->type = "PPPOE";
+ $t->save();
+
+ if ($p['validity_unit'] == 'Period' && $p['price'] != 0) {
+ // insert price to fields for invoice next month
+ $fl = ORM::for_table('tbl_customers_fields')->where('field_name', 'Invoice')->where('customer_id', $c['id'])->find_one();
+ if (!$fl) {
+ $fl = ORM::for_table('tbl_customers_fields')->create();
+ $fl->customer_id = $c['id'];
+ $fl->field_name = 'Invoice';
+ // Calculating Price
+ $sd = new DateTime("$date_only");
+ $ed = new DateTime("$date_exp");
+ $td = $ed->diff($sd);
+ $fd = $td->format("%a");
+ $gi = ($p['price'] / (30 * $p['validity'])) * $fd;
+ if ($gi > $p['price']) {
+ $fl->field_value = $p['price'];
+ } else {
+ $fl->field_value = $gi;
+ }
+ $fl->save();
+ } else {
+ $fl->customer_id = $c['id'];
+ $fl->field_value = $p['price'];
+ $fl->save();
+ }
+ }
+
+ Message::sendTelegram("#u$c[username] $c[fullname] #buy #PPPOE \n" . $p['name_plan'] .
+ "\nRouter: " . $router_name .
+ "\nGateway: " . $gateway .
+ "\nChannel: " . $channel .
+ "\nPrice: " . Lang::moneyFormat($p['price'] + $add_cost) .
+ "\nNote:\n" . $note);
+ }
+ }
+ if (is_array($bills) && count($bills) > 0) {
+ User::billsPaid($bills, $id_customer);
+ }
+ run_hook("recharge_user_finish");
+ Message::sendInvoice($c, $t);
+ if ($trx) {
+ $trx->trx_invoice = $inv;
+ }
+ return $inv;
+ }
+
+ public static function changeTo($username, $plan_id, $from_id)
+ {
+ $c = ORM::for_table('tbl_customers')->where('username', $username)->find_one();
+ $p = ORM::for_table('tbl_plans')->where('id', $plan_id)->find_one();
+ $b = ORM::for_table('tbl_user_recharges')->find_one($from_id);
+ if ($p['routers'] == $b['routers'] && $b['routers'] != 'radius') {
+ $mikrotik = Mikrotik::info($p['routers']);
+ } else {
+ $mikrotik = Mikrotik::info($b['routers']);
+ }
+ // delete first
+ if ($p['type'] == 'Hotspot') {
+ if ($b) {
+ if (!$p['is_radius']) {
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ Mikrotik::removeHotspotUser($client, $c['username']);
+ Mikrotik::removeHotspotActiveUser($client, $c['username']);
+ }
+ } else {
+ if (!$p['is_radius']) {
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ Mikrotik::removeHotspotUser($client, $c['username']);
+ Mikrotik::removeHotspotActiveUser($client, $c['username']);
+ }
+ }
+ } else {
+ if ($b) {
+ if (!$p['is_radius']) {
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ Mikrotik::removePpoeUser($client, $c['username']);
+ Mikrotik::removePpoeActive($client, $c['username']);
+ }
+ } else {
+ if (!$p['is_radius']) {
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ Mikrotik::removePpoeUser($client, $c['username']);
+ Mikrotik::removePpoeActive($client, $c['username']);
+ }
+ }
+ }
+ // call the next mikrotik
+ if ($p['routers'] != $b['routers'] && $p['routers'] != 'radius') {
+ $mikrotik = Mikrotik::info($p['routers']);
+ }
+ if ($p['type'] == 'Hotspot') {
+ if ($b) {
+ if ($p['is_radius']) {
+ Radius::customerAddPlan($c, $p, $b['expiration'] . '' . $b['time']);
+ } else {
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ Mikrotik::addHotspotUser($client, $p, $c);
+ }
+ } else {
+ if ($p['is_radius']) {
+ Radius::customerAddPlan($c, $p, $b['expiration'] . '' . $b['time']);
+ } else {
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ Mikrotik::addHotspotUser($client, $p, $c);
+ }
+ }
+ } else {
+ if ($b) {
+ if ($p['is_radius']) {
+ Radius::customerAddPlan($c, $p);
+ } else {
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ Mikrotik::addPpoeUser($client, $p, $c);
+ }
+ } else {
+ if ($p['is_radius']) {
+ Radius::customerAddPlan($c, $p);
+ } else {
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ Mikrotik::addPpoeUser($client, $p, $c);
+ }
+ }
+ }
+ }
+
+
+ public static function _raid()
+ {
+ return ORM::for_table('tbl_transactions')->max('id') + 1;
+ }
+
+ /**
+ * @param in tbl_transactions
+ * @param string $router_name router name for this package
+ * @param int $plan_id plan id for this package
+ * @param string $gateway payment gateway name
+ * @param string $channel channel payment gateway
+ * @return boolean
+ */
+ public static function createInvoice($in)
+ {
+ global $config, $admin, $ui;
+ $date = Lang::dateAndTimeFormat($in['recharged_on'], $in['recharged_time']);
+ if ($admin['id'] != $in['admin_id'] && $in['admin_id'] > 0) {
+ $_admin = Admin::_info($in['admin_id']);
+ // if admin not deleted
+ if ($_admin) $admin = $_admin;
+ } else {
+ $admin['fullname'] = 'Customer';
+ }
+ $cust = ORM::for_table('tbl_customers')->where('username', $in['username'])->findOne();
+
+ $note = '';
+ //print
+ $invoice = Lang::pad($config['CompanyName'], ' ', 2) . "\n";
+ $invoice .= Lang::pad($config['address'], ' ', 2) . "\n";
+ $invoice .= Lang::pad($config['phone'], ' ', 2) . "\n";
+ $invoice .= Lang::pad("", '=') . "\n";
+ $invoice .= Lang::pads("Invoice", $in['invoice'], ' ') . "\n";
+ $invoice .= Lang::pads(Lang::T('Date'), $date, ' ') . "\n";
+ $invoice .= Lang::pads(Lang::T('Sales'), $admin['fullname'], ' ') . "\n";
+ $invoice .= Lang::pad("", '=') . "\n";
+ $invoice .= Lang::pads(Lang::T('Type'), $in['type'], ' ') . "\n";
+ $invoice .= Lang::pads(Lang::T('Plan Name'), $in['plan_name'], ' ') . "\n";
+ if (!empty($in['note'])) {
+ $in['note'] = str_replace("\r", "", $in['note']);
+ $tmp = explode("\n", $in['note']);
+ foreach ($tmp as $t) {
+ if (strpos($t, " : ") === false) {
+ if (!empty($t)) {
+ $note .= "$t\n";
+ }
+ } else {
+ $tmp2 = explode(" : ", $t);
+ $invoice .= Lang::pads($tmp2[0], $tmp2[1], ' ') . "\n";
+ }
+ }
+ }
+ $invoice .= Lang::pads(Lang::T('Total'), Lang::moneyFormat($in['price']), ' ') . "\n";
+ $method = explode("-", $in['method']);
+ $invoice .= Lang::pads($method[0], $method[1], ' ') . "\n";
+ if (!empty($note)) {
+ $invoice .= Lang::pad("", '=') . "\n";
+ $invoice .= Lang::pad($note, ' ', 2) . "\n";
+ }
+ $invoice .= Lang::pad("", '=') . "\n";
+ if ($cust) {
+ $invoice .= Lang::pads(Lang::T('Full Name'), $cust['fullname'], ' ') . "\n";
+ }
+ $invoice .= Lang::pads(Lang::T('Username'), $in['username'], ' ') . "\n";
+ $invoice .= Lang::pads(Lang::T('Password'), '**********', ' ') . "\n";
+ if ($in['type'] != 'Balance') {
+ $invoice .= Lang::pads(Lang::T('Created On'), Lang::dateAndTimeFormat($in['recharged_on'], $in['recharged_time']), ' ') . "\n";
+ $invoice .= Lang::pads(Lang::T('Expires On'), Lang::dateAndTimeFormat($in['expiration'], $in['time']), ' ') . "\n";
+ }
+ $invoice .= Lang::pad("", '=') . "\n";
+ $invoice .= Lang::pad($config['note'], ' ', 2) . "\n";
+ $ui->assign('invoice', $invoice);
+ $config['printer_cols'] = 30;
+ //whatsapp
+ $invoice = Lang::pad($config['CompanyName'], ' ', 2) . "\n";
+ $invoice .= Lang::pad($config['address'], ' ', 2) . "\n";
+ $invoice .= Lang::pad($config['phone'], ' ', 2) . "\n";
+ $invoice .= Lang::pad("", '=') . "\n";
+ $invoice .= Lang::pads("Invoice", $in['invoice'], ' ') . "\n";
+ $invoice .= Lang::pads(Lang::T('Date'), $date, ' ') . "\n";
+ $invoice .= Lang::pads(Lang::T('Sales'), $admin['fullname'], ' ') . "\n";
+ $invoice .= Lang::pad("", '=') . "\n";
+ $invoice .= Lang::pads(Lang::T('Type'), $in['type'], ' ') . "\n";
+ $invoice .= Lang::pads(Lang::T('Plan Name'), $in['plan_name'], ' ') . "\n";
+ if (!empty($in['note'])) {
+ $invoice .= Lang::pad("", '=') . "\n";
+ foreach ($tmp as $t) {
+ if (strpos($t, " : ") === false) {
+ if (!empty($t)) {
+ $invoice .= Lang::pad($t, ' ', 2) . "\n";
+ }
+ } else {
+ $tmp2 = explode(" : ", $t);
+ $invoice .= Lang::pads($tmp2[0], $tmp2[1], ' ') . "\n";
+ }
+ }
+ }
+ $invoice .= Lang::pads(Lang::T('Total'), Lang::moneyFormat($in['price']), ' ') . "\n";
+ $invoice .= Lang::pads($method[0], $method[1], ' ') . "\n";
+ if (!empty($note)) {
+ $invoice .= Lang::pad("", '=') . "\n";
+ $invoice .= Lang::pad($note, ' ', 2) . "\n";
+ }
+ $invoice .= Lang::pad("", '=') . "\n";
+ if ($cust) {
+ $invoice .= Lang::pads(Lang::T('Full Name'), $cust['fullname'], ' ') . "\n";
+ }
+ $invoice .= Lang::pads(Lang::T('Username'), $in['username'], ' ') . "\n";
+ $invoice .= Lang::pads(Lang::T('Password'), '**********', ' ') . "\n";
+ if ($in['type'] != 'Balance') {
+ $invoice .= Lang::pads(Lang::T('Created On'), Lang::dateAndTimeFormat($in['recharged_on'], $in['recharged_time']), ' ') . "\n";
+ $invoice .= Lang::pads(Lang::T('Expires On'), Lang::dateAndTimeFormat($in['expiration'], $in['time']), ' ') . "\n";
+ }
+ $invoice .= Lang::pad("", '=') . "\n";
+ $invoice .= Lang::pad($config['note'], ' ', 2) . "\n";
+ $ui->assign('whatsapp', urlencode("```$invoice```"));
+ $ui->assign('in', $in);
+ }
+ public static function tax($price, $tax_rate = 1)
+ {
+ // Convert tax rate to decimal
+ $tax_rate_decimal = $tax_rate / 100;
+ $tax = $price * $tax_rate_decimal;
+ return $tax;
+ }
+}
diff --git a/system/autoload/Paginator.php b/system/autoload/Paginator.php
new file mode 100644
index 0000000..85441b8
--- /dev/null
+++ b/system/autoload/Paginator.php
@@ -0,0 +1,366 @@
+ 0) {
+ $url .= '&' . http_build_query($search);
+ }
+ $url .= '&p=';
+ $totalReq = $query->count();
+ $lastpage = ceil($totalReq / $per_page);
+ $lpm1 = $lastpage - 1;
+ $limit = $per_page;
+ $startpoint = ($page * $limit) - $limit;
+ if ($lastpage >= 1) {
+ $pages = [];
+ if ($lastpage < 7 + ($adjacents * 2)) {
+ for ($counter = 1; $counter <= $lastpage; $counter++) {
+ $pages[] = $counter;
+ }
+ } elseif ($lastpage > 5 + ($adjacents * 2)) {
+ if ($page < 1 + ($adjacents * 2)) {
+ for ($counter = 1; $counter < 4 + ($adjacents * 2); $counter++) {
+ $pages[] = $counter;
+ }
+ $pages[] = "...";
+ $pages[] = $lpm1;
+ $pages[] = $lastpage;
+ } elseif ($lastpage - ($adjacents * 2) > $page && $page > ($adjacents * 2)) {
+ $pages[] = "1";
+ $pages[] = "2";
+ $pages[] = "...";
+ for ($counter = $page - $adjacents; $counter <= $page + $adjacents; $counter++) {
+ $pages[] = $counter;
+ }
+ $pages[] = "...";
+ $pages[] = $lpm1;
+ $pages[] = $lastpage;
+ } else {
+ $pages[] = "1";
+ $pages[] = "2";
+ $pages[] = "...";
+ for ($counter = $lastpage - (2 + ($adjacents * 2)); $counter <= $lastpage; $counter++) {
+ $pages[] = $counter;
+ }
+ }
+ }
+
+ $result = [
+ 'count' => $lastpage,
+ 'limit' => $per_page,
+ 'startpoint' => $startpoint,
+ 'url' => $url,
+ 'page' => $page,
+ 'pages' => $pages,
+ 'prev' => ($page > 0) ? ($page - 1) : "0",
+ 'next' => ($page >= $lastpage) ? $lastpage : $page + 1
+ ];
+ if ($ui) {
+ $ui->assign('paginator', $result);
+ }
+ return $query->offset($startpoint)->limit($per_page)->find_many();
+ }
+ }
+
+ public static function build($table, $colVal = [], $query = '', $per_page = '10')
+ {
+ global $routes;
+ global $_L;
+ $url = U . implode('/', $routes);
+ $query = urlencode($query);
+ $adjacents = "2";
+ $page = (int)(empty(_get('p')) ? 1 : _get('p'));
+ $pagination = "";
+ foreach ($colVal as $k => $v) {
+ if (!is_array($v) && strpos($v, '%') === false) {
+ $table = $table->where($k, $v);
+ } else {
+ if (is_array($v)) {
+ $table = $table->where_in($k, $v);
+ } else {
+ $table = $table->where_like($k, $v);
+ }
+ }
+ }
+ $totalReq = $table->count();
+ $page = ($page == 0 ? 1 : $page);
+ $next = $page + 1;
+ $lastpage = ceil($totalReq / $per_page);
+ $lpm1 = $lastpage - 1;
+ $limit = $per_page;
+ $startpoint = ($page * $limit) - $limit;
+ if ($lastpage >= 1) {
+ $pagination .= '";
+ $pagination = '';
+ return array("startpoint" => $startpoint, "limit" => $limit, "found" => $totalReq, "page" => $page, "lastpage" => $lastpage, "contents" => $pagination);
+ }
+ }
+
+ public static function bootstrap($table, $w1 = '', $c1 = '', $w2 = '', $c2 = '', $w3 = '', $c3 = '', $w4 = '', $c4 = '', $per_page = '10')
+ {
+ global $routes;
+ global $_L;
+ $url = U . $routes['0'] . '/' . $routes['1'] . '/';
+ $adjacents = "2";
+ $page = (int)(!isset($routes['2']) ? 1 : $routes['2']);
+ $pagination = "";
+
+ if (is_object($table)) {
+ if ($w1 != '') {
+ $totalReq = $table->where($w1, $c1)->count();
+ } elseif ($w2 != '') {
+ $totalReq = $table->where($w1, $c1)->where($w2, $c2)->count();
+ } elseif ($w3 != '') {
+ $totalReq = $table->where($w1, $c1)->where($w2, $c2)->where($w3, $c3)->count();
+ } elseif ($w4 != '') {
+ $totalReq = $table->where($w1, $c1)->where($w2, $c2)->where($w3, $c3)->where($w4, $c4)->count();
+ } else {
+ $totalReq = $table->count();
+ }
+ } else {
+ if ($w1 != '') {
+ $totalReq = ORM::for_table($table)->where($w1, $c1)->count();
+ } elseif ($w2 != '') {
+ $totalReq = ORM::for_table($table)->where($w1, $c1)->where($w2, $c2)->count();
+ } elseif ($w3 != '') {
+ $totalReq = ORM::for_table($table)->where($w1, $c1)->where($w2, $c2)->where($w3, $c3)->count();
+ } elseif ($w4 != '') {
+ $totalReq = ORM::for_table($table)->where($w1, $c1)->where($w2, $c2)->where($w3, $c3)->where($w4, $c4)->count();
+ } else {
+ $totalReq = ORM::for_table($table)->count();
+ }
+ }
+
+ $i = 0;
+ $page = ($page == 0 ? 1 : $page);
+ $start = ($page - 1) * $per_page;
+
+ $prev = $page - 1;
+ $next = $page + 1;
+ $lastpage = ceil($totalReq / $per_page);
+
+ $lpm1 = $lastpage - 1;
+ $limit = $per_page;
+ $startpoint = ($page * $limit) - $limit;
+
+ if ($lastpage >= 1) {
+ $pagination .= '";
+ $pagination = '';
+
+ $gen = array("startpoint" => $startpoint, "limit" => $limit, "found" => $totalReq, "page" => $page, "lastpage" => $lastpage, "contents" => $pagination);
+ return $gen;
+ }
+ }
+
+ public static function bootstrapRaw($table, $w1 = '', $c1 = [], $per_page = '10')
+ {
+ global $routes;
+ global $_L;
+ $url = U . $routes['0'] . '/' . $routes['1'] . '/';
+ $adjacents = "2";
+ $page = (int)(!isset($routes['2']) ? 1 : $routes['2']);
+ $pagination = "";
+ if (is_object($table)) {
+ if ($w1 != '') {
+ $totalReq = $table->where_raw($w1, $c1)->count();
+ } else {
+ $totalReq = $table->count();
+ }
+ } else {
+ if ($w1 != '') {
+ $totalReq = ORM::for_table($table)->where_raw($w1, $c1)->count();
+ } else {
+ $totalReq = ORM::for_table($table)->count();
+ }
+ }
+
+ $i = 0;
+ $page = ($page == 0 ? 1 : $page);
+ $start = ($page - 1) * $per_page;
+
+ $prev = $page - 1;
+ $next = $page + 1;
+ $lastpage = ceil($totalReq / $per_page);
+
+ $lpm1 = $lastpage - 1;
+ $limit = $per_page;
+ $startpoint = ($page * $limit) - $limit;
+
+ if ($lastpage >= 1) {
+ $pagination .= '";
+ $pagination = '';
+
+ $gen = array("startpoint" => $startpoint, "limit" => $limit, "found" => $totalReq, "page" => $page, "lastpage" => $lastpage, "contents" => $pagination);
+ return $gen;
+ }
+ }
+}
diff --git a/system/autoload/Parsedown.php b/system/autoload/Parsedown.php
new file mode 100644
index 0000000..ae0cbde
--- /dev/null
+++ b/system/autoload/Parsedown.php
@@ -0,0 +1,1994 @@
+textElements($text);
+
+ # convert to markup
+ $markup = $this->elements($Elements);
+
+ # trim line breaks
+ $markup = trim($markup, "\n");
+
+ return $markup;
+ }
+
+ protected function textElements($text)
+ {
+ # make sure no definitions are set
+ $this->DefinitionData = array();
+
+ # standardize line breaks
+ $text = str_replace(array("\r\n", "\r"), "\n", $text);
+
+ # remove surrounding line breaks
+ $text = trim($text, "\n");
+
+ # split text into lines
+ $lines = explode("\n", $text);
+
+ # iterate through lines to identify blocks
+ return $this->linesElements($lines);
+ }
+
+ #
+ # Setters
+ #
+
+ function setBreaksEnabled($breaksEnabled)
+ {
+ $this->breaksEnabled = $breaksEnabled;
+
+ return $this;
+ }
+
+ protected $breaksEnabled;
+
+ function setMarkupEscaped($markupEscaped)
+ {
+ $this->markupEscaped = $markupEscaped;
+
+ return $this;
+ }
+
+ protected $markupEscaped;
+
+ function setUrlsLinked($urlsLinked)
+ {
+ $this->urlsLinked = $urlsLinked;
+
+ return $this;
+ }
+
+ protected $urlsLinked = true;
+
+ function setSafeMode($safeMode)
+ {
+ $this->safeMode = (bool) $safeMode;
+
+ return $this;
+ }
+
+ protected $safeMode;
+
+ function setStrictMode($strictMode)
+ {
+ $this->strictMode = (bool) $strictMode;
+
+ return $this;
+ }
+
+ protected $strictMode;
+
+ protected $safeLinksWhitelist = array(
+ 'http://',
+ 'https://',
+ 'ftp://',
+ 'ftps://',
+ 'mailto:',
+ 'tel:',
+ 'data:image/png;base64,',
+ 'data:image/gif;base64,',
+ 'data:image/jpeg;base64,',
+ 'irc:',
+ 'ircs:',
+ 'git:',
+ 'ssh:',
+ 'news:',
+ 'steam:',
+ );
+
+ #
+ # Lines
+ #
+
+ protected $BlockTypes = array(
+ '#' => array('Header'),
+ '*' => array('Rule', 'List'),
+ '+' => array('List'),
+ '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
+ '0' => array('List'),
+ '1' => array('List'),
+ '2' => array('List'),
+ '3' => array('List'),
+ '4' => array('List'),
+ '5' => array('List'),
+ '6' => array('List'),
+ '7' => array('List'),
+ '8' => array('List'),
+ '9' => array('List'),
+ ':' => array('Table'),
+ '<' => array('Comment', 'Markup'),
+ '=' => array('SetextHeader'),
+ '>' => array('Quote'),
+ '[' => array('Reference'),
+ '_' => array('Rule'),
+ '`' => array('FencedCode'),
+ '|' => array('Table'),
+ '~' => array('FencedCode'),
+ );
+
+ # ~
+
+ protected $unmarkedBlockTypes = array(
+ 'Code',
+ );
+
+ #
+ # Blocks
+ #
+
+ protected function lines(array $lines)
+ {
+ return $this->elements($this->linesElements($lines));
+ }
+
+ protected function linesElements(array $lines)
+ {
+ $Elements = array();
+ $CurrentBlock = null;
+
+ foreach ($lines as $line)
+ {
+ if (chop($line) === '')
+ {
+ if (isset($CurrentBlock))
+ {
+ $CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted'])
+ ? $CurrentBlock['interrupted'] + 1 : 1
+ );
+ }
+
+ continue;
+ }
+
+ while (($beforeTab = strstr($line, "\t", true)) !== false)
+ {
+ $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4;
+
+ $line = $beforeTab
+ . str_repeat(' ', $shortage)
+ . substr($line, strlen($beforeTab) + 1)
+ ;
+ }
+
+ $indent = strspn($line, ' ');
+
+ $text = $indent > 0 ? substr($line, $indent) : $line;
+
+ # ~
+
+ $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
+
+ # ~
+
+ if (isset($CurrentBlock['continuable']))
+ {
+ $methodName = 'block' . $CurrentBlock['type'] . 'Continue';
+ $Block = $this->$methodName($Line, $CurrentBlock);
+
+ if (isset($Block))
+ {
+ $CurrentBlock = $Block;
+
+ continue;
+ }
+ else
+ {
+ if ($this->isBlockCompletable($CurrentBlock['type']))
+ {
+ $methodName = 'block' . $CurrentBlock['type'] . 'Complete';
+ $CurrentBlock = $this->$methodName($CurrentBlock);
+ }
+ }
+ }
+
+ # ~
+
+ $marker = $text[0];
+
+ # ~
+
+ $blockTypes = $this->unmarkedBlockTypes;
+
+ if (isset($this->BlockTypes[$marker]))
+ {
+ foreach ($this->BlockTypes[$marker] as $blockType)
+ {
+ $blockTypes []= $blockType;
+ }
+ }
+
+ #
+ # ~
+
+ foreach ($blockTypes as $blockType)
+ {
+ $Block = $this->{"block$blockType"}($Line, $CurrentBlock);
+
+ if (isset($Block))
+ {
+ $Block['type'] = $blockType;
+
+ if ( ! isset($Block['identified']))
+ {
+ if (isset($CurrentBlock))
+ {
+ $Elements[] = $this->extractElement($CurrentBlock);
+ }
+
+ $Block['identified'] = true;
+ }
+
+ if ($this->isBlockContinuable($blockType))
+ {
+ $Block['continuable'] = true;
+ }
+
+ $CurrentBlock = $Block;
+
+ continue 2;
+ }
+ }
+
+ # ~
+
+ if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph')
+ {
+ $Block = $this->paragraphContinue($Line, $CurrentBlock);
+ }
+
+ if (isset($Block))
+ {
+ $CurrentBlock = $Block;
+ }
+ else
+ {
+ if (isset($CurrentBlock))
+ {
+ $Elements[] = $this->extractElement($CurrentBlock);
+ }
+
+ $CurrentBlock = $this->paragraph($Line);
+
+ $CurrentBlock['identified'] = true;
+ }
+ }
+
+ # ~
+
+ if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
+ {
+ $methodName = 'block' . $CurrentBlock['type'] . 'Complete';
+ $CurrentBlock = $this->$methodName($CurrentBlock);
+ }
+
+ # ~
+
+ if (isset($CurrentBlock))
+ {
+ $Elements[] = $this->extractElement($CurrentBlock);
+ }
+
+ # ~
+
+ return $Elements;
+ }
+
+ protected function extractElement(array $Component)
+ {
+ if ( ! isset($Component['element']))
+ {
+ if (isset($Component['markup']))
+ {
+ $Component['element'] = array('rawHtml' => $Component['markup']);
+ }
+ elseif (isset($Component['hidden']))
+ {
+ $Component['element'] = array();
+ }
+ }
+
+ return $Component['element'];
+ }
+
+ protected function isBlockContinuable($Type)
+ {
+ return method_exists($this, 'block' . $Type . 'Continue');
+ }
+
+ protected function isBlockCompletable($Type)
+ {
+ return method_exists($this, 'block' . $Type . 'Complete');
+ }
+
+ #
+ # Code
+
+ protected function blockCode($Line, $Block = null)
+ {
+ if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if ($Line['indent'] >= 4)
+ {
+ $text = substr($Line['body'], 4);
+
+ $Block = array(
+ 'element' => array(
+ 'name' => 'pre',
+ 'element' => array(
+ 'name' => 'code',
+ 'text' => $text,
+ ),
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockCodeContinue($Line, $Block)
+ {
+ if ($Line['indent'] >= 4)
+ {
+ if (isset($Block['interrupted']))
+ {
+ $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
+
+ unset($Block['interrupted']);
+ }
+
+ $Block['element']['element']['text'] .= "\n";
+
+ $text = substr($Line['body'], 4);
+
+ $Block['element']['element']['text'] .= $text;
+
+ return $Block;
+ }
+ }
+
+ protected function blockCodeComplete($Block)
+ {
+ return $Block;
+ }
+
+ #
+ # Comment
+
+ protected function blockComment($Line)
+ {
+ if ($this->markupEscaped or $this->safeMode)
+ {
+ return;
+ }
+
+ if (strpos($Line['text'], '') !== false)
+ {
+ $Block['closed'] = true;
+ }
+
+ return $Block;
+ }
+ }
+
+ protected function blockCommentContinue($Line, array $Block)
+ {
+ if (isset($Block['closed']))
+ {
+ return;
+ }
+
+ $Block['element']['rawHtml'] .= "\n" . $Line['body'];
+
+ if (strpos($Line['text'], '-->') !== false)
+ {
+ $Block['closed'] = true;
+ }
+
+ return $Block;
+ }
+
+ #
+ # Fenced Code
+
+ protected function blockFencedCode($Line)
+ {
+ $marker = $Line['text'][0];
+
+ $openerLength = strspn($Line['text'], $marker);
+
+ if ($openerLength < 3)
+ {
+ return;
+ }
+
+ $infostring = trim(substr($Line['text'], $openerLength), "\t ");
+
+ if (strpos($infostring, '`') !== false)
+ {
+ return;
+ }
+
+ $Element = array(
+ 'name' => 'code',
+ 'text' => '',
+ );
+
+ if ($infostring !== '')
+ {
+ /**
+ * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
+ * Every HTML element may have a class attribute specified.
+ * The attribute, if specified, must have a value that is a set
+ * of space-separated tokens representing the various classes
+ * that the element belongs to.
+ * [...]
+ * The space characters, for the purposes of this specification,
+ * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
+ * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
+ * U+000D CARRIAGE RETURN (CR).
+ */
+ $language = substr($infostring, 0, strcspn($infostring, " \t\n\f\r"));
+
+ $Element['attributes'] = array('class' => "language-$language");
+ }
+
+ $Block = array(
+ 'char' => $marker,
+ 'openerLength' => $openerLength,
+ 'element' => array(
+ 'name' => 'pre',
+ 'element' => $Element,
+ ),
+ );
+
+ return $Block;
+ }
+
+ protected function blockFencedCodeContinue($Line, $Block)
+ {
+ if (isset($Block['complete']))
+ {
+ return;
+ }
+
+ if (isset($Block['interrupted']))
+ {
+ $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
+
+ unset($Block['interrupted']);
+ }
+
+ if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength']
+ and chop(substr($Line['text'], $len), ' ') === ''
+ ) {
+ $Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1);
+
+ $Block['complete'] = true;
+
+ return $Block;
+ }
+
+ $Block['element']['element']['text'] .= "\n" . $Line['body'];
+
+ return $Block;
+ }
+
+ protected function blockFencedCodeComplete($Block)
+ {
+ return $Block;
+ }
+
+ #
+ # Header
+
+ protected function blockHeader($Line)
+ {
+ $level = strspn($Line['text'], '#');
+
+ if ($level > 6)
+ {
+ return;
+ }
+
+ $text = trim($Line['text'], '#');
+
+ if ($this->strictMode and isset($text[0]) and $text[0] !== ' ')
+ {
+ return;
+ }
+
+ $text = trim($text, ' ');
+
+ $Block = array(
+ 'element' => array(
+ 'name' => 'h' . $level,
+ 'handler' => array(
+ 'function' => 'lineElements',
+ 'argument' => $text,
+ 'destination' => 'elements',
+ )
+ ),
+ );
+
+ return $Block;
+ }
+
+ #
+ # List
+
+ protected function blockList($Line, array $CurrentBlock = null)
+ {
+ list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}+[.\)]');
+
+ if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches))
+ {
+ $contentIndent = strlen($matches[2]);
+
+ if ($contentIndent >= 5)
+ {
+ $contentIndent -= 1;
+ $matches[1] = substr($matches[1], 0, -$contentIndent);
+ $matches[3] = str_repeat(' ', $contentIndent) . $matches[3];
+ }
+ elseif ($contentIndent === 0)
+ {
+ $matches[1] .= ' ';
+ }
+
+ $markerWithoutWhitespace = strstr($matches[1], ' ', true);
+
+ $Block = array(
+ 'indent' => $Line['indent'],
+ 'pattern' => $pattern,
+ 'data' => array(
+ 'type' => $name,
+ 'marker' => $matches[1],
+ 'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)),
+ ),
+ 'element' => array(
+ 'name' => $name,
+ 'elements' => array(),
+ ),
+ );
+ $Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/');
+
+ if ($name === 'ol')
+ {
+ $listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0';
+
+ if ($listStart !== '1')
+ {
+ if (
+ isset($CurrentBlock)
+ and $CurrentBlock['type'] === 'Paragraph'
+ and ! isset($CurrentBlock['interrupted'])
+ ) {
+ return;
+ }
+
+ $Block['element']['attributes'] = array('start' => $listStart);
+ }
+ }
+
+ $Block['li'] = array(
+ 'name' => 'li',
+ 'handler' => array(
+ 'function' => 'li',
+ 'argument' => !empty($matches[3]) ? array($matches[3]) : array(),
+ 'destination' => 'elements'
+ )
+ );
+
+ $Block['element']['elements'] []= & $Block['li'];
+
+ return $Block;
+ }
+ }
+
+ protected function blockListContinue($Line, array $Block)
+ {
+ if (isset($Block['interrupted']) and empty($Block['li']['handler']['argument']))
+ {
+ return null;
+ }
+
+ $requiredIndent = ($Block['indent'] + strlen($Block['data']['marker']));
+
+ if ($Line['indent'] < $requiredIndent
+ and (
+ (
+ $Block['data']['type'] === 'ol'
+ and preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
+ ) or (
+ $Block['data']['type'] === 'ul'
+ and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
+ )
+ )
+ ) {
+ if (isset($Block['interrupted']))
+ {
+ $Block['li']['handler']['argument'] []= '';
+
+ $Block['loose'] = true;
+
+ unset($Block['interrupted']);
+ }
+
+ unset($Block['li']);
+
+ $text = isset($matches[1]) ? $matches[1] : '';
+
+ $Block['indent'] = $Line['indent'];
+
+ $Block['li'] = array(
+ 'name' => 'li',
+ 'handler' => array(
+ 'function' => 'li',
+ 'argument' => array($text),
+ 'destination' => 'elements'
+ )
+ );
+
+ $Block['element']['elements'] []= & $Block['li'];
+
+ return $Block;
+ }
+ elseif ($Line['indent'] < $requiredIndent and $this->blockList($Line))
+ {
+ return null;
+ }
+
+ if ($Line['text'][0] === '[' and $this->blockReference($Line))
+ {
+ return $Block;
+ }
+
+ if ($Line['indent'] >= $requiredIndent)
+ {
+ if (isset($Block['interrupted']))
+ {
+ $Block['li']['handler']['argument'] []= '';
+
+ $Block['loose'] = true;
+
+ unset($Block['interrupted']);
+ }
+
+ $text = substr($Line['body'], $requiredIndent);
+
+ $Block['li']['handler']['argument'] []= $text;
+
+ return $Block;
+ }
+
+ if ( ! isset($Block['interrupted']))
+ {
+ $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']);
+
+ $Block['li']['handler']['argument'] []= $text;
+
+ return $Block;
+ }
+ }
+
+ protected function blockListComplete(array $Block)
+ {
+ if (isset($Block['loose']))
+ {
+ foreach ($Block['element']['elements'] as &$li)
+ {
+ if (end($li['handler']['argument']) !== '')
+ {
+ $li['handler']['argument'] []= '';
+ }
+ }
+ }
+
+ return $Block;
+ }
+
+ #
+ # Quote
+
+ protected function blockQuote($Line)
+ {
+ if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
+ {
+ $Block = array(
+ 'element' => array(
+ 'name' => 'blockquote',
+ 'handler' => array(
+ 'function' => 'linesElements',
+ 'argument' => (array) $matches[1],
+ 'destination' => 'elements',
+ )
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockQuoteContinue($Line, array $Block)
+ {
+ if (isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
+ {
+ $Block['element']['handler']['argument'] []= $matches[1];
+
+ return $Block;
+ }
+
+ if ( ! isset($Block['interrupted']))
+ {
+ $Block['element']['handler']['argument'] []= $Line['text'];
+
+ return $Block;
+ }
+ }
+
+ #
+ # Rule
+
+ protected function blockRule($Line)
+ {
+ $marker = $Line['text'][0];
+
+ if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '')
+ {
+ $Block = array(
+ 'element' => array(
+ 'name' => 'hr',
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # Setext
+
+ protected function blockSetextHeader($Line, array $Block = null)
+ {
+ if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '')
+ {
+ $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
+
+ return $Block;
+ }
+ }
+
+ #
+ # Markup
+
+ protected function blockMarkup($Line)
+ {
+ if ($this->markupEscaped or $this->safeMode)
+ {
+ return;
+ }
+
+ if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches))
+ {
+ $element = strtolower($matches[1]);
+
+ if (in_array($element, $this->textLevelElements))
+ {
+ return;
+ }
+
+ $Block = array(
+ 'name' => $matches[1],
+ 'element' => array(
+ 'rawHtml' => $Line['text'],
+ 'autobreak' => true,
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockMarkupContinue($Line, array $Block)
+ {
+ if (isset($Block['closed']) or isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ $Block['element']['rawHtml'] .= "\n" . $Line['body'];
+
+ return $Block;
+ }
+
+ #
+ # Reference
+
+ protected function blockReference($Line)
+ {
+ if (strpos($Line['text'], ']') !== false
+ and preg_match('/^\[(.+?)\]:[ ]*+(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches)
+ ) {
+ $id = strtolower($matches[1]);
+
+ $Data = array(
+ 'url' => $matches[2],
+ 'title' => isset($matches[3]) ? $matches[3] : null,
+ );
+
+ $this->DefinitionData['Reference'][$id] = $Data;
+
+ $Block = array(
+ 'element' => array(),
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # Table
+
+ protected function blockTable($Line, array $Block = null)
+ {
+ if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if (
+ strpos($Block['element']['handler']['argument'], '|') === false
+ and strpos($Line['text'], '|') === false
+ and strpos($Line['text'], ':') === false
+ or strpos($Block['element']['handler']['argument'], "\n") !== false
+ ) {
+ return;
+ }
+
+ if (chop($Line['text'], ' -:|') !== '')
+ {
+ return;
+ }
+
+ $alignments = array();
+
+ $divider = $Line['text'];
+
+ $divider = trim($divider);
+ $divider = trim($divider, '|');
+
+ $dividerCells = explode('|', $divider);
+
+ foreach ($dividerCells as $dividerCell)
+ {
+ $dividerCell = trim($dividerCell);
+
+ if ($dividerCell === '')
+ {
+ return;
+ }
+
+ $alignment = null;
+
+ if ($dividerCell[0] === ':')
+ {
+ $alignment = 'left';
+ }
+
+ if (substr($dividerCell, - 1) === ':')
+ {
+ $alignment = $alignment === 'left' ? 'center' : 'right';
+ }
+
+ $alignments []= $alignment;
+ }
+
+ # ~
+
+ $HeaderElements = array();
+
+ $header = $Block['element']['handler']['argument'];
+
+ $header = trim($header);
+ $header = trim($header, '|');
+
+ $headerCells = explode('|', $header);
+
+ if (count($headerCells) !== count($alignments))
+ {
+ return;
+ }
+
+ foreach ($headerCells as $index => $headerCell)
+ {
+ $headerCell = trim($headerCell);
+
+ $HeaderElement = array(
+ 'name' => 'th',
+ 'handler' => array(
+ 'function' => 'lineElements',
+ 'argument' => $headerCell,
+ 'destination' => 'elements',
+ )
+ );
+
+ if (isset($alignments[$index]))
+ {
+ $alignment = $alignments[$index];
+
+ $HeaderElement['attributes'] = array(
+ 'style' => "text-align: $alignment;",
+ );
+ }
+
+ $HeaderElements []= $HeaderElement;
+ }
+
+ # ~
+
+ $Block = array(
+ 'alignments' => $alignments,
+ 'identified' => true,
+ 'element' => array(
+ 'name' => 'table',
+ 'elements' => array(),
+ ),
+ );
+
+ $Block['element']['elements'] []= array(
+ 'name' => 'thead',
+ );
+
+ $Block['element']['elements'] []= array(
+ 'name' => 'tbody',
+ 'elements' => array(),
+ );
+
+ $Block['element']['elements'][0]['elements'] []= array(
+ 'name' => 'tr',
+ 'elements' => $HeaderElements,
+ );
+
+ return $Block;
+ }
+
+ protected function blockTableContinue($Line, array $Block)
+ {
+ if (isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if (count($Block['alignments']) === 1 or $Line['text'][0] === '|' or strpos($Line['text'], '|'))
+ {
+ $Elements = array();
+
+ $row = $Line['text'];
+
+ $row = trim($row);
+ $row = trim($row, '|');
+
+ preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches);
+
+ $cells = array_slice($matches[0], 0, count($Block['alignments']));
+
+ foreach ($cells as $index => $cell)
+ {
+ $cell = trim($cell);
+
+ $Element = array(
+ 'name' => 'td',
+ 'handler' => array(
+ 'function' => 'lineElements',
+ 'argument' => $cell,
+ 'destination' => 'elements',
+ )
+ );
+
+ if (isset($Block['alignments'][$index]))
+ {
+ $Element['attributes'] = array(
+ 'style' => 'text-align: ' . $Block['alignments'][$index] . ';',
+ );
+ }
+
+ $Elements []= $Element;
+ }
+
+ $Element = array(
+ 'name' => 'tr',
+ 'elements' => $Elements,
+ );
+
+ $Block['element']['elements'][1]['elements'] []= $Element;
+
+ return $Block;
+ }
+ }
+
+ #
+ # ~
+ #
+
+ protected function paragraph($Line)
+ {
+ return array(
+ 'type' => 'Paragraph',
+ 'element' => array(
+ 'name' => 'p',
+ 'handler' => array(
+ 'function' => 'lineElements',
+ 'argument' => $Line['text'],
+ 'destination' => 'elements',
+ ),
+ ),
+ );
+ }
+
+ protected function paragraphContinue($Line, array $Block)
+ {
+ if (isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ $Block['element']['handler']['argument'] .= "\n".$Line['text'];
+
+ return $Block;
+ }
+
+ #
+ # Inline Elements
+ #
+
+ protected $InlineTypes = array(
+ '!' => array('Image'),
+ '&' => array('SpecialCharacter'),
+ '*' => array('Emphasis'),
+ ':' => array('Url'),
+ '<' => array('UrlTag', 'EmailTag', 'Markup'),
+ '[' => array('Link'),
+ '_' => array('Emphasis'),
+ '`' => array('Code'),
+ '~' => array('Strikethrough'),
+ '\\' => array('EscapeSequence'),
+ );
+
+ # ~
+
+ protected $inlineMarkerList = '!*_&[:<`~\\';
+
+ #
+ # ~
+ #
+
+ public function line($text, $nonNestables = array())
+ {
+ return $this->elements($this->lineElements($text, $nonNestables));
+ }
+
+ protected function lineElements($text, $nonNestables = array())
+ {
+ # standardize line breaks
+ $text = str_replace(array("\r\n", "\r"), "\n", $text);
+
+ $Elements = array();
+
+ $nonNestables = (empty($nonNestables)
+ ? array()
+ : array_combine($nonNestables, $nonNestables)
+ );
+
+ # $excerpt is based on the first occurrence of a marker
+
+ while ($excerpt = strpbrk($text, $this->inlineMarkerList))
+ {
+ $marker = $excerpt[0];
+
+ $markerPosition = strlen($text) - strlen($excerpt);
+
+ $Excerpt = array('text' => $excerpt, 'context' => $text);
+
+ foreach ($this->InlineTypes[$marker] as $inlineType)
+ {
+ # check to see if the current inline type is nestable in the current context
+
+ if (isset($nonNestables[$inlineType]))
+ {
+ continue;
+ }
+
+ $Inline = $this->{"inline$inlineType"}($Excerpt);
+
+ if ( ! isset($Inline))
+ {
+ continue;
+ }
+
+ # makes sure that the inline belongs to "our" marker
+
+ if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
+ {
+ continue;
+ }
+
+ # sets a default inline position
+
+ if ( ! isset($Inline['position']))
+ {
+ $Inline['position'] = $markerPosition;
+ }
+
+ # cause the new element to 'inherit' our non nestables
+
+
+ $Inline['element']['nonNestables'] = isset($Inline['element']['nonNestables'])
+ ? array_merge($Inline['element']['nonNestables'], $nonNestables)
+ : $nonNestables
+ ;
+
+ # the text that comes before the inline
+ $unmarkedText = substr($text, 0, $Inline['position']);
+
+ # compile the unmarked text
+ $InlineText = $this->inlineText($unmarkedText);
+ $Elements[] = $InlineText['element'];
+
+ # compile the inline
+ $Elements[] = $this->extractElement($Inline);
+
+ # remove the examined text
+ $text = substr($text, $Inline['position'] + $Inline['extent']);
+
+ continue 2;
+ }
+
+ # the marker does not belong to an inline
+
+ $unmarkedText = substr($text, 0, $markerPosition + 1);
+
+ $InlineText = $this->inlineText($unmarkedText);
+ $Elements[] = $InlineText['element'];
+
+ $text = substr($text, $markerPosition + 1);
+ }
+
+ $InlineText = $this->inlineText($text);
+ $Elements[] = $InlineText['element'];
+
+ foreach ($Elements as &$Element)
+ {
+ if ( ! isset($Element['autobreak']))
+ {
+ $Element['autobreak'] = false;
+ }
+ }
+
+ return $Elements;
+ }
+
+ #
+ # ~
+ #
+
+ protected function inlineText($text)
+ {
+ $Inline = array(
+ 'extent' => strlen($text),
+ 'element' => array(),
+ );
+
+ $Inline['element']['elements'] = self::pregReplaceElements(
+ $this->breaksEnabled ? '/[ ]*+\n/' : '/(?:[ ]*+\\\\|[ ]{2,}+)\n/',
+ array(
+ array('name' => 'br'),
+ array('text' => "\n"),
+ ),
+ $text
+ );
+
+ return $Inline;
+ }
+
+ protected function inlineCode($Excerpt)
+ {
+ $marker = $Excerpt['text'][0];
+
+ if (preg_match('/^(['.$marker.']++)[ ]*+(.+?)[ ]*+(? strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'code',
+ 'text' => $text,
+ ),
+ );
+ }
+ }
+
+ protected function inlineEmailTag($Excerpt)
+ {
+ $hostnameLabel = '[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?';
+
+ $commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]++@'
+ . $hostnameLabel . '(?:\.' . $hostnameLabel . ')*';
+
+ if (strpos($Excerpt['text'], '>') !== false
+ and preg_match("/^<((mailto:)?$commonMarkEmail)>/i", $Excerpt['text'], $matches)
+ ){
+ $url = $matches[1];
+
+ if ( ! isset($matches[2]))
+ {
+ $url = "mailto:$url";
+ }
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $matches[1],
+ 'attributes' => array(
+ 'href' => $url,
+ ),
+ ),
+ );
+ }
+ }
+
+ protected function inlineEmphasis($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]))
+ {
+ return;
+ }
+
+ $marker = $Excerpt['text'][0];
+
+ if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
+ {
+ $emphasis = 'strong';
+ }
+ elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
+ {
+ $emphasis = 'em';
+ }
+ else
+ {
+ return;
+ }
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => $emphasis,
+ 'handler' => array(
+ 'function' => 'lineElements',
+ 'argument' => $matches[1],
+ 'destination' => 'elements',
+ )
+ ),
+ );
+ }
+
+ protected function inlineEscapeSequence($Excerpt)
+ {
+ if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
+ {
+ return array(
+ 'element' => array('rawHtml' => $Excerpt['text'][1]),
+ 'extent' => 2,
+ );
+ }
+ }
+
+ protected function inlineImage($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
+ {
+ return;
+ }
+
+ $Excerpt['text']= substr($Excerpt['text'], 1);
+
+ $Link = $this->inlineLink($Excerpt);
+
+ if ($Link === null)
+ {
+ return;
+ }
+
+ $Inline = array(
+ 'extent' => $Link['extent'] + 1,
+ 'element' => array(
+ 'name' => 'img',
+ 'attributes' => array(
+ 'src' => $Link['element']['attributes']['href'],
+ 'alt' => $Link['element']['handler']['argument'],
+ ),
+ 'autobreak' => true,
+ ),
+ );
+
+ $Inline['element']['attributes'] += $Link['element']['attributes'];
+
+ unset($Inline['element']['attributes']['href']);
+
+ return $Inline;
+ }
+
+ protected function inlineLink($Excerpt)
+ {
+ $Element = array(
+ 'name' => 'a',
+ 'handler' => array(
+ 'function' => 'lineElements',
+ 'argument' => null,
+ 'destination' => 'elements',
+ ),
+ 'nonNestables' => array('Url', 'Link'),
+ 'attributes' => array(
+ 'href' => null,
+ 'title' => null,
+ ),
+ );
+
+ $extent = 0;
+
+ $remainder = $Excerpt['text'];
+
+ if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
+ {
+ $Element['handler']['argument'] = $matches[1];
+
+ $extent += strlen($matches[0]);
+
+ $remainder = substr($remainder, $extent);
+ }
+ else
+ {
+ return;
+ }
+
+ if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches))
+ {
+ $Element['attributes']['href'] = $matches[1];
+
+ if (isset($matches[2]))
+ {
+ $Element['attributes']['title'] = substr($matches[2], 1, - 1);
+ }
+
+ $extent += strlen($matches[0]);
+ }
+ else
+ {
+ if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
+ {
+ $definition = strlen($matches[1]) ? $matches[1] : $Element['handler']['argument'];
+ $definition = strtolower($definition);
+
+ $extent += strlen($matches[0]);
+ }
+ else
+ {
+ $definition = strtolower($Element['handler']['argument']);
+ }
+
+ if ( ! isset($this->DefinitionData['Reference'][$definition]))
+ {
+ return;
+ }
+
+ $Definition = $this->DefinitionData['Reference'][$definition];
+
+ $Element['attributes']['href'] = $Definition['url'];
+ $Element['attributes']['title'] = $Definition['title'];
+ }
+
+ return array(
+ 'extent' => $extent,
+ 'element' => $Element,
+ );
+ }
+
+ protected function inlineMarkup($Excerpt)
+ {
+ if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
+ {
+ return;
+ }
+
+ if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'element' => array('rawHtml' => $matches[0]),
+ 'extent' => strlen($matches[0]),
+ );
+ }
+
+ if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'element' => array('rawHtml' => $matches[0]),
+ 'extent' => strlen($matches[0]),
+ );
+ }
+
+ if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'element' => array('rawHtml' => $matches[0]),
+ 'extent' => strlen($matches[0]),
+ );
+ }
+ }
+
+ protected function inlineSpecialCharacter($Excerpt)
+ {
+ if (substr($Excerpt['text'], 1, 1) !== ' ' and strpos($Excerpt['text'], ';') !== false
+ and preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches)
+ ) {
+ return array(
+ 'element' => array('rawHtml' => '&' . $matches[1] . ';'),
+ 'extent' => strlen($matches[0]),
+ );
+ }
+
+ return;
+ }
+
+ protected function inlineStrikethrough($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]))
+ {
+ return;
+ }
+
+ if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'del',
+ 'handler' => array(
+ 'function' => 'lineElements',
+ 'argument' => $matches[1],
+ 'destination' => 'elements',
+ )
+ ),
+ );
+ }
+ }
+
+ protected function inlineUrl($Excerpt)
+ {
+ if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
+ {
+ return;
+ }
+
+ if (strpos($Excerpt['context'], 'http') !== false
+ and preg_match('/\bhttps?+:[\/]{2}[^\s<]+\b\/*+/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)
+ ) {
+ $url = $matches[0][0];
+
+ $Inline = array(
+ 'extent' => strlen($matches[0][0]),
+ 'position' => $matches[0][1],
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $url,
+ 'attributes' => array(
+ 'href' => $url,
+ ),
+ ),
+ );
+
+ return $Inline;
+ }
+ }
+
+ protected function inlineUrlTag($Excerpt)
+ {
+ if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches))
+ {
+ $url = $matches[1];
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $url,
+ 'attributes' => array(
+ 'href' => $url,
+ ),
+ ),
+ );
+ }
+ }
+
+ # ~
+
+ protected function unmarkedText($text)
+ {
+ $Inline = $this->inlineText($text);
+ return $this->element($Inline['element']);
+ }
+
+ #
+ # Handlers
+ #
+
+ protected function handle(array $Element)
+ {
+ if (isset($Element['handler']))
+ {
+ if (!isset($Element['nonNestables']))
+ {
+ $Element['nonNestables'] = array();
+ }
+
+ if (is_string($Element['handler']))
+ {
+ $function = $Element['handler'];
+ $argument = $Element['text'];
+ unset($Element['text']);
+ $destination = 'rawHtml';
+ }
+ else
+ {
+ $function = $Element['handler']['function'];
+ $argument = $Element['handler']['argument'];
+ $destination = $Element['handler']['destination'];
+ }
+
+ $Element[$destination] = $this->{$function}($argument, $Element['nonNestables']);
+
+ if ($destination === 'handler')
+ {
+ $Element = $this->handle($Element);
+ }
+
+ unset($Element['handler']);
+ }
+
+ return $Element;
+ }
+
+ protected function handleElementRecursive(array $Element)
+ {
+ return $this->elementApplyRecursive(array($this, 'handle'), $Element);
+ }
+
+ protected function handleElementsRecursive(array $Elements)
+ {
+ return $this->elementsApplyRecursive(array($this, 'handle'), $Elements);
+ }
+
+ protected function elementApplyRecursive($closure, array $Element)
+ {
+ $Element = call_user_func($closure, $Element);
+
+ if (isset($Element['elements']))
+ {
+ $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']);
+ }
+ elseif (isset($Element['element']))
+ {
+ $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']);
+ }
+
+ return $Element;
+ }
+
+ protected function elementApplyRecursiveDepthFirst($closure, array $Element)
+ {
+ if (isset($Element['elements']))
+ {
+ $Element['elements'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['elements']);
+ }
+ elseif (isset($Element['element']))
+ {
+ $Element['element'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['element']);
+ }
+
+ $Element = call_user_func($closure, $Element);
+
+ return $Element;
+ }
+
+ protected function elementsApplyRecursive($closure, array $Elements)
+ {
+ foreach ($Elements as &$Element)
+ {
+ $Element = $this->elementApplyRecursive($closure, $Element);
+ }
+
+ return $Elements;
+ }
+
+ protected function elementsApplyRecursiveDepthFirst($closure, array $Elements)
+ {
+ foreach ($Elements as &$Element)
+ {
+ $Element = $this->elementApplyRecursiveDepthFirst($closure, $Element);
+ }
+
+ return $Elements;
+ }
+
+ protected function element(array $Element)
+ {
+ if ($this->safeMode)
+ {
+ $Element = $this->sanitiseElement($Element);
+ }
+
+ # identity map if element has no handler
+ $Element = $this->handle($Element);
+
+ $hasName = isset($Element['name']);
+
+ $markup = '';
+
+ if ($hasName)
+ {
+ $markup .= '<' . $Element['name'];
+
+ if (isset($Element['attributes']))
+ {
+ foreach ($Element['attributes'] as $name => $value)
+ {
+ if ($value === null)
+ {
+ continue;
+ }
+
+ $markup .= " $name=\"".self::escape($value).'"';
+ }
+ }
+ }
+
+ $permitRawHtml = false;
+
+ if (isset($Element['text']))
+ {
+ $text = $Element['text'];
+ }
+ // very strongly consider an alternative if you're writing an
+ // extension
+ elseif (isset($Element['rawHtml']))
+ {
+ $text = $Element['rawHtml'];
+
+ $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode'];
+ $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode;
+ }
+
+ $hasContent = isset($text) || isset($Element['element']) || isset($Element['elements']);
+
+ if ($hasContent)
+ {
+ $markup .= $hasName ? '>' : '';
+
+ if (isset($Element['elements']))
+ {
+ $markup .= $this->elements($Element['elements']);
+ }
+ elseif (isset($Element['element']))
+ {
+ $markup .= $this->element($Element['element']);
+ }
+ else
+ {
+ if (!$permitRawHtml)
+ {
+ $markup .= self::escape($text, true);
+ }
+ else
+ {
+ $markup .= $text;
+ }
+ }
+
+ $markup .= $hasName ? '' . $Element['name'] . '>' : '';
+ }
+ elseif ($hasName)
+ {
+ $markup .= ' />';
+ }
+
+ return $markup;
+ }
+
+ protected function elements(array $Elements)
+ {
+ $markup = '';
+
+ $autoBreak = true;
+
+ foreach ($Elements as $Element)
+ {
+ if (empty($Element))
+ {
+ continue;
+ }
+
+ $autoBreakNext = (isset($Element['autobreak'])
+ ? $Element['autobreak'] : isset($Element['name'])
+ );
+ // (autobreak === false) covers both sides of an element
+ $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext;
+
+ $markup .= ($autoBreak ? "\n" : '') . $this->element($Element);
+ $autoBreak = $autoBreakNext;
+ }
+
+ $markup .= $autoBreak ? "\n" : '';
+
+ return $markup;
+ }
+
+ # ~
+
+ protected function li($lines)
+ {
+ $Elements = $this->linesElements($lines);
+
+ if ( ! in_array('', $lines)
+ and isset($Elements[0]) and isset($Elements[0]['name'])
+ and $Elements[0]['name'] === 'p'
+ ) {
+ unset($Elements[0]['name']);
+ }
+
+ return $Elements;
+ }
+
+ #
+ # AST Convenience
+ #
+
+ /**
+ * Replace occurrences $regexp with $Elements in $text. Return an array of
+ * elements representing the replacement.
+ */
+ protected static function pregReplaceElements($regexp, $Elements, $text)
+ {
+ $newElements = array();
+
+ while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE))
+ {
+ $offset = $matches[0][1];
+ $before = substr($text, 0, $offset);
+ $after = substr($text, $offset + strlen($matches[0][0]));
+
+ $newElements[] = array('text' => $before);
+
+ foreach ($Elements as $Element)
+ {
+ $newElements[] = $Element;
+ }
+
+ $text = $after;
+ }
+
+ $newElements[] = array('text' => $text);
+
+ return $newElements;
+ }
+
+ #
+ # Deprecated Methods
+ #
+
+ function parse($text)
+ {
+ $markup = $this->text($text);
+
+ return $markup;
+ }
+
+ protected function sanitiseElement(array $Element)
+ {
+ static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/';
+ static $safeUrlNameToAtt = array(
+ 'a' => 'href',
+ 'img' => 'src',
+ );
+
+ if ( ! isset($Element['name']))
+ {
+ unset($Element['attributes']);
+ return $Element;
+ }
+
+ if (isset($safeUrlNameToAtt[$Element['name']]))
+ {
+ $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]);
+ }
+
+ if ( ! empty($Element['attributes']))
+ {
+ foreach ($Element['attributes'] as $att => $val)
+ {
+ # filter out badly parsed attribute
+ if ( ! preg_match($goodAttribute, $att))
+ {
+ unset($Element['attributes'][$att]);
+ }
+ # dump onevent attribute
+ elseif (self::striAtStart($att, 'on'))
+ {
+ unset($Element['attributes'][$att]);
+ }
+ }
+ }
+
+ return $Element;
+ }
+
+ protected function filterUnsafeUrlInAttribute(array $Element, $attribute)
+ {
+ foreach ($this->safeLinksWhitelist as $scheme)
+ {
+ if (self::striAtStart($Element['attributes'][$attribute], $scheme))
+ {
+ return $Element;
+ }
+ }
+
+ $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]);
+
+ return $Element;
+ }
+
+ #
+ # Static Methods
+ #
+
+ protected static function escape($text, $allowQuotes = false)
+ {
+ return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8');
+ }
+
+ protected static function striAtStart($string, $needle)
+ {
+ $len = strlen($needle);
+
+ if ($len > strlen($string))
+ {
+ return false;
+ }
+ else
+ {
+ return strtolower(substr($string, 0, $len)) === strtolower($needle);
+ }
+ }
+
+ static function instance($name = 'default')
+ {
+ if (isset(self::$instances[$name]))
+ {
+ return self::$instances[$name];
+ }
+
+ $instance = new static();
+
+ self::$instances[$name] = $instance;
+
+ return $instance;
+ }
+
+ private static $instances = array();
+
+ #
+ # Fields
+ #
+
+ protected $DefinitionData;
+
+ #
+ # Read-Only
+
+ protected $specialCharacters = array(
+ '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', '~'
+ );
+
+ protected $StrongRegex = array(
+ '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s',
+ '_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us',
+ );
+
+ protected $EmRegex = array(
+ '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
+ '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
+ );
+
+ protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+';
+
+ protected $voidElements = array(
+ 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
+ );
+
+ protected $textLevelElements = array(
+ 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
+ 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
+ 'i', 'rp', 'del', 'code', 'strike', 'marquee',
+ 'q', 'rt', 'ins', 'font', 'strong',
+ 's', 'tt', 'kbd', 'mark',
+ 'u', 'xm', 'sub', 'nobr',
+ 'sup', 'ruby',
+ 'var', 'span',
+ 'wbr', 'time',
+ );
+}
diff --git a/system/autoload/Password.php b/system/autoload/Password.php
new file mode 100644
index 0000000..989e27b
--- /dev/null
+++ b/system/autoload/Password.php
@@ -0,0 +1,35 @@
+create();
+ $n->nasname = $ip;
+ $n->shortname = $name;
+ $n->type = $type;
+ $n->ports = $ports;
+ $n->secret = $secret;
+ $n->description = $description;
+ $n->server = $server;
+ $n->community = $community;
+ $n->routers = $routers;
+ $n->save();
+ return $n->id();
+ }
+
+ public static function nasUpdate($id, $name, $ip, $ports, $secret, $routers = "", $description = "", $type = 'other', $server = null, $community = null)
+ {
+ $n = Radius::getTableNas()->find_one($id);
+ if (empty($n)) {
+ return false;
+ }
+ $n->nasname = $ip;
+ $n->shortname = $name;
+ $n->type = $type;
+ $n->ports = $ports;
+ $n->secret = $secret;
+ $n->description = $description;
+ $n->server = $server;
+ $n->community = $community;
+ $n->routers = $routers;
+ return $n->save();
+ }
+
+ public static function planUpSert($plan_id, $rate, $pool = null)
+ {
+ $rates = explode('/', $rate);
+ ##burst fixed
+ if (strpos($rate, ' ')) {
+ $ratos = $rates[0] . '/' . $rates[1] . ' ' . $rates[2] . '/' . $rates[3] . '/' . $rates[4] . '/' . $rates[5] . '/' . $rates[6];
+ } else {
+ $ratos = $rates[0] . '/' . $rates[1];
+ }
+
+ Radius::upsertPackage($plan_id, 'Ascend-Data-Rate', $rates[1], ':=');
+ Radius::upsertPackage($plan_id, 'Ascend-Xmit-Rate', $rates[0], ':=');
+ Radius::upsertPackage($plan_id, 'Mikrotik-Rate-Limit', $ratos, ':=');
+ // if ($pool != null) {
+ // Radius::upsertPackage($plan_id, 'Framed-Pool', $pool, ':=');
+ // }
+ }
+
+ public static function planDelete($plan_id)
+ {
+ // Delete Plan
+ Radius::getTablePackage()->where_equal('plan_id', "plan_" . $plan_id)->delete_many();
+ // Reset User Plan
+ $c = Radius::getTableUserPackage()->where_equal('groupname', "plan_" . $plan_id)->findMany();
+ if ($c) {
+ foreach ($c as $u) {
+ $u->groupname = '';
+ $u->save();
+ }
+ }
+ }
+
+
+ public static function customerChangeUsername($from, $to)
+ {
+ $c = Radius::getTableCustomer()->where_equal('username', $from)->findMany();
+ if ($c) {
+ foreach ($c as $u) {
+ $u->username = $to;
+ $u->save();
+ }
+ }
+ $c = Radius::getTableUserPackage()->where_equal('username', $from)->findMany();
+ if ($c) {
+ foreach ($c as $u) {
+ $u->username = $to;
+ $u->save();
+ }
+ }
+ }
+
+ public static function customerDeactivate($username, $radiusDisconnect = true)
+ { {
+ global $radius_pass;
+ $r = Radius::getTableCustomer()->where_equal('username', $username)->whereEqual('attribute', 'Cleartext-Password')->findOne();
+ if ($r) {
+ // no need to delete, because it will make ID got higher
+ // we just change the password
+ $r->value = md5(time() . $username . $radius_pass);
+ $r->save();
+ if ($radiusDisconnect)
+ return Radius::disconnectCustomer($username);
+ }
+ }
+ return '';
+ }
+
+ public static function customerDelete($username)
+ {
+ Radius::getTableCustomer()->where_equal('username', $username)->delete_many();
+ Radius::getTableUserPackage()->where_equal('username', $username)->delete_many();
+ }
+
+ /**
+ * When add a plan to Customer, use this
+ */
+ public static function customerAddPlan($customer, $plan, $expired = null)
+ {
+ global $config;
+ if (Radius::customerUpsert($customer, $plan)) {
+ $p = Radius::getTableUserPackage()->where_equal('username', $customer['username'])->findOne();
+ if ($p) {
+ // if exists
+ Radius::delAtribute(Radius::getTableCustomer(), 'Max-All-Session', 'username', $customer['username']);
+ Radius::delAtribute(Radius::getTableCustomer(), 'Max-Volume', 'username', $customer['username']);
+ $p->groupname = "plan_" . $plan['id'];
+ $p->save();
+ } else {
+ $p = Radius::getTableUserPackage()->create();
+ $p->username = $customer['username'];
+ $p->groupname = "plan_" . $plan['id'];
+ $p->priority = 1;
+ $p->save();
+ }
+ if ($plan['type'] == 'Hotspot' && $plan['typebp'] == "Limited") {
+ if ($plan['limit_type'] == "Time_Limit") {
+ if ($plan['time_unit'] == 'Hrs')
+ $timelimit = $plan['time_limit'] * 60 * 60;
+ else
+ $timelimit = $plan['time_limit'] * 60;
+ Radius::upsertCustomer($customer['username'], 'Max-All-Session', $timelimit);
+ //Radius::upsertCustomer($customer['username'], 'Expire-After', $timelimit);
+ } else if ($plan['limit_type'] == "Data_Limit") {
+ if ($plan['data_unit'] == 'GB')
+ $datalimit = $plan['data_limit'] . "000000000";
+ else
+ $datalimit = $plan['data_limit'] . "000000";
+ Radius::upsertCustomer($customer['username'], 'Max-Volume', $datalimit);
+ // Mikrotik Spesific
+ //Radius::upsertCustomer($customer['username'], 'Max-Data', $datalimit);
+ //Radius::upsertCustomer($customer['username'], 'Mikrotik-Total-Limit', $datalimit);
+ } else if ($plan['limit_type'] == "Both_Limit") {
+ if ($plan['time_unit'] == 'Hrs')
+ $timelimit = $plan['time_limit'] * 60 * 60;
+ else
+ $timelimit = $plan['time_limit'] * 60;
+ if ($plan['data_unit'] == 'GB')
+ $datalimit = $plan['data_limit'] . "000000000";
+ else
+ $datalimit = $plan['data_limit'] . "000000";
+ Radius::upsertCustomer($customer['username'], 'Max-Volume', $datalimit);
+ Radius::upsertCustomer($customer['username'], 'Max-All-Session', $timelimit);
+ // Mikrotik Spesific
+ //Radius::upsertCustomer($customer['username'], 'Max-Data', $datalimit);
+ //Radius::upsertCustomer($customer['username'], 'Mikrotik-Total-Limit', $datalimit);
+
+
+
+
+ }
+ } else {
+ Radius::delAtribute(Radius::getTableCustomer(), 'Max-Volume', 'username', $customer['username']);
+ Radius::delAtribute(Radius::getTableCustomer(), 'Max-All-Session', 'username', $customer['username']);
+ //Radius::delAtribute(Radius::getTableCustomer(), 'Max-Data', 'username', $customer['username']);
+ }
+
+ Radius::disconnectCustomer($customer['username']);
+ Radius::getTableAcct()->where_equal('username', $customer['username'])->delete_many();
+
+
+ // expired user
+ if ($expired != null) {
+ //Radius::upsertCustomer($customer['username'], 'access-period', strtotime($expired) - time());
+ Radius::upsertCustomer($customer['username'], 'Max-All-Session', strtotime($expired) - time());
+ //Radius::upsertCustomer($customer['username'], 'expiration', date('d M Y H:i:s', strtotime($expired)));
+ // Mikrotik Spesific
+ Radius::upsertCustomer(
+ $customer['username'],
+ 'WISPr-Session-Terminate-Time',
+ date('Y-m-d', strtotime($expired)) . 'T' . date('H:i:s', strtotime($expired)) . Timezone::getTimeOffset($config['timezone'])
+ );
+ } else {
+ Radius::delAtribute(Radius::getTableCustomer(), 'Max-All-Session', 'username', $customer['username']);
+ //Radius::delAtribute(Radius::getTableCustomer(), 'access-period', 'username', $customer['username']);
+ //Radius::delAtribute(Radius::getTableCustomer(), 'expiration', 'username', $customer['username']);
+ }
+
+ if ($plan['type'] == 'PPPOE') {
+ Radius::upsertCustomerAttr($customer['username'], 'Framed-Pool', $plan['pool'], ':=');
+ }
+
+
+ return true;
+ }
+ return false;
+ }
+
+ public static function customerUpsert($customer, $plan)
+ {
+ if ($plan['type'] == 'PPPOE') {
+ Radius::upsertCustomer($customer['username'], 'Cleartext-Password', (empty($customer['pppoe_password'])) ? $customer['password'] : $customer['pppoe_password']);
+ } else {
+ Radius::upsertCustomer($customer['username'], 'Cleartext-Password', $customer['password']);
+ }
+ Radius::upsertCustomer($customer['username'], 'Simultaneous-Use', ($plan['type'] == 'PPPOE') ? 1 : $plan['shared_users']);
+ // Mikrotik Spesific
+ Radius::upsertCustomer($customer['username'], 'Port-Limit', ($plan['type'] == 'PPPOE') ? 1 : $plan['shared_users']);
+ Radius::upsertCustomer($customer['username'], 'Mikrotik-Wireless-Comment', $customer['fullname']);
+ return true;
+ }
+
+ private static function delAtribute($tabel, $attribute, $key, $value)
+ {
+ $r = $tabel->where_equal($key, $value)->whereEqual('attribute', $attribute)->findOne();
+ if ($r) $r->delete();
+ }
+
+ /**
+ * To insert or update existing plan
+ */
+ private static function upsertPackage($plan_id, $attr, $value, $op = ':=')
+ {
+ $r = Radius::getTablePackage()->where_equal('plan_id', $plan_id)->whereEqual('attribute', $attr)->find_one();
+ if (!$r) {
+ $r = Radius::getTablePackage()->create();
+ $r->groupname = "plan_" . $plan_id;
+ $r->plan_id = $plan_id;
+ }
+ $r->attribute = $attr;
+ $r->op = $op;
+ $r->value = $value;
+ return $r->save();
+ }
+
+ /**
+ * To insert or update existing customer
+ */
+ public static function upsertCustomer($username, $attr, $value, $op = ':=')
+ {
+ $r = Radius::getTableCustomer()->where_equal('username', $username)->whereEqual('attribute', $attr)->find_one();
+ if (!$r) {
+ $r = Radius::getTableCustomer()->create();
+ $r->username = $username;
+ }
+ $r->attribute = $attr;
+ $r->op = $op;
+ $r->value = $value;
+ return $r->save();
+ }
+ /**
+ * To insert or update existing customer Attribute
+ */
+ public static function upsertCustomerAttr($username, $attr, $value, $op = ':=')
+ {
+ $r = Radius::getTableCustomerAttr()->where_equal('username', $username)->whereEqual('attribute', $attr)->find_one();
+ if (!$r) {
+ $r = Radius::getTableCustomerAttr()->create();
+ $r->username = $username;
+ }
+ $r->attribute = $attr;
+ $r->op = $op;
+ $r->value = $value;
+ return $r->save();
+ }
+
+ public static function disconnectCustomer($username)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ /**
+ * Fix loop to all Nas but still detecting Hotspot Multylogin from other Nas
+ */
+ $act = ORM::for_table('radacct')->where_raw("acctstoptime IS NULL")->where('username', $username)->find_one();
+ $nas = Radius::getTableNas()->where('nasname', $act['nasipaddress'])->find_many();
+ $count = count($nas) * 15;
+ set_time_limit($count);
+ $result = [];
+ foreach ($nas as $n) {
+ $port = 3799;
+ if (!empty($n['ports'])) {
+ $port = $n['ports'];
+ }
+ $result[] = $n['nasname'] . ': ' . @shell_exec("echo 'User-Name = $username,Framed-IP-Address = " . $act['framedipaddress'] . "' | radclient -x " . trim($n['nasname']) . ":$port disconnect '" . $n['secret'] . "'");
+ }
+ return $result;
+ }
+}
diff --git a/system/autoload/Text.php b/system/autoload/Text.php
new file mode 100644
index 0000000..bc094e1
--- /dev/null
+++ b/system/autoload/Text.php
@@ -0,0 +1,64 @@
+ (int)$currentTimezone->getOffset($utcTime),
+ 'identifier' => $timezoneIdentifier
+ );
+ }
+
+ // Sort the array by offset,identifier ascending
+ usort($tempTimezones, function($a, $b) {
+ return ($a['offset'] == $b['offset'])
+ ? strcmp($a['identifier'], $b['identifier'])
+ : $a['offset'] - $b['offset'];
+ });
+
+ $timezoneList = array();
+ foreach ($tempTimezones as $tz) {
+ $sign = ($tz['offset'] > 0) ? '+' : '-';
+ $offset = gmdate('H:i', abs($tz['offset']));
+ $timezoneList[$tz['identifier']] = '(UTC ' . $sign . $offset . ') ' .
+ $tz['identifier'];
+ }
+
+ return $timezoneList;
+ }
+
+ public static function getTimeOffset($tz = 'Asia/Jakarta'){
+ $utcTime = new DateTime('now', new DateTimeZone('UTC'));
+ $currentTimezone = new DateTimeZone($tz);
+ $offset = $currentTimezone->getOffset($utcTime);
+ $sign = ($offset > 0) ? '+' : '-';
+ $offset = gmdate('H:i', abs($offset));
+ return $sign.$offset;
+ }
+}
\ No newline at end of file
diff --git a/system/autoload/User.php b/system/autoload/User.php
new file mode 100644
index 0000000..c8b0919
--- /dev/null
+++ b/system/autoload/User.php
@@ -0,0 +1,202 @@
+ $v) {
+ // if has : then its an installment
+ if (strpos($v, ":") === false) {
+ // Not installment
+ $bills[$k] = $v;
+ $addcost += $v;
+ } else {
+ // installment
+ list($cost, $rem) = explode(":", $v);
+ // :0 installment is done
+ if (!empty($rem)) {
+ $bills[$k] = $cost;
+ $addcost += $cost;
+ }
+ }
+ }
+ return [$bills, $addcost];
+ }
+
+ public static function billsPaid($bills, $id = 0)
+ {
+ if (!$id) {
+ $id = User::getID();
+ if (!$id) {
+ return [];
+ }
+ }
+ foreach ($bills as $k => $v) {
+ // if has : then its an installment
+ $v = User::getAttribute($k, $id);
+ if (strpos($v, ":") === false) {
+ // Not installment, no need decrement
+ } else {
+ // installment
+ list($cost, $rem) = explode(":", $v);
+ // :0 installment is done
+ if ($rem != 0) {
+ User::setAttribute($k, "$cost:" . ($rem - 1), $id);
+ }
+ }
+ }
+ }
+
+ public static function setAttribute($name, $value, $id = 0)
+ {
+ if (!$id) {
+ $id = User::getID();
+ if (!$id) {
+ return '';
+ }
+ }
+ $f = ORM::for_table('tbl_customers_fields')->where('field_name', $name)->where('customer_id', $id)->find_one();
+ if (!$f) {
+ $f = ORM::for_table('tbl_customers_fields')->create();
+ $f->customer_id = $id;
+ $f->field_name = $name;
+ $f->field_value = $value;
+ $f->save();
+ $result = $f->id();
+ if ($result) {
+ return $result;
+ }
+ } else {
+ $f->field_value = $value;
+ $f->save();
+ return $f['id'];
+ }
+ return 0;
+ }
+
+ public static function getAttribute($name, $id = 0)
+ {
+ if (!$id) {
+ $id = User::getID();
+ if (!$id) {
+ return [];
+ }
+ }
+ $f = ORM::for_table('tbl_customers_fields')->where('field_name', $name)->where('customer_id', $id)->find_one();
+ if ($f) {
+ return $f['field_value'];
+ }
+ return '';
+ }
+
+ public static function getAttributes($endWith, $id = 0)
+ {
+ if (!$id) {
+ $id = User::getID();
+ if (!$id) {
+ return [];
+ }
+ }
+ $attrs = [];
+ $f = ORM::for_table('tbl_customers_fields')->where_like('field_name', "%$endWith")->where('customer_id', $id)->find_many();
+ if ($f) {
+ foreach ($f as $k) {
+ $attrs[$k['field_name']] = $k['field_value'];
+ }
+ return $attrs;
+ }
+ return [];
+ }
+
+ public static function setCookie($uid)
+ {
+ global $db_password;
+ if (isset($uid)) {
+ $time = time();
+ setcookie('uid', $uid . '.' . $time . '.' . sha1($uid . '.' . $time . '.' . $db_password), time() + 86400 * 30);
+ }
+ }
+
+ public static function removeCookie()
+ {
+ if (isset($_COOKIE['uid'])) {
+ setcookie('uid', '', time() - 86400);
+ }
+ }
+
+ public static function _info($id = 0)
+ {
+ global $config;
+ if ($config['maintenance_mode'] == true) {
+ if ($config['maintenance_mode_logout'] == true) {
+ r2(U . 'logout', 'd', '');
+ } else {
+ displayMaintenanceMessage();
+ }
+ }
+ if (!$id) {
+ $id = User::getID();
+ }
+ $d = ORM::for_table('tbl_customers')->find_one($id);
+ if ($d['status'] == 'Banned') {
+ _alert(Lang::T('This account status') . ' : ' . Lang::T($d['status']), 'danger', "logout");
+ }
+ if (empty($d['username'])) {
+ r2(U . 'logout', 'd', '');
+ }
+ return $d;
+ }
+
+ public static function _billing($id = 0)
+ {
+ if (!$id) {
+ $id = User::getID();
+ }
+ $d = ORM::for_table('tbl_user_recharges')
+ ->select('tbl_user_recharges.id', 'id')
+ ->selects([
+ 'customer_id', 'username', 'plan_id', 'namebp', 'recharged_on', 'recharged_time', 'expiration', 'time',
+ 'status', 'method', 'plan_type',
+ ['tbl_user_recharges.routers', 'routers'],
+ ['tbl_user_recharges.type', 'type'],
+ 'admin_id', 'prepaid'
+ ])
+ ->where('customer_id', $id)
+ ->left_outer_join('tbl_plans', array('tbl_plans.id', '=', 'tbl_user_recharges.plan_id'))
+ ->find_many();
+ return $d;
+ }
+}
diff --git a/system/autoload/Validator.php b/system/autoload/Validator.php
new file mode 100644
index 0000000..c200a2b
--- /dev/null
+++ b/system/autoload/Validator.php
@@ -0,0 +1,323 @@
+= $max) return false;
+ return true;
+ }
+
+ /**
+ * Email addres check
+ *
+ * @access public
+ * @param string $string
+ * @param array $exclude
+ * @return bool
+ */
+ public static function Email($string, $exclude = "")
+ {
+ if (self::textHit($string, $exclude)) return false;
+ return (bool)preg_match("/^([a-z0-9])(([-a-z0-9._])*([a-z0-9]))*\@([a-z0-9])(([a-z0-9-])*([a-z0-9]))+(\.([a-z0-9])([-a-z0-9_-])?([a-z0-9])+)+$/i", $string);
+ }
+
+ /**
+ * URL check
+ *
+ * @access public
+ * @param strin $string
+ * @return bool
+ */
+ public static function Url($string, $exclude = "")
+ {
+ if (self::textHit($string, $exclude)) return false;
+ return (bool)preg_match("/^(http|https|ftp):\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?/i", $string);
+ }
+
+ /**
+ * IP
+ *
+ * @access public
+ * @param string $string
+ * @return void
+ */
+ public static function Ip($string)
+ {
+ return (bool)preg_match("/^(1?\d{1,2}|2([0-4]\d|5[0-5]))(\.(1?\d{1,2}|2([0-4]\d|5[0-5]))){3}$/", $string);
+ }
+
+ /**
+ * Check if it is an number
+ *
+ * @access public
+ * @param int $integer
+ * @param int $max
+ * @param int $min
+ * @return bool
+ */
+ public static function Number($integer, $max = null, $min = 0)
+ {
+ if (preg_match("/^\-?\+?[0-9e1-9]+$/", $integer)) {
+ if (!self::numberBetween($integer, $max, $min)) return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Check if it is an unsigned number
+ *
+ * @access public
+ * @param int $integer
+ * @return bool
+ */
+ public static function UnsignedNumber($integer)
+ {
+ return (bool)preg_match("/^\+?[0-9]+$/", $integer);
+ }
+
+ /**
+ * Float
+ *
+ * @access public
+ * @param string $string
+ * @return bool
+ */
+ public static function Float($string)
+ {
+ return (bool)($string == strval(floatval($string))) ? true : false;
+ }
+
+ /**
+ * Alpha check
+ *
+ * @access public
+ * @param string $string
+ * @return void
+ */
+ public static function Alpha($string)
+ {
+ return (bool)preg_match("/^[a-zA-Z]+$/", $string);
+ }
+
+ /**
+ * Alpha numeric check
+ *
+ * @access public
+ * @param string $string
+ * @return void
+ */
+ public static function AlphaNumeric($string)
+ {
+ return (bool)preg_match("/^[0-9a-zA-Z]+$/", $string);
+ }
+
+ /**
+ * Specific chars check
+ *
+ * @access public
+ * @param string $string
+ * @param array $allowed
+ * @return void
+ */
+ public static function Chars($string, $allowed = array("a-z"))
+ {
+ return (bool)preg_match("/^[" . implode("", $allowed) . "]+$/", $string);
+ }
+
+ /**
+ * Check length of an string
+ *
+ * @access public
+ * @param string $stirng
+ * @param int $max
+ * @param int $min
+ * @return bool
+ */
+ public static function Length($string, $max = null, $min = 0)
+ {
+ $length = strlen($string);
+ if (!self::numberBetween($length, $max, $min)) return false;
+ return true;
+ }
+
+ /**
+ * Hex color check
+ *
+ * @access public
+ * @param string $string
+ * @return void
+ */
+ public static function HexColor($string)
+ {
+ return (bool)preg_match("/^(#)?([0-9a-f]{1,2}){3}$/i", $string);
+ }
+
+ /**
+ * Data validation
+ *
+ * Does'nt matter how you provide the date
+ * dd/mm/yyyy
+ * dd-mm-yyyy
+ * yyyy/mm/dd
+ * yyyy-mm-dd
+ *
+ * @access public
+ * @param string $string
+ * @return bool
+ */
+ public static function Date($string)
+ {
+ $date = date('Y', strtotime($string));
+ return ($date == "1970" || $date == '') ? false : true;
+ }
+
+ /**
+ * Older than check
+ *
+ * @access public
+ * @param string $string
+ * @param int $age
+ * @return bool
+ */
+ public static function OlderThan($string, $age)
+ {
+ $date = date('Y', strtotime($string));
+ if ($date == "1970" || $date == '') return false;
+ return (date('Y') - $date) > $age ? true : false;
+ }
+
+ /**
+ * XML valid
+ *
+ * @access public
+ * @param string $string
+ * @return bool
+ */
+ public static function Xml($string)
+ {
+ $Xml = @simplexml_load_string($string);
+ return ($Xml === false) ? false : true;
+ }
+
+ /**
+ * Is filesize between
+ *
+ * @access public
+ * @param string $file
+ * @param int $max
+ * @param int $min
+ * @return bool
+ */
+ public static function FilesizeBetween($file, $max = null, $min = 0)
+ {
+ $filesize = filesize($file);
+ return self::numberBetween($filesize, $max, $min);
+ }
+
+ /**
+ * Is image width between
+ *
+ * @access public
+ * @param string $image
+ * @param int $max_width
+ * @param int $min_width
+ * @param int $max_height
+ * @param int $min_height
+ * @return void
+ */
+ public static function ImageSizeBetween($image, $max_width = "", $min_width = 0, $max_height = "", $min_height = 0)
+ {
+ $size = getimagesize($image);
+ if (!self::numberBetween($size[0], $max_width, $min_width)) return false;
+ if (!self::numberBetween($size[1], $max_height, $min_height)) return false;
+ return true;
+ }
+
+ /**
+ * Phone numbers
+ *
+ * @access public
+ * @param string $phone
+ * @return bool
+ */
+ public static function Phone($phone)
+ {
+ $formats = array(
+ '###-###-####',
+ '####-###-###',
+ '(###) ###-###',
+ '####-####-####',
+ '##-###-####-####',
+ '####-####',
+ '###-###-###',
+ '#####-###-###',
+ '##########',
+ '####-##-##-##'
+ );
+ $format = trim(preg_replace("/[0-9]/", "#", $phone));
+ return (bool)in_array($format, $formats);
+ }
+
+ public static function countRouterPlan($plans, $router){
+ $n = 0;
+ foreach ($plans as $plan){
+ if($plan['routers'] == $router){
+ $n++;
+ }
+ }
+ return $n;
+ }
+
+ public static function isRouterHasPlan($plans, $router){
+ foreach ($plans as $plan){
+ if($plan['routers'] == $router){
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/system/autoload/Widget.php b/system/autoload/Widget.php
new file mode 100644
index 0000000..0a368c2
--- /dev/null
+++ b/system/autoload/Widget.php
@@ -0,0 +1,50 @@
+';
+ foreach($rows as $row){
+
+ }
+ $result .= '';
+ }
+
+ public static function columns($cols, $result){
+ $c = count($cols);
+ switch($c){
+ case 1:
+ $result .= '';
+ break;
+ case 2:
+ $result .= '
';
+ break;
+ case 3:
+ $result .= '
';
+ break;
+ case 4:
+ $result .= '
';
+ break;
+ case 5:
+ $result .= '
';
+ break;
+ default:
+ $result .= '
';
+ break;
+ }
+
+ foreach($cols as $col){
+ }
+ $result .= '
';
+ }
+}
\ No newline at end of file
diff --git a/system/autoload/index.html b/system/autoload/index.html
new file mode 100644
index 0000000..9757970
--- /dev/null
+++ b/system/autoload/index.html
@@ -0,0 +1,8 @@
+
+
+
403 Forbidden
+
+
+
Directory access is forbidden.
+
+
\ No newline at end of file