From a4d9f9bd5032771f344faacead1052ebb52adfe1 Mon Sep 17 00:00:00 2001 From: nestict Date: Sat, 24 May 2025 11:05:49 +0200 Subject: [PATCH 01/11] Upload files to "system/autoload" Signed-off-by: nestict --- system/autoload/User.php | 202 +++++++++++++++++++++ system/autoload/Validator.php | 323 ++++++++++++++++++++++++++++++++++ system/autoload/Widget.php | 50 ++++++ 3 files changed, 575 insertions(+) create mode 100644 system/autoload/User.php create mode 100644 system/autoload/Validator.php create mode 100644 system/autoload/Widget.php 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 -- 2.47.2 From d758ba30425b7dae71f25ddfc7ddfa986fbe0fec Mon Sep 17 00:00:00 2001 From: nestict Date: Sat, 24 May 2025 11:09:37 +0200 Subject: [PATCH 02/11] Upload files to "system/autoload/mail" Signed-off-by: nestict --- system/autoload/mail/Exception.php | 40 + system/autoload/mail/PHPMailer.php | 5252 ++++++++++++++++++++++++++++ system/autoload/mail/SMTP.php | 1497 ++++++++ 3 files changed, 6789 insertions(+) create mode 100644 system/autoload/mail/Exception.php create mode 100644 system/autoload/mail/PHPMailer.php create mode 100644 system/autoload/mail/SMTP.php diff --git a/system/autoload/mail/Exception.php b/system/autoload/mail/Exception.php new file mode 100644 index 0000000..52eaf95 --- /dev/null +++ b/system/autoload/mail/Exception.php @@ -0,0 +1,40 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer exception handler. + * + * @author Marcus Bointon + */ +class Exception extends \Exception +{ + /** + * Prettify error message output. + * + * @return string + */ + public function errorMessage() + { + return '' . htmlspecialchars($this->getMessage(), ENT_COMPAT | ENT_HTML401) . "
\n"; + } +} diff --git a/system/autoload/mail/PHPMailer.php b/system/autoload/mail/PHPMailer.php new file mode 100644 index 0000000..ba4bcd4 --- /dev/null +++ b/system/autoload/mail/PHPMailer.php @@ -0,0 +1,5252 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer - PHP email creation and transport class. + * + * @author Marcus Bointon (Synchro/coolbru) + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + */ +class PHPMailer +{ + const CHARSET_ASCII = 'us-ascii'; + const CHARSET_ISO88591 = 'iso-8859-1'; + const CHARSET_UTF8 = 'utf-8'; + + const CONTENT_TYPE_PLAINTEXT = 'text/plain'; + const CONTENT_TYPE_TEXT_CALENDAR = 'text/calendar'; + const CONTENT_TYPE_TEXT_HTML = 'text/html'; + const CONTENT_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative'; + const CONTENT_TYPE_MULTIPART_MIXED = 'multipart/mixed'; + const CONTENT_TYPE_MULTIPART_RELATED = 'multipart/related'; + + const ENCODING_7BIT = '7bit'; + const ENCODING_8BIT = '8bit'; + const ENCODING_BASE64 = 'base64'; + const ENCODING_BINARY = 'binary'; + const ENCODING_QUOTED_PRINTABLE = 'quoted-printable'; + + const ENCRYPTION_STARTTLS = 'tls'; + const ENCRYPTION_SMTPS = 'ssl'; + + const ICAL_METHOD_REQUEST = 'REQUEST'; + const ICAL_METHOD_PUBLISH = 'PUBLISH'; + const ICAL_METHOD_REPLY = 'REPLY'; + const ICAL_METHOD_ADD = 'ADD'; + const ICAL_METHOD_CANCEL = 'CANCEL'; + const ICAL_METHOD_REFRESH = 'REFRESH'; + const ICAL_METHOD_COUNTER = 'COUNTER'; + const ICAL_METHOD_DECLINECOUNTER = 'DECLINECOUNTER'; + + /** + * Email priority. + * Options: null (default), 1 = High, 3 = Normal, 5 = low. + * When null, the header is not set at all. + * + * @var int|null + */ + public $Priority; + + /** + * The character set of the message. + * + * @var string + */ + public $CharSet = self::CHARSET_ISO88591; + + /** + * The MIME Content-type of the message. + * + * @var string + */ + public $ContentType = self::CONTENT_TYPE_PLAINTEXT; + + /** + * The message encoding. + * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". + * + * @var string + */ + public $Encoding = self::ENCODING_8BIT; + + /** + * Holds the most recent mailer error message. + * + * @var string + */ + public $ErrorInfo = ''; + + /** + * The From email address for the message. + * + * @var string + */ + public $From = ''; + + /** + * The From name of the message. + * + * @var string + */ + public $FromName = ''; + + /** + * The envelope sender of the message. + * This will usually be turned into a Return-Path header by the receiver, + * and is the address that bounces will be sent to. + * If not empty, will be passed via `-f` to sendmail or as the 'MAIL FROM' value over SMTP. + * + * @var string + */ + public $Sender = ''; + + /** + * The Subject of the message. + * + * @var string + */ + public $Subject = ''; + + /** + * An HTML or plain text message body. + * If HTML then call isHTML(true). + * + * @var string + */ + public $Body = ''; + + /** + * The plain-text message body. + * This body can be read by mail clients that do not have HTML email + * capability such as mutt & Eudora. + * Clients that can read HTML will view the normal Body. + * + * @var string + */ + public $AltBody = ''; + + /** + * An iCal message part body. + * Only supported in simple alt or alt_inline message types + * To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator. + * + * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ + * @see http://kigkonsult.se/iCalcreator/ + * + * @var string + */ + public $Ical = ''; + + /** + * Value-array of "method" in Contenttype header "text/calendar" + * + * @var string[] + */ + protected static $IcalMethods = [ + self::ICAL_METHOD_REQUEST, + self::ICAL_METHOD_PUBLISH, + self::ICAL_METHOD_REPLY, + self::ICAL_METHOD_ADD, + self::ICAL_METHOD_CANCEL, + self::ICAL_METHOD_REFRESH, + self::ICAL_METHOD_COUNTER, + self::ICAL_METHOD_DECLINECOUNTER, + ]; + + /** + * The complete compiled MIME message body. + * + * @var string + */ + protected $MIMEBody = ''; + + /** + * The complete compiled MIME message headers. + * + * @var string + */ + protected $MIMEHeader = ''; + + /** + * Extra headers that createHeader() doesn't fold in. + * + * @var string + */ + protected $mailHeader = ''; + + /** + * Word-wrap the message body to this number of chars. + * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. + * + * @see static::STD_LINE_LENGTH + * + * @var int + */ + public $WordWrap = 0; + + /** + * Which method to use to send mail. + * Options: "mail", "sendmail", or "smtp". + * + * @var string + */ + public $Mailer = 'mail'; + + /** + * The path to the sendmail program. + * + * @var string + */ + public $Sendmail = '/usr/sbin/sendmail'; + + /** + * Whether mail() uses a fully sendmail-compatible MTA. + * One which supports sendmail's "-oi -f" options. + * + * @var bool + */ + public $UseSendmailOptions = true; + + /** + * The email address that a reading confirmation should be sent to, also known as read receipt. + * + * @var string + */ + public $ConfirmReadingTo = ''; + + /** + * The hostname to use in the Message-ID header and as default HELO string. + * If empty, PHPMailer attempts to find one with, in order, + * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value + * 'localhost.localdomain'. + * + * @see PHPMailer::$Helo + * + * @var string + */ + public $Hostname = ''; + + /** + * An ID to be used in the Message-ID header. + * If empty, a unique id will be generated. + * You can set your own, but it must be in the format "", + * as defined in RFC5322 section 3.6.4 or it will be ignored. + * + * @see https://tools.ietf.org/html/rfc5322#section-3.6.4 + * + * @var string + */ + public $MessageID = ''; + + /** + * The message Date to be used in the Date header. + * If empty, the current date will be added. + * + * @var string + */ + public $MessageDate = ''; + + /** + * SMTP hosts. + * Either a single hostname or multiple semicolon-delimited hostnames. + * You can also specify a different port + * for each host by using this format: [hostname:port] + * (e.g. "smtp1.example.com:25;smtp2.example.com"). + * You can also specify encryption type, for example: + * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). + * Hosts will be tried in order. + * + * @var string + */ + public $Host = 'localhost'; + + /** + * The default SMTP server port. + * + * @var int + */ + public $Port = 25; + + /** + * The SMTP HELO/EHLO name used for the SMTP connection. + * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find + * one with the same method described above for $Hostname. + * + * @see PHPMailer::$Hostname + * + * @var string + */ + public $Helo = ''; + + /** + * What kind of encryption to use on the SMTP connection. + * Options: '', static::ENCRYPTION_STARTTLS, or static::ENCRYPTION_SMTPS. + * + * @var string + */ + public $SMTPSecure = ''; + + /** + * Whether to enable TLS encryption automatically if a server supports it, + * even if `SMTPSecure` is not set to 'tls'. + * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid. + * + * @var bool + */ + public $SMTPAutoTLS = true; + + /** + * Whether to use SMTP authentication. + * Uses the Username and Password properties. + * + * @see PHPMailer::$Username + * @see PHPMailer::$Password + * + * @var bool + */ + public $SMTPAuth = false; + + /** + * Options array passed to stream_context_create when connecting via SMTP. + * + * @var array + */ + public $SMTPOptions = []; + + /** + * SMTP username. + * + * @var string + */ + public $Username = ''; + + /** + * SMTP password. + * + * @var string + */ + public $Password = ''; + + /** + * SMTP authentication type. Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2. + * If not specified, the first one from that list that the server supports will be selected. + * + * @var string + */ + public $AuthType = ''; + + /** + * SMTP SMTPXClient command attibutes + * + * @var array + */ + protected $SMTPXClient = []; + + /** + * An implementation of the PHPMailer OAuthTokenProvider interface. + * + * @var OAuthTokenProvider + */ + protected $oauth; + + /** + * The SMTP server timeout in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. + * + * @var int + */ + public $Timeout = 300; + + /** + * Comma separated list of DSN notifications + * 'NEVER' under no circumstances a DSN must be returned to the sender. + * If you use NEVER all other notifications will be ignored. + * 'SUCCESS' will notify you when your mail has arrived at its destination. + * 'FAILURE' will arrive if an error occurred during delivery. + * 'DELAY' will notify you if there is an unusual delay in delivery, but the actual + * delivery's outcome (success or failure) is not yet decided. + * + * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY + */ + public $dsn = ''; + + /** + * SMTP class debug output mode. + * Debug output level. + * Options: + * @see SMTP::DEBUG_OFF: No output + * @see SMTP::DEBUG_CLIENT: Client messages + * @see SMTP::DEBUG_SERVER: Client and server messages + * @see SMTP::DEBUG_CONNECTION: As SERVER plus connection status + * @see SMTP::DEBUG_LOWLEVEL: Noisy, low-level data output, rarely needed + * + * @see SMTP::$do_debug + * + * @var int + */ + public $SMTPDebug = 0; + + /** + * How to handle debug output. + * Options: + * * `echo` Output plain-text as-is, appropriate for CLI + * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output + * * `error_log` Output to error log as configured in php.ini + * By default PHPMailer will use `echo` if run from a `cli` or `cli-server` SAPI, `html` otherwise. + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * ```php + * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * ``` + * + * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug` + * level output is used: + * + * ```php + * $mail->Debugoutput = new myPsr3Logger; + * ``` + * + * @see SMTP::$Debugoutput + * + * @var string|callable|\Psr\Log\LoggerInterface + */ + public $Debugoutput = 'echo'; + + /** + * Whether to keep the SMTP connection open after each message. + * If this is set to true then the connection will remain open after a send, + * and closing the connection will require an explicit call to smtpClose(). + * It's a good idea to use this if you are sending multiple messages as it reduces overhead. + * See the mailing list example for how to use it. + * + * @var bool + */ + public $SMTPKeepAlive = false; + + /** + * Whether to split multiple to addresses into multiple messages + * or send them all in one message. + * Only supported in `mail` and `sendmail` transports, not in SMTP. + * + * @var bool + * + * @deprecated 6.0.0 PHPMailer isn't a mailing list manager! + */ + public $SingleTo = false; + + /** + * Storage for addresses when SingleTo is enabled. + * + * @var array + */ + protected $SingleToArray = []; + + /** + * Whether to generate VERP addresses on send. + * Only applicable when sending via SMTP. + * + * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path + * @see http://www.postfix.org/VERP_README.html Postfix VERP info + * + * @var bool + */ + public $do_verp = false; + + /** + * Whether to allow sending messages with an empty body. + * + * @var bool + */ + public $AllowEmpty = false; + + /** + * DKIM selector. + * + * @var string + */ + public $DKIM_selector = ''; + + /** + * DKIM Identity. + * Usually the email address used as the source of the email. + * + * @var string + */ + public $DKIM_identity = ''; + + /** + * DKIM passphrase. + * Used if your key is encrypted. + * + * @var string + */ + public $DKIM_passphrase = ''; + + /** + * DKIM signing domain name. + * + * @example 'example.com' + * + * @var string + */ + public $DKIM_domain = ''; + + /** + * DKIM Copy header field values for diagnostic use. + * + * @var bool + */ + public $DKIM_copyHeaderFields = true; + + /** + * DKIM Extra signing headers. + * + * @example ['List-Unsubscribe', 'List-Help'] + * + * @var array + */ + public $DKIM_extraHeaders = []; + + /** + * DKIM private key file path. + * + * @var string + */ + public $DKIM_private = ''; + + /** + * DKIM private key string. + * + * If set, takes precedence over `$DKIM_private`. + * + * @var string + */ + public $DKIM_private_string = ''; + + /** + * Callback Action function name. + * + * The function that handles the result of the send email action. + * It is called out by send() for each email sent. + * + * Value can be any php callable: http://www.php.net/is_callable + * + * Parameters: + * bool $result result of the send action + * array $to email addresses of the recipients + * array $cc cc email addresses + * array $bcc bcc email addresses + * string $subject the subject + * string $body the email body + * string $from email address of sender + * string $extra extra information of possible use + * "smtp_transaction_id' => last smtp transaction id + * + * @var string + */ + public $action_function = ''; + + /** + * What to put in the X-Mailer header. + * Options: An empty string for PHPMailer default, whitespace/null for none, or a string to use. + * + * @var string|null + */ + public $XMailer = ''; + + /** + * Which validator to use by default when validating email addresses. + * May be a callable to inject your own validator, but there are several built-in validators. + * The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option. + * + * @see PHPMailer::validateAddress() + * + * @var string|callable + */ + public static $validator = 'php'; + + /** + * An instance of the SMTP sender class. + * + * @var SMTP + */ + protected $smtp; + + /** + * The array of 'to' names and addresses. + * + * @var array + */ + protected $to = []; + + /** + * The array of 'cc' names and addresses. + * + * @var array + */ + protected $cc = []; + + /** + * The array of 'bcc' names and addresses. + * + * @var array + */ + protected $bcc = []; + + /** + * The array of reply-to names and addresses. + * + * @var array + */ + protected $ReplyTo = []; + + /** + * An array of all kinds of addresses. + * Includes all of $to, $cc, $bcc. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * + * @var array + */ + protected $all_recipients = []; + + /** + * An array of names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $all_recipients + * and one of $to, $cc, or $bcc. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * @see PHPMailer::$all_recipients + * + * @var array + */ + protected $RecipientsQueue = []; + + /** + * An array of reply-to names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $ReplyTo. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$ReplyTo + * + * @var array + */ + protected $ReplyToQueue = []; + + /** + * The array of attachments. + * + * @var array + */ + protected $attachment = []; + + /** + * The array of custom headers. + * + * @var array + */ + protected $CustomHeader = []; + + /** + * The most recent Message-ID (including angular brackets). + * + * @var string + */ + protected $lastMessageID = ''; + + /** + * The message's MIME type. + * + * @var string + */ + protected $message_type = ''; + + /** + * The array of MIME boundary strings. + * + * @var array + */ + protected $boundary = []; + + /** + * The array of available text strings for the current language. + * + * @var array + */ + protected $language = []; + + /** + * The number of errors encountered. + * + * @var int + */ + protected $error_count = 0; + + /** + * The S/MIME certificate file path. + * + * @var string + */ + protected $sign_cert_file = ''; + + /** + * The S/MIME key file path. + * + * @var string + */ + protected $sign_key_file = ''; + + /** + * The optional S/MIME extra certificates ("CA Chain") file path. + * + * @var string + */ + protected $sign_extracerts_file = ''; + + /** + * The S/MIME password for the key. + * Used only if the key is encrypted. + * + * @var string + */ + protected $sign_key_pass = ''; + + /** + * Whether to throw exceptions for errors. + * + * @var bool + */ + protected $exceptions = false; + + /** + * Unique ID used for message ID and boundaries. + * + * @var string + */ + protected $uniqueid = ''; + + /** + * The PHPMailer Version number. + * + * @var string + */ + const VERSION = '6.9.1'; + + /** + * Error severity: message only, continue processing. + * + * @var int + */ + const STOP_MESSAGE = 0; + + /** + * Error severity: message, likely ok to continue processing. + * + * @var int + */ + const STOP_CONTINUE = 1; + + /** + * Error severity: message, plus full stop, critical error reached. + * + * @var int + */ + const STOP_CRITICAL = 2; + + /** + * The SMTP standard CRLF line break. + * If you want to change line break format, change static::$LE, not this. + */ + const CRLF = "\r\n"; + + /** + * "Folding White Space" a white space string used for line folding. + */ + const FWS = ' '; + + /** + * SMTP RFC standard line ending; Carriage Return, Line Feed. + * + * @var string + */ + protected static $LE = self::CRLF; + + /** + * The maximum line length supported by mail(). + * + * Background: mail() will sometimes corrupt messages + * with headers longer than 65 chars, see #818. + * + * @var int + */ + const MAIL_MAX_LINE_LENGTH = 63; + + /** + * The maximum line length allowed by RFC 2822 section 2.1.1. + * + * @var int + */ + const MAX_LINE_LENGTH = 998; + + /** + * The lower maximum line length allowed by RFC 2822 section 2.1.1. + * This length does NOT include the line break + * 76 means that lines will be 77 or 78 chars depending on whether + * the line break format is LF or CRLF; both are valid. + * + * @var int + */ + const STD_LINE_LENGTH = 76; + + /** + * Constructor. + * + * @param bool $exceptions Should we throw external exceptions? + */ + public function __construct($exceptions = null) + { + if (null !== $exceptions) { + $this->exceptions = (bool) $exceptions; + } + //Pick an appropriate debug output format automatically + $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html'); + } + + /** + * Destructor. + */ + public function __destruct() + { + //Close any open SMTP connection nicely + $this->smtpClose(); + } + + /** + * Call mail() in a safe_mode-aware fashion. + * Also, unless sendmail_path points to sendmail (or something that + * claims to be sendmail), don't pass params (not a perfect fix, + * but it will do). + * + * @param string $to To + * @param string $subject Subject + * @param string $body Message Body + * @param string $header Additional Header(s) + * @param string|null $params Params + * + * @return bool + */ + private function mailPassthru($to, $subject, $body, $header, $params) + { + //Check overloading of mail function to avoid double-encoding + if ((int)ini_get('mbstring.func_overload') & 1) { + $subject = $this->secureHeader($subject); + } else { + $subject = $this->encodeHeader($this->secureHeader($subject)); + } + //Calling mail() with null params breaks + $this->edebug('Sending with mail()'); + $this->edebug('Sendmail path: ' . ini_get('sendmail_path')); + $this->edebug("Envelope sender: {$this->Sender}"); + $this->edebug("To: {$to}"); + $this->edebug("Subject: {$subject}"); + $this->edebug("Headers: {$header}"); + if (!$this->UseSendmailOptions || null === $params) { + $result = @mail($to, $subject, $body, $header); + } else { + $this->edebug("Additional params: {$params}"); + $result = @mail($to, $subject, $body, $header, $params); + } + $this->edebug('Result: ' . ($result ? 'true' : 'false')); + return $result; + } + + /** + * Output debugging info via a user-defined method. + * Only generates output if debug output is enabled. + * + * @see PHPMailer::$Debugoutput + * @see PHPMailer::$SMTPDebug + * + * @param string $str + */ + protected function edebug($str) + { + if ($this->SMTPDebug <= 0) { + return; + } + //Is this a PSR-3 logger? + if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { + $this->Debugoutput->debug($str); + + return; + } + //Avoid clash with built-in function names + if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) { + call_user_func($this->Debugoutput, $str, $this->SMTPDebug); + + return; + } + switch ($this->Debugoutput) { + case 'error_log': + //Don't output, just log + /** @noinspection ForgottenDebugOutputInspection */ + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking, HTML-safe output + echo htmlentities( + preg_replace('/[\r\n]+/', '', $str), + ENT_QUOTES, + 'UTF-8' + ), "
\n"; + break; + case 'echo': + default: + //Normalize line breaks + $str = preg_replace('/\r\n|\r/m', "\n", $str); + echo gmdate('Y-m-d H:i:s'), + "\t", + //Trim trailing space + trim( + //Indent for readability, except for trailing break + str_replace( + "\n", + "\n \t ", + trim($str) + ) + ), + "\n"; + } + } + + /** + * Sets message type to HTML or plain. + * + * @param bool $isHtml True for HTML mode + */ + public function isHTML($isHtml = true) + { + if ($isHtml) { + $this->ContentType = static::CONTENT_TYPE_TEXT_HTML; + } else { + $this->ContentType = static::CONTENT_TYPE_PLAINTEXT; + } + } + + /** + * Send messages using SMTP. + */ + public function isSMTP() + { + $this->Mailer = 'smtp'; + } + + /** + * Send messages using PHP's mail() function. + */ + public function isMail() + { + $this->Mailer = 'mail'; + } + + /** + * Send messages using $Sendmail. + */ + public function isSendmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'sendmail')) { + $this->Sendmail = '/usr/sbin/sendmail'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'sendmail'; + } + + /** + * Send messages using qmail. + */ + public function isQmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'qmail')) { + $this->Sendmail = '/var/qmail/bin/qmail-inject'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'qmail'; + } + + /** + * Add a "To" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addAddress($address, $name = '') + { + return $this->addOrEnqueueAnAddress('to', $address, $name); + } + + /** + * Add a "CC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('cc', $address, $name); + } + + /** + * Add a "BCC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addBCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('bcc', $address, $name); + } + + /** + * Add a "Reply-To" address. + * + * @param string $address The email address to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addReplyTo($address, $name = '') + { + return $this->addOrEnqueueAnAddress('Reply-To', $address, $name); + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer + * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still + * be modified after calling this function), addition of such addresses is delayed until send(). + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address + * @param string $name An optional username associated with the address + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addOrEnqueueAnAddress($kind, $address, $name) + { + $pos = false; + if ($address !== null) { + $address = trim($address); + $pos = strrpos($address, '@'); + } + if (false === $pos) { + //At-sign is missing. + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if ($name !== null && is_string($name)) { + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + } else { + $name = ''; + } + $params = [$kind, $address, $name]; + //Enqueue addresses with IDN until we know the PHPMailer::$CharSet. + //Domain is assumed to be whatever is after the last @ symbol in the address + if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) { + if ('Reply-To' !== $kind) { + if (!array_key_exists($address, $this->RecipientsQueue)) { + $this->RecipientsQueue[$address] = $params; + + return true; + } + } elseif (!array_key_exists($address, $this->ReplyToQueue)) { + $this->ReplyToQueue[$address] = $params; + + return true; + } + + return false; + } + + //Immediately add standard addresses without IDN. + return call_user_func_array([$this, 'addAnAddress'], $params); + } + + /** + * Set the boundaries to use for delimiting MIME parts. + * If you override this, ensure you set all 3 boundaries to unique values. + * The default boundaries include a "=_" sequence which cannot occur in quoted-printable bodies, + * as suggested by https://www.rfc-editor.org/rfc/rfc2045#section-6.7 + * + * @return void + */ + public function setBoundaries() + { + $this->uniqueid = $this->generateId(); + $this->boundary[1] = 'b1=_' . $this->uniqueid; + $this->boundary[2] = 'b2=_' . $this->uniqueid; + $this->boundary[3] = 'b3=_' . $this->uniqueid; + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addAnAddress($kind, $address, $name = '') + { + if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) { + $error_message = sprintf( + '%s: %s', + $this->lang('Invalid recipient kind'), + $kind + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if (!static::validateAddress($address)) { + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if ('Reply-To' !== $kind) { + if (!array_key_exists(strtolower($address), $this->all_recipients)) { + $this->{$kind}[] = [$address, $name]; + $this->all_recipients[strtolower($address)] = true; + + return true; + } + } elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) { + $this->ReplyTo[strtolower($address)] = [$address, $name]; + + return true; + } + + return false; + } + + /** + * Parse and validate a string containing one or more RFC822-style comma-separated email addresses + * of the form "display name
" into an array of name/address pairs. + * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. + * Note that quotes in the name part are removed. + * + * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation + * + * @param string $addrstr The address list string + * @param bool $useimap Whether to use the IMAP extension to parse the list + * @param string $charset The charset to use when decoding the address list string. + * + * @return array + */ + public static function parseAddresses($addrstr, $useimap = true, $charset = self::CHARSET_ISO88591) + { + $addresses = []; + if ($useimap && function_exists('imap_rfc822_parse_adrlist')) { + //Use this built-in parser if it's available + $list = imap_rfc822_parse_adrlist($addrstr, ''); + // Clear any potential IMAP errors to get rid of notices being thrown at end of script. + imap_errors(); + foreach ($list as $address) { + if ( + '.SYNTAX-ERROR.' !== $address->host && + static::validateAddress($address->mailbox . '@' . $address->host) + ) { + //Decode the name part if it's present and encoded + if ( + property_exists($address, 'personal') && + //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled + defined('MB_CASE_UPPER') && + preg_match('/^=\?.*\?=$/s', $address->personal) + ) { + $origCharset = mb_internal_encoding(); + mb_internal_encoding($charset); + //Undo any RFC2047-encoded spaces-as-underscores + $address->personal = str_replace('_', '=20', $address->personal); + //Decode the name + $address->personal = mb_decode_mimeheader($address->personal); + mb_internal_encoding($origCharset); + } + + $addresses[] = [ + 'name' => (property_exists($address, 'personal') ? $address->personal : ''), + 'address' => $address->mailbox . '@' . $address->host, + ]; + } + } + } else { + //Use this simpler parser + $list = explode(',', $addrstr); + foreach ($list as $address) { + $address = trim($address); + //Is there a separate name part? + if (strpos($address, '<') === false) { + //No separate name, just use the whole thing + if (static::validateAddress($address)) { + $addresses[] = [ + 'name' => '', + 'address' => $address, + ]; + } + } else { + list($name, $email) = explode('<', $address); + $email = trim(str_replace('>', '', $email)); + $name = trim($name); + if (static::validateAddress($email)) { + //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled + //If this name is encoded, decode it + if (defined('MB_CASE_UPPER') && preg_match('/^=\?.*\?=$/s', $name)) { + $origCharset = mb_internal_encoding(); + mb_internal_encoding($charset); + //Undo any RFC2047-encoded spaces-as-underscores + $name = str_replace('_', '=20', $name); + //Decode the name + $name = mb_decode_mimeheader($name); + mb_internal_encoding($origCharset); + } + $addresses[] = [ + //Remove any surrounding quotes and spaces from the name + 'name' => trim($name, '\'" '), + 'address' => $email, + ]; + } + } + } + } + + return $addresses; + } + + /** + * Set the From and FromName properties. + * + * @param string $address + * @param string $name + * @param bool $auto Whether to also set the Sender address, defaults to true + * + * @throws Exception + * + * @return bool + */ + public function setFrom($address, $name = '', $auto = true) + { + $address = trim((string)$address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + //Don't validate now addresses with IDN. Will be done in send(). + $pos = strrpos($address, '@'); + if ( + (false === $pos) + || ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported()) + && !static::validateAddress($address)) + ) { + $error_message = sprintf( + '%s (From): %s', + $this->lang('invalid_address'), + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + $this->From = $address; + $this->FromName = $name; + if ($auto && empty($this->Sender)) { + $this->Sender = $address; + } + + return true; + } + + /** + * Return the Message-ID header of the last email. + * Technically this is the value from the last time the headers were created, + * but it's also the message ID of the last sent message except in + * pathological cases. + * + * @return string + */ + public function getLastMessageID() + { + return $this->lastMessageID; + } + + /** + * Check that a string looks like an email address. + * Validation patterns supported: + * * `auto` Pick best pattern automatically; + * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0; + * * `pcre` Use old PCRE implementation; + * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; + * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. + * * `noregex` Don't use a regex: super fast, really dumb. + * Alternatively you may pass in a callable to inject your own validator, for example: + * + * ```php + * PHPMailer::validateAddress('user@example.com', function($address) { + * return (strpos($address, '@') !== false); + * }); + * ``` + * + * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator. + * + * @param string $address The email address to check + * @param string|callable $patternselect Which pattern to use + * + * @return bool + */ + public static function validateAddress($address, $patternselect = null) + { + if (null === $patternselect) { + $patternselect = static::$validator; + } + //Don't allow strings as callables, see SECURITY.md and CVE-2021-3603 + if (is_callable($patternselect) && !is_string($patternselect)) { + return call_user_func($patternselect, $address); + } + //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 + if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) { + return false; + } + switch ($patternselect) { + case 'pcre': //Kept for BC + case 'pcre8': + /* + * A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL + * is based. + * In addition to the addresses allowed by filter_var, also permits: + * * dotless domains: `a@b` + * * comments: `1234 @ local(blah) .machine .example` + * * quoted elements: `'"test blah"@example.org'` + * * numeric TLDs: `a@b.123` + * * unbracketed IPv4 literals: `a@192.168.0.1` + * * IPv6 literals: 'first.last@[IPv6:a1::]' + * Not all of these will necessarily work for sending! + * + * @see http://squiloople.com/2009/12/20/email-address-validation/ + * @copyright 2009-2010 Michael Rushton + * Feel free to use and redistribute this code. But please keep this copyright notice. + */ + return (bool) preg_match( + '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . + '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . + '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . + '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' . + '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' . + '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' . + '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' . + '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' . + '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', + $address + ); + case 'html5': + /* + * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. + * + * @see https://html.spec.whatwg.org/#e-mail-state-(type=email) + */ + return (bool) preg_match( + '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . + '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', + $address + ); + case 'php': + default: + return filter_var($address, FILTER_VALIDATE_EMAIL) !== false; + } + } + + /** + * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the + * `intl` and `mbstring` PHP extensions. + * + * @return bool `true` if required functions for IDN support are present + */ + public static function idnSupported() + { + return function_exists('idn_to_ascii') && function_exists('mb_convert_encoding'); + } + + /** + * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. + * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. + * This function silently returns unmodified address if: + * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form) + * - Conversion to punycode is impossible (e.g. required PHP functions are not available) + * or fails for any reason (e.g. domain contains characters not allowed in an IDN). + * + * @see PHPMailer::$CharSet + * + * @param string $address The email address to convert + * + * @return string The encoded address in ASCII form + */ + public function punyencodeAddress($address) + { + //Verify we have required functions, CharSet, and at-sign. + $pos = strrpos($address, '@'); + if ( + !empty($this->CharSet) && + false !== $pos && + static::idnSupported() + ) { + $domain = substr($address, ++$pos); + //Verify CharSet string is a valid one, and domain properly encoded in this CharSet. + if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) { + //Convert the domain from whatever charset it's in to UTF-8 + $domain = mb_convert_encoding($domain, self::CHARSET_UTF8, $this->CharSet); + //Ignore IDE complaints about this line - method signature changed in PHP 5.4 + $errorcode = 0; + if (defined('INTL_IDNA_VARIANT_UTS46')) { + //Use the current punycode standard (appeared in PHP 7.2) + $punycode = idn_to_ascii( + $domain, + \IDNA_DEFAULT | \IDNA_USE_STD3_RULES | \IDNA_CHECK_BIDI | + \IDNA_CHECK_CONTEXTJ | \IDNA_NONTRANSITIONAL_TO_ASCII, + \INTL_IDNA_VARIANT_UTS46 + ); + } elseif (defined('INTL_IDNA_VARIANT_2003')) { + //Fall back to this old, deprecated/removed encoding + $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_2003); + } else { + //Fall back to a default we don't know about + $punycode = idn_to_ascii($domain, $errorcode); + } + if (false !== $punycode) { + return substr($address, 0, $pos) . $punycode; + } + } + } + + return $address; + } + + /** + * Create a message and send it. + * Uses the sending method specified by $Mailer. + * + * @throws Exception + * + * @return bool false on error - See the ErrorInfo property for details of the error + */ + public function send() + { + try { + if (!$this->preSend()) { + return false; + } + + return $this->postSend(); + } catch (Exception $exc) { + $this->mailHeader = ''; + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Prepare a message for sending. + * + * @throws Exception + * + * @return bool + */ + public function preSend() + { + if ( + 'smtp' === $this->Mailer + || ('mail' === $this->Mailer && (\PHP_VERSION_ID >= 80000 || stripos(PHP_OS, 'WIN') === 0)) + ) { + //SMTP mandates RFC-compliant line endings + //and it's also used with mail() on Windows + static::setLE(self::CRLF); + } else { + //Maintain backward compatibility with legacy Linux command line mailers + static::setLE(PHP_EOL); + } + //Check for buggy PHP versions that add a header with an incorrect line break + if ( + 'mail' === $this->Mailer + && ((\PHP_VERSION_ID >= 70000 && \PHP_VERSION_ID < 70017) + || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70103)) + && ini_get('mail.add_x_header') === '1' + && stripos(PHP_OS, 'WIN') === 0 + ) { + trigger_error($this->lang('buggy_php'), E_USER_WARNING); + } + + try { + $this->error_count = 0; //Reset errors + $this->mailHeader = ''; + + //Dequeue recipient and Reply-To addresses with IDN + foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { + $params[1] = $this->punyencodeAddress($params[1]); + call_user_func_array([$this, 'addAnAddress'], $params); + } + if (count($this->to) + count($this->cc) + count($this->bcc) < 1) { + throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL); + } + + //Validate From, Sender, and ConfirmReadingTo addresses + foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) { + if ($this->{$address_kind} === null) { + $this->{$address_kind} = ''; + continue; + } + $this->{$address_kind} = trim($this->{$address_kind}); + if (empty($this->{$address_kind})) { + continue; + } + $this->{$address_kind} = $this->punyencodeAddress($this->{$address_kind}); + if (!static::validateAddress($this->{$address_kind})) { + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $address_kind, + $this->{$address_kind} + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + } + + //Set whether the message is multipart/alternative + if ($this->alternativeExists()) { + $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE; + } + + $this->setMessageType(); + //Refuse to send an empty message unless we are specifically allowing it + if (!$this->AllowEmpty && empty($this->Body)) { + throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL); + } + + //Trim subject consistently + $this->Subject = trim($this->Subject); + //Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) + $this->MIMEHeader = ''; + $this->MIMEBody = $this->createBody(); + //createBody may have added some headers, so retain them + $tempheaders = $this->MIMEHeader; + $this->MIMEHeader = $this->createHeader(); + $this->MIMEHeader .= $tempheaders; + + //To capture the complete message when using mail(), create + //an extra header list which createHeader() doesn't fold in + if ('mail' === $this->Mailer) { + if (count($this->to) > 0) { + $this->mailHeader .= $this->addrAppend('To', $this->to); + } else { + $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;'); + } + $this->mailHeader .= $this->headerLine( + 'Subject', + $this->encodeHeader($this->secureHeader($this->Subject)) + ); + } + + //Sign with DKIM if enabled + if ( + !empty($this->DKIM_domain) + && !empty($this->DKIM_selector) + && (!empty($this->DKIM_private_string) + || (!empty($this->DKIM_private) + && static::isPermittedPath($this->DKIM_private) + && file_exists($this->DKIM_private) + ) + ) + ) { + $header_dkim = $this->DKIM_Add( + $this->MIMEHeader . $this->mailHeader, + $this->encodeHeader($this->secureHeader($this->Subject)), + $this->MIMEBody + ); + $this->MIMEHeader = static::stripTrailingWSP($this->MIMEHeader) . static::$LE . + static::normalizeBreaks($header_dkim) . static::$LE; + } + + return true; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Actually send a message via the selected mechanism. + * + * @throws Exception + * + * @return bool + */ + public function postSend() + { + try { + //Choose the mailer and send through it + switch ($this->Mailer) { + case 'sendmail': + case 'qmail': + return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody); + case 'smtp': + return $this->smtpSend($this->MIMEHeader, $this->MIMEBody); + case 'mail': + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + default: + $sendMethod = $this->Mailer . 'Send'; + if (method_exists($this, $sendMethod)) { + return $this->{$sendMethod}($this->MIMEHeader, $this->MIMEBody); + } + + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + } + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->Mailer === 'smtp' && $this->SMTPKeepAlive == true && $this->smtp->connected()) { + $this->smtp->reset(); + } + if ($this->exceptions) { + throw $exc; + } + } + + return false; + } + + /** + * Send mail using the $Sendmail program. + * + * @see PHPMailer::$Sendmail + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function sendmailSend($header, $body) + { + if ($this->Mailer === 'qmail') { + $this->edebug('Sending with qmail'); + } else { + $this->edebug('Sending with sendmail'); + } + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver + //A space after `-f` is optional, but there is a long history of its presence + //causing problems, so we don't use one + //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html + //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Example problem: https://www.drupal.org/node/1057954 + + //PHP 5.6 workaround + $sendmail_from_value = ini_get('sendmail_from'); + if (empty($this->Sender) && !empty($sendmail_from_value)) { + //PHP config has a sender address we can use + $this->Sender = ini_get('sendmail_from'); + } + //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) { + if ($this->Mailer === 'qmail') { + $sendmailFmt = '%s -f%s'; + } else { + $sendmailFmt = '%s -oi -f%s -t'; + } + } else { + //allow sendmail to choose a default envelope sender. It may + //seem preferable to force it to use the From header as with + //SMTP, but that introduces new problems (see + //), and + //it has historically worked this way. + $sendmailFmt = '%s -oi -t'; + } + + $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender); + $this->edebug('Sendmail path: ' . $this->Sendmail); + $this->edebug('Sendmail command: ' . $sendmail); + $this->edebug('Envelope sender: ' . $this->Sender); + $this->edebug("Headers: {$header}"); + + if ($this->SingleTo) { + foreach ($this->SingleToArray as $toAddr) { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + $this->edebug("To: {$toAddr}"); + fwrite($mail, 'To: ' . $toAddr . "\n"); + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet); + $this->doCallback( + ($result === 0), + [[$addrinfo['address'], $addrinfo['name']]], + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + } else { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $this->doCallback( + ($result === 0), + $this->to, + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + + return true; + } + + /** + * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters. + * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows. + * + * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report + * + * @param string $string The string to be validated + * + * @return bool + */ + protected static function isShellSafe($string) + { + //It's not possible to use shell commands safely (which includes the mail() function) without escapeshellarg, + //but some hosting providers disable it, creating a security problem that we don't want to have to deal with, + //so we don't. + if (!function_exists('escapeshellarg') || !function_exists('escapeshellcmd')) { + return false; + } + + if ( + escapeshellcmd($string) !== $string + || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""]) + ) { + return false; + } + + $length = strlen($string); + + for ($i = 0; $i < $length; ++$i) { + $c = $string[$i]; + + //All other characters have a special meaning in at least one common shell, including = and +. + //Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here. + //Note that this does permit non-Latin alphanumeric characters based on the current locale. + if (!ctype_alnum($c) && strpos('@_-.', $c) === false) { + return false; + } + } + + return true; + } + + /** + * Check whether a file path is of a permitted type. + * Used to reject URLs and phar files from functions that access local file paths, + * such as addAttachment. + * + * @param string $path A relative or absolute path to a file + * + * @return bool + */ + protected static function isPermittedPath($path) + { + //Matches scheme definition from https://tools.ietf.org/html/rfc3986#section-3.1 + return !preg_match('#^[a-z][a-z\d+.-]*://#i', $path); + } + + /** + * Check whether a file path is safe, accessible, and readable. + * + * @param string $path A relative or absolute path to a file + * + * @return bool + */ + protected static function fileIsAccessible($path) + { + if (!static::isPermittedPath($path)) { + return false; + } + $readable = is_file($path); + //If not a UNC path (expected to start with \\), check read permission, see #2069 + if (strpos($path, '\\\\') !== 0) { + $readable = $readable && is_readable($path); + } + return $readable; + } + + /** + * Send mail using the PHP mail() function. + * + * @see http://www.php.net/manual/en/book.mail.php + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function mailSend($header, $body) + { + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + + $toArr = []; + foreach ($this->to as $toaddr) { + $toArr[] = $this->addrFormat($toaddr); + } + $to = trim(implode(', ', $toArr)); + + //If there are no To-addresses (e.g. when sending only to BCC-addresses) + //the following should be added to get a correct DKIM-signature. + //Compare with $this->preSend() + if ($to === '') { + $to = 'undisclosed-recipients:;'; + } + + $params = null; + //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver + //A space after `-f` is optional, but there is a long history of its presence + //causing problems, so we don't use one + //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html + //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Example problem: https://www.drupal.org/node/1057954 + //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + + //PHP 5.6 workaround + $sendmail_from_value = ini_get('sendmail_from'); + if (empty($this->Sender) && !empty($sendmail_from_value)) { + //PHP config has a sender address we can use + $this->Sender = ini_get('sendmail_from'); + } + if (!empty($this->Sender) && static::validateAddress($this->Sender)) { + if (self::isShellSafe($this->Sender)) { + $params = sprintf('-f%s', $this->Sender); + } + $old_from = ini_get('sendmail_from'); + ini_set('sendmail_from', $this->Sender); + } + $result = false; + if ($this->SingleTo && count($toArr) > 1) { + foreach ($toArr as $toAddr) { + $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); + $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet); + $this->doCallback( + $result, + [[$addrinfo['address'], $addrinfo['name']]], + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + } + } else { + $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); + $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []); + } + if (isset($old_from)) { + ini_set('sendmail_from', $old_from); + } + if (!$result) { + throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL); + } + + return true; + } + + /** + * Get an instance to use for SMTP operations. + * Override this function to load your own SMTP implementation, + * or set one with setSMTPInstance. + * + * @return SMTP + */ + public function getSMTPInstance() + { + if (!is_object($this->smtp)) { + $this->smtp = new SMTP(); + } + + return $this->smtp; + } + + /** + * Provide an instance to use for SMTP operations. + * + * @return SMTP + */ + public function setSMTPInstance(SMTP $smtp) + { + $this->smtp = $smtp; + + return $this->smtp; + } + + /** + * Provide SMTP XCLIENT attributes + * + * @param string $name Attribute name + * @param ?string $value Attribute value + * + * @return bool + */ + public function setSMTPXclientAttribute($name, $value) + { + if (!in_array($name, SMTP::$xclient_allowed_attributes)) { + return false; + } + if (isset($this->SMTPXClient[$name]) && $value === null) { + unset($this->SMTPXClient[$name]); + } elseif ($value !== null) { + $this->SMTPXClient[$name] = $value; + } + + return true; + } + + /** + * Get SMTP XCLIENT attributes + * + * @return array + */ + public function getSMTPXclientAttributes() + { + return $this->SMTPXClient; + } + + /** + * Send mail via SMTP. + * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. + * + * @see PHPMailer::setSMTPInstance() to use a different class. + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function smtpSend($header, $body) + { + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + $bad_rcpt = []; + if (!$this->smtpConnect($this->SMTPOptions)) { + throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); + } + //Sender already validated in preSend() + if ('' === $this->Sender) { + $smtp_from = $this->From; + } else { + $smtp_from = $this->Sender; + } + if (count($this->SMTPXClient)) { + $this->smtp->xclient($this->SMTPXClient); + } + if (!$this->smtp->mail($smtp_from)) { + $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); + throw new Exception($this->ErrorInfo, self::STOP_CRITICAL); + } + + $callbacks = []; + //Attempt to send to all recipients + foreach ([$this->to, $this->cc, $this->bcc] as $togroup) { + foreach ($togroup as $to) { + if (!$this->smtp->recipient($to[0], $this->dsn)) { + $error = $this->smtp->getError(); + $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']]; + $isSent = false; + } else { + $isSent = true; + } + + $callbacks[] = ['issent' => $isSent, 'to' => $to[0], 'name' => $to[1]]; + } + } + + //Only send the DATA command if we have viable recipients + if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) { + throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL); + } + + $smtp_transaction_id = $this->smtp->getLastTransactionID(); + + if ($this->SMTPKeepAlive) { + $this->smtp->reset(); + } else { + $this->smtp->quit(); + $this->smtp->close(); + } + + foreach ($callbacks as $cb) { + $this->doCallback( + $cb['issent'], + [[$cb['to'], $cb['name']]], + [], + [], + $this->Subject, + $body, + $this->From, + ['smtp_transaction_id' => $smtp_transaction_id] + ); + } + + //Create error message for any bad addresses + if (count($bad_rcpt) > 0) { + $errstr = ''; + foreach ($bad_rcpt as $bad) { + $errstr .= $bad['to'] . ': ' . $bad['error']; + } + throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE); + } + + return true; + } + + /** + * Initiate a connection to an SMTP server. + * Returns false if the operation failed. + * + * @param array $options An array of options compatible with stream_context_create() + * + * @throws Exception + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @return bool + */ + public function smtpConnect($options = null) + { + if (null === $this->smtp) { + $this->smtp = $this->getSMTPInstance(); + } + + //If no options are provided, use whatever is set in the instance + if (null === $options) { + $options = $this->SMTPOptions; + } + + //Already connected? + if ($this->smtp->connected()) { + return true; + } + + $this->smtp->setTimeout($this->Timeout); + $this->smtp->setDebugLevel($this->SMTPDebug); + $this->smtp->setDebugOutput($this->Debugoutput); + $this->smtp->setVerp($this->do_verp); + if ($this->Host === null) { + $this->Host = 'localhost'; + } + $hosts = explode(';', $this->Host); + $lastexception = null; + + foreach ($hosts as $hostentry) { + $hostinfo = []; + if ( + !preg_match( + '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/', + trim($hostentry), + $hostinfo + ) + ) { + $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry)); + //Not a valid host entry + continue; + } + //$hostinfo[1]: optional ssl or tls prefix + //$hostinfo[2]: the hostname + //$hostinfo[3]: optional port number + //The host string prefix can temporarily override the current setting for SMTPSecure + //If it's not specified, the default value is used + + //Check the host name is a valid name or IP address before trying to use it + if (!static::isValidHost($hostinfo[2])) { + $this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]); + continue; + } + $prefix = ''; + $secure = $this->SMTPSecure; + $tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure); + if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) { + $prefix = 'ssl://'; + $tls = false; //Can't have SSL and TLS at the same time + $secure = static::ENCRYPTION_SMTPS; + } elseif ('tls' === $hostinfo[1]) { + $tls = true; + //TLS doesn't use a prefix + $secure = static::ENCRYPTION_STARTTLS; + } + //Do we need the OpenSSL extension? + $sslext = defined('OPENSSL_ALGO_SHA256'); + if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) { + //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled + if (!$sslext) { + throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL); + } + } + $host = $hostinfo[2]; + $port = $this->Port; + if ( + array_key_exists(3, $hostinfo) && + is_numeric($hostinfo[3]) && + $hostinfo[3] > 0 && + $hostinfo[3] < 65536 + ) { + $port = (int) $hostinfo[3]; + } + if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { + try { + if ($this->Helo) { + $hello = $this->Helo; + } else { + $hello = $this->serverHostname(); + } + $this->smtp->hello($hello); + //Automatically enable TLS encryption if: + //* it's not disabled + //* we are not connecting to localhost + //* we have openssl extension + //* we are not already using SSL + //* the server offers STARTTLS + if ( + $this->SMTPAutoTLS && + $this->Host !== 'localhost' && + $sslext && + $secure !== 'ssl' && + $this->smtp->getServerExt('STARTTLS') + ) { + $tls = true; + } + if ($tls) { + if (!$this->smtp->startTLS()) { + $message = $this->getSmtpErrorMessage('connect_host'); + throw new Exception($message); + } + //We must resend EHLO after TLS negotiation + $this->smtp->hello($hello); + } + if ( + $this->SMTPAuth && !$this->smtp->authenticate( + $this->Username, + $this->Password, + $this->AuthType, + $this->oauth + ) + ) { + throw new Exception($this->lang('authenticate')); + } + + return true; + } catch (Exception $exc) { + $lastexception = $exc; + $this->edebug($exc->getMessage()); + //We must have connected, but then failed TLS or Auth, so close connection nicely + $this->smtp->quit(); + } + } + } + //If we get here, all connection attempts have failed, so close connection hard + $this->smtp->close(); + //As we've caught all exceptions, just report whatever the last one was + if ($this->exceptions && null !== $lastexception) { + throw $lastexception; + } + if ($this->exceptions) { + // no exception was thrown, likely $this->smtp->connect() failed + $message = $this->getSmtpErrorMessage('connect_host'); + throw new Exception($message); + } + + return false; + } + + /** + * Close the active SMTP session if one exists. + */ + public function smtpClose() + { + if ((null !== $this->smtp) && $this->smtp->connected()) { + $this->smtp->quit(); + $this->smtp->close(); + } + } + + /** + * Set the language for error messages. + * The default language is English. + * + * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") + * Optionally, the language code can be enhanced with a 4-character + * script annotation and/or a 2-character country annotation. + * @param string $lang_path Path to the language file directory, with trailing separator (slash) + * Do not set this from user input! + * + * @return bool Returns true if the requested language was loaded, false otherwise. + */ + public function setLanguage($langcode = 'en', $lang_path = '') + { + //Backwards compatibility for renamed language codes + $renamed_langcodes = [ + 'br' => 'pt_br', + 'cz' => 'cs', + 'dk' => 'da', + 'no' => 'nb', + 'se' => 'sv', + 'rs' => 'sr', + 'tg' => 'tl', + 'am' => 'hy', + ]; + + if (array_key_exists($langcode, $renamed_langcodes)) { + $langcode = $renamed_langcodes[$langcode]; + } + + //Define full set of translatable strings in English + $PHPMAILER_LANG = [ + 'authenticate' => 'SMTP Error: Could not authenticate.', + 'buggy_php' => 'Your version of PHP is affected by a bug that may result in corrupted messages.' . + ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' . + ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.', + 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', + 'data_not_accepted' => 'SMTP Error: data not accepted.', + 'empty_message' => 'Message body empty', + 'encoding' => 'Unknown encoding: ', + 'execute' => 'Could not execute: ', + 'extension_missing' => 'Extension missing: ', + 'file_access' => 'Could not access file: ', + 'file_open' => 'File Error: Could not open file: ', + 'from_failed' => 'The following From address failed: ', + 'instantiate' => 'Could not instantiate mail function.', + 'invalid_address' => 'Invalid address: ', + 'invalid_header' => 'Invalid header name or value', + 'invalid_hostentry' => 'Invalid hostentry: ', + 'invalid_host' => 'Invalid host: ', + 'mailer_not_supported' => ' mailer is not supported.', + 'provide_address' => 'You must provide at least one recipient email address.', + 'recipients_failed' => 'SMTP Error: The following recipients failed: ', + 'signing' => 'Signing Error: ', + 'smtp_code' => 'SMTP code: ', + 'smtp_code_ex' => 'Additional SMTP info: ', + 'smtp_connect_failed' => 'SMTP connect() failed.', + 'smtp_detail' => 'Detail: ', + 'smtp_error' => 'SMTP server error: ', + 'variable_set' => 'Cannot set or reset variable: ', + ]; + if (empty($lang_path)) { + //Calculate an absolute path so it can work if CWD is not here + $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR; + } + + //Validate $langcode + $foundlang = true; + $langcode = strtolower($langcode); + if ( + !preg_match('/^(?P[a-z]{2})(?P'); + run_hook('view_list_bandwidth'); #HOOK + $name = _post('name'); + if ($name != '') { + $query = ORM::for_table('tbl_bandwidth')->where_like('name_bw', '%' . $name . '%')->order_by_desc('id'); + $d = Paginator::findMany($query, ['name' => $name]); + } else { + $query = ORM::for_table('tbl_bandwidth')->order_by_desc('id'); + $d = Paginator::findMany($query); + } + + $ui->assign('d', $d); + $ui->display('bandwidth.tpl'); + break; + + case 'add': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + run_hook('view_add_bandwidth'); #HOOK + $ui->display('bandwidth-add.tpl'); + break; + + case 'edit': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $id = $routes['2']; + run_hook('view_edit_bandwith'); #HOOK + $d = ORM::for_table('tbl_bandwidth')->find_one($id); + if ($d) { + $ui->assign('burst', explode(" ", $d['burst'])); + $ui->assign('d', $d); + $ui->display('bandwidth-edit.tpl'); + } else { + r2(U . 'bandwidth/list', 'e', Lang::T('Account Not Found')); + } + break; + + case 'delete': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $id = $routes['2']; + run_hook('delete_bandwidth'); #HOOK + $d = ORM::for_table('tbl_bandwidth')->find_one($id); + if ($d) { + $d->delete(); + r2(U . 'bandwidth/list', 's', Lang::T('Data Deleted Successfully')); + } + break; + + case 'add-post': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $name = _post('name'); + $rate_down = _post('rate_down'); + $rate_down_unit = _post('rate_down_unit'); + $rate_up = _post('rate_up'); + $rate_up_unit = _post('rate_up_unit'); + run_hook('add_bandwidth'); #HOOK + $isBurst = true; + $burst = ""; + if (isset($_POST['burst'])) { + foreach ($_POST['burst'] as $b) { + if (empty($b)) { + $isBurst = false; + } + } + if ($isBurst) { + $burst = implode(' ', $_POST['burst']); + }; + } + $msg = ''; + if (Validator::Length($name, 16, 4) == false) { + $msg .= 'Name should be between 5 to 15 characters' . '
'; + } + + if ($rate_down_unit == 'Kbps') { + $unit_rate_down = $rate_down * 1024; + } else { + $unit_rate_down = $rate_down * 1048576; + } + if ($rate_up_unit == 'Kbps') { + $unit_rate_up = $min_up * 1024; + } else { + $unit_rate_up = $min_up * 1048576; + } + + $d = ORM::for_table('tbl_bandwidth')->where('name_bw', $name)->find_one(); + if ($d) { + $msg .= Lang::T('Name Bandwidth Already Exist') . '
'; + } + + if ($msg == '') { + $d = ORM::for_table('tbl_bandwidth')->create(); + $d->name_bw = $name; + $d->rate_down = $rate_down; + $d->rate_down_unit = $rate_down_unit; + $d->rate_up = $rate_up; + $d->rate_up_unit = $rate_up_unit; + $d->burst = $burst; + $d->save(); + + r2(U . 'bandwidth/list', 's', Lang::T('Data Created Successfully')); + } else { + r2(U . 'bandwidth/add', 'e', $msg); + } + break; + + case 'edit-post': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $name = _post('name'); + $rate_down = _post('rate_down'); + $rate_down_unit = _post('rate_down_unit'); + $rate_up = _post('rate_up'); + $rate_up_unit = _post('rate_up_unit'); + run_hook('edit_bandwidth'); #HOOK + $isBurst = true; + $burst = ""; + if (isset($_POST['burst'])) { + foreach ($_POST['burst'] as $b) { + if (empty($b)) { + $isBurst = false; + } + } + if ($isBurst) { + $burst = implode(' ', $_POST['burst']); + }; + } + $msg = ''; + if (Validator::Length($name, 16, 4) == false) { + $msg .= 'Name should be between 5 to 15 characters' . '
'; + } + + $id = _post('id'); + $d = ORM::for_table('tbl_bandwidth')->find_one($id); + if ($d) { + } else { + $msg .= Lang::T('Data Not Found') . '
'; + } + + if ($d['name_bw'] != $name) { + $c = ORM::for_table('tbl_bandwidth')->where('name_bw', $name)->find_one(); + if ($c) { + $msg .= Lang::T('Name Bandwidth Already Exist') . '
'; + } + } + + if ($msg == '') { + $d->name_bw = $name; + $d->rate_down = $rate_down; + $d->rate_down_unit = $rate_down_unit; + $d->rate_up = $rate_up; + $d->rate_up_unit = $rate_up_unit; + $d->burst = $burst; + $d->save(); + + r2(U . 'bandwidth/list', 's', Lang::T('Data Updated Successfully')); + } else { + r2(U . 'bandwidth/edit/' . $id, 'e', $msg); + } + break; + + default: + $ui->display('a404.tpl'); +} -- 2.47.2 From 8e24155b2d79a8d024031689c059b795e1c87bed Mon Sep 17 00:00:00 2001 From: nestict Date: Sat, 24 May 2025 11:17:30 +0200 Subject: [PATCH 08/11] Upload files to "system/controllers" Signed-off-by: nestict --- system/controllers/callback.php | 22 + system/controllers/codecanyon.php | 126 ++++++ system/controllers/community.php | 26 ++ system/controllers/customers.php | 705 ++++++++++++++++++++++++++++++ system/controllers/dashboard.php | 204 +++++++++ 5 files changed, 1083 insertions(+) create mode 100644 system/controllers/callback.php create mode 100644 system/controllers/codecanyon.php create mode 100644 system/controllers/community.php create mode 100644 system/controllers/customers.php create mode 100644 system/controllers/dashboard.php diff --git a/system/controllers/callback.php b/system/controllers/callback.php new file mode 100644 index 0000000..352f2eb --- /dev/null +++ b/system/controllers/callback.php @@ -0,0 +1,22 @@ +assign('_title', 'CodeCanyon.net'); +$ui->assign('_system_menu', 'settings'); + +$plugin_repository = 'https://hotspotbilling.github.io/Plugin-Repository/repository.json'; + +$action = $routes['1']; +$ui->assign('_admin', $admin); +$cache = File::pathFixer($CACHE_PATH . '/codecanyon.json'); + +if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); +} +if (empty($config['envato_token'])) { + r2(U . 'settings/app', 'w', 'Envato Personal Access Token is not set'); +} + +switch ($action) { + + case 'install': + if (!is_writeable(File::pathFixer($CACHE_PATH . '/'))) { + r2(U . "codecanyon", 'e', 'Folder system/cache/ is not writable'); + } + if (!is_writeable($PLUGIN_PATH)) { + r2(U . "codecanyon", 'e', 'Folder plugin/ is not writable'); + } + if (!is_writeable($PAYMENTGATEWAY_PATH)) { + r2(U . "codecanyon", 'e', 'Folder paymentgateway/ is not writable'); + } + set_time_limit(-1); + $item_id = $routes['2']; + $tipe = $routes['3']; + $result = Http::getData('https://api.envato.com/v3/market/buyer/download?item_id=' . $item_id, ['Authorization: Bearer ' . $config['envato_token']]); + $json = json_decode($result, true); + if (!isset($json['download_url'])) { + r2(U . 'codecanyon', 'e', 'Failed to get download url. ' . $json['description']); + } + $file = File::pathFixer($CACHE_PATH . '/codecanyon/'); + if (!file_exists($file)) { + mkdir($file); + } + $file .= $item_id . '.zip'; + if (file_exists($file)) + unlink($file); + //download + $fp = fopen($file, 'w+'); + $ch = curl_init($json['download_url']); + curl_setopt($ch, CURLOPT_POST, 0); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 120); + curl_setopt($ch, CURLOPT_TIMEOUT, 120); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_FILE, $fp); + curl_exec($ch); + curl_close($ch); + fclose($fp); + //extract + $target = File::pathFixer($CACHE_PATH . '/codecanyon/' . $item_id . '/'); + $zip = new ZipArchive(); + $zip->open($file); + $zip->extractTo($target); + $zip->close(); + //moving + if (file_exists($target . 'plugin')) { + File::copyFolder($target . 'plugin', $PLUGIN_PATH . DIRECTORY_SEPARATOR); + } else if (file_exists($target . 'paymentgateway')) { + File::copyFolder($target . 'paymentgateway', $PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR); + } else if (file_exists($target . 'theme')) { + File::copyFolder($target . 'theme', File::pathFixer('ui/themes/')); + } + //Cleaning + File::deleteFolder($target); + unlink($file); + r2(U . "codecanyon", 's', 'Installation success'); + case 'reload': + if (file_exists($cache)) + unlink($cache); + default: + if (class_exists('ZipArchive')) { + $zipExt = true; + } else { + $zipExt = false; + } + $ui->assign('zipExt', $zipExt); + + if (file_exists($cache) && time() - filemtime($cache) < (24 * 60 * 60)) { + $txt = file_get_contents($cache); + $plugins = json_decode($txt, true); + $ui->assign('chached_until', date($config['date_format'] . ' H:i', filemtime($cache) + (24 * 60 * 60))); + if (count($plugins) == 0) { + unlink($cache); + r2(U . 'codecanyon'); + } + } else { + $plugins = []; + $page = _get('page', 1); + back: + $result = Http::getData('https://api.envato.com/v3/market/buyer/list-purchases?&page=' . $page, ['Authorization: Bearer ' . $config['envato_token']]); + $items = json_decode($result, true); + if ($items && count($items['results']) > 0) { + foreach ($items['results'] as $item) { + $name = strtolower($item['item']['name']); + if (strpos($name, 'phpnuxbill') !== false) { + $plugins[] = $item; + } + } + $page++; + goto back; + } + if (count($plugins) > 0) { + file_put_contents($cache, json_encode($plugins)); + if (file_exists($cache)) { + $ui->assign('chached_until', date($config['date_format'] . ' H:i', filemtime($cache) + (24 * 60 * 60))); + } + } + } + $ui->assign('plugins', $plugins); + $ui->display('codecanyon.tpl'); +} diff --git a/system/controllers/community.php b/system/controllers/community.php new file mode 100644 index 0000000..df60c08 --- /dev/null +++ b/system/controllers/community.php @@ -0,0 +1,26 @@ +assign('_title', 'Community'); +$ui->assign('_system_menu', 'community'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +switch ($action) { + case 'rollback': + $ui->assign('_title', 'Rollback Update'); + $masters = json_decode(Http::getData("https://api.github.com/repos/hotspotbilling/phpnuxbill/commits?per_page=100",['User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:125.0) Gecko/20100101 Firefox/125.0']), true); + $devs = json_decode(Http::getData("https://api.github.com/repos/hotspotbilling/phpnuxbill/commits?sha=Development&per_page=100",['User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:125.0) Gecko/20100101 Firefox/125.0']), true); + + $ui->assign('masters', $masters); + $ui->assign('devs', $devs); + $ui->display('community-rollback.tpl'); + break; + default: + $ui->display('community.tpl'); +} \ No newline at end of file diff --git a/system/controllers/customers.php b/system/controllers/customers.php new file mode 100644 index 0000000..93477f8 --- /dev/null +++ b/system/controllers/customers.php @@ -0,0 +1,705 @@ +assign('_title', Lang::T('Customer')); +$ui->assign('_system_menu', 'customers'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +if (empty($action)) { + $action = 'list'; +} + +$leafletpickerHeader = << +EOT; + +switch ($action) { + case 'csv': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + + $cs = ORM::for_table('tbl_customers') + ->select('tbl_customers.id', 'id') + ->select('tbl_customers.username', 'username') + ->select('fullname') + ->select('address') + ->select('phonenumber') + ->select('email') + ->select('balance') + ->select('service_type') + ->order_by_asc('tbl_customers.id') + ->find_array(); + + $h = false; + set_time_limit(-1); + header('Pragma: public'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header("Content-type: text/csv"); + header('Content-Disposition: attachment;filename="phpnuxbill_customers_' . date('Y-m-d_H_i') . '.csv"'); + header('Content-Transfer-Encoding: binary'); + + $headers = [ + 'id', + 'username', + 'fullname', + 'address', + 'phonenumber', + 'email', + 'balance', + 'service_type', + ]; + + if (!$h) { + echo '"' . implode('","', $headers) . "\"\n"; + $h = true; + } + + foreach ($cs as $c) { + $row = [ + $c['id'], + $c['username'], + $c['fullname'], + $c['address'], + $c['phonenumber'], + $c['email'], + $c['balance'], + $c['service_type'], + ]; + echo '"' . implode('","', $row) . "\"\n"; + } + break; + //case csv-prepaid can be moved later to (plan.php) php file dealing with prepaid users + case 'csv-prepaid': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + + $cs = ORM::for_table('tbl_customers') + ->select('tbl_customers.id', 'id') + ->select('tbl_customers.username', 'username') + ->select('fullname') + ->select('address') + ->select('phonenumber') + ->select('email') + ->select('balance') + ->select('service_type') + ->select('namebp') + ->select('routers') + ->select('status') + ->select('method', 'Payment') + ->left_outer_join('tbl_user_recharges', array('tbl_customers.id', '=', 'tbl_user_recharges.customer_id')) + ->order_by_asc('tbl_customers.id') + ->find_array(); + + $h = false; + set_time_limit(-1); + header('Pragma: public'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header("Content-type: text/csv"); + header('Content-Disposition: attachment;filename="phpnuxbill_prepaid_users' . date('Y-m-d_H_i') . '.csv"'); + header('Content-Transfer-Encoding: binary'); + + $headers = [ + 'id', + 'username', + 'fullname', + 'address', + 'phonenumber', + 'email', + 'balance', + 'service_type', + 'namebp', + 'routers', + 'status', + 'Payment' + ]; + + if (!$h) { + echo '"' . implode('","', $headers) . "\"\n"; + $h = true; + } + + foreach ($cs as $c) { + $row = [ + $c['id'], + $c['username'], + $c['fullname'], + $c['address'], + $c['phonenumber'], + $c['email'], + $c['balance'], + $c['service_type'], + $c['namebp'], + $c['routers'], + $c['status'], + $c['Payment'] + ]; + echo '"' . implode('","', $row) . "\"\n"; + } + break; + case 'add': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $ui->assign('xheader', $leafletpickerHeader); + run_hook('view_add_customer'); #HOOK + $ui->display('customers-add.tpl'); + break; + case 'recharge': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $id_customer = $routes['2']; + $plan_id = $routes['3']; + $b = ORM::for_table('tbl_user_recharges')->where('customer_id', $id_customer)->where('plan_id', $plan_id)->find_one(); + if ($b) { + $gateway = 'Recharge'; + $channel = $admin['fullname']; + $cust = User::_info($id_customer); + $plan = ORM::for_table('tbl_plans')->find_one($b['plan_id']); + list($bills, $add_cost) = User::getBills($id_customer); + if ($using == 'balance' && $config['enable_balance'] == 'yes') { + if (!$cust) { + r2(U . 'plan/recharge', 'e', Lang::T('Customer not found')); + } + if (!$plan) { + r2(U . 'plan/recharge', 'e', Lang::T('Plan not found')); + } + if ($cust['balance'] < ($plan['price'] + $add_cost)) { + r2(U . 'plan/recharge', 'e', Lang::T('insufficient balance')); + } + $gateway = 'Recharge Balance'; + } + if ($using == 'zero') { + $zero = 1; + $gateway = 'Recharge Zero'; + } + $usings = explode(',', $config['payment_usings']); + $usings = array_filter(array_unique($usings)); + if (count($usings) == 0) { + $usings[] = Lang::T('Cash'); + } + $ui->assign('usings', $usings); + $ui->assign('bills', $bills); + $ui->assign('add_cost', $add_cost); + $ui->assign('cust', $cust); + $ui->assign('gateway', $gateway); + $ui->assign('channel', $channel); + $ui->assign('server', $b['routers']); + $ui->assign('plan', $plan); + $ui->display('recharge-confirm.tpl'); + } else { + r2(U . 'customers/view/' . $id_customer, 'e', 'Cannot find active plan'); + } + break; + case 'deactivate': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $id_customer = $routes['2']; + $plan_id = $routes['3']; + $b = ORM::for_table('tbl_user_recharges')->where('customer_id', $id_customer)->where('plan_id', $plan_id)->find_one(); + if ($b) { + $p = ORM::for_table('tbl_plans')->where('id', $b['plan_id'])->find_one(); + if ($p) { + if ($p['is_radius']) { + Radius::customerDeactivate($b['username']); + } else { + $mikrotik = Mikrotik::info($b['routers']); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + if ($b['type'] == 'Hotspot') { + Mikrotik::removeHotspotUser($client, $b['username']); + Mikrotik::removeHotspotActiveUser($client, $b['username']); + } else if ($b['type'] == 'PPPOE') { + Mikrotik::removePpoeUser($client, $b['username']); + Mikrotik::removePpoeActive($client, $b['username']); + } + } + $b->status = 'off'; + $b->expiration = date('Y-m-d'); + $b->time = date('H:i:s'); + $b->save(); + _log('Admin ' . $admin['username'] . ' Deactivate ' . $b['namebp'] . ' for ' . $b['username'], 'User', $b['customer_id']); + Message::sendTelegram('Admin ' . $admin['username'] . ' Deactivate ' . $b['namebp'] . ' for u' . $b['username']); + r2(U . 'customers/view/' . $id_customer, 's', 'Success deactivate customer to Mikrotik'); + } + } + r2(U . 'customers/view/' . $id_customer, 'e', 'Cannot find active plan'); + break; + case 'sync': + $id_customer = $routes['2']; + $bs = ORM::for_table('tbl_user_recharges')->where('customer_id', $id_customer)->where('status', 'on')->findMany(); + if ($bs) { + $routers = []; + foreach ($bs as $b) { + $c = ORM::for_table('tbl_customers')->find_one($id_customer); + $p = ORM::for_table('tbl_plans')->where('id', $b['plan_id'])->where('enabled', '1')->find_one(); + if ($p) { + $routers[] = $b['routers']; + if ($p['is_radius']) { + Radius::customerAddPlan($c, $p, $p['expiration'] . ' ' . $p['time']); + } else { + $mikrotik = Mikrotik::info($b['routers']); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + if ($b['type'] == 'Hotspot') { + Mikrotik::addHotspotUser($client, $p, $c); + } else if ($b['type'] == 'PPPOE') { + Mikrotik::addPpoeUser($client, $p, $c); + } + } + } + } + r2(U . 'customers/view/' . $id_customer, 's', 'Sync success to ' . implode(", ", $routers)); + } + r2(U . 'customers/view/' . $id_customer, 'e', 'Cannot find active plan'); + break; + case 'viewu': + $customer = ORM::for_table('tbl_customers')->where('username', $routes['2'])->find_one(); + case 'view': + $id = $routes['2']; + run_hook('view_customer'); #HOOK + if (!$customer) { + $customer = ORM::for_table('tbl_customers')->find_one($id); + } + if ($customer) { + + + // Fetch the Customers Attributes values from the tbl_customer_custom_fields table + $customFields = ORM::for_table('tbl_customers_fields') + ->where('customer_id', $customer['id']) + ->find_many(); + $v = $routes['3']; + if (empty($v)) { + $v = 'activation'; + } + if ($v == 'order') { + $v = 'order'; + $query = ORM::for_table('tbl_transactions')->where('username', $customer['username'])->order_by_desc('id'); + $order = Paginator::findMany($query); + $ui->assign('order', $order); + } else if ($v == 'activation') { + $query = ORM::for_table('tbl_transactions')->where('username', $customer['username'])->order_by_desc('id'); + $activation = Paginator::findMany($query); + $ui->assign('activation', $activation); + } + $ui->assign('packages', User::_billing($customer['id'])); + $ui->assign('v', $v); + $ui->assign('d', $customer); + $ui->assign('customFields', $customFields); + $ui->assign('xheader', $leafletpickerHeader); + $ui->display('customers-view.tpl'); + } else { + r2(U . 'customers/list', 'e', Lang::T('Account Not Found')); + } + break; + case 'edit': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $id = $routes['2']; + run_hook('edit_customer'); #HOOK + $d = ORM::for_table('tbl_customers')->find_one($id); + // Fetch the Customers Attributes values from the tbl_customers_fields table + $customFields = ORM::for_table('tbl_customers_fields') + ->where('customer_id', $id) + ->find_many(); + if ($d) { + $ui->assign('d', $d); + $ui->assign('statuses', ORM::for_table('tbl_customers')->getEnum("status")); + $ui->assign('customFields', $customFields); + $ui->assign('xheader', $leafletpickerHeader); + $ui->display('customers-edit.tpl'); + } else { + r2(U . 'customers/list', 'e', Lang::T('Account Not Found')); + } + break; + + case 'delete': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $id = $routes['2']; + run_hook('delete_customer'); #HOOK + $d = ORM::for_table('tbl_customers')->find_one($id); + if ($d) { + // Delete the associated Customers Attributes records from tbl_customer_custom_fields table + ORM::for_table('tbl_customers_fields')->where('customer_id', $id)->delete_many(); + $c = ORM::for_table('tbl_user_recharges')->where('username', $d['username'])->find_one(); + if ($c) { + $p = ORM::for_table('tbl_plans')->find_one($c['plan_id']); + if ($p['is_radius']) { + Radius::customerDelete($d['username']); + } else { + $mikrotik = Mikrotik::info($c['routers']); + if ($c['type'] == 'Hotspot') { + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + Mikrotik::removeHotspotUser($client, $d['username']); + Mikrotik::removeHotspotActiveUser($client, $d['username']); + } else { + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + Mikrotik::removePpoeUser($client, $d['username']); + Mikrotik::removePpoeActive($client, $d['username']); + } + try { + $d->delete(); + } catch (Exception $e) { + } catch (Throwable $e) { + } + try { + $c->delete(); + } catch (Exception $e) { + } + } + } else { + try { + $d->delete(); + } catch (Exception $e) { + } catch (Throwable $e) { + } + try { + if ($c) + $c->delete(); + } catch (Exception $e) { + } catch (Throwable $e) { + } + } + + r2(U . 'customers/list', 's', Lang::T('User deleted Successfully')); + } + break; + + case 'add-post': + $username = _post('username'); + $fullname = _post('fullname'); + $password = _post('password'); + $pppoe_password = _post('pppoe_password'); + $email = _post('email'); + $address = _post('address'); + $phonenumber = _post('phonenumber'); + $service_type = _post('service_type'); + $account_type = _post('account_type'); + $coordinates = _post('coordinates'); + //post Customers Attributes + $custom_field_names = (array) $_POST['custom_field_name']; + $custom_field_values = (array) $_POST['custom_field_value']; + //additional information + $city = _post('city'); + $district = _post('district'); + $state = _post('state'); + $zip = _post('zip'); + + run_hook('add_customer'); #HOOK + $msg = ''; + if (Validator::Length($username, 35, 2) == false) { + $msg .= 'Username should be between 3 to 55 characters' . '
'; + } + if (Validator::Length($fullname, 36, 2) == false) { + $msg .= 'Full Name should be between 3 to 25 characters' . '
'; + } + if (!Validator::Length($password, 36, 2)) { + $msg .= 'Password should be between 3 to 35 characters' . '
'; + } + + $d = ORM::for_table('tbl_customers')->where('username', $username)->find_one(); + if ($d) { + $msg .= Lang::T('Account already axist') . '
'; + } + + if ($msg == '') { + $d = ORM::for_table('tbl_customers')->create(); + $d->username = Lang::phoneFormat($username); + $d->password = $password; + $d->pppoe_password = $pppoe_password; + $d->email = $email; + $d->account_type = $account_type; + $d->fullname = $fullname; + $d->address = $address; + $d->created_by = $admin['id']; + $d->phonenumber = Lang::phoneFormat($phonenumber); + $d->service_type = $service_type; + $d->coordinates = $coordinates; + $d->city = $city; + $d->district = $district; + $d->state = $state; + $d->zip = $zip; + $d->save(); + + // Retrieve the customer ID of the newly created customer + $customerId = $d->id(); + // Save Customers Attributes details + if (!empty($custom_field_names) && !empty($custom_field_values)) { + $totalFields = min(count($custom_field_names), count($custom_field_values)); + for ($i = 0; $i < $totalFields; $i++) { + $name = $custom_field_names[$i]; + $value = $custom_field_values[$i]; + + if (!empty($name)) { + $customField = ORM::for_table('tbl_customers_fields')->create(); + $customField->customer_id = $customerId; + $customField->field_name = $name; + $customField->field_value = $value; + $customField->save(); + } + } + } + r2(U . 'customers/list', 's', Lang::T('Account Created Successfully')); + } else { + r2(U . 'customers/add', 'e', $msg); + } + break; + + case 'edit-post': + $username = Lang::phoneFormat(_post('username')); + $fullname = _post('fullname'); + $account_type = _post('account_type'); + $password = _post('password'); + $pppoe_password = _post('pppoe_password'); + $email = _post('email'); + $address = _post('address'); + $phonenumber = Lang::phoneFormat(_post('phonenumber')); + $service_type = _post('service_type'); + $coordinates = _post('coordinates'); + $status = _post('status'); + //additional information + $city = _post('city'); + $district = _post('district'); + $state = _post('state'); + $zip = _post('zip'); + run_hook('edit_customer'); #HOOK + $msg = ''; + if (Validator::Length($username, 35, 2) == false) { + $msg .= 'Username should be between 3 to 15 characters' . '
'; + } + if (Validator::Length($fullname, 36, 1) == false) { + $msg .= 'Full Name should be between 2 to 25 characters' . '
'; + } + if ($password != '') { + if (!Validator::Length($password, 36, 2)) { + $msg .= 'Password should be between 3 to 15 characters' . '
'; + } + } + + $id = _post('id'); + $d = ORM::for_table('tbl_customers')->find_one($id); + + //lets find user Customers Attributes using id + $customFields = ORM::for_table('tbl_customers_fields') + ->where('customer_id', $id) + ->find_many(); + + if (!$d) { + $msg .= Lang::T('Data Not Found') . '
'; + } + + $oldusername = $d['username']; + $oldPppoePassword = $d['password']; + $oldPassPassword = $d['pppoe_password']; + $userDiff = false; + $pppoeDiff = false; + $passDiff = false; + if ($oldusername != $username) { + $c = ORM::for_table('tbl_customers')->where('username', $username)->find_one(); + if ($c) { + $msg .= Lang::T('Account already exist') . '
'; + } + $userDiff = true; + } + if ($oldPppoePassword != $pppoe_password) { + $pppoeDiff = true; + } + if ($password != '' && $oldPassPassword != $password) { + $passDiff = true; + } + + if ($msg == '') { + if ($userDiff) { + $d->username = $username; + } + if ($password != '') { + $d->password = $password; + } + $d->pppoe_password = $pppoe_password; + $d->fullname = $fullname; + $d->email = $email; + $d->account_type = $account_type; + $d->address = $address; + $d->status = $status; + $d->phonenumber = $phonenumber; + $d->service_type = $service_type; + $d->coordinates = $coordinates; + $d->city = $city; + $d->district = $district; + $d->state = $state; + $d->zip = $zip; + $d->save(); + + + // Update Customers Attributes values in tbl_customers_fields table + foreach ($customFields as $customField) { + $fieldName = $customField['field_name']; + if (isset($_POST['custom_fields'][$fieldName])) { + $customFieldValue = $_POST['custom_fields'][$fieldName]; + $customField->set('field_value', $customFieldValue); + $customField->save(); + } + } + + // Add new Customers Attributess + if (isset($_POST['custom_field_name']) && isset($_POST['custom_field_value'])) { + $newCustomFieldNames = $_POST['custom_field_name']; + $newCustomFieldValues = $_POST['custom_field_value']; + + // Check if the number of field names and values match + if (count($newCustomFieldNames) == count($newCustomFieldValues)) { + $numNewFields = count($newCustomFieldNames); + + for ($i = 0; $i < $numNewFields; $i++) { + $fieldName = $newCustomFieldNames[$i]; + $fieldValue = $newCustomFieldValues[$i]; + + // Insert the new Customers Attributes + $newCustomField = ORM::for_table('tbl_customers_fields')->create(); + $newCustomField->set('customer_id', $id); + $newCustomField->set('field_name', $fieldName); + $newCustomField->set('field_value', $fieldValue); + $newCustomField->save(); + } + } + } + + // Delete Customers Attributess + if (isset($_POST['delete_custom_fields'])) { + $fieldsToDelete = $_POST['delete_custom_fields']; + foreach ($fieldsToDelete as $fieldName) { + // Delete the Customers Attributes with the given field name + ORM::for_table('tbl_customers_fields') + ->where('field_name', $fieldName) + ->where('customer_id', $id) + ->delete_many(); + } + } + + if ($userDiff || $pppoeDiff || $passDiff) { + $c = ORM::for_table('tbl_user_recharges')->where('username', ($userDiff) ? $oldusername : $username)->find_one(); + if ($c) { + $c->username = $username; + $c->save(); + $p = ORM::for_table('tbl_plans')->find_one($c['plan_id']); + if ($p['is_radius']) { + if ($userDiff) { + Radius::customerChangeUsername($oldusername, $username); + } + Radius::customerAddPlan($d, $p, $p['expiration'] . ' ' . $p['time']); + } else { + $mikrotik = Mikrotik::info($c['routers']); + if ($c['type'] == 'Hotspot') { + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + Mikrotik::setHotspotUser($client, $c['username'], $password); + Mikrotik::removeHotspotActiveUser($client, $d['username']); + } else { + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + if (!empty($d['pppoe_password'])) { + Mikrotik::setPpoeUser($client, $c['username'], $d['pppoe_password']); + } else { + Mikrotik::setPpoeUser($client, $c['username'], $password); + } + Mikrotik::removePpoeActive($client, $d['username']); + } + } + } + } + r2(U . 'customers/view/' . $id, 's', 'User Updated Successfully'); + } else { + r2(U . 'customers/edit/' . $id, 'e', $msg); + } + break; + + default: + run_hook('list_customers'); #HOOK + $search = _post('search'); + $order = _post('order', 'username'); + $filter = _post('filter', 'Active'); + $orderby = _post('orderby', 'asc'); + $order_pos = [ + 'username' => 0, + 'created_at' => 8, + 'balance' => 3, + 'status' => 7 + ]; + + if ($search != '') { + $query = ORM::for_table('tbl_customers') + ->whereRaw("username LIKE '%$search%' OR fullname LIKE '%$search%' OR address LIKE '%$search%' " . + "OR phonenumber LIKE '%$search%' OR email LIKE '%$search%' AND status='$filter'"); + } else { + $query = ORM::for_table('tbl_customers'); + $query->where("status", $filter); + } + if ($orderby == 'asc') { + $query->order_by_asc($order); + } else { + $query->order_by_desc($order); + } + $d = $query->findMany(); + if (_post('export', '') == 'csv') { + $h = false; + set_time_limit(-1); + header('Pragma: public'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header("Content-type: text/csv"); + header('Content-Disposition: attachment;filename="phpnuxbill_customers_' . $filter . '_' . date('Y-m-d_H_i') . '.csv"'); + header('Content-Transfer-Encoding: binary'); + + $headers = [ + 'id', + 'username', + 'fullname', + 'address', + 'phonenumber', + 'email', + 'balance', + 'service_type', + ]; + $fp = fopen('php://output', 'wb'); + if (!$h) { + fputcsv($fp, $headers, ";"); + $h = true; + } + foreach ($d as $c) { + $row = [ + $c['id'], + $c['username'], + $c['fullname'], + str_replace("\n", " ", $c['address']), + $c['phonenumber'], + $c['email'], + $c['balance'], + $c['service_type'], + ]; + fputcsv($fp, $row, ";"); + } + fclose($fp); + die(); + } + $ui->assign('xheader', ''); + $ui->assign('d', $d); + $ui->assign('statuses', ORM::for_table('tbl_customers')->getEnum("status")); + $ui->assign('filter', $filter); + $ui->assign('search', $search); + $ui->assign('order', $order); + $ui->assign('order_pos', $order_pos[$order]); + $ui->assign('orderby', $orderby); + $ui->display('customers.tpl'); + break; +} diff --git a/system/controllers/dashboard.php b/system/controllers/dashboard.php new file mode 100644 index 0000000..ad8afe3 --- /dev/null +++ b/system/controllers/dashboard.php @@ -0,0 +1,204 @@ +assign('_title', Lang::T('Dashboard')); +$ui->assign('_admin', $admin); + +if(isset($_GET['refresh'])){ + $files = scandir($CACHE_PATH); + foreach ($files as $file) { + $ext = pathinfo($file, PATHINFO_EXTENSION); + if (is_file($CACHE_PATH . DIRECTORY_SEPARATOR . $file) && $ext == 'temp') { + unlink($CACHE_PATH . DIRECTORY_SEPARATOR . $file); + } + } + r2(U . 'dashboard', 's', 'Data Refreshed'); +} + +$fdate = date('Y-m-01'); +$tdate = date('Y-m-t'); +//first day of month +$first_day_month = date('Y-m-01'); +$mdate = date('Y-m-d'); +$month_n = date('n'); + +$iday = ORM::for_table('tbl_transactions') + ->where('recharged_on', $mdate) + ->where_not_equal('method', 'Customer - Balance') + ->where_not_equal('method', 'Recharge Balance - Administrator') + ->sum('price'); + +if ($iday == '') { + $iday = '0.00'; +} +$ui->assign('iday', $iday); + +$imonth = ORM::for_table('tbl_transactions')->where_not_equal('method', 'Customer - Balance')->where_not_equal('method', 'Recharge Balance - Administrator')->where_gte('recharged_on', $first_day_month)->where_lte('recharged_on', $mdate)->sum('price'); +if ($imonth == '') { + $imonth = '0.00'; +} +$ui->assign('imonth', $imonth); + +$u_act = ORM::for_table('tbl_user_recharges')->where('status', 'on')->count(); +if (empty($u_act)) { + $u_act = '0'; +} +$ui->assign('u_act', $u_act); + +$u_all = ORM::for_table('tbl_user_recharges')->count(); +if (empty($u_all)) { + $u_all = '0'; +} +$ui->assign('u_all', $u_all); + + +$c_all = ORM::for_table('tbl_customers')->count(); +if (empty($c_all)) { + $c_all = '0'; +} +$ui->assign('c_all', $c_all); + +if ($config['hide_uet'] != 'yes') { + //user expire + $query = ORM::for_table('tbl_user_recharges') + ->where_lte('expiration', $mdate) + ->order_by_desc('expiration'); + $expire = Paginator::findMany($query); + + // Get the total count of expired records for pagination + $totalCount = ORM::for_table('tbl_user_recharges') + ->where_lte('expiration', $mdate) + ->count(); + + // Pass the total count and current page to the paginator + $paginator['total_count'] = $totalCount; + + // Assign the pagination HTML to the template variable + $ui->assign('expire', $expire); +} + +//activity log +$dlog = ORM::for_table('tbl_logs')->limit(5)->order_by_desc('id')->find_many(); +$ui->assign('dlog', $dlog); +$log = ORM::for_table('tbl_logs')->count(); +$ui->assign('log', $log); + + +if ($config['hide_vs'] != 'yes') { + $cacheStocksfile = $CACHE_PATH . File::pathFixer('/VoucherStocks.temp'); + $cachePlanfile = $CACHE_PATH . File::pathFixer('/VoucherPlans.temp'); + //Cache for 5 minutes + if (file_exists($cacheStocksfile) && time() - filemtime($cacheStocksfile) < 600) { + $stocks = json_decode(file_get_contents($cacheStocksfile), true); + $plans = json_decode(file_get_contents($cachePlanfile), true); + } else { + // Count stock + $tmp = $v = ORM::for_table('tbl_plans')->select('id')->select('name_plan')->find_many(); + $plans = array(); + $stocks = array("used" => 0, "unused" => 0); + $n = 0; + foreach ($tmp as $plan) { + $unused = ORM::for_table('tbl_voucher') + ->where('id_plan', $plan['id']) + ->where('status', 0)->count(); + $used = ORM::for_table('tbl_voucher') + ->where('id_plan', $plan['id']) + ->where('status', 1)->count(); + if ($unused > 0 || $used > 0) { + $plans[$n]['name_plan'] = $plan['name_plan']; + $plans[$n]['unused'] = $unused; + $plans[$n]['used'] = $used; + $stocks["unused"] += $unused; + $stocks["used"] += $used; + $n++; + } + } + file_put_contents($cacheStocksfile, json_encode($stocks)); + file_put_contents($cachePlanfile, json_encode($plans)); + } +} + +$cacheMRfile = File::pathFixer('/monthlyRegistered.temp'); +//Cache for 1 hour +if (file_exists($cacheMRfile) && time() - filemtime($cacheMRfile) < 3600) { + $monthlyRegistered = json_decode(file_get_contents($cacheMRfile), true); +} else { + //Monthly Registered Customers + $result = ORM::for_table('tbl_customers') + ->select_expr('MONTH(created_at)', 'month') + ->select_expr('COUNT(*)', 'count') + ->where_raw('YEAR(created_at) = YEAR(NOW())') + ->group_by_expr('MONTH(created_at)') + ->find_many(); + + $monthlyRegistered = []; + foreach ($result as $row) { + $monthlyRegistered[] = [ + 'date' => $row->month, + 'count' => $row->count + ]; + } + file_put_contents($cacheMRfile, json_encode($monthlyRegistered)); +} + +$cacheMSfile = $CACHE_PATH . File::pathFixer('/monthlySales.temp'); +//Cache for 12 hours +if (file_exists($cacheMSfile) && time() - filemtime($cacheMSfile) < 43200) { + $monthlySales = json_decode(file_get_contents($cacheMSfile), true); +} else { + // Query to retrieve monthly data + $results = ORM::for_table('tbl_transactions') + ->select_expr('MONTH(recharged_on)', 'month') + ->select_expr('SUM(price)', 'total') + ->where_raw("YEAR(recharged_on) = YEAR(CURRENT_DATE())") // Filter by the current year + ->where_not_equal('method', 'Customer - Balance') + ->where_not_equal('method', 'Recharge Balance - Administrator') + ->group_by_expr('MONTH(recharged_on)') + ->find_many(); + + // Create an array to hold the monthly sales data + $monthlySales = array(); + + // Iterate over the results and populate the array + foreach ($results as $result) { + $month = $result->month; + $totalSales = $result->total; + + $monthlySales[$month] = array( + 'month' => $month, + 'totalSales' => $totalSales + ); + } + + // Fill in missing months with zero sales + for ($month = 1; $month <= 12; $month++) { + if (!isset($monthlySales[$month])) { + $monthlySales[$month] = array( + 'month' => $month, + 'totalSales' => 0 + ); + } + } + + // Sort the array by month + ksort($monthlySales); + + // Reindex the array + $monthlySales = array_values($monthlySales); + file_put_contents($cacheMSfile, json_encode($monthlySales)); +} + +// Assign the monthly sales data to Smarty +$ui->assign('monthlySales', $monthlySales); +$ui->assign('xfooter', ''); +$ui->assign('monthlyRegistered', $monthlyRegistered); +$ui->assign('stocks', $stocks); +$ui->assign('plans', $plans); + +run_hook('view_dashboard'); #HOOK +$ui->display('dashboard.tpl'); -- 2.47.2 From 91f3fbad90d8a9c0a30f8c80a87d6171e9e2da70 Mon Sep 17 00:00:00 2001 From: nestict Date: Sat, 24 May 2025 11:19:52 +0200 Subject: [PATCH 09/11] Upload files to "system/controllers" Signed-off-by: nestict --- system/controllers/default.php | 13 ++ system/controllers/export.php | 356 +++++++++++++++++++++++++++++++++ system/controllers/home.php | 252 +++++++++++++++++++++++ system/controllers/index.html | 8 + system/controllers/login.php | 195 ++++++++++++++++++ 5 files changed, 824 insertions(+) create mode 100644 system/controllers/default.php create mode 100644 system/controllers/export.php create mode 100644 system/controllers/home.php create mode 100644 system/controllers/index.html create mode 100644 system/controllers/login.php diff --git a/system/controllers/default.php b/system/controllers/default.php new file mode 100644 index 0000000..d32f8ce --- /dev/null +++ b/system/controllers/default.php @@ -0,0 +1,13 @@ +assign('_title', Lang::T('Reports')); +$ui->assign('_sysfrm_menu', 'reports'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +$mdate = date('Y-m-d'); +$tdate = date('Y-m-d', strtotime('today - 30 days')); + +//first day of month +$first_day_month = date('Y-m-01'); +// +$this_week_start = date('Y-m-d', strtotime('previous sunday')); +// 30 days before +$before_30_days = date('Y-m-d', strtotime('today - 30 days')); +//this month +$month_n = date('n'); + +switch ($action) { + + case 'print-by-date': + $mdate = date('Y-m-d'); + $d = ORM::for_table('tbl_transactions'); + $d->where('recharged_on', $mdate); + $d->order_by_desc('id'); + $x = $d->find_many(); + + $dr = ORM::for_table('tbl_transactions'); + $dr->where('recharged_on', $mdate); + $dr->order_by_desc('id'); + $xy = $dr->sum('price'); + + $ui->assign('d', $x); + $ui->assign('dr', $xy); + $ui->assign('mdate', $mdate); + $ui->assign('recharged_on', $mdate); + run_hook('print_by_date'); #HOOK + $ui->display('print-by-date.tpl'); + break; + + case 'pdf-by-date': + $mdate = date('Y-m-d'); + + $d = ORM::for_table('tbl_transactions'); + $d->where('recharged_on', $mdate); + $d->order_by_desc('id'); + $x = $d->find_many(); + + $dr = ORM::for_table('tbl_transactions'); + $dr->where('recharged_on', $mdate); + $dr->order_by_desc('id'); + $xy = $dr->sum('price'); + + $title = ' Reports [' . $mdate . ']'; + $title = str_replace('-', ' ', $title); + + $UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH); + if (file_exists($UPLOAD_PATH . '/logo.png')) { + $logo = $UPLOAD_URL_PATH . '/logo.png'; + } else { + $logo = $UPLOAD_URL_PATH . '/logo.default.png'; + } + + if ($x) { + $html = ' +
+
+

' . $config['CompanyName'] . '

+ ' . $config['address'] . '
+ ' . Lang::T('Phone Number') . ': ' . $config['phone'] . '
+
+ +
+ + + + + + + + + + + + '; + $c = true; + foreach ($x as $value) { + + $username = $value['username']; + $plan_name = $value['plan_name']; + $type = $value['type']; + $price = $config['currency_code'] . ' ' . number_format($value['price'], 0, $config['dec_point'], $config['thousands_sep']); + $recharged_on = date($config['date_format'], strtotime($value['recharged_on'])); + $expiration = date($config['date_format'], strtotime($value['expiration'])); + $time = $value['time']; + $method = $value['method']; + $routers = $value['routers']; + + $html .= "" . " + + + + + + + + + "; + } + $html .= '
' . Lang::T('Username') . '' . Lang::T('Plan Name') . '' . Lang::T('Type') . '' . Lang::T('Plan Price') . '' . Lang::T('Created On') . '' . Lang::T('Expires On') . '' . Lang::T('Method') . '' . Lang::T('Routers') . '
$username$plan_name$type$price$recharged_on$expiration $time $method$routers
+

' . Lang::T('Total Income') . ':

+

' . $config['currency_code'] . ' ' . number_format($xy, 2, $config['dec_point'], $config['thousands_sep']) . '

'; + run_hook('print_pdf_by_date'); #HOOK + + $mpdf = new \Mpdf\Mpdf(); + $mpdf->SetProtection(array('print')); + $mpdf->SetTitle($config['CompanyName'] . ' Reports'); + $mpdf->SetAuthor($config['CompanyName']); + $mpdf->SetWatermarkText($d['price']); + $mpdf->showWatermarkText = true; + $mpdf->watermark_font = 'Helvetica'; + $mpdf->watermarkTextAlpha = 0.1; + $mpdf->SetDisplayMode('fullpage'); + + $style = ''; + + $nhtml = <<WriteHTML($nhtml); + $mpdf->Output(date('Ymd_His') . '.pdf', 'D'); + } else { + echo 'No Data'; + } + + break; + + case 'print-by-period': + $fdate = _post('fdate'); + $tdate = _post('tdate'); + $stype = _post('stype'); + + $d = ORM::for_table('tbl_transactions'); + if ($stype != '') { + $d->where('type', $stype); + } + $d->where_gte('recharged_on', $fdate); + $d->where_lte('recharged_on', $tdate); + $d->order_by_desc('id'); + $x = $d->find_many(); + + $dr = ORM::for_table('tbl_transactions'); + if ($stype != '') { + $dr->where('type', $stype); + } + + $dr->where_gte('recharged_on', $fdate); + $dr->where_lte('recharged_on', $tdate); + $xy = $dr->sum('price'); + + $ui->assign('d', $x); + $ui->assign('dr', $xy); + $ui->assign('fdate', $fdate); + $ui->assign('tdate', $tdate); + $ui->assign('stype', $stype); + run_hook('print_by_period'); #HOOK + $ui->display('print-by-period.tpl'); + break; + + + case 'pdf-by-period': + $fdate = _post('fdate'); + $tdate = _post('tdate'); + $stype = _post('stype'); + $d = ORM::for_table('tbl_transactions'); + if ($stype != '') { + $d->where('type', $stype); + } + + $d->where_gte('recharged_on', $fdate); + $d->where_lte('recharged_on', $tdate); + $d->order_by_desc('id'); + $x = $d->find_many(); + + $dr = ORM::for_table('tbl_transactions'); + if ($stype != '') { + $dr->where('type', $stype); + } + + $dr->where_gte('recharged_on', $fdate); + $dr->where_lte('recharged_on', $tdate); + $xy = $dr->sum('price'); + + $title = ' Reports [' . $mdate . ']'; + $title = str_replace('-', ' ', $title); + + $UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH); + if (file_exists($UPLOAD_PATH . '/logo.png')) { + $logo = $UPLOAD_URL_PATH . '/logo.png'; + } else { + $logo = $UPLOAD_URL_PATH . '/logo.default.png'; + } + + if ($x) { + $html = ' +
+
+

' . $config['CompanyName'] . '

+ ' . $config['address'] . '
+ ' . Lang::T('Phone Number') . ': ' . $config['phone'] . '
+
+ +
+ + + + + + + + + + + + '; + $c = true; + foreach ($x as $value) { + + $username = $value['username']; + $plan_name = $value['plan_name']; + $type = $value['type']; + $price = $config['currency_code'] . ' ' . number_format($value['price'], 0, $config['dec_point'], $config['thousands_sep']); + $recharged_on = date($config['date_format'], strtotime($value['recharged_on'])); + $expiration = date($config['date_format'], strtotime($value['expiration'])); + $time = $value['time']; + $method = $value['method']; + $routers = $value['routers']; + + $html .= "" . " + + + + + + + + + "; + } + $html .= '
' . Lang::T('Username') . '' . Lang::T('Plan Name') . '' . Lang::T('Type') . '' . Lang::T('Plan Price') . '' . Lang::T('Created On') . '' . Lang::T('Expires On') . '' . Lang::T('Method') . '' . Lang::T('Routers') . '
$username$plan_name$type$price$recharged_on $expiration $time $method$routers
+

' . Lang::T('Total Income') . ':

+

' . $config['currency_code'] . ' ' . number_format($xy, 2, $config['dec_point'], $config['thousands_sep']) . '

'; + + run_hook('pdf_by_period'); #HOOK + $mpdf = new \Mpdf\Mpdf(); + $mpdf->SetProtection(array('print')); + $mpdf->SetTitle($config['CompanyName'] . ' Reports'); + $mpdf->SetAuthor($config['CompanyName']); + $mpdf->SetWatermarkText($d['price']); + $mpdf->showWatermarkText = true; + $mpdf->watermark_font = 'Helvetica'; + $mpdf->watermarkTextAlpha = 0.1; + $mpdf->SetDisplayMode('fullpage'); + + $style = ''; + + $nhtml = <<WriteHTML($nhtml); + $mpdf->Output(date('Ymd_His') . '.pdf', 'D'); + } else { + echo 'No Data'; + } + + break; + + default: + $ui->display('a404.tpl'); +} diff --git a/system/controllers/home.php b/system/controllers/home.php new file mode 100644 index 0000000..7640d05 --- /dev/null +++ b/system/controllers/home.php @@ -0,0 +1,252 @@ +assign('_title', Lang::T('Dashboard')); + +$user = User::_info(); +$ui->assign('_user', $user); + +if (isset($_GET['renewal'])) { + $user->auto_renewal = $_GET['renewal']; + $user->save(); +} + +if (_post('send') == 'balance') { + if ($config['enable_balance'] == 'yes' && $config['allow_balance_transfer'] == 'yes') { + if ($user['status'] != 'Active') { + _alert(Lang::T('This account status') . ' : ' . Lang::T($user['status']), 'danger', ""); + } + $target = ORM::for_table('tbl_customers')->where('username', _post('username'))->find_one(); + if (!$target) { + r2(U . 'home', 'd', Lang::T('Username not found')); + } + $username = _post('username'); + $balance = _post('balance'); + if ($user['balance'] < $balance) { + r2(U . 'home', 'd', Lang::T('insufficient balance')); + } + if (!empty($config['minimum_transfer']) && intval($balance) < intval($config['minimum_transfer'])) { + r2(U . 'home', 'd', Lang::T('Minimum Transfer') . ' ' . Lang::moneyFormat($config['minimum_transfer'])); + } + if ($user['username'] == $target['username']) { + r2(U . 'home', 'd', Lang::T('Cannot send to yourself')); + } + if (Balance::transfer($user['id'], $username, $balance)) { + //sender + $d = ORM::for_table('tbl_payment_gateway')->create(); + $d->username = $user['username']; + $d->gateway = $target['username']; + $d->plan_id = 0; + $d->plan_name = 'Send Balance'; + $d->routers_id = 0; + $d->routers = 'balance'; + $d->price = $balance; + $d->payment_method = "Customer"; + $d->payment_channel = "Balance"; + $d->created_date = date('Y-m-d H:i:s'); + $d->paid_date = date('Y-m-d H:i:s'); + $d->expired_date = date('Y-m-d H:i:s'); + $d->pg_url_payment = 'balance'; + $d->status = 2; + $d->save(); + //receiver + $d = ORM::for_table('tbl_payment_gateway')->create(); + $d->username = $target['username']; + $d->gateway = $user['username']; + $d->plan_id = 0; + $d->plan_name = 'Receive Balance'; + $d->routers_id = 0; + $d->routers = 'balance'; + $d->payment_method = "Customer"; + $d->payment_channel = "Balance"; + $d->price = $balance; + $d->created_date = date('Y-m-d H:i:s'); + $d->paid_date = date('Y-m-d H:i:s'); + $d->expired_date = date('Y-m-d H:i:s'); + $d->pg_url_payment = 'balance'; + $d->status = 2; + $d->save(); + Message::sendBalanceNotification($user['phonenumber'], $target['fullname'] . ' (' . $target['username'] . ')', $balance, ($user['balance'] - $balance), Lang::getNotifText('balance_send'), $config['user_notification_payment']); + Message::sendBalanceNotification($target['phonenumber'], $user['fullname'] . ' (' . $user['username'] . ')', $balance, ($target['balance'] + $balance), Lang::getNotifText('balance_received'), $config['user_notification_payment']); + Message::sendTelegram("#u$user[username] send balance to #u$target[username] \n" . Lang::moneyFormat($balance)); + r2(U . 'home', 's', Lang::T('Sending balance success')); + } + } else { + r2(U . 'home', 'd', Lang::T('Failed, balance is not available')); + } +} else if (_post('send') == 'plan') { + if ($user['status'] != 'Active') { + _alert(Lang::T('This account status') . ' : ' . Lang::T($user['status']), 'danger', ""); + } + $actives = ORM::for_table('tbl_user_recharges') + ->where('username', _post('username')) + ->find_many(); + foreach ($actives as $active) { + $router = ORM::for_table('tbl_routers')->where('name', $active['routers'])->find_one(); + if ($router) { + r2(U . "order/send/$router[id]/$active[plan_id]&u=" . trim(_post('username')), 's', Lang::T('Review package before recharge')); + } + } + r2(U . 'home', 'w', Lang::T('Your friend do not have active package')); +} + +$ui->assign('_bills', User::_billing()); + +if (isset($_GET['recharge']) && !empty($_GET['recharge'])) { + if ($user['status'] != 'Active') { + _alert(Lang::T('This account status') . ' : ' . Lang::T($user['status']), 'danger', ""); + } + if (!empty(App::getTokenValue(_get('stoken')))) { + r2(U . "voucher/invoice/"); + die(); + } + $bill = ORM::for_table('tbl_user_recharges')->where('id', $_GET['recharge'])->where('username', $user['username'])->findOne(); + if ($bill) { + if ($bill['routers'] == 'radius') { + $router = 'radius'; + } else { + $routers = ORM::for_table('tbl_routers')->where('name', $bill['routers'])->find_one(); + $router = $routers['id']; + } + if ($config['enable_balance'] == 'yes') { + $plan = ORM::for_table('tbl_plans')->find_one($bill['plan_id']); + if (!$plan['enabled']) { + r2(U . "home", 'e', 'Plan is not exists'); + } + if ($user['balance'] > $plan['price']) { + r2(U . "order/pay/$router/$bill[plan_id]&stoken=" . _get('stoken'), 'e', 'Order Plan'); + } else { + r2(U . "order/buy/$router/$bill[plan_id]", 'e', 'Order Plan'); + } + } else { + r2(U . "order/buy/$router/$bill[plan_id]", 'e', 'Order Plan'); + } + } +} else if (!empty(_get('extend'))) { + if ($user['status'] != 'Active') { + _alert(Lang::T('This account status') . ' : ' . Lang::T($user['status']), 'danger', ""); + } + if (!$config['extend_expired']) { + r2(U . 'home', 'e', "cannot extend"); + } + if (!empty(App::getTokenValue(_get('stoken')))) { + r2(U . 'home', 'e', "You already extend"); + } + $id = _get('extend'); + $tur = ORM::for_table('tbl_user_recharges')->where('customer_id', $user['id'])->where('id', $id)->find_one(); + if ($tur) { + $m = date("m"); + $path = $CACHE_PATH . DIRECTORY_SEPARATOR . "extends" . DIRECTORY_SEPARATOR; + if (!file_exists($path)) { + mkdir($path); + } + $path .= $user['id'] . ".txt"; + if (file_exists($path)) { + // is already extend + $last = file_get_contents($path); + if ($last == $m) { + r2(U . 'home', 'e', "You already extend for this month"); + } + } + if ($tur['status'] != 'on') { + if ($tur['routers'] != 'radius') { + $mikrotik = Mikrotik::info($tur['routers']); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + $router = $tur['routers']; + } + $p = ORM::for_table('tbl_plans')->findOne($tur['plan_id']); + if (!$p) { + r2(U . 'home', '3', "Plan Not Found"); + } + if ($tur['routers'] == 'radius') { + Radius::customerAddPlan($user, $p, $tur['expiration'] . ' ' . $tur['time']); + } else { + if ($tur['type'] == 'Hotspot') { + Mikrotik::removeHotspotUser($client, $user['username']); + Mikrotik::addHotspotUser($client, $p, $user); + } else if ($tur['type'] == 'PPPOE') { + Mikrotik::removePpoeUser($client, $user['username']); + Mikrotik::addPpoeUser($client, $p, $user); + } + } + // make customer cannot extend again + $days = $config['extend_days']; + $expiration = date('Y-m-d', strtotime(" +$days day")); + $tur->expiration = $expiration; + $tur->status = "on"; + $tur->save(); + App::setToken(_get('stoken'), $id); + file_put_contents($path, $m); + _log("Customer $tur[customer_id] $tur[username] extend for $days days", "Customer", $user['id']); + Message::sendTelegram("#u$user[username] #extend #" . $p['type'] . " \n" . $p['name_plan'] . + "\nLocation: " . $p['routers'] . + "\nCustomer: " . $user['fullname'] . + "\nNew Expired: " . Lang::dateAndTimeFormat($expiration, $tur['time'])); + r2(U . 'home', 's', "Extend until $expiration"); + } else { + r2(U . 'home', 'e', "Plan is not expired"); + } + } else { + r2(U . 'home', 'e', "Plan Not Found or Not Active"); + } +} else if (isset($_GET['deactivate']) && !empty($_GET['deactivate'])) { + $bill = ORM::for_table('tbl_user_recharges')->where('id', $_GET['deactivate'])->where('username', $user['username'])->findOne(); + if ($bill) { + $p = ORM::for_table('tbl_plans')->where('id', $bill['plan_id'])->find_one(); + if ($p['is_radius']) { + Radius::customerDeactivate($user['username']); + } else { + try { + $mikrotik = Mikrotik::info($bill['routers']); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + if ($bill['type'] == 'Hotspot') { + Mikrotik::removeHotspotUser($client, $bill['username']); + Mikrotik::removeHotspotActiveUser($client, $bill['username']); + } else if ($bill['type'] == 'PPPOE') { + Mikrotik::removePpoeUser($client, $bill['username']); + Mikrotik::removePpoeActive($client, $bill['username']); + } + } catch (Exception $e) { + //ignore it maybe mikrotik has been deleted + } + } + $bill->status = 'off'; + $bill->expiration = date('Y-m-d'); + $bill->time = date('H:i:s'); + $bill->save(); + _log('User ' . $bill['username'] . ' Deactivate ' . $bill['namebp'], 'Customer', $bill['customer_id']); + Message::sendTelegram('User u' . $bill['username'] . ' Deactivate ' . $bill['namebp']); + r2(U . 'home', 's', 'Success deactivate ' . $bill['namebp']); + } else { + r2(U . 'home', 'e', 'No Active Plan'); + } +} + +if (!empty($_SESSION['nux-mac']) && !empty($_SESSION['nux-ip'])) { + $ui->assign('nux_mac', $_SESSION['nux-mac']); + $ui->assign('nux_ip', $_SESSION['nux-ip']); + $bill = ORM::for_table('tbl_user_recharges')->where('id', $_GET['id'])->where('username', $user['username'])->findOne(); + if ($_GET['mikrotik'] == 'login') { + $m = Mikrotik::info($bill['routers']); + $c = Mikrotik::getClient($m['ip_address'], $m['username'], $m['password']); + Mikrotik::logMeIn($c, $user['username'], $user['password'], $_SESSION['nux-ip'], $_SESSION['nux-mac']); + r2(U . 'home', 's', Lang::T('Login Request successfully')); + } else if ($_GET['mikrotik'] == 'logout') { + $m = Mikrotik::info($bill['routers']); + $c = Mikrotik::getClient($m['ip_address'], $m['username'], $m['password']); + Mikrotik::logMeOut($c, $user['username']); + r2(U . 'home', 's', Lang::T('Logout Request successfully')); + } +} + +$ui->assign('unpaid', ORM::for_table('tbl_payment_gateway') + ->where('username', $user['username']) + ->where('status', 1) + ->find_one()); +run_hook('view_customer_dashboard'); #HOOK +$ui->display('user-dashboard.tpl'); diff --git a/system/controllers/index.html b/system/controllers/index.html new file mode 100644 index 0000000..9757970 --- /dev/null +++ b/system/controllers/index.html @@ -0,0 +1,8 @@ + + + 403 Forbidden + + +

Directory access is forbidden.

+ + \ No newline at end of file diff --git a/system/controllers/login.php b/system/controllers/login.php new file mode 100644 index 0000000..449b6cc --- /dev/null +++ b/system/controllers/login.php @@ -0,0 +1,195 @@ +where('username', $username)->find_one(); + if ($d) { + $d_pass = $d['password']; + if ($d['status'] == 'Banned') { + echo '
' . Lang::T('This account status') . ': ' . Lang::T($d['status']) . '
'; + } + if (Password::_uverify($password, $d_pass) == true) { + $_SESSION['uid'] = $d['id']; + User::setCookie($d['id']); + $d->last_login = date('Y-m-d H:i:s'); + $d->save(); + _log($username . ' ' . Lang::T('Login Successful'), 'User', $d['id']); + echo '
' . Lang::T('Login Successful') . '
'; + r2(U . 'home'); + } else { + echo '
' . Lang::T('Invalid Username or Password') . '
'; + _log($username . ' ' . Lang::T('Failed Login'), 'User'); + r2(U . 'login'); + } + } else { + $d = ORM::for_table('tbl_users')->where('username', $username)->find_one(); + if ($d) { + $d_pass = $d['password']; + if (Password::_verify($password, $d_pass) == true) { + $_SESSION['aid'] = $d['id']; + $token = Admin::setCookie($d['id']); + $d->last_login = date('Y-m-d H:i:s'); + $d->save(); + _log($username . ' ' . Lang::T('Login Successful'), $d['user_type'], $d['id']); + echo '
' . Lang::T('Login Successful') . '
'; + r2(U . 'dashboard'); + } else { + echo '
' . Lang::T('Invalid Username or Password') . '
'; + _log($username . ' ' . Lang::T('Failed Login'), $d['user_type']); + r2(U . 'login'); + } + } else { + echo '
' . Lang::T('Invalid Username or Password') . '
'; + r2(U . 'login'); + } + } + } else { + echo '
' . Lang::T('Invalid Username or Password') . '
'; + r2(U . 'login'); + } + + break; + + case 'activation': + $voucher = _post('voucher'); + $username = _post('username'); + $v1 = ORM::for_table('tbl_voucher')->where('code', $voucher)->find_one(); + if ($v1) { + // voucher exists, check customer exists or not + $user = ORM::for_table('tbl_customers')->where('username', $username)->find_one(); + if (!$user) { + $d = ORM::for_table('tbl_customers')->create(); + $d->username = alphanumeric($username, "+_."); + $d->password = $voucher; + $d->fullname = ''; + $d->address = ''; + $d->email = ''; + $d->phonenumber = (strlen($username) < 21) ? $username : ''; + if ($d->save()) { + $user = ORM::for_table('tbl_customers')->where('username', $username)->find_one($d->id()); + if (!$user) { + r2(U . 'login', 'e', Lang::T('Voucher activation failed')); + } + } else { + _alert(Lang::T('Login Successful'), 'success', "dashboard"); + r2(U . 'login', 'e', Lang::T('Voucher activation failed') . '.'); + } + } + if ($v1['status'] == 0) { + $oldPass = $user['password']; + // change customer password to voucher code + $user->password = $voucher; + $user->save(); + // voucher activation + if (Package::rechargeUser($user['id'], $v1['routers'], $v1['id_plan'], "Voucher", $voucher)) { + $v1->status = "1"; + $v1->user = $user['username']; + $v1->save(); + $user->last_login = date('Y-m-d H:i:s'); + $user->save(); + // add customer to mikrotik + if (!empty($_SESSION['nux-mac']) && !empty($_SESSION['nux-ip'])) { + try { + $m = Mikrotik::info($v1['routers']); + $c = Mikrotik::getClient($m['ip_address'], $m['username'], $m['password']); + Mikrotik::logMeIn($c, $user['username'], $user['password'], $_SESSION['nux-ip'], $_SESSION['nux-mac']); + if (!empty($config['voucher_redirect'])) { + r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, you are connected to internet")); + } else { + r2(U . "login", 's', Lang::T("Voucher activation success, you are connected to internet")); + } + } catch (Exception $e) { + if (!empty($config['voucher_redirect'])) { + r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, now you can login")); + } else { + r2(U . "login", 's', Lang::T("Voucher activation success, now you can login")); + } + } + } + if (!empty($config['voucher_redirect'])) { + r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, now you can login")); + } else { + r2(U . "login", 's', Lang::T("Voucher activation success, now you can login")); + } + } else { + // if failed to recharge, restore old password + $user->password = $oldPass; + $user->save(); + r2(U . 'login', 'e', Lang::T("Failed to activate voucher")); + } + } else { + // used voucher + // check if voucher used by this username + if ($v1['user'] == $user['username']) { + $user->last_login = date('Y-m-d H:i:s'); + $user->save(); + if (!empty($_SESSION['nux-mac']) && !empty($_SESSION['nux-ip'])) { + try { + $m = Mikrotik::info($v1['routers']); + $c = Mikrotik::getClient($m['ip_address'], $m['username'], $m['password']); + Mikrotik::logMeIn($c, $user['username'], $user['password'], $_SESSION['nux-ip'], $_SESSION['nux-mac']); + if (!empty($config['voucher_redirect'])) { + r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, you are connected to internet")); + } else { + r2(U . "login", 's', Lang::T("Voucher activation success, now you can login")); + } + } catch (Exception $e) { + if (!empty($config['voucher_redirect'])) { + r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, now you can login")); + } else { + r2(U . "login", 's', Lang::T("Voucher activation success, now you can login")); + } + } + } else { + if (!empty($config['voucher_redirect'])) { + r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, you are connected to internet")); + } else { + r2(U . "login", 's', Lang::T("Voucher activation success, now you can login")); + } + } + } else { + // voucher used by other customer + r2(U . 'login', 'e', Lang::T('Voucher Not Valid')); + } + } + } else { + _msglog('e', Lang::T('Invalid Username or Password')); + r2(U . 'login'); + } + default: + run_hook('customer_view_login'); #HOOK + if ($config['disable_registration'] == 'yes') { + $ui->display('user-login-noreg.tpl'); + } else { + $ui->display('user-login.tpl'); + } + break; +} -- 2.47.2 From ed5007236525e0868f711308329e99efa6959584 Mon Sep 17 00:00:00 2001 From: nestict Date: Sat, 24 May 2025 11:20:26 +0200 Subject: [PATCH 10/11] Upload files to "system/controllers" Signed-off-by: nestict --- system/controllers/logout.php | 12 ++ system/controllers/logs.php | 125 +++++++++++++++++ system/controllers/map.php | 54 +++++++ system/controllers/message.php | 242 ++++++++++++++++++++++++++++++++ system/controllers/messages.php | 22 +++ 5 files changed, 455 insertions(+) create mode 100644 system/controllers/logout.php create mode 100644 system/controllers/logs.php create mode 100644 system/controllers/map.php create mode 100644 system/controllers/message.php create mode 100644 system/controllers/messages.php diff --git a/system/controllers/logout.php b/system/controllers/logout.php new file mode 100644 index 0000000..3db002b --- /dev/null +++ b/system/controllers/logout.php @@ -0,0 +1,12 @@ +assign('_title', 'PHPNuxBill Logs'); +$ui->assign('_system_menu', 'logs'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); +} + + +switch ($action) { + case 'list-csv': + $logs = ORM::for_table('tbl_logs') + ->select('id') + ->select('date') + ->select('type') + ->select('description') + ->select('userid') + ->select('ip') + ->order_by_asc('id')->find_array(); + $h = false; + set_time_limit(-1); + header('Pragma: public'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header("Content-type: text/csv"); + header('Content-Disposition: attachment;filename="activity-logs_' . date('Y-m-d_H_i') . '.csv"'); + header('Content-Transfer-Encoding: binary'); + foreach ($logs as $log) { + $ks = []; + $vs = []; + foreach ($log as $k => $v) { + $ks[] = $k; + $vs[] = $v; + } + if (!$h) { + echo '"' . implode('";"', $ks) . "\"\n"; + $h = true; + } + echo '"' . implode('";"', $vs) . "\"\n"; + } + break; + case 'radius-csv': + $logs = ORM::for_table('radpostauth') + ->select('id') + ->select('username') + ->select('pass') + ->select('reply') + ->select('authdate') + ->order_by_asc('id')->find_array(); + $h = false; + set_time_limit(-1); + header('Pragma: public'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header("Content-type: text/csv"); + header('Content-Disposition: attachment;filename="radius-logs_' . date('Y-m-d_H_i') . '.csv"'); + header('Content-Transfer-Encoding: binary'); + foreach ($logs as $log) { + $ks = []; + $vs = []; + foreach ($log as $k => $v) { + $ks[] = $k; + $vs[] = $v; + } + if (!$h) { + echo '"' . implode('";"', $ks) . "\"\n"; + $h = true; + } + echo '"' . implode('";"', $vs) . "\"\n"; + } + break; + + case 'list': + $q = (_post('q') ? _post('q') : _get('q')); + $keep = _post('keep'); + if (!empty($keep)) { + ORM::raw_execute("DELETE FROM tbl_logs WHERE UNIX_TIMESTAMP(date) < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL $keep DAY))"); + r2(U . "logs/list/", 's', "Delete logs older than $keep days"); + } + if ($q != '') { + $query = ORM::for_table('tbl_logs')->where_like('description', '%' . $q . '%')->order_by_desc('id'); + $d = Paginator::findMany($query, ['q' => $q]); + } else { + $query = ORM::for_table('tbl_logs')->order_by_desc('id'); + $d = Paginator::findMany($query); + } + + $ui->assign('d', $d); + $ui->assign('q', $q); + $ui->display('logs.tpl'); + break; + case 'radius': + $q = (_post('q') ? _post('q') : _get('q')); + $keep = _post('keep'); + if (!empty($keep)) { + ORM::raw_execute("DELETE FROM radpostauth WHERE UNIX_TIMESTAMP(authdate) < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL $keep DAY))", [], 'radius'); + r2(U . "logs/radius/", 's', "Delete logs older than $keep days"); + } + if ($q != '') { + $query = ORM::for_table('radpostauth', 'radius')->where_like('username', '%' . $q . '%')->order_by_desc('id'); + $d = Paginator::findMany($query, ['q' => $q]); + } else { + $query = ORM::for_table('radpostauth', 'radius')->order_by_desc('id'); + $d = Paginator::findMany($query); + } + + $ui->assign('d', $d); + $ui->assign('q', $q); + $ui->display('logs-radius.tpl'); + break; + + + default: + r2(U . 'logs/list/', 's', ''); +} diff --git a/system/controllers/map.php b/system/controllers/map.php new file mode 100644 index 0000000..9ef0827 --- /dev/null +++ b/system/controllers/map.php @@ -0,0 +1,54 @@ +assign('_system_menu', 'map'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +if (empty($action)) { + $action = 'customer'; +} + +switch ($action) { + case 'customer': + if(!empty(_req('search'))){ + $search = _req('search'); + $query = ORM::for_table('tbl_customers')->whereRaw("coordinates != '' AND fullname LIKE '%$search%' OR username LIKE '%$search%' OR email LIKE '%$search%' OR phonenumber LIKE '%$search%'")->order_by_desc('fullname'); + $c = Paginator::findMany($query, ['search' => $search], 50); + }else{ + $query = ORM::for_table('tbl_customers')->where_not_equal('coordinates',''); + $c = Paginator::findMany($query, ['search'=>''], 50); + } + $customerData = []; + + foreach ($c as $customer) { + if (!empty($customer->coordinates)) { + $customerData[] = [ + 'id' => $customer->id, + 'name' => $customer->fullname, + 'balance' => $customer->balance, + 'address' => $customer->address, + 'direction' => $customer->coordinates, + 'info' => Lang::T("Username") . ": " . $customer->username . " - " . Lang::T("Full Name") . ": " . $customer->fullname . " - " . Lang::T("Email") . ": " . $customer->email . " - " . Lang::T("Phone") . ": " . $customer->phonenumber . " - " . Lang::T("Service Type") . ": " . $customer->service_type, + 'coordinates' => '[' . $customer->coordinates . ']', + ]; + } + } + $ui->assign('search', $search); + $ui->assign('customers', $customerData); + $ui->assign('xheader', ''); + $ui->assign('_title', Lang::T('Customer Geo Location Information')); + $ui->assign('xfooter', ''); + $ui->display('customers-map.tpl'); + break; + + default: + r2(U . 'map/customer', 'e', 'action not defined'); + break; +} diff --git a/system/controllers/message.php b/system/controllers/message.php new file mode 100644 index 0000000..c0daae4 --- /dev/null +++ b/system/controllers/message.php @@ -0,0 +1,242 @@ +assign('_title', Lang::T('Send Message')); +$ui->assign('_system_menu', 'message'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +if (empty($action)) { + $action = 'send'; +} + +switch ($action) { + case 'send': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + + $select2_customer = << +document.addEventListener("DOMContentLoaded", function(event) { + $('#personSelect').select2({ + theme: "bootstrap", + ajax: { + url: function(params) { + if(params.term != undefined){ + return './index.php?_route=autoload/customer_select2&s='+params.term; + }else{ + return './index.php?_route=autoload/customer_select2'; + } + } + } + }); +}); + +EOT; + if (isset($routes['2']) && !empty($routes['2'])) { + $ui->assign('cust', ORM::for_table('tbl_customers')->find_one($routes['2'])); + } + $id = $routes['2']; + $ui->assign('id', $id); + $ui->assign('xfooter', $select2_customer); + $ui->display('message.tpl'); + break; + + case 'send-post': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + + // Get form data + $id_customer = $_POST['id_customer']; + $message = $_POST['message']; + $via = $_POST['via']; + + // Check if fields are empty + if ($id_customer == '' or $message == '' or $via == '') { + r2(U . 'message/send', 'e', Lang::T('All field is required')); + } else { + // Get customer details from the database + $c = ORM::for_table('tbl_customers')->find_one($id_customer); + + // Replace placeholders in the message with actual values + $message = str_replace('[[name]]', $c['fullname'], $message); + $message = str_replace('[[user_name]]', $c['username'], $message); + $message = str_replace('[[phone]]', $c['phonenumber'], $message); + $message = str_replace('[[company_name]]', $config['CompanyName'], $message); + + // Send the message + if ($via == 'sms' || $via == 'both') { + $smsSent = Message::sendSMS($c['phonenumber'], $message); + } + + if ($via == 'wa' || $via == 'both') { + $waSent = Message::sendWhatsapp($c['phonenumber'], $message); + } + + if (isset($smsSent) || isset($waSent)) { + r2(U . 'message/send', 's', Lang::T('Message Sent Successfully')); + } else { + r2(U . 'message/send', 'e', Lang::T('Failed to send message')); + } + } + break; + + case 'send_bulk': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + + // Get form data + $group = $_POST['group']; + $message = $_POST['message']; + $via = $_POST['via']; + $test = isset($_POST['test']) && $_POST['test'] === 'on' ? 'yes' : 'no'; + $batch = $_POST['batch']; + $delay = $_POST['delay']; + + // Initialize counters + $totalSMSSent = 0; + $totalSMSFailed = 0; + $totalWhatsappSent = 0; + $totalWhatsappFailed = 0; + $batchStatus = []; + + if (_req('send') == 'now') { + // Check if fields are empty + if ($group == '' || $message == '' || $via == '') { + r2(U . 'message/send_bulk', 'e', Lang::T('All fields are required')); + } else { + // Get customer details from the database based on the selected group + if ($group == 'all') { + $customers = ORM::for_table('tbl_customers')->find_many()->as_array(); + } elseif ($group == 'new') { + // Get customers created just a month ago + $customers = ORM::for_table('tbl_customers')->where_raw("DATE(created_at) >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH)")->find_many()->as_array(); + } elseif ($group == 'expired') { + // Get expired user recharges where status is 'off' + $expired = ORM::for_table('tbl_user_recharges')->where('status', 'off')->find_many(); + $customer_ids = []; + foreach ($expired as $recharge) { + $customer_ids[] = $recharge->customer_id; + } + $customers = ORM::for_table('tbl_customers')->where_in('id', $customer_ids)->find_many()->as_array(); + } elseif ($group == 'active') { + // Get active user recharges where status is 'on' + $active = ORM::for_table('tbl_user_recharges')->where('status', 'on')->find_many(); + $customer_ids = []; + foreach ($active as $recharge) { + $customer_ids[] = $recharge->customer_id; + } + $customers = ORM::for_table('tbl_customers')->where_in('id', $customer_ids)->find_many()->as_array(); + } elseif ($group == 'pppoe') { + // Get customers with PPPoE service type + $customers = ORM::for_table('tbl_customers')->where('service_type', 'PPPoE')->find_many()->as_array(); + } elseif ($group == 'hotspot') { + // Get customers with Hotspot service type + $customers = ORM::for_table('tbl_customers')->where('service_type', 'Hotspot')->find_many()->as_array(); + } + + // Set the batch size + $batchSize = $batch; + + // Calculate the number of batches + $totalCustomers = count($customers); + $totalBatches = ceil($totalCustomers / $batchSize); + + // Loop through batches + for ($batchIndex = 0; $batchIndex < $totalBatches; $batchIndex++) { + // Get the starting and ending index for the current batch + $start = $batchIndex * $batchSize; + $end = min(($batchIndex + 1) * $batchSize, $totalCustomers); + $batchCustomers = array_slice($customers, $start, $end - $start); + + // Loop through customers in the current batch and send messages + foreach ($batchCustomers as $customer) { + // Create a copy of the original message for each customer and save it as currentMessage + $currentMessage = $message; + $currentMessage = str_replace('[[name]]', $customer['fullname'], $currentMessage); + $currentMessage = str_replace('[[user_name]]', $customer['username'], $currentMessage); + $currentMessage = str_replace('[[phone]]', $customer['phonenumber'], $currentMessage); + $currentMessage = str_replace('[[company_name]]', $config['CompanyName'], $currentMessage); + + // Send the message based on the selected method + if ($test === 'yes') { + // Only for testing, do not send messages to customers + $batchStatus[] = [ + 'name' => $customer['fullname'], + 'phone' => $customer['phonenumber'], + 'message' => $currentMessage, + 'status' => 'Test Mode - Message not sent' + ]; + } else { + // Send the actual messages + if ($via == 'sms' || $via == 'both') { + $smsSent = Message::sendSMS($customer['phonenumber'], $currentMessage); + if ($smsSent) { + $totalSMSSent++; + $batchStatus[] = [ + 'name' => $customer['fullname'], + 'phone' => $customer['phonenumber'], + 'message' => $currentMessage, + 'status' => 'SMS Message Sent' + ]; + } else { + $totalSMSFailed++; + $batchStatus[] = [ + 'name' => $customer['fullname'], + 'phone' => $customer['phonenumber'], + 'message' => $currentMessage, + 'status' => 'SMS Message Failed' + ]; + } + } + + if ($via == 'wa' || $via == 'both') { + $waSent = Message::sendWhatsapp($customer['phonenumber'], $currentMessage); + if ($waSent) { + $totalWhatsappSent++; + $batchStatus[] = [ + 'name' => $customer['fullname'], + 'phone' => $customer['phonenumber'], + 'message' => $currentMessage, + 'status' => 'WhatsApp Message Sent' + ]; + } else { + $totalWhatsappFailed++; + $batchStatus[] = [ + 'name' => $customer['fullname'], + 'phone' => $customer['phonenumber'], + 'message' => $currentMessage, + 'status' => 'WhatsApp Message Failed' + ]; + } + } + } + } + + // Introduce a delay between each batch + if ($batchIndex < $totalBatches - 1) { + sleep($delay); + } + } + } + } + $ui->assign('batchStatus', $batchStatus); + $ui->assign('totalSMSSent', $totalSMSSent); + $ui->assign('totalSMSFailed', $totalSMSFailed); + $ui->assign('totalWhatsappSent', $totalWhatsappSent); + $ui->assign('totalWhatsappFailed', $totalWhatsappFailed); + $ui->display('message-bulk.tpl'); + break; + + default: + r2(U . 'message/send_sms', 'e', 'action not defined'); +} diff --git a/system/controllers/messages.php b/system/controllers/messages.php new file mode 100644 index 0000000..3042f8d --- /dev/null +++ b/system/controllers/messages.php @@ -0,0 +1,22 @@ +assign('_title', Lang::T('Messages')); +$ui->assign('_system_menu', 'messages'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +// Fetch all messages +$msgs = ORM::for_table('tbl_message') + ->order_by_desc('date') + ->find_array(); + +$ui->assign('messages', $msgs); +$ui->display('message-list.tpl'); + +?> -- 2.47.2 From 3a4ac7c4a1ae0fd9633000531e001bdf13146f67 Mon Sep 17 00:00:00 2001 From: nestict Date: Sat, 24 May 2025 11:20:57 +0200 Subject: [PATCH 11/11] Upload files to "system/controllers" Signed-off-by: nestict --- system/controllers/onlinehotspot.php | 130 +++++++ system/controllers/onlineusers.php | 347 +++++++++++++++++++ system/controllers/order.php | 493 +++++++++++++++++++++++++++ system/controllers/page.php | 21 ++ system/controllers/pages.php | 71 ++++ 5 files changed, 1062 insertions(+) create mode 100644 system/controllers/onlinehotspot.php create mode 100644 system/controllers/onlineusers.php create mode 100644 system/controllers/order.php create mode 100644 system/controllers/page.php create mode 100644 system/controllers/pages.php diff --git a/system/controllers/onlinehotspot.php b/system/controllers/onlinehotspot.php new file mode 100644 index 0000000..4b4fd04 --- /dev/null +++ b/system/controllers/onlinehotspot.php @@ -0,0 +1,130 @@ +assign('_title', Lang::T('online')); +$ui->assign('_system_menu', 'onlineusers'); +$ui->assign('onlineusers', $online); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +use PEAR2\Net\RouterOS; + +function handle_action($action) +{ + switch ($action) { + case 'get_hotspot_online_users': + mikrotik_get_hotspot_online_users(); + break; + case 'disconnect_online_user': + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + mikrotik_disconnect_online_user($_POST['router'], $_POST['username'], $_POST['userType']); + } + break; + case 'list': + // Assuming you have a function to fetch the data for the online hotspot users list + $onlineHotspotUsers = fetch_online_hotspot_users(); + $ui->assign('onlineHotspotUsers', $onlineHotspotUsers); + $ui->display('onlinehotspot.tpl'); + break; + default: + // Handle default case, maybe return an error or redirect + break; + } +} + +function mikrotik_get_hotspot_online_users() +{ + global $routes; + $router = $routes['2']; + $mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($router); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + $hotspotActive = $client->sendSync(new RouterOS\Request('/ip/hotspot/active/print')); + $hotspotList = []; + + foreach ($hotspotActive as $hotspot) { + $username = $hotspot->getProperty('user'); + $address = $hotspot->getProperty('address'); + $uptime = $hotspot->getProperty('uptime'); + $server = $hotspot->getProperty('server'); + $mac = $hotspot->getProperty('mac-address'); + $sessionTime = $hotspot->getProperty('session-time-left'); + $rxBytes = $hotspot->getProperty('bytes-in'); + $txBytes = $hotspot->getProperty('bytes-out'); + $hotspotList[] = [ + 'username' => $username, + 'address' => $address, + 'uptime' => $uptime, + 'server' => $server, + 'mac' => $mac, + 'session_time' => $sessionTime, + 'rx_bytes' => mikrotik_formatBytes($rxBytes), + 'tx_bytes' => mikrotik_formatBytes($txBytes), + 'total' => mikrotik_formatBytes($txBytes + $rxBytes), + ]; + } + + // Return the Hotspot online user list as JSON + header('Content-Type: application/json'); + echo json_encode($hotspotList); +} + +function mikrotik_disconnect_online_user($router, $username, $userType) +{ + // Check if the form was submitted + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + // Retrieve the form data + $router = $_POST['router']; + $username = $_POST['username']; + $userType = $_POST['userType']; + + $mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($router); + if (!$mikrotik) { + // Handle the error response or redirection + return; + } + + try { + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + if ($userType == 'hotspot') { + Mikrotik::removeHotspotActiveUser($client, $username); + // Handle the success response or redirection + } elseif ($userType == 'pppoe') { + Mikrotik::removePpoeActive($client, $username); + // Handle the success response or redirection + } else { + // Handle the error response or redirection + return; + } + } catch (Exception $e) { + // Handle the error response or redirection + } finally { + // Disconnect from the MikroTik router + if (isset($client)) { + $client->disconnect(); + } + } + } +} + +// Helper function to format bytes +function mikrotik_formatBytes($bytes) +{ + $units = ['B', 'KB', 'MB', 'GB', 'TB']; + $factor = floor((strlen($bytes) - 1) / 3); + return sprintf("%.2f %s", $bytes / pow(1024, $factor), @$units[$factor]); +} + +// Call the main function with the action provided in the URL +$action = $routes['1'] ?? ''; // Assuming $routes is defined elsewhere +handle_action($action); + +$ui->assign('onlineusers', $online); + +$ui->display('onlinehotspot.tpl'); +?> diff --git a/system/controllers/onlineusers.php b/system/controllers/onlineusers.php new file mode 100644 index 0000000..6dc1925 --- /dev/null +++ b/system/controllers/onlineusers.php @@ -0,0 +1,347 @@ +assign('_title', Lang::T('Online Users')); +$ui->assign('_system_menu', 'onlineusers'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +use PEAR2\Net\RouterOS; + +require_once 'system/autoload/PEAR2/Autoload.php'; + +if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); +} + +// Handle cases for hotspot users and PPP users +switch ($action) { + case 'hotspot': + $ui->display('hotspot_users.tpl'); + break; +case 'hotspot_users': + $hotspotUsers = mikrotik_get_hotspot_online_users(); + + // Filter out entries where all values are null + $filteredHotspotUsers = array_filter($hotspotUsers, function($user) { + // Check if all specified fields are null + return !( + is_null($user['username']) && + is_null($user['address']) && + is_null($user['uptime']) && + is_null($user['server']) && + is_null($user['mac']) && + is_null($user['session_time']) && + $user['rx_bytes'] === '0 B' && + $user['tx_bytes'] === '0 B' && + $user['total'] === '0 B' + ); + }); + + header('Content-Type: application/json'); + echo json_encode($filteredHotspotUsers); + exit; + break; + +case 'pppoe': + $ui->display('ppp_users.tpl'); + break; + +case 'ppp_users': + $pppUsers = mikrotik_get_ppp_online_users(); + header('Content-Type: application/json'); + echo json_encode($pppUsers); + exit; + break; + + case 'disconnect': + $routerId = $routes['2']; + $username = $routes['3']; + $userType = $routes['4']; + mikrotik_disconnect_online_user($routerId, $username, $userType); + // Redirect or handle the response as needed + break; + + case 'summary': + // Fetch summary of online users and total bytes used + $summary = mikrotik_get_online_users_summary(); + header('Content-Type: application/json'); + echo json_encode($summary); + exit; + break; + + default: + // Handle default case or invalid action + break; +} + +// Function to round the value and append the appropriate unit +function mikrotik_formatBytes($bytes, $precision = 2) +{ +$units = array('B', 'KB', 'MB', 'GB', 'TB'); + + $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, $precision) . ' ' . $units[$pow]; +} + +function filter_null_users($users) { + return array_filter($users, function($user) { + return array_reduce($user, function($carry, $value) { + return $carry || $value !== null; + }, false); + }); +} + +function mikrotik_get_hotspot_online_users() +{ + global $routes; + $routerId = $routes['2']; + $mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routerId); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + $hotspotActive = $client->sendSync(new RouterOS\Request('/ip/hotspot/active/print')); + + $hotspotList = []; + foreach ($hotspotActive as $hotspot) { + $username = $hotspot->getProperty('user'); + $address = $hotspot->getProperty('address'); + $uptime = $hotspot->getProperty('uptime'); + $server = $hotspot->getProperty('server'); + $mac = $hotspot->getProperty('mac-address'); + $sessionTime = $hotspot->getProperty('session-time-left'); + $rxBytes = $hotspot->getProperty('bytes-in'); + $txBytes = $hotspot->getProperty('bytes-out'); + + $hotspotList[] = [ + 'username' => $username, + 'address' => $address, + 'uptime' => $uptime, + 'server' => $server, + 'mac' => $mac, + 'session_time' => $sessionTime, + 'rx_bytes' => mikrotik_formatBytes($rxBytes), + 'tx_bytes' => mikrotik_formatBytes($txBytes), + 'total' => mikrotik_formatBytes($rxBytes + $txBytes), + ]; + } + + // Filter out users with all null properties + $filteredHotspotList = filter_null_users($hotspotList); + + // Return an empty array if no users are left after filtering + return empty($filteredHotspotList) ? [] : $filteredHotspotList; +} + + + +function mikrotik_get_ppp_online_users() +{ + global $routes; + $routerId = $routes['2']; + $mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routerId); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + $pppUsers = $client->sendSync(new RouterOS\Request('/ppp/active/print')); + + $userList = []; + foreach ($pppUsers as $pppUser) { + $username = $pppUser->getProperty('name'); + $address = $pppUser->getProperty('address'); + $uptime = $pppUser->getProperty('uptime'); + $service = $pppUser->getProperty('service'); + $callerid = $pppUser->getProperty('caller-id'); + $bytes_in = $pppUser->getProperty('limit-bytes-in'); + $bytes_out = $pppUser->getProperty('limit-bytes-out'); + + $userList[] = [ + 'username' => $username, + 'address' => $address, + 'uptime' => $uptime, + 'service' => $service, + 'caller_id' => $callerid, + 'bytes_in' => $bytes_in, + 'bytes_out' => $bytes_out, + ]; + } + + // Filter out users with all null properties + return filter_null_users($userList); +} + +function save_data_usage($username, $bytes_in, $bytes_out, $connection_type) { + if (!$username) { + error_log("Error: Missing username in save_data_usage()"); + return; + } + + $currentTime = date('Y-m-d H:i:s'); + $currentDate = date('Y-m-d'); + + // Check if there's an existing record for this user today + $existingRecord = ORM::for_table('tbl_user_data_usage') + ->where('username', $username) + ->where('connection_type', $connection_type) + ->where_raw('DATE(timestamp) = ?', [$currentDate]) + ->find_one(); + + if ($existingRecord) { + // Update existing record for today + $existingRecord->bytes_in = ($bytes_in ?: 0); + $existingRecord->bytes_out = ($bytes_out ?: 0); + $existingRecord->last_updated = $currentTime; + $existingRecord->save(); + } else { + // Create new record for today + $newRecord = ORM::for_table('tbl_user_data_usage')->create(); + $newRecord->username = $username; + $newRecord->bytes_in = ($bytes_in ?: 0); + $newRecord->bytes_out = ($bytes_out ?: 0); + $newRecord->connection_type = $connection_type; + $newRecord->timestamp = $currentTime; + $newRecord->last_updated = $currentTime; + $newRecord->save(); + } +} + +function mikrotik_get_online_users_summary() +{ + global $routes; + $routerId = $routes['2']; + $mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routerId); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + + // Get Hotspot users + $hotspotActive = $client->sendSync(new RouterOS\Request('/ip/hotspot/active/print')); + $hotspotList = []; + $totalHotspotUsage = 0; + foreach ($hotspotActive as $hotspot) { + $rxBytes = $hotspot->getProperty('bytes-in'); + $txBytes = $hotspot->getProperty('bytes-out'); + $totalHotspotUsage += $rxBytes + $txBytes; + $username = $hotspot->getProperty('user'); + save_data_usage($username, $rxBytes, $txBytes, 'hotspot'); + + $hotspotList[] = [ + 'username' => $username, + 'address' => $hotspot->getProperty('address'), + 'uptime' => $hotspot->getProperty('uptime'), + 'server' => $hotspot->getProperty('server'), + 'mac' => $hotspot->getProperty('mac-address'), + 'session_time' => $hotspot->getProperty('session-time-left'), + 'rx_bytes' => mikrotik_formatBytes($rxBytes), + 'tx_bytes' => mikrotik_formatBytes($txBytes), + 'total' => mikrotik_formatBytes($rxBytes + $txBytes), + ]; + } + + // Filter out null hotspot users + $hotspotList = array_filter($hotspotList, function($user) { + return !( + is_null($user['username']) && + is_null($user['address']) && + is_null($user['uptime']) && + is_null($user['server']) && + is_null($user['mac']) && + is_null($user['session_time']) && + $user['rx_bytes'] === '0 B' && + $user['tx_bytes'] === '0 B' && + $user['total'] === '0 B' + ); + }); + + // Get PPPoE users + $pppUsers = $client->sendSync(new RouterOS\Request('/ppp/active/print')); + $pppoeList = []; + $totalPPPoEUsage = 0; + foreach ($pppUsers as $pppUser) { + $bytes_in = $pppUser->getProperty('limit-bytes-in'); + $bytes_out = $pppUser->getProperty('limit-bytes-out'); + $totalPPPoEUsage += $bytes_in + $bytes_out; + $username = $pppUser->getProperty('name'); + save_data_usage($username, $bytes_in, $bytes_out, 'pppoe'); + + $pppoeList[] = [ + 'username' => $username, + 'address' => $pppUser->getProperty('address'), + 'uptime' => $pppUser->getProperty('uptime'), + 'service' => $pppUser->getProperty('service'), + 'caller_id' => $pppUser->getProperty('caller-id'), + 'bytes_in' => mikrotik_formatBytes($bytes_in), + 'bytes_out' => mikrotik_formatBytes($bytes_out), + 'total' => mikrotik_formatBytes($bytes_in + $bytes_out), + ]; + } + + // Filter out null PPPoE users + $pppoeList = array_filter($pppoeList, function($user) { + return !( + is_null($user['username']) && + is_null($user['address']) && + is_null($user['uptime']) && + is_null($user['service']) && + is_null($user['caller_id']) && + $user['bytes_in'] === '0 B' && + $user['bytes_out'] === '0 B' && + $user['total'] === '0 B' + ); + }); + // Calculate total data usage + $totalDataUsage = $totalHotspotUsage + $totalPPPoEUsage; + + // Calculate total users + $totalHotspotUsers = count($hotspotList); + $totalPPPoEUsers = count($pppoeList); + $totalUsers = $totalHotspotUsers + $totalPPPoEUsers; + + return [ + 'hotspot_users' => $totalHotspotUsers, + 'ppoe_users' => $totalPPPoEUsers, + 'total_users' => $totalUsers, + 'total_bytes' => mikrotik_formatBytes($totalDataUsage), + ]; +} + +function mikrotik_disconnect_online_user($router, $username, $userType) +{ + // Check if the form was submitted + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + // Retrieve the form data + $router = $_POST['router']; + $username = $_POST['username']; + $userType = $_POST['userType']; + $mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($router); + if (!$mikrotik) { + // Handle the error response or redirection + return; + } + try { + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + if ($userType == 'hotspot') { + Mikrotik::removeHotspotActiveUser($client, $username); + // Handle the success response or redirection + } elseif ($userType == 'pppoe') { + Mikrotik::removePpoeActive($client, $username); + // Handle the success response or redirection + } else { + // Handle the error response or redirection + return; + } + } catch (Exception $e) { + // Handle the error response or redirection + } finally { + // Disconnect from the MikroTik router + if (isset($client)) { + $client->disconnect(); + } + } + } +} + +?> diff --git a/system/controllers/order.php b/system/controllers/order.php new file mode 100644 index 0000000..bc07be1 --- /dev/null +++ b/system/controllers/order.php @@ -0,0 +1,493 @@ +assign('_user', $user); + +switch ($action) { + case 'voucher': + $ui->assign('_system_menu', 'voucher'); + $ui->assign('_title', Lang::T('Order Voucher')); + run_hook('customer_view_order'); #HOOK + $ui->display('user-order.tpl'); + break; + case 'history': + $ui->assign('_system_menu', 'history'); + $query = ORM::for_table('tbl_payment_gateway')->where('username', $user['username'])->order_by_desc('id'); + $d = Paginator::findMany($query); + $ui->assign('d', $d); + $ui->assign('_title', Lang::T('Order History')); + run_hook('customer_view_order_history'); #HOOK + $ui->display('user-orderHistory.tpl'); + break; + case 'balance': + if (strpos($user['email'], '@') === false) { + r2(U . 'accounts/profile', 'e', Lang::T("Please enter your email address")); + } + $ui->assign('_title', 'Top Up'); + $ui->assign('_system_menu', 'balance'); + $plans_balance = ORM::for_table('tbl_plans')->where('enabled', '1')->where('type', 'Balance')->where('prepaid', 'yes')->find_many(); + $ui->assign('plans_balance', $plans_balance); + $ui->display('user-orderBalance.tpl'); + break; + case 'package': + if (strpos($user['email'], '@') === false) { + r2(U . 'accounts/profile', 'e', Lang::T("Please enter your email address")); + } + $ui->assign('_title', 'Order Plan'); + $ui->assign('_system_menu', 'package'); + $account_type = $user['account_type']; + if (empty($account_type)) { + $account_type = 'Personal'; + } + if (!empty($_SESSION['nux-router'])) { + if ($_SESSION['nux-router'] == 'radius') { + $radius_pppoe = ORM::for_table('tbl_plans')->where('plan_type', $account_type)->where('enabled', '1')->where('is_radius', 1)->where('type', 'PPPOE')->where('prepaid', 'yes')->find_many(); + $radius_hotspot = ORM::for_table('tbl_plans')->where('plan_type', $account_type)->where('enabled', '1')->where('is_radius', 1)->where('type', 'Hotspot')->where('prepaid', 'yes')->find_many(); + } else { + $routers = ORM::for_table('tbl_routers')->where('id', $_SESSION['nux-router'])->find_many(); + $rs = []; + foreach ($routers as $r) { + $rs[] = $r['name']; + } + $plans_pppoe = ORM::for_table('tbl_plans')->where('plan_type', $account_type)->where('enabled', '1')->where_in('routers', $rs)->where('is_radius', 0)->where('type', 'PPPOE')->where('prepaid', 'yes')->find_many(); + $plans_hotspot = ORM::for_table('tbl_plans')->where('plan_type', $account_type)->where('enabled', '1')->where_in('routers', $rs)->where('is_radius', 0)->where('type', 'Hotspot')->where('prepaid', 'yes')->find_many(); + } + } else { + $radius_pppoe = ORM::for_table('tbl_plans')->where('plan_type', $account_type)->where('enabled', '1')->where('is_radius', 1)->where('type', 'PPPOE')->where('prepaid', 'yes')->find_many(); + $radius_hotspot = ORM::for_table('tbl_plans')->where('plan_type', $account_type)->where('enabled', '1')->where('is_radius', 1)->where('type', 'Hotspot')->where('prepaid', 'yes')->find_many(); + + $routers = ORM::for_table('tbl_routers')->find_many(); + $plans_pppoe = ORM::for_table('tbl_plans')->where('plan_type', $account_type)->where('enabled', '1')->where('is_radius', 0)->where('type', 'PPPOE')->where('prepaid', 'yes')->find_many(); + $plans_hotspot = ORM::for_table('tbl_plans')->where('plan_type', $account_type)->where('enabled', '1')->where('is_radius', 0)->where('type', 'Hotspot')->where('prepaid', 'yes')->find_many(); + } + $ui->assign('routers', $routers); + $ui->assign('radius_pppoe', $radius_pppoe); + $ui->assign('radius_hotspot', $radius_hotspot); + $ui->assign('plans_pppoe', $plans_pppoe); + $ui->assign('plans_hotspot', $plans_hotspot); + run_hook('customer_view_order_plan'); #HOOK + $ui->display('user-orderPlan.tpl'); + break; + case 'unpaid': + $d = ORM::for_table('tbl_payment_gateway') + ->where('username', $user['username']) + ->where('status', 1) + ->find_one(); + run_hook('custome + r_find_unpaid'); #HOOK + if ($d) { + if (empty($d['pg_url_payment'])) { + r2(U . "order/buy/" . $trx['routers_id'] . '/' . $trx['plan_id'], 'w', Lang::T("Checking payment")); + } else { + r2(U . "order/view/" . $d['id'] . '/check/', 's', Lang::T("You have unpaid transaction")); + } + } else { + r2(U . "order/package/", 's', Lang::T("You have no unpaid transaction")); + } + break; + case 'view': + $trxid = $routes['2']; + $trx = ORM::for_table('tbl_payment_gateway') + ->where('username', $user['username']) + ->find_one($trxid); + run_hook('customer_view_payment'); #HOOK + // jika tidak ditemukan, berarti punya orang lain + if (empty($trx)) { + r2(U . "order/package", 'w', Lang::T("Payment not found")); + } + // jika url kosong, balikin ke buy, kecuali cancel + if (empty($trx['pg_url_payment']) && $routes['3'] != 'cancel') { + r2(U . "order/buy/" . (($trx['routers_id'] == 0) ? $trx['routers'] : $trx['routers_id']) . '/' . $trx['plan_id'], 'w', Lang::T("Checking payment")); + } + if ($routes['3'] == 'check') { + if (!file_exists($PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR . $trx['gateway'] . '.php')) { + r2(U . 'order/view/' . $trxid, 'e', Lang::T("No Payment Gateway Available")); + } + run_hook('customer_check_payment_status'); #HOOK + include $PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR . $trx['gateway'] . '.php'; + call_user_func($trx['gateway'] . '_validate_config'); + call_user_func($trx['gateway'] . '_get_status', $trx, $user); + } else if ($routes['3'] == 'cancel') { + run_hook('customer_cancel_payment'); #HOOK + $trx->pg_paid_response = '{}'; + $trx->status = 4; + $trx->paid_date = date('Y-m-d H:i:s'); + $trx->save(); + $trx = ORM::for_table('tbl_payment_gateway') + ->where('username', $user['username']) + ->find_one($trxid); + } + if (empty($trx)) { + r2(U . "order/package", 'e', Lang::T("Transaction Not found")); + } + + $router = Mikrotik::info($trx['routers']); + $plan = ORM::for_table('tbl_plans')->find_one($trx['plan_id']); + $bandw = ORM::for_table('tbl_bandwidth')->find_one($plan['id_bw']); + $invoice = ORM::for_table('tbl_transactions')->where("invoice", $trx['trx_invoice'])->find_one(); + $ui->assign('invoice', $invoice); + $ui->assign('trx', $trx); + $ui->assign('router', $router); + $ui->assign('plan', $plan); + $ui->assign('bandw', $bandw); + $ui->assign('_title', 'TRX #' . $trxid); + $ui->display('user-orderView.tpl'); + break; + case 'pay': + if ($config['enable_balance'] != 'yes') { + r2(U . "order/package", 'e', Lang::T("Balance not enabled")); + } + if (!empty(App::getTokenValue($_GET['stoken']))) { + r2(U . "voucher/invoice/"); + die(); + } + if ($user['status'] != 'Active') { + _alert(Lang::T('This account status') . ' : ' . Lang::T($user['status']), 'danger', ""); + } + $plan = ORM::for_table('tbl_plans')->where('enabled', '1')->find_one($routes['3']); + if (empty($plan)) { + r2(U . "order/package", 'e', Lang::T("Plan Not found")); + } + if (!$plan['enabled']) { + r2(U . "home", 'e', 'Plan is not exists'); + } + if ($routes['2'] == 'radius') { + $router_name = 'radius'; + } else { + $router_name = $plan['routers']; + } + + list($bills, $add_cost) = User::getBills($id_customer); + + // Tax calculation start + $tax_enable = isset($config['enable_tax']) ? $config['enable_tax'] : 'no'; + $tax_rate_setting = isset($config['tax_rate']) ? $config['tax_rate'] : null; + $custom_tax_rate = isset($config['custom_tax_rate']) ? (float)$config['custom_tax_rate'] : null; + + if ($tax_rate_setting === 'custom') { + $tax_rate = $custom_tax_rate; + } else { + $tax_rate = $tax_rate_setting; + } + + if ($tax_enable === 'yes') { + $tax = Package::tax($plan['price'], $tax_rate); + } else { + $tax = 0; + } + // Tax calculation stop + + if ($plan && $plan['enabled'] && $user['balance'] >= $plan['price'] + $tax) { + if (Package::rechargeUser($user['id'], $router_name, $plan['id'], 'Customer', 'Balance')) { + // if success, then get the balance + Balance::min($user['id'], $plan['price'] + $add_cost + $tax); + App::setToken($_GET['stoken'], "success"); + r2(U . "voucher/invoice/", 's', Lang::T("Success to buy package")); + } else { + r2(U . "order/package", 'e', Lang::T("Failed to buy package")); + Message::sendTelegram("Buy Package with Balance Failed\n\n#u$c[username] #buy \n" . $plan['name_plan'] . + "\nRouter: " . $router_name . + "\nPrice: " . $plan['price'] + $tax); + } + } else { + r2(U . "home", 'e', 'Plan is not exists'); + } + break; + + case 'send': + if ($config['enable_balance'] != 'yes') { + r2(U . "order/package", 'e', Lang::T("Balance not enabled")); + } + if ($user['status'] != 'Active') { + _alert(Lang::T('This account status') . ' : ' . Lang::T($user['status']), 'danger', ""); + } + $ui->assign('_title', Lang::T('Buy for friend')); + $ui->assign('_system_menu', 'package'); + $plan = ORM::for_table('tbl_plans')->find_one($routes['3']); + if (empty($plan)) { + r2(U . "order/package", 'e', Lang::T("Plan Not found")); + } + if (!$plan['enabled']) { + r2(U . "home", 'e', 'Plan is not exists'); + } + if ($routes['2'] == 'radius') { + $router_name = 'radius'; + } else { + $router_name = $plan['routers']; + } + $tax_rate_setting = isset($config['tax_rate']) ? $config['tax_rate'] : null; + $custom_tax_rate = isset($config['custom_tax_rate']) ? (float)$config['custom_tax_rate'] : null; + + if ($tax_rate_setting === 'custom') { + $tax_rate = $custom_tax_rate; + } else { + $tax_rate = $tax_rate_setting; + } + + $tax_enable = isset($config['enable_tax']) ? $config['enable_tax'] : 'no'; + + if ($tax_enable === 'yes') { + $tax = Package::tax($plan['price'], $tax_rate); + $ui->assign('tax', $tax); + } else { + $tax = 0; + } + + // Add tax to plan price + $plan['price'] += $tax; + + if (isset($_POST['send']) && $_POST['send'] == 'plan') { + $target = ORM::for_table('tbl_customers')->where('username', _post('username'))->find_one(); + list($bills, $add_cost) = User::getBills($target['id']); + if (!empty($add_cost)) { + $ui->assign('bills', $bills); + $ui->assign('add_cost', $add_cost); + $plan['price'] += $add_cost; + } + + if (!$target) { + r2(U . 'home', 'd', Lang::T('Username not found')); + } + if ($user['balance'] < $plan['price']) { + r2(U . 'home', 'd', Lang::T('insufficient balance')); + } + if ($user['username'] == $target['username']) { + r2(U . "order/pay/$routes[2]/$routes[3]", 's', '^_^ v'); + } + $active = ORM::for_table('tbl_user_recharges') + ->where('username', _post('username')) + ->where('status', 'on') + ->find_one(); + + if ($active && $active['plan_id'] != $plan['id']) { + r2(U . "order/package", 'e', Lang::T("Target has active plan, different with current plant.") . " [ $active[namebp] ]"); + } + $result = Package::rechargeUser($target['id'], $router_name, $plan['id'], $user['username'], 'Balance'); + if (!empty($result)) { + // if success, then get the balance + Balance::min($user['id'], $plan['price']); + //sender + $d = ORM::for_table('tbl_payment_gateway')->create(); + $d->username = $user['username']; + $d->gateway = $target['username']; + $d->plan_id = $plan['id']; + $d->plan_name = $plan['name_plan']; + $d->routers_id = $routes['2']; + $d->routers = $router_name; + $d->price = $plan['price']; + $d->payment_method = "Balance"; + $d->payment_channel = "Send Plan"; + $d->created_date = date('Y-m-d H:i:s'); + $d->paid_date = date('Y-m-d H:i:s'); + $d->expired_date = date('Y-m-d H:i:s'); + $d->pg_url_payment = 'balance'; + $d->trx_invoice = $result; + $d->status = 2; + $d->save(); + $trx_id = $d->id(); + //receiver + $d = ORM::for_table('tbl_payment_gateway')->create(); + $d->username = $target['username']; + $d->gateway = $user['username']; + $d->plan_id = $plan['id']; + $d->plan_name = $plan['name_plan']; + $d->routers_id = $routes['2']; + $d->routers = $router_name; + $d->price = $plan['price']; + $d->payment_method = "Balance"; + $d->payment_channel = "Received Plan"; + $d->created_date = date('Y-m-d H:i:s'); + $d->paid_date = date('Y-m-d H:i:s'); + $d->expired_date = date('Y-m-d H:i:s'); + $d->pg_url_payment = 'balance'; + $d->trx_invoice = $result; + $d->status = 2; + $d->save(); + r2(U . "order/view/$trx_id", 's', Lang::T("Success to send package")); + } else { + $errorMessage = "Send Package with Balance Failed\n\n#u$user[username] #send \n" . $plan['name_plan'] . + "\nRouter: " . $router_name . + "\nPrice: " . $plan['price']; + + if ($tax_enable === 'yes') { + $errorMessage .= "\nTax: " . $tax; + } + + r2(U . "order/package", 'e', Lang::T("Failed to Send package")); + Message::sendTelegram($errorMessage); + } + } + $ui->assign('username', $_GET['u']); + $ui->assign('router', $router_name); + $ui->assign('plan', $plan); + $ui->assign('tax', $tax); + $ui->display('user-sendPlan.tpl'); + break; + case 'gateway': + $ui->assign('_title', Lang::T('Select Payment Gateway')); + $ui->assign('_system_menu', 'package'); + if (strpos($user['email'], '@') === false) { + r2(U . 'accounts/profile', 'e', Lang::T("Please enter your email address")); + } + $tax_enable = isset($config['enable_tax']) ? $config['enable_tax'] : 'no'; + $tax_rate_setting = isset($config['tax_rate']) ? $config['tax_rate'] : null; + $custom_tax_rate = isset($config['custom_tax_rate']) ? (float)$config['custom_tax_rate'] : null; + if ($tax_rate_setting === 'custom') { + $tax_rate = $custom_tax_rate; + } else { + $tax_rate = $tax_rate_setting; + } + $plan = ORM::for_table('tbl_plans')->find_one($routes['3']); + $tax = Package::tax($plan['price'], $tax_rate); + $pgs = array_values(explode(',', $config['payment_gateway'])); + if (count($pgs) == 0) { + sendTelegram("Payment Gateway not set, please set it in Settings"); + _log(Lang::T("Payment Gateway not set, please set it in Settings")); + r2(U . "home", 'e', Lang::T("Failed to create Transaction..")); + } + if (count($pgs) > 1) { + $ui->assign('pgs', $pgs); + if ($tax_enable === 'yes') { + $ui->assign('tax', $tax); + } + $ui->assign('route2', $routes[2]); + $ui->assign('route3', $routes[3]); + $ui->assign('plan', $plan); + $ui->display('user-selectGateway.tpl'); + break; + } else { + if (empty($pgs[0])) { + sendTelegram("Payment Gateway not set, please set it in Settings"); + _log(Lang::T("Payment Gateway not set, please set it in Settings")); + r2(U . "home", 'e', Lang::T("Failed to create Transaction..")); + } else { + $_POST['gateway'] = $pgs[0]; + } + } + case 'buy': + $gateway = _post('gateway'); + if (empty($gateway) && !empty($_SESSION['gateway'])) { + $gateway = $_SESSION['gateway']; + } else if (!empty($gateway)) { + $_SESSION['gateway'] = $gateway; + } + if ($user['status'] != 'Active') { + _alert(Lang::T('This account status') . ' : ' . Lang::T($user['status']), 'danger', ""); + } + if (empty($gateway)) { + r2(U . 'order/gateway/' . $routes[2] . '/' . $routes[3], 'w', Lang::T("Please select Payment Gateway")); + } + run_hook('customer_buy_plan'); #HOOK + include $PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR . $gateway . '.php'; + call_user_func($gateway . '_validate_config'); + + if ($routes['2'] == 'radius') { + $router['id'] = 0; + $router['name'] = 'radius'; + } else if ($routes['2'] > 0) { + $router = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routes['2']); + } else { + $router['id'] = 0; + $router['name'] = 'balance'; + } + $plan = ORM::for_table('tbl_plans')->where('enabled', '1')->find_one($routes['3']); + if (empty($router) || empty($plan)) { + r2(U . "order/package", 'e', Lang::T("Plan Not found")); + } + $d = ORM::for_table('tbl_payment_gateway') + ->where('username', $user['username']) + ->where('status', 1) + ->find_one(); + if ($d) { + if ($d['pg_url_payment']) { + r2(U . "order/view/" . $d['id'], 'w', Lang::T("You already have unpaid transaction, cancel it or pay it.")); + } else { + if ($gateway == $d['gateway']) { + $id = $d['id']; + } else { + $d->status = 4; + $d->save(); + } + } + } + $add_cost = 0; + $tax = 0; + if ($router['name'] != 'balance') { + list($bills, $add_cost) = User::getBills($id_customer); + } + // Tax calculation start + $tax_enable = isset($config['enable_tax']) ? $config['enable_tax'] : 'no'; + $tax_rate_setting = isset($config['tax_rate']) ? $config['tax_rate'] : null; + $custom_tax_rate = isset($config['custom_tax_rate']) ? (float)$config['custom_tax_rate'] : null; + if ($tax_rate_setting === 'custom') { + $tax_rate = $custom_tax_rate; + } else { + $tax_rate = $tax_rate_setting; + } + if ($tax_enable === 'yes') { + $tax = Package::tax($plan['price'], $tax_rate); + } + // Tax calculation stop + if (empty($id)) { + $d = ORM::for_table('tbl_payment_gateway')->create(); + $d->username = $user['username']; + $d->gateway = $gateway; + $d->plan_id = $plan['id']; + $d->plan_name = $plan['name_plan']; + $d->routers_id = $router['id']; + $d->routers = $router['name']; + if ($plan['validity_unit'] == 'Period') { + // Postpaid price from field + $add_inv = User::getAttribute("Invoice", $id_customer); + if (empty($add_inv) or $add_inv == 0) { + $d->price = ($plan['price'] + $add_cost + $tax); + } else { + $d->price = ($add_inv + $add_cost + $tax); + } + } else { + $d->price = ($plan['price'] + $add_cost + $tax); + } + //$d->price = ($plan['price'] + $add_cost); + $d->created_date = date('Y-m-d H:i:s'); + $d->status = 1; + $d->save(); + $id = $d->id(); + } else { + $d->username = $user['username']; + $d->gateway = $gateway; + $d->plan_id = $plan['id']; + $d->plan_name = $plan['name_plan']; + $d->routers_id = $router['id']; + $d->routers = $router['name']; + if ($plan['validity_unit'] == 'Period') { + // Postpaid price from field + $add_inv = User::getAttribute("Invoice", $id_customer); + if (empty($add_inv) or $add_inv == 0) { + $d->price = ($plan['price'] + $add_cost + $tax); + } else { + $d->price = ($add_inv + $add_cost + $tax); + } + } else { + $d->price = ($plan['price'] + $add_cost + $tax); + } + //$d->price = ($plan['price'] + $add_cost); + $d->created_date = date('Y-m-d H:i:s'); + $d->status = 1; + $d->save(); + } + if (!$id) { + r2(U . "order/package/" . $d['id'], 'e', Lang::T("Failed to create Transaction..")); + } else { + call_user_func($gateway . '_create_transaction', $d, $user); + } + break; + default: + r2(U . "order/package/", 's', ''); +} diff --git a/system/controllers/page.php b/system/controllers/page.php new file mode 100644 index 0000000..d1bd9ad --- /dev/null +++ b/system/controllers/page.php @@ -0,0 +1,21 @@ +assign('_title', Lang::T('Order Voucher')); +$ui->assign('_system_menu', 'order'); + +$action = $routes['1']; +$user = User::_info(); +$ui->assign('_user', $user); + +if(file_exists(__DIR__."/../../pages/".str_replace(".","",$action).".html")){ + $ui->assign("PageFile",$action); + $ui->assign("pageHeader",$action); + run_hook('customer_view_page'); #HOOK + $ui->display('user-pages.tpl'); +}else + $ui->display('404.tpl'); \ No newline at end of file diff --git a/system/controllers/pages.php b/system/controllers/pages.php new file mode 100644 index 0000000..1d60dfa --- /dev/null +++ b/system/controllers/pages.php @@ -0,0 +1,71 @@ +assign('_title', 'Pages'); +$ui->assign('_system_menu', 'pages'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +if(strpos($action,"-reset")!==false){ + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'),'danger', "dashboard"); + } + $action = str_replace("-reset","",$action); + $path = "pages/".str_replace(".","",$action).".html"; + $temp = "pages_template/".str_replace(".","",$action).".html"; + if(file_exists($temp)){ + if(!copy($temp, $path)){ + file_put_contents($path, Http::getData('https://raw.githubusercontent.com/hotspotbilling/phpnuxbill/master/pages_template/'.$action.'.html')); + } + }else{ + file_put_contents($path, Http::getData('https://raw.githubusercontent.com/hotspotbilling/phpnuxbill/master/pages_template/'.$action.'.html')); + } + r2(U . 'pages/'.$action); +}else if(strpos($action,"-post")===false){ + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'),'danger', "dashboard"); + } + $path = "pages/".str_replace(".","",$action).".html"; + //echo $path; + run_hook('view_edit_pages'); #HOOK + if(!file_exists($path)){ + $temp = "pages_template/".str_replace(".","",$action).".html"; + if(file_exists($temp)){ + if(!copy($temp, $path)){ + touch($path); + } + }else{ + touch($path); + } + } + if(file_exists($path)){ + $html = file_get_contents($path); + $ui->assign("htmls",str_replace([""],"",$html)); + $ui->assign("writeable",is_writable($path)); + $ui->assign("pageHeader",str_replace('_', ' ', $action)); + $ui->assign("PageFile",$action); + $ui->display('page-edit.tpl'); + }else + $ui->display('a404.tpl'); +}else{ + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'),'danger', "dashboard"); + } + $action = str_replace("-post","",$action); + $path = "pages/".str_replace(".","",$action).".html"; + if(file_exists($path)){ + $html = _post("html"); + run_hook('save_pages'); #HOOK + if(file_put_contents($path, str_replace([""],"",$html))){ + r2(U . 'pages/'.$action, 's', Lang::T("Saving page success")); + }else{ + r2(U . 'pages/'.$action, 'e', Lang::T("Failed to save page, make sure i can write to folder pages, chmod 664 pages/*.html")); + } + }else + $ui->display('a404.tpl'); +} \ No newline at end of file -- 2.47.2