Compare commits

...

52 Commits

Author SHA1 Message Date
ca4a290a72 Upload files to "system/uploads/paymentgateway"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:41:35 +02:00
837df68e89 Upload files to "system/uploads/system"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:40:47 +02:00
f16b4caeef Upload files to "system/uploads/sms"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:40:15 +02:00
5ed64ed548 Upload files to "system/uploads/_sysfrm_tmp_"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:39:46 +02:00
a70827a6b6 Upload files to "system/uploads"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:39:02 +02:00
143f2b2402 Upload files to "system/uploads"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:38:43 +02:00
717cb65516 Upload files to "system/plugin/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:37:49 +02:00
3a49bf9afa Upload files to "system/plugin/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:37:17 +02:00
21a5212120 Upload files to "system/plugin/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:36:52 +02:00
f5ec9276bd Upload files to "system/plugin"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:35:37 +02:00
b71d6aa44d Upload files to "system/plugin"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:35:14 +02:00
015b57ddfd Upload files to "system/plugin"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:34:29 +02:00
2a327ddd3d Upload files to "system/plugin"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:34:11 +02:00
fd6a88f443 Upload files to "system/mpesa"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:32:45 +02:00
1524ae841f Upload files to "ui/ui/sections" 2025-05-24 12:30:59 +02:00
427ad9d085 Upload files to "ui/ui/sections"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:30:41 +02:00
eba008c916 Upload files to "ui/ui/Ass/icons"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:29:29 +02:00
3d75acc1be Upload files to "ui/ui/Ass/css"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:28:12 +02:00
cec3f6b007 Upload files to "ui/ui/Ass"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:27:28 +02:00
479b1d55e4 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:26:21 +02:00
9ba7a1c003 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:26:02 +02:00
dc570270a5 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:24:38 +02:00
5242f5a262 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:24:03 +02:00
b2eb51fff8 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:23:06 +02:00
aa8c6c0b34 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:22:33 +02:00
909c867ba5 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:22:03 +02:00
4ea01347b2 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:21:18 +02:00
f57c730219 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:20:42 +02:00
6d4e964bf1 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:20:12 +02:00
712224be37 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:18:49 +02:00
ce6b63f3a3 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:17:47 +02:00
c954394f5c Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:17:15 +02:00
64e8157c1c Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:16:20 +02:00
e5db5b605f Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:15:47 +02:00
ce74640689 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:13:20 +02:00
fdadc713af Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:11:28 +02:00
04693a88e5 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:10:37 +02:00
77bd3ded5b Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:09:20 +02:00
e79b6ffc00 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:08:53 +02:00
4e9a4d12d4 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:08:03 +02:00
2b04174ed7 Upload files to "ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:07:18 +02:00
e279c52952 Upload files to "ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:06:55 +02:00
3a4ac7c4a1 Upload files to "system/controllers"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 11:20:57 +02:00
ed50072365 Upload files to "system/controllers"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 11:20:26 +02:00
91f3fbad90 Upload files to "system/controllers"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 11:19:52 +02:00
8e24155b2d Upload files to "system/controllers"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 11:17:30 +02:00
d96892275b Upload files to "system/controllers"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 11:17:08 +02:00
6661aea217 Upload files to "system/autoload/PEAR2/Cache/SHM/Adaptor"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 11:13:12 +02:00
2bbfa8c1ad Upload files to "system/autoload/PEAR2/Cache/SHM"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 11:12:33 +02:00
4a28e2314e Upload files to "system/autoload/PEAR2/Cache"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 11:11:22 +02:00
935ea51dbd Upload files to "system/autoload/PEAR2"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 11:10:48 +02:00
d758ba3042 Upload files to "system/autoload/mail"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 11:09:37 +02:00
192 changed files with 63172 additions and 0 deletions

View File

@ -0,0 +1,376 @@
<?php
/**
* Standard Autoloader for PEAR2
*
* PEAR2_Autoload is the standard method of class loading for development and
* low-volume web sites using PEAR2 packages.
*
* PHP version 5
*
* @category PEAR2
* @package PEAR2_Autoload
* @author Gregory Beaver <cellog@php.net>
* @author Brett Bieber <saltybeagle@php.net>
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
* @version 0.3.0
* @link http://pear2.php.net/PEAR2_Autoload
*/
namespace PEAR2;
if (!class_exists('\PEAR2\Autoload', false)) {
/**
* Standard Autoloader for PEAR2
*
* PEAR2_Autoload is the standard method of class loading for development
* and low-volume web sites using PEAR2 packages.
*
* PHP version 5
*
* @category PEAR2
* @package PEAR2_Autoload
* @author Gregory Beaver <cellog@php.net>
* @author Brett Bieber <saltybeagle@php.net>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* New BSDLicense
* @link http://pear2.php.net/PEAR2_Autoload
*/
class Autoload
{
/**
* Used at {@link initialize()} to specify that the load function, path
* and map should be appended to the respective lists.
*/
const APPEND = 0;
/**
* Used at {@link initialize()} to specify that the load function should
* be prepended on the autoload stack, instead of being appended.
*/
const PREPEND_LOAD = 1;
/**
* Used at {@link initialize()} to specify that the path should be
* prepended on the list of paths, instead of being appended.
*/
const PREPEND_PATH = 2;
/**
* Used at {@link initialize()} to specify that the map should be
* prepended on the list of maps, instead of being appended.
*/
const PREPEND_MAP = 4;
/**
* Used at {@link initialize()} to specify that the load function, path
* and map should be prepended on their respective lists, instead of
* being appended.
*/
const PREPEND = 7;
/**
* Whether the autoload class has been spl_autoload_register-ed
*
* @var bool
*/
protected static $registered = false;
/**
* Array of PEAR2 autoload paths registered
*
* @var array
*/
protected static $paths = array();
/**
* Array of classname-to-file mapping
*
* @var array
*/
protected static $map = array();
/**
* Array of class maps loaded
*
* @var array
*/
protected static $maps = array();
/**
* Last classmap specified
*
* @var array
*/
protected static $mapfile = null;
/**
* Array of classes loaded automatically not in the map
*
* @var array
*/
protected static $unmapped = array();
/**
* Array of functions to be checked in exception traces.
*
* @var array
*/
protected static $checkFunctions = array(
'class_exists', 'interface_exists'
);
/**
* Initialize the PEAR2 autoloader
*
* @param string $path Directory path(s) to register.
* @param string $mapfile Path to a mapping file to register.
* @param int $flags A bitmaks with options for the autoloader.
* See the PREPEND(_*) constants for details.
*
* @return void
*/
public static function initialize(
$path,
$mapfile = null,
$flags = self::APPEND
) {
self::register(0 !== $flags & self::PREPEND_LOAD);
self::addPath($path, 0 !== ($flags & self::PREPEND_PATH));
self::addMap($mapfile, 0 !== ($flags & self::PREPEND_MAP));
}
/**
* Register the PEAR2 autoload class with spl_autoload_register
*
* @param bool $prepend Whether to prepend the load function to the
* autoload stack instead of appending it.
*
* @return void
*/
protected static function register($prepend = false)
{
if (!self::$registered) {
// set up __autoload
$autoload = spl_autoload_functions();
spl_autoload_register('PEAR2\Autoload::load', true, $prepend);
if (function_exists('__autoload') && ($autoload === false)) {
// __autoload() was being used, but now would be ignored,
// add it to the autoload stack
spl_autoload_register('__autoload');
}
if (function_exists('trait_exists')) {
self::$checkFunctions[] = 'trait_exists';
}
self::$registered = true;
}
}
/**
* Add a path
*
* @param string $paths The folder(s) to add to the set of paths.
* @param bool $prepend Whether to prepend the path to the list of
* paths instead of appending it.
*
* @return void
*/
protected static function addPath($paths, $prepend = false)
{
foreach (explode(PATH_SEPARATOR, $paths) as $path) {
if (!in_array($path, self::$paths)) {
if ($prepend) {
self::$paths = array_merge(array($path), self::$paths);
} else {
self::$paths[] = $path;
}
}
}
}
/**
* Add a classname-to-file map
*
* @param string $mapfile The filename of the classmap.
* @param bool $prepend Whether to prepend the map to the list of maps
* instead of appending it.
*
* @return void
*/
protected static function addMap($mapfile, $prepend = false)
{
if (!in_array($mapfile, self::$maps)) {
// keep track of specific map file loaded in this
// instance so we can update it if necessary
self::$mapfile = $mapfile;
if (is_file($mapfile)) {
$map = include $mapfile;
if (is_array($map)) {
// mapfile contains a valid map, so we'll keep it
if ($prepend) {
self::$maps = array_merge(
array($mapfile),
self::$maps
);
self::$map = array_merge($map, self::$map);
} else {
self::$maps[] = $mapfile;
self::$map = array_merge(self::$map, $map);
}
}
}
}
}
/**
* Check if the class is already defined in a classmap
*
* @param string $class The class to look for
*
* @return bool
*/
protected static function isMapped($class)
{
if (isset(self::$map[$class])) {
return true;
}
if (isset(self::$mapfile) && ! isset(self::$map[$class])) {
self::$unmapped[] = $class;
return false;
}
return false;
}
/**
* Load a PEAR2 class
*
* @param string $class The class to load
*
* @return bool
*/
public static function load($class)
{
// need to check if there's a current map file specified ALSO.
// this could be the first time writing it.
$mapped = self::isMapped($class);
if ($mapped && is_file(self::$map[$class])) {
include self::$map[$class];
if (!self::loadSuccessful($class)) {
// record this failure & keep going, we may still find it
self::$unmapped[] = $class;
} else {
return true;
}
}
$file = '';
$className = $class;
if (false !== $lastNsPos = strrpos($class, '\\')) {
$namespace = substr($class, 0, $lastNsPos);
$className = substr($class, $lastNsPos + 1);
$file = str_replace(
'\\',
DIRECTORY_SEPARATOR,
$namespace
) . DIRECTORY_SEPARATOR;
}
$file .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
foreach (self::$paths as $path) {
if (is_file($path . DIRECTORY_SEPARATOR . $file)) {
include $path . DIRECTORY_SEPARATOR . $file;
if (!self::loadSuccessful($class)) {
if (count(spl_autoload_functions()) > 1) {
return false;
}
throw new \Exception(
'Class ' . $class . ' was not present in ' .
$path . DIRECTORY_SEPARATOR . $file .
'") [PEAR2_Autoload-@PACKAGE_VERSION@]'
);
}
if (in_array($class, self::$unmapped)) {
self::updateMap(
$class,
$path . DIRECTORY_SEPARATOR . $file
);
}
return true;
}
}
if (count(spl_autoload_functions()) > 1) {
return false;
}
$e = new \Exception(
'Class ' . $class . ' could not be loaded from ' .
$file . ', file does not exist (registered paths="' .
implode(PATH_SEPARATOR, self::$paths) .
'") [PEAR2_Autoload-@PACKAGE_VERSION@]'
);
$trace = $e->getTrace();
if (isset($trace[2]) && isset($trace[2]['function'])
&& in_array($trace[2]['function'], self::$checkFunctions)
) {
return false;
}
if (isset($trace[1]) && isset($trace[1]['function'])
&& in_array($trace[1]['function'], self::$checkFunctions)
) {
return false;
}
throw $e;
}
/**
* Check if the requested class was loaded from the specified path
*
* @param string $class The name of the class to check.
*
* @return bool
*/
protected static function loadSuccessful($class)
{
return class_exists($class, false)
|| interface_exists($class, false)
|| (in_array('trait_exists', self::$checkFunctions, true)
&& trait_exists($class, false));
}
/**
* If possible, update the classmap file with newly-discovered
* mapping.
*
* @param string $class Class name discovered
* @param string $origin File where class was found
*
* @return void
*/
protected static function updateMap($class, $origin)
{
if (is_writable(self::$mapfile)
|| is_writable(dirname(self::$mapfile))
) {
self::$map[$class] = $origin;
file_put_contents(
self::$mapfile,
'<'."?php\n"
. "// PEAR2\Autoload auto-generated classmap\n"
. "return " . var_export(self::$map, true) . ';',
LOCK_EX
);
}
}
/**
* Return the array of paths PEAR2 autoload has registered
*
* @return array
*/
public static function getPaths()
{
return self::$paths;
}
}
}
Autoload::initialize(dirname(__DIR__));

View File

@ -0,0 +1,375 @@
<?php
/**
* Wrapper for shared memory and locking functionality across different extensions.
*
* Allows you to share data across requests as long as the PHP process is running. One of APC or WinCache is required to accomplish this, with other extensions being potentially pluggable as adapters.
*
* PHP version 5
*
* @category Caching
* @package PEAR2_Cache_SHM
* @author Vasil Rangelov <boen.robot@gmail.com>
* @copyright 2011 Vasil Rangelov
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version 0.2.0
* @link http://pear2.php.net/PEAR2_Cache_SHM
*/
/**
* The namespace declaration.
*/
namespace PEAR2\Cache;
/**
* Used as a catch-all for adapter initialization.
*/
use Exception as E;
/**
* Implements this class.
*/
use IteratorAggregate;
/**
* Used on failures by this class.
*/
use PEAR2\Cache\SHM\InvalidArgumentException;
/**
* Main class for this package.
*
* Automatically chooses an adapter based on the available extensions.
*
* @category Caching
* @package PEAR2_Cache_SHM
* @author Vasil Rangelov <boen.robot@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear2.php.net/PEAR2_Cache_SHM
*/
abstract class SHM implements IteratorAggregate
{
/**
* An array of adapter names that meet their requirements.
*
* @var array
*/
private static $_adapters = array();
/**
* Creates a new shared memory storage.
*
* Establishes a separate persistent storage. Adapter is automatically
* chosen based on the available extensions.
*
* @param string $persistentId The ID for the storage.
*
* @return static|SHM A new instance of an SHM adapter (child of this
* class).
*/
final public static function factory($persistentId)
{
foreach (self::$_adapters as $adapter) {
try {
return new $adapter($persistentId);
} catch (E $e) {
//In case of a runtime error, try to fallback to other adapters.
}
}
throw new InvalidArgumentException(
'No appropriate adapter available',
1
);
}
/**
* Checks if the adapter meets its requirements.
*
* @return bool TRUE on success, FALSE on failure.
*/
public static function isMeetingRequirements()
{
return true;
}
/**
* Registers an adapter.
*
* Registers an SHM adapter, allowing you to call it with {@link factory()}.
*
* @param string $adapter FQCN of adapter. A valid adapter is one that
* extends this class. The class will be autoloaded if not already
* present.
* @param bool $prepend Whether to prepend this adapter into the list of
* possible adapters, instead of appending to it.
*
* @return bool TRUE on success, FALSE on failure.
*/
final public static function registerAdapter($adapter, $prepend = false)
{
if (class_exists($adapter, true)
&& is_subclass_of($adapter, '\\' . __CLASS__)
&& $adapter::isMeetingRequirements()
) {
if ($prepend) {
self::$_adapters = array_merge(
array($adapter),
self::$_adapters
);
} else {
self::$_adapters[] = $adapter;
}
return true;
}
return false;
}
/**
* Adds a value to the shared memory storage.
*
* Adds a value to the storage if it doesn't exist, or fails if it does.
*
* @param string $key Name of key to associate the value with.
* @param mixed $value Value for the specified key.
* @param int $ttl Seconds to store the value. If set to 0 indicates no
* time limit.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function __invoke($key, $value, $ttl = 0)
{
return $this->add($key, $value, $ttl);
}
/**
* Gets a value from the shared memory storage.
*
* This is a magic method, thanks to which any property you attempt to get
* the value of will be fetched from the adapter, treating the property name
* as the key of the value to get.
*
* @param string $key Name of key to get.
*
* @return mixed The current value of the specified key.
*/
public function __get($key)
{
return $this->get($key);
}
/**
* Sets a value in the shared memory storage.
*
* This is a magic method, thanks to which any property you attempt to set
* the value of will be set by the adapter, treating the property name as
* the key of the value to set. The value is set without a TTL.
*
* @param string $key Name of key to associate the value with.
* @param mixed $value Value for the specified key.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function __set($key, $value)
{
return $this->set($key, $value);
}
/**
* Checks if a specified key is in the storage.
*
* This is a magic method, thanks to which any property you call isset() on
* will be checked by the adapter, treating the property name as the key
* of the value to check.
*
* @param string $key Name of key to check.
*
* @return bool TRUE if the key is in the storage, FALSE otherwise.
*/
public function __isset($key)
{
return $this->exists($key);
}
/**
* Deletes a value from the shared memory storage.
*
* This is a magic method, thanks to which any property you attempt to unset
* the value of will be unset by the adapter, treating the property name as
* the key of the value to delete.
*
* @param string $key Name of key to delete.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function __unset($key)
{
return $this->delete($key);
}
/**
* Creates a new shared memory storage.
*
* Establishes a separate persistent storage.
*
* @param string $persistentId The ID for the storage. The storage will be
* reused if it exists, or created if it doesn't exist. Data and locks
* are namespaced by this ID.
*/
abstract public function __construct($persistentId);
/**
* Obtains a named lock.
*
* @param string $key Name of the key to obtain. Note that $key may
* repeat for each distinct $persistentId.
* @param double $timeout If the lock can't be immediately obtained, the
* script will block for at most the specified amount of seconds.
* Setting this to 0 makes lock obtaining non blocking, and setting it
* to NULL makes it block without a time limit.
*
* @return bool TRUE on success, FALSE on failure.
*/
abstract public function lock($key, $timeout = null);
/**
* Releases a named lock.
*
* @param string $key Name of the key to release. Note that $key may
* repeat for each distinct $persistentId.
*
* @return bool TRUE on success, FALSE on failure.
*/
abstract public function unlock($key);
/**
* Checks if a specified key is in the storage.
*
* @param string $key Name of key to check.
*
* @return bool TRUE if the key is in the storage, FALSE otherwise.
*/
abstract public function exists($key);
/**
* Adds a value to the shared memory storage.
*
* Adds a value to the storage if it doesn't exist, or fails if it does.
*
* @param string $key Name of key to associate the value with.
* @param mixed $value Value for the specified key.
* @param int $ttl Seconds to store the value. If set to 0 indicates no
* time limit.
*
* @return bool TRUE on success, FALSE on failure.
*/
abstract public function add($key, $value, $ttl = 0);
/**
* Sets a value in the shared memory storage.
*
* Adds a value to the storage if it doesn't exist, overwrites it otherwise.
*
* @param string $key Name of key to associate the value with.
* @param mixed $value Value for the specified key.
* @param int $ttl Seconds to store the value. If set to 0 indicates no
* time limit.
*
* @return bool TRUE on success, FALSE on failure.
*/
abstract public function set($key, $value, $ttl = 0);
/**
* Gets a value from the shared memory storage.
*
* Gets the current value, or throws an exception if it's not stored.
*
* @param string $key Name of key to get the value of.
*
* @return mixed The current value of the specified key.
*/
abstract public function get($key);
/**
* Deletes a value from the shared memory storage.
*
* @param string $key Name of key to delete.
*
* @return bool TRUE on success, FALSE on failure.
*/
abstract public function delete($key);
/**
* Increases a value from the shared memory storage.
*
* Increases a value from the shared memory storage. Unlike a plain
* set($key, get($key)+$step) combination, this function also implicitly
* performs locking.
*
* @param string $key Name of key to increase.
* @param int $step Value to increase the key by.
*
* @return int The new value.
*/
abstract public function inc($key, $step = 1);
/**
* Decreases a value from the shared memory storage.
*
* Decreases a value from the shared memory storage. Unlike a plain
* set($key, get($key)-$step) combination, this function also implicitly
* performs locking.
*
* @param string $key Name of key to decrease.
* @param int $step Value to decrease the key by.
*
* @return int The new value.
*/
abstract public function dec($key, $step = 1);
/**
* Sets a new value if a key has a certain value.
*
* Sets a new value if a key has a certain value. This function only works
* when $old and $new are longs.
*
* @param string $key Key of the value to compare and set.
* @param int $old The value to compare the key against.
* @param int $new The value to set the key to.
*
* @return bool TRUE on success, FALSE on failure.
*/
abstract public function cas($key, $old, $new);
/**
* Clears the persistent storage.
*
* Clears the persistent storage, i.e. removes all keys. Locks are left
* intact.
*
* @return void
*/
abstract public function clear();
/**
* Retrieve an external iterator
*
* Returns an external iterator.
*
* @param string|null $filter A PCRE regular expression.
* Only matching keys will be iterated over.
* Setting this to NULL matches all keys of this instance.
* @param bool $keysOnly Whether to return only the keys,
* or return both the keys and values.
*
* @return \Traversable An array with all matching keys as array keys,
* and values as array values. If $keysOnly is TRUE, the array keys are
* numeric, and the array values are key names.
*/
abstract public function getIterator($filter = null, $keysOnly = false);
}
SHM::registerAdapter('\\' . __NAMESPACE__ . '\SHM\Adapter\Placebo');
SHM::registerAdapter('\\' . __NAMESPACE__ . '\SHM\Adapter\Wincache');
SHM::registerAdapter('\\' . __NAMESPACE__ . '\SHM\Adapter\APCu');
SHM::registerAdapter('\\' . __NAMESPACE__ . '\SHM\Adapter\APC');

View File

@ -0,0 +1,417 @@
<?php
/**
* Wrapper for shared memory and locking functionality across different extensions.
*
* Allows you to share data across requests as long as the PHP process is running. One of APC or WinCache is required to accomplish this, with other extensions being potentially pluggable as adapters.
*
* PHP version 5
*
* @category Caching
* @package PEAR2_Cache_SHM
* @author Vasil Rangelov <boen.robot@gmail.com>
* @copyright 2011 Vasil Rangelov
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version 0.2.0
* @link http://pear2.php.net/PEAR2_Cache_SHM
*/
/**
* The namespace declaration.
*/
namespace PEAR2\Cache\SHM\Adapter;
/**
* Throws exceptions from this namespace, and extends from this class.
*/
use PEAR2\Cache\SHM;
/**
* {@link APC::getIterator()} returns this object.
*/
use ArrayObject;
/**
* Shared memory adapter for the APC extension.
*
* @category Caching
* @package PEAR2_Cache_SHM
* @author Vasil Rangelov <boen.robot@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear2.php.net/PEAR2_Cache_SHM
*/
class APC extends SHM
{
/**
* ID of the current storage.
*
* @var string
*/
protected $persistentId;
/**
* List of persistent IDs.
*
* A list of persistent IDs within the current request (as keys) with an int
* (as a value) specifying the number of instances in the current request.
* Used as an attempt to ensure implicit lock releases even on errors in the
* critical sections, since APC doesn't have an actual locking function.
*
* @var array
*/
protected static $requestInstances = array();
/**
* Array of lock names for each persistent ID.
*
* Array of lock names (as values) for each persistent ID (as key) obtained
* during the current request.
*
* @var array
*/
protected static $locksBackup = array();
/**
* Creates a new shared memory storage.
*
* Establishes a separate persistent storage.
*
* @param string $persistentId The ID for the storage. The storage will be
* reused if it exists, or created if it doesn't exist. Data and locks
* are namespaced by this ID.
*/
public function __construct($persistentId)
{
$this->persistentId = __CLASS__ . ' ' . $persistentId;
if (isset(static::$requestInstances[$this->persistentId])) {
static::$requestInstances[$this->persistentId]++;
} else {
static::$requestInstances[$this->persistentId] = 1;
static::$locksBackup[$this->persistentId] = array();
}
register_shutdown_function(
get_called_class() . '::releaseLocks',
$this->persistentId,
true
);
}
/**
* Checks if the adapter meets its requirements.
*
* @return bool TRUE on success, FALSE on failure.
*/
public static function isMeetingRequirements()
{
return extension_loaded('apc')
&& version_compare(phpversion('apc'), '3.1.1', '>=')
&& ini_get('apc.enabled')
&& ('cli' !== PHP_SAPI || ini_get('apc.enable_cli'));
}
/**
* Releases all locks in a storage.
*
* This function is not meant to be used directly. It is implicitly called
* by the the destructor and as a shutdown function when the request ends.
* One of these calls ends up releasing any unreleased locks obtained
* during the request. A lock is also implicitly released as soon as there
* are no objects left in the current request using the same persistent ID.
*
* @param string $internalPersistentId The internal persistent ID, the locks
* of which are being released.
* @param bool $isAtShutdown Whether the function was executed at
* shutdown.
*
* @return void
*
* @internal
*/
public static function releaseLocks($internalPersistentId, $isAtShutdown)
{
$hasInstances = 0 !== static::$requestInstances[$internalPersistentId];
if ($isAtShutdown === $hasInstances) {
foreach (static::$locksBackup[$internalPersistentId] as $key) {
apc_delete($internalPersistentId . 'l ' . $key);
}
}
}
/**
* Releases any locks obtained by this instance as soon as there are no more
* references to the object's persistent ID.
*/
public function __destruct()
{
static::$requestInstances[$this->persistentId]--;
static::releaseLocks($this->persistentId, false);
}
/**
* Obtains a named lock.
*
* @param string $key Name of the key to obtain. Note that $key may
* repeat for each distinct $persistentId.
* @param double $timeout If the lock can't be immediately obtained, the
* script will block for at most the specified amount of seconds.
* Setting this to 0 makes lock obtaining non blocking, and setting it
* to NULL makes it block without a time limit.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function lock($key, $timeout = null)
{
$lock = $this->persistentId . 'l ' . $key;
$hasTimeout = $timeout !== null;
$start = microtime(true);
while (!apc_add($lock, 1)) {
if ($hasTimeout && (microtime(true) - $start) > $timeout) {
return false;
}
}
static::$locksBackup[$this->persistentId] = $key;
return true;
}
/**
* Releases a named lock.
*
* @param string $key Name of the key to release. Note that $key may
* repeat for each distinct $persistentId.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function unlock($key)
{
$lock = $this->persistentId . 'l ' . $key;
$success = apc_delete($lock);
if ($success) {
unset(
static::$locksBackup[$this->persistentId][array_search(
$key,
static::$locksBackup[$this->persistentId],
true
)]
);
return true;
}
return false;
}
/**
* Checks if a specified key is in the storage.
*
* @param string $key Name of key to check.
*
* @return bool TRUE if the key is in the storage, FALSE otherwise.
*/
public function exists($key)
{
return apc_exists($this->persistentId . 'd ' . $key);
}
/**
* Adds a value to the shared memory storage.
*
* Adds a value to the storage if it doesn't exist, or fails if it does.
*
* @param string $key Name of key to associate the value with.
* @param mixed $value Value for the specified key.
* @param int $ttl Seconds to store the value. If set to 0 indicates no
* time limit.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function add($key, $value, $ttl = 0)
{
return apc_add($this->persistentId . 'd ' . $key, $value, $ttl);
}
/**
* Sets a value in the shared memory storage.
*
* Adds a value to the storage if it doesn't exist, overwrites it otherwise.
*
* @param string $key Name of key to associate the value with.
* @param mixed $value Value for the specified key.
* @param int $ttl Seconds to store the value. If set to 0 indicates no
* time limit.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function set($key, $value, $ttl = 0)
{
return apc_store($this->persistentId . 'd ' . $key, $value, $ttl);
}
/**
* Gets a value from the shared memory storage.
*
* Gets the current value, or throws an exception if it's not stored.
*
* @param string $key Name of key to get the value of.
*
* @return mixed The current value of the specified key.
*/
public function get($key)
{
$fullKey = $this->persistentId . 'd ' . $key;
if (apc_exists($fullKey)) {
$value = apc_fetch($fullKey, $success);
if (!$success) {
throw new SHM\InvalidArgumentException(
'Unable to fetch key. ' .
'Key has either just now expired or (if no TTL was set) ' .
'is possibly in a race condition with another request.',
100
);
}
return $value;
}
throw new SHM\InvalidArgumentException('No such key in cache', 101);
}
/**
* Deletes a value from the shared memory storage.
*
* @param string $key Name of key to delete.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function delete($key)
{
return apc_delete($this->persistentId . 'd ' . $key);
}
/**
* Increases a value from the shared memory storage.
*
* Increases a value from the shared memory storage. Unlike a plain
* set($key, get($key)+$step) combination, this function also implicitly
* performs locking.
*
* @param string $key Name of key to increase.
* @param int $step Value to increase the key by.
*
* @return int The new value.
*/
public function inc($key, $step = 1)
{
$newValue = apc_inc(
$this->persistentId . 'd ' . $key,
(int) $step,
$success
);
if (!$success) {
throw new SHM\InvalidArgumentException(
'Unable to increase the value. Are you sure the value is int?',
102
);
}
return $newValue;
}
/**
* Decreases a value from the shared memory storage.
*
* Decreases a value from the shared memory storage. Unlike a plain
* set($key, get($key)-$step) combination, this function also implicitly
* performs locking.
*
* @param string $key Name of key to decrease.
* @param int $step Value to decrease the key by.
*
* @return int The new value.
*/
public function dec($key, $step = 1)
{
$newValue = apc_dec(
$this->persistentId . 'd ' . $key,
(int) $step,
$success
);
if (!$success) {
throw new SHM\InvalidArgumentException(
'Unable to decrease the value. Are you sure the value is int?',
103
);
}
return $newValue;
}
/**
* Sets a new value if a key has a certain value.
*
* Sets a new value if a key has a certain value. This function only works
* when $old and $new are longs.
*
* @param string $key Key of the value to compare and set.
* @param int $old The value to compare the key against.
* @param int $new The value to set the key to.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function cas($key, $old, $new)
{
return apc_cas($this->persistentId . 'd ' . $key, $old, $new);
}
/**
* Clears the persistent storage.
*
* Clears the persistent storage, i.e. removes all keys. Locks are left
* intact.
*
* @return void
*/
public function clear()
{
foreach (new APCIterator(
'user',
'/^' . preg_quote($this->persistentId, '/') . 'd /',
APC_ITER_KEY,
100,
APC_LIST_ACTIVE
) as $key) {
apc_delete($key);
}
}
/**
* Retrieve an external iterator
*
* Returns an external iterator.
*
* @param string|null $filter A PCRE regular expression.
* Only matching keys will be iterated over.
* Setting this to NULL matches all keys of this instance.
* @param bool $keysOnly Whether to return only the keys,
* or return both the keys and values.
*
* @return ArrayObject An array with all matching keys as array keys,
* and values as array values. If $keysOnly is TRUE, the array keys are
* numeric, and the array values are key names.
*/
public function getIterator($filter = null, $keysOnly = false)
{
$result = array();
foreach (new APCIterator(
'user',
'/^' . preg_quote($this->persistentId, '/') . 'd /',
APC_ITER_KEY,
100,
APC_LIST_ACTIVE
) as $key) {
$localKey = strstr($key, $this->persistentId . 'd ');
if (null === $filter || preg_match($filter, $localKey)) {
if ($keysOnly) {
$result[] = $localKey;
} else {
$result[$localKey] = apc_fetch($key);
}
}
}
return new ArrayObject($result);
}
}

View File

@ -0,0 +1,416 @@
<?php
/**
* Wrapper for shared memory and locking functionality across different extensions.
*
* Allows you to share data across requests as long as the PHP process is running. One of APC or WinCache is required to accomplish this, with other extensions being potentially pluggable as adapters.
*
* PHP version 5
*
* @category Caching
* @package PEAR2_Cache_SHM
* @author Vasil Rangelov <boen.robot@gmail.com>
* @copyright 2011 Vasil Rangelov
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version 0.2.0
* @link http://pear2.php.net/PEAR2_Cache_SHM
*/
/**
* The namespace declaration.
*/
namespace PEAR2\Cache\SHM\Adapter;
/**
* Throws exceptions from this namespace, and extends from this class.
*/
use PEAR2\Cache\SHM;
/**
* {@link APC::getIterator()} returns this object.
*/
use ArrayObject;
/**
* Shared memory adapter for the APC extension.
*
* @category Caching
* @package PEAR2_Cache_SHM
* @author Vasil Rangelov <boen.robot@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear2.php.net/PEAR2_Cache_SHM
*/
class APCu extends SHM
{
/**
* ID of the current storage.
*
* @var string
*/
protected $persistentId;
/**
* List of persistent IDs.
*
* A list of persistent IDs within the current request (as keys) with an int
* (as a value) specifying the number of instances in the current request.
* Used as an attempt to ensure implicit lock releases even on errors in the
* critical sections, since APC doesn't have an actual locking function.
*
* @var array
*/
protected static $requestInstances = array();
/**
* Array of lock names for each persistent ID.
*
* Array of lock names (as values) for each persistent ID (as key) obtained
* during the current request.
*
* @var array
*/
protected static $locksBackup = array();
/**
* Creates a new shared memory storage.
*
* Establishes a separate persistent storage.
*
* @param string $persistentId The ID for the storage. The storage will be
* reused if it exists, or created if it doesn't exist. Data and locks
* are namespaced by this ID.
*/
public function __construct($persistentId)
{
$this->persistentId = __CLASS__ . ' ' . $persistentId;
if (isset(static::$requestInstances[$this->persistentId])) {
static::$requestInstances[$this->persistentId]++;
} else {
static::$requestInstances[$this->persistentId] = 1;
static::$locksBackup[$this->persistentId] = array();
}
register_shutdown_function(
get_called_class() . '::releaseLocks',
$this->persistentId,
true
);
}
/**
* Checks if the adapter meets its requirements.
*
* @return bool TRUE on success, FALSE on failure.
*/
public static function isMeetingRequirements()
{
return extension_loaded('apcu')
&& version_compare(phpversion('apcu'), '5.0.0', '>=')
&& ini_get('apc.enabled')
&& ('cli' !== PHP_SAPI || ini_get('apc.enable_cli'));
}
/**
* Releases all locks in a storage.
*
* This function is not meant to be used directly. It is implicitly called
* by the the destructor and as a shutdown function when the request ends.
* One of these calls ends up releasing any unreleased locks obtained
* during the request. A lock is also implicitly released as soon as there
* are no objects left in the current request using the same persistent ID.
*
* @param string $internalPersistentId The internal persistent ID, the locks
* of which are being released.
* @param bool $isAtShutdown Whether the function was executed at
* shutdown.
*
* @return void
*
* @internal
*/
public static function releaseLocks($internalPersistentId, $isAtShutdown)
{
$hasInstances = 0 !== static::$requestInstances[$internalPersistentId];
if ($isAtShutdown === $hasInstances) {
foreach (static::$locksBackup[$internalPersistentId] as $key) {
apcu_delete($internalPersistentId . 'l ' . $key);
}
}
}
/**
* Releases any locks obtained by this instance as soon as there are no more
* references to the object's persistent ID.
*/
public function __destruct()
{
static::$requestInstances[$this->persistentId]--;
static::releaseLocks($this->persistentId, false);
}
/**
* Obtains a named lock.
*
* @param string $key Name of the key to obtain. Note that $key may
* repeat for each distinct $persistentId.
* @param double $timeout If the lock can't be immediately obtained, the
* script will block for at most the specified amount of seconds.
* Setting this to 0 makes lock obtaining non blocking, and setting it
* to NULL makes it block without a time limit.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function lock($key, $timeout = null)
{
$lock = $this->persistentId . 'l ' . $key;
$hasTimeout = $timeout !== null;
$start = microtime(true);
while (!apcu_add($lock, 1)) {
if ($hasTimeout && (microtime(true) - $start) > $timeout) {
return false;
}
}
static::$locksBackup[$this->persistentId] = $key;
return true;
}
/**
* Releases a named lock.
*
* @param string $key Name of the key to release. Note that $key may
* repeat for each distinct $persistentId.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function unlock($key)
{
$lock = $this->persistentId . 'l ' . $key;
$success = apcu_delete($lock);
if ($success) {
unset(
static::$locksBackup[$this->persistentId][array_search(
$key,
static::$locksBackup[$this->persistentId],
true
)]
);
return true;
}
return false;
}
/**
* Checks if a specified key is in the storage.
*
* @param string $key Name of key to check.
*
* @return bool TRUE if the key is in the storage, FALSE otherwise.
*/
public function exists($key)
{
return apcu_exists($this->persistentId . 'd ' . $key);
}
/**
* Adds a value to the shared memory storage.
*
* Adds a value to the storage if it doesn't exist, or fails if it does.
*
* @param string $key Name of key to associate the value with.
* @param mixed $value Value for the specified key.
* @param int $ttl Seconds to store the value. If set to 0 indicates no
* time limit.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function add($key, $value, $ttl = 0)
{
return apcu_add($this->persistentId . 'd ' . $key, $value, $ttl);
}
/**
* Sets a value in the shared memory storage.
*
* Adds a value to the storage if it doesn't exist, overwrites it otherwise.
*
* @param string $key Name of key to associate the value with.
* @param mixed $value Value for the specified key.
* @param int $ttl Seconds to store the value. If set to 0 indicates no
* time limit.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function set($key, $value, $ttl = 0)
{
return apcu_store($this->persistentId . 'd ' . $key, $value, $ttl);
}
/**
* Gets a value from the shared memory storage.
*
* Gets the current value, or throws an exception if it's not stored.
*
* @param string $key Name of key to get the value of.
*
* @return mixed The current value of the specified key.
*/
public function get($key)
{
$fullKey = $this->persistentId . 'd ' . $key;
if (apcu_exists($fullKey)) {
$value = apcu_fetch($fullKey, $success);
if (!$success) {
throw new SHM\InvalidArgumentException(
'Unable to fetch key. ' .
'Key has either just now expired or (if no TTL was set) ' .
'is possibly in a race condition with another request.',
100
);
}
return $value;
}
throw new SHM\InvalidArgumentException('No such key in cache', 101);
}
/**
* Deletes a value from the shared memory storage.
*
* @param string $key Name of key to delete.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function delete($key)
{
return apcu_delete($this->persistentId . 'd ' . $key);
}
/**
* Increases a value from the shared memory storage.
*
* Increases a value from the shared memory storage. Unlike a plain
* set($key, get($key)+$step) combination, this function also implicitly
* performs locking.
*
* @param string $key Name of key to increase.
* @param int $step Value to increase the key by.
*
* @return int The new value.
*/
public function inc($key, $step = 1)
{
$newValue = apcu_inc(
$this->persistentId . 'd ' . $key,
(int) $step,
$success
);
if (!$success) {
throw new SHM\InvalidArgumentException(
'Unable to increase the value. Are you sure the value is int?',
102
);
}
return $newValue;
}
/**
* Decreases a value from the shared memory storage.
*
* Decreases a value from the shared memory storage. Unlike a plain
* set($key, get($key)-$step) combination, this function also implicitly
* performs locking.
*
* @param string $key Name of key to decrease.
* @param int $step Value to decrease the key by.
*
* @return int The new value.
*/
public function dec($key, $step = 1)
{
$newValue = apcu_dec(
$this->persistentId . 'd ' . $key,
(int) $step,
$success
);
if (!$success) {
throw new SHM\InvalidArgumentException(
'Unable to decrease the value. Are you sure the value is int?',
103
);
}
return $newValue;
}
/**
* Sets a new value if a key has a certain value.
*
* Sets a new value if a key has a certain value. This function only works
* when $old and $new are longs.
*
* @param string $key Key of the value to compare and set.
* @param int $old The value to compare the key against.
* @param int $new The value to set the key to.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function cas($key, $old, $new)
{
return apcu_cas($this->persistentId . 'd ' . $key, $old, $new);
}
/**
* Clears the persistent storage.
*
* Clears the persistent storage, i.e. removes all keys. Locks are left
* intact.
*
* @return void
*/
public function clear()
{
foreach (new APCIterator(
'user',
'/^' . preg_quote($this->persistentId, '/') . 'd /',
APC_ITER_KEY,
100,
APC_LIST_ACTIVE
) as $key) {
apcu_delete($key);
}
}
/**
* Retrieve an external iterator
*
* Returns an external iterator.
*
* @param string|null $filter A PCRE regular expression.
* Only matching keys will be iterated over.
* Setting this to NULL matches all keys of this instance.
* @param bool $keysOnly Whether to return only the keys,
* or return both the keys and values.
*
* @return ArrayObject An array with all matching keys as array keys,
* and values as array values. If $keysOnly is TRUE, the array keys are
* numeric, and the array values are key names.
*/
public function getIterator($filter = null, $keysOnly = false)
{
$result = array();
foreach (new APCUIterator(
'/^' . preg_quote($this->persistentId, '/') . 'd /',
APC_ITER_KEY,
100,
APC_LIST_ACTIVE
) as $key) {
$localKey = strstr($key, $this->persistentId . 'd ');
if (null === $filter || preg_match($filter, $localKey)) {
if ($keysOnly) {
$result[] = $localKey;
} else {
$result[$localKey] = apcu_fetch($key);
}
}
}
return new ArrayObject($result);
}
}

View File

@ -0,0 +1,369 @@
<?php
/**
* Wrapper for shared memory and locking functionality across different extensions.
*
* Allows you to share data across requests as long as the PHP process is running. One of APC or WinCache is required to accomplish this, with other extensions being potentially pluggable as adapters.
*
* PHP version 5
*
* @category Caching
* @package PEAR2_Cache_SHM
* @author Vasil Rangelov <boen.robot@gmail.com>
* @copyright 2011 Vasil Rangelov
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version 0.2.0
* @link http://pear2.php.net/PEAR2_Cache_SHM
*/
/**
* The namespace declaration.
*/
namespace PEAR2\Cache\SHM\Adapter;
/**
* Throws exceptions from this namespace, and extends from this class.
*/
use PEAR2\Cache\SHM;
/**
* {@link Placebo::getIterator()} returns this object.
*/
use ArrayObject;
/**
* This adapter is not truly persistent. It is intended to emulate persistence
* in non persistent environments, so that upper level applications can use a
* single code path for persistent and non persistent code.
*
* @category Caching
* @package PEAR2_Cache_SHM
* @author Vasil Rangelov <boen.robot@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear2.php.net/PEAR2_Cache_SHM
*/
class Placebo extends SHM
{
/**
* ID of the current storage.
*
* @var string
*/
protected $persistentId;
/**
* List of persistent IDs.
*
* A list of persistent IDs within the current request (as keys) with an int
* (as a value) specifying the number of instances in the current request.
* Used as an attempt to ensure implicit lock releases on destruction.
*
* @var array
*/
protected static $requestInstances = array();
/**
* Array of lock names for each persistent ID.
*
* Array of lock names (as values) for each persistent ID (as
* key) obtained during the current request.
*
* @var array
*/
protected static $locksBackup = array();
/**
* The data storage.
*
* Each persistent ID is a key, and the value is an array.
* Each such array has data keys as its keys, and an array as a value.
* Each such array has as its elements the value, the timeout and the time
* the data was set.
*
* @var array
*/
protected static $data = array();
/**
* Creates a new shared memory storage.
*
* Establishes a separate persistent storage.
*
* @param string $persistentId The ID for the storage. The storage will be
* reused if it exists, or created if it doesn't exist. Data and locks
* are namespaced by this ID.
*/
public function __construct($persistentId)
{
if (isset(static::$requestInstances[$persistentId])) {
++static::$requestInstances[$persistentId];
} else {
static::$requestInstances[$persistentId] = 1;
static::$locksBackup[$persistentId] = array();
static::$data[$persistentId] = array();
}
$this->persistentId = $persistentId;
}
/**
* Releases any unreleased locks.
*/
public function __destruct()
{
if (0 === --static::$requestInstances[$this->persistentId]) {
static::$locksBackup[$this->persistentId] = array();
}
}
/**
* Checks if the adapter meets its requirements.
*
* @return bool TRUE on success, FALSE on failure.
*/
public static function isMeetingRequirements()
{
return 'cli' === PHP_SAPI;
}
/**
* Pretends to obtain a lock.
*
* @param string $key Ignored.
* @param double $timeout Ignored.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function lock($key, $timeout = null)
{
$key = (string) $key;
if (in_array($key, static::$locksBackup[$this->persistentId], true)) {
return false;
}
static::$locksBackup[$this->persistentId][] = $key;
return true;
}
/**
* Pretends to release a lock.
*
* @param string $key Ignored
*
* @return bool TRUE on success, FALSE on failure.
*/
public function unlock($key)
{
$key = (string) $key;
if (!in_array($key, static::$locksBackup[$this->persistentId], true)) {
return false;
}
unset(
static::$locksBackup[$this->persistentId][array_search(
$key,
static::$locksBackup[$this->persistentId],
true
)]
);
return true;
}
/**
* Checks if a specified key is in the storage.
*
* @param string $key Name of key to check.
*
* @return bool TRUE if the key is in the storage, FALSE otherwise.
*/
public function exists($key)
{
return array_key_exists($key, static::$data[$this->persistentId]);
}
/**
* Adds a value to the shared memory storage.
*
* Adds a value to the storage if it doesn't exist, or fails if it does.
*
* @param string $key Name of key to associate the value with.
* @param mixed $value Value for the specified key.
* @param int $ttl Because "true" adapters purge the cache at the next
* request, this setting is ignored.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function add($key, $value, $ttl = 0)
{
if ($this->exists($key)) {
return false;
}
return $this->set($key, $value, $ttl);
}
/**
* Sets a value in the shared memory storage.
*
* Adds a value to the storage if it doesn't exist, overwrites it otherwise.
*
* @param string $key Name of key to associate the value with.
* @param mixed $value Value for the specified key.
* @param int $ttl Because "true" adapters purge the cache at the next
* request, this setting is ignored.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function set($key, $value, $ttl = 0)
{
static::$data[$this->persistentId][$key] = $value;
return true;
}
/**
* Gets a value from the shared memory storage.
*
* Gets the current value, or throws an exception if it's not stored.
*
* @param string $key Name of key to get the value of.
*
* @return mixed The current value of the specified key.
*/
public function get($key)
{
if ($this->exists($key)) {
return static::$data[$this->persistentId][$key];
}
throw new SHM\InvalidArgumentException(
'Unable to fetch key. No such key.',
200
);
}
/**
* Deletes a value from the shared memory storage.
*
* @param string $key Name of key to delete.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function delete($key)
{
if ($this->exists($key)) {
unset(static::$data[$this->persistentId][$key]);
return true;
}
return false;
}
/**
* Increases a value from the shared memory storage.
*
* Increases a value from the shared memory storage. Unlike a plain
* set($key, get($key)+$step) combination, this function also implicitly
* performs locking.
*
* @param string $key Name of key to increase.
* @param int $step Value to increase the key by.
*
* @return int The new value.
*/
public function inc($key, $step = 1)
{
if (!$this->exists($key) || !is_int($value = $this->get($key))
|| !$this->set($key, $value + (int) $step)
) {
throw new SHM\InvalidArgumentException(
'Unable to increase the value. Are you sure the value is int?',
201
);
}
return $this->get($key);
}
/**
* Decreases a value from the shared memory storage.
*
* Decreases a value from the shared memory storage. Unlike a plain
* set($key, get($key)-$step) combination, this function also implicitly
* performs locking.
*
* @param string $key Name of key to decrease.
* @param int $step Value to decrease the key by.
*
* @return int The new value.
*/
public function dec($key, $step = 1)
{
if (!$this->exists($key) || !is_int($value = $this->get($key))
|| !$this->set($key, $value - (int) $step)
) {
throw new SHM\InvalidArgumentException(
'Unable to increase the value. Are you sure the value is int?',
202
);
}
return $this->get($key);
}
/**
* Sets a new value if a key has a certain value.
*
* Sets a new value if a key has a certain value. This function only works
* when $old and $new are longs.
*
* @param string $key Key of the value to compare and set.
* @param int $old The value to compare the key against.
* @param int $new The value to set the key to.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function cas($key, $old, $new)
{
return $this->exists($key) && ($this->get($key) === $old)
&& is_int($new) && $this->set($key, $new);
}
/**
* Clears the persistent storage.
*
* Clears the persistent storage, i.e. removes all keys. Locks are left
* intact.
*
* @return void
*/
public function clear()
{
static::$data[$this->persistentId] = array();
}
/**
* Retrieve an external iterator
*
* Returns an external iterator.
*
* @param string|null $filter A PCRE regular expression.
* Only matching keys will be iterated over.
* Setting this to NULL matches all keys of this instance.
* @param bool $keysOnly Whether to return only the keys,
* or return both the keys and values.
*
* @return ArrayObject An array with all matching keys as array keys,
* and values as array values. If $keysOnly is TRUE, the array keys are
* numeric, and the array values are key names.
*/
public function getIterator($filter = null, $keysOnly = false)
{
if (null === $filter) {
return new ArrayObject(
$keysOnly
? array_keys(static::$data[$this->persistentId])
: static::$data[$this->persistentId]
);
}
$result = array();
foreach (static::$data[$this->persistentId] as $key => $value) {
if (preg_match($filter, $key)) {
$result[$key] = $value;
}
}
return new ArrayObject($keysOnly ? array_keys($result) : $result);
}
}

View File

@ -0,0 +1,392 @@
<?php
/**
* Wrapper for shared memory and locking functionality across different extensions.
*
* Allows you to share data across requests as long as the PHP process is running. One of APC or WinCache is required to accomplish this, with other extensions being potentially pluggable as adapters.
*
* PHP version 5
*
* @category Caching
* @package PEAR2_Cache_SHM
* @author Vasil Rangelov <boen.robot@gmail.com>
* @copyright 2011 Vasil Rangelov
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version 0.2.0
* @link http://pear2.php.net/PEAR2_Cache_SHM
*/
/**
* The namespace declaration.
*/
namespace PEAR2\Cache\SHM\Adapter;
/**
* Throws exceptions from this namespace, and extends from this class.
*/
use PEAR2\Cache\SHM;
/**
* {@link Wincache::getIterator()} returns this object.
*/
use ArrayObject;
/**
* Shared memory adapter for the WinCache extension.
*
* @category Caching
* @package PEAR2_Cache_SHM
* @author Vasil Rangelov <boen.robot@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear2.php.net/PEAR2_Cache_SHM
*/
class Wincache extends SHM
{
/**
* ID of the current storage.
*
* @var string
*/
protected $persistentId;
/**
* List of persistent IDs.
*
* A list of persistent IDs within the current request (as keys) with an int
* (as a value) specifying the number of instances in the current request.
* Used as an attempt to ensure implicit lock releases on destruction.
*
* @var array
*/
protected static $requestInstances = array();
/**
* Array of lock names obtained during the current request.
*
* @var array
*/
protected static $locksBackup = array();
/**
* Creates a new shared memory storage.
*
* Establishes a separate persistent storage.
*
* @param string $persistentId The ID for the storage. The storage will be
* reused if it exists, or created if it doesn't exist. Data and locks
* are namespaced by this ID.
*/
public function __construct($persistentId)
{
$this->persistentId
= static::encodeLockName(__CLASS__ . ' ' . $persistentId) . ' ';
if (isset(static::$requestInstances[$this->persistentId])) {
static::$requestInstances[$this->persistentId]++;
} else {
static::$requestInstances[$this->persistentId] = 1;
static::$locksBackup[$this->persistentId] = array();
}
}
/**
* Encodes a lock name
*
* Encodes a lock name, so that it can be properly obtained. The scheme used
* is a subset of URL encoding, with only the "%" and "\" characters being
* escaped. The encoding itself is necessary, since lock names can't contain
* the "\" character.
*
* @param string $name The lock name to encode.
*
* @return string The encoded name.
*
* @link http://msdn.microsoft.com/en-us/library/ms682411(VS.85).aspx
*/
protected static function encodeLockName($name)
{
return str_replace(array('%', '\\'), array('%25', '%5C'), $name);
}
/**
* Checks if the adapter meets its requirements.
*
* @return bool TRUE on success, FALSE on failure.
*/
public static function isMeetingRequirements()
{
return extension_loaded('wincache')
&& version_compare(phpversion('wincache'), '1.1.0', '>=')
&& ini_get('wincache.ucenabled')
&& ('cli' !== PHP_SAPI || ini_get('wincache.enablecli'));
}
/**
* Releases any locks obtained by this instance as soon as there are no more
* references to the object's persistent ID.
*/
public function __destruct()
{
if (0 === --static::$requestInstances[$this->persistentId]) {
foreach (static::$locksBackup[$this->persistentId] as $key) {
wincache_unlock(
$this->persistentId . static::encodeLockName($key)
);
}
}
}
/**
* Obtains a named lock.
*
* @param string $key Name of the key to obtain. Note that $key may
* repeat for each distinct $persistentId.
* @param double $timeout Ignored with WinCache. Script will always block if
* the lock can't be immediately obtained.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function lock($key, $timeout = null)
{
$result = wincache_lock(
$this->persistentId . static::encodeLockName($key)
);
if ($result) {
static::$locksBackup[$this->persistentId] = $key;
}
return $result;
}
/**
* Releases a named lock.
*
* @param string $key Name of the key to release. Note that $key may
* repeat for each distinct $persistentId.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function unlock($key)
{
$result = wincache_unlock(
$this->persistentId . static::encodeLockName($key)
);
if ($result) {
unset(
static::$locksBackup[$this->persistentId][array_search(
$key,
static::$locksBackup[$this->persistentId],
true
)]
);
}
return $result;
}
/**
* Checks if a specified key is in the storage.
*
* @param string $key Name of key to check.
*
* @return bool TRUE if the key is in the storage, FALSE otherwise.
*/
public function exists($key)
{
return wincache_ucache_exists($this->persistentId . $key);
}
/**
* Adds a value to the shared memory storage.
*
* Sets a value to the storage if it doesn't exist, or fails if it does.
*
* @param string $key Name of key to associate the value with.
* @param mixed $value Value for the specified key.
* @param int $ttl Seconds to store the value. If set to 0 indicates no
* time limit.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function add($key, $value, $ttl = 0)
{
return wincache_ucache_add($this->persistentId . $key, $value, $ttl);
}
/**
* Sets a value in the shared memory storage.
*
* Adds a value to the storage if it doesn't exist, overwrites it otherwise.
*
* @param string $key Name of key to associate the value with.
* @param mixed $value Value for the specified key.
* @param int $ttl Seconds to store the value. If set to 0 indicates no
* time limit.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function set($key, $value, $ttl = 0)
{
return wincache_ucache_set($this->persistentId . $key, $value, $ttl);
}
/**
* Gets a value from the shared memory storage.
*
* Gets the current value, or throws an exception if it's not stored.
*
* @param string $key Name of key to get the value of.
*
* @return mixed The current value of the specified key.
*/
public function get($key)
{
$value = wincache_ucache_get($this->persistentId . $key, $success);
if (!$success) {
throw new SHM\InvalidArgumentException(
'Unable to fetch key. No such key, or key has expired.',
300
);
}
return $value;
}
/**
* Deletes a value from the shared memory storage.
*
* @param string $key Name of key to delete.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function delete($key)
{
return wincache_ucache_delete($this->persistentId . $key);
}
/**
* Increases a value from the shared memory storage.
*
* Increases a value from the shared memory storage. Unlike a plain
* set($key, get($key)+$step) combination, this function also implicitly
* performs locking.
*
* @param string $key Name of key to increase.
* @param int $step Value to increase the key by.
*
* @return int The new value.
*/
public function inc($key, $step = 1)
{
$newValue = wincache_ucache_inc(
$this->persistentId . $key,
(int) $step,
$success
);
if (!$success) {
throw new SHM\InvalidArgumentException(
'Unable to increase the value. Are you sure the value is int?',
301
);
}
return $newValue;
}
/**
* Decreases a value from the shared memory storage.
*
* Decreases a value from the shared memory storage. Unlike a plain
* set($key, get($key)-$step) combination, this function also implicitly
* performs locking.
*
* @param string $key Name of key to decrease.
* @param int $step Value to decrease the key by.
*
* @return int The new value.
*/
public function dec($key, $step = 1)
{
$newValue = wincache_ucache_dec(
$this->persistentId . $key,
(int) $step,
$success
);
if (!$success) {
throw new SHM\InvalidArgumentException(
'Unable to decrease the value. Are you sure the value is int?',
302
);
}
return $newValue;
}
/**
* Sets a new value if a key has a certain value.
*
* Sets a new value if a key has a certain value. This function only works
* when $old and $new are longs.
*
* @param string $key Key of the value to compare and set.
* @param int $old The value to compare the key against.
* @param int $new The value to set the key to.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function cas($key, $old, $new)
{
return wincache_ucache_cas($this->persistentId . $key, $old, $new);
}
/**
* Clears the persistent storage.
*
* Clears the persistent storage, i.e. removes all keys. Locks are left
* intact.
*
* @return void
*/
public function clear()
{
$info = wincache_ucache_info();
foreach ($info['ucache_entries'] as $entry) {
if (!$entry['is_session']
&& 0 === strpos($entry['key_name'], $this->persistentId)
) {
wincache_ucache_delete($entry['key_name']);
}
}
}
/**
* Retrieve an external iterator
*
* Returns an external iterator.
*
* @param string|null $filter A PCRE regular expression.
* Only matching keys will be iterated over.
* Setting this to NULL matches all keys of this instance.
* @param bool $keysOnly Whether to return only the keys,
* or return both the keys and values.
*
* @return ArrayObject An array with all matching keys as array keys,
* and values as array values. If $keysOnly is TRUE, the array keys are
* numeric, and the array values are key names.
*/
public function getIterator($filter = null, $keysOnly = false)
{
$info = wincache_ucache_info();
$result = array();
foreach ($info['ucache_entries'] as $entry) {
if (!$entry['is_session']
&& 0 === strpos($entry['key_name'], $this->persistentId)
) {
$localKey = strstr($entry['key_name'], $this->persistentId);
if (null === $filter || preg_match($filter, $localKey)) {
if ($keysOnly) {
$result[] = $localKey;
} else {
$result[$localKey] = apc_fetch($localKey);
}
}
}
}
return new ArrayObject($result);
}
}

View File

@ -0,0 +1,35 @@
<?php
/**
* Wrapper for shared memory and locking functionality across different extensions.
*
* Allows you to share data across requests as long as the PHP process is running. One of APC or WinCache is required to accomplish this, with other extensions being potentially pluggable as adapters.
*
* PHP version 5
*
* @category Caching
* @package PEAR2_Cache_SHM
* @author Vasil Rangelov <boen.robot@gmail.com>
* @copyright 2011 Vasil Rangelov
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version 0.2.0
* @link http://pear2.php.net/PEAR2_Cache_SHM
*/
/**
* The namespace declaration.
*/
namespace PEAR2\Cache\SHM;
/**
* Generic exception class of this package.
*
* @category Caching
* @package PEAR2_Cache_SHM
* @author Vasil Rangelov <boen.robot@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear2.php.net/PEAR2_Cache_SHM
*/
interface Exception
{
}

View File

@ -0,0 +1,36 @@
<?php
/**
* Wrapper for shared memory and locking functionality across different extensions.
*
* Allows you to share data across requests as long as the PHP process is running. One of APC or WinCache is required to accomplish this, with other extensions being potentially pluggable as adapters.
*
* PHP version 5
*
* @category Caching
* @package PEAR2_Cache_SHM
* @author Vasil Rangelov <boen.robot@gmail.com>
* @copyright 2011 Vasil Rangelov
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version 0.2.0
* @link http://pear2.php.net/PEAR2_Cache_SHM
*/
/**
* The namespace declaration.
*/
namespace PEAR2\Cache\SHM;
/**
* Exception thrown when there's something wrong with an argument.
*
* @category Caching
* @package PEAR2_Cache_SHM
* @author Vasil Rangelov <boen.robot@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear2.php.net/PEAR2_Cache_SHM
*/
class InvalidArgumentException extends \InvalidArgumentException
implements Exception
{
}

View File

@ -0,0 +1,40 @@
<?php
/**
* PHPMailer Exception class.
* PHP Version 5.5.
*
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
*
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
* @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 <phpmailer@synchromedia.co.uk>
*/
class Exception extends \Exception
{
/**
* Prettify error message output.
*
* @return string
*/
public function errorMessage()
{
return '<strong>' . htmlspecialchars($this->getMessage(), ENT_COMPAT | ENT_HTML401) . "</strong><br />\n";
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,254 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_auth();
$ui->assign('_title', Lang::T('My Account'));
$ui->assign('_system_menu', 'accounts');
$action = $routes['1'];
$user = User::_info();
$ui->assign('_user', $user);
switch ($action) {
case 'change-password':
run_hook('customer_view_change_password'); #HOOK
$ui->display('user-change-password.tpl');
break;
case 'change-password-post':
$password = _post('password');
run_hook('customer_change_password'); #HOOK
if ($password != '') {
$d = ORM::for_table('tbl_customers')->where('username', $user['username'])->find_one();
if ($d) {
$d_pass = $d['password'];
$npass = _post('npass');
$cnpass = _post('cnpass');
if (Password::_uverify($password, $d_pass) == true) {
if (!Validator::Length($npass, 15, 2)) {
r2(U . 'accounts/change-password', 'e', 'New Password must be 3 to 14 character');
}
if ($npass != $cnpass) {
r2(U . 'accounts/change-password', 'e', 'Both Password should be same');
}
$c = ORM::for_table('tbl_user_recharges')->where('username', $user['username'])->find_one();
if ($c) {
$p = ORM::for_table('tbl_plans')->where('id', $c['plan_id'])->find_one();
if ($p['is_radius']) {
if ($c['type'] == 'Hotspot' || ($c['type'] == 'PPPOE' && empty($d['pppoe_password']))) {
Radius::customerUpsert($d, $p);
}
} else {
$mikrotik = Mikrotik::info($c['routers']);
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
if ($c['type'] == 'Hotspot') {
Mikrotik::setHotspotUser($client, $c['username'], $npass);
Mikrotik::removeHotspotActiveUser($client, $user['username']);
} else if (empty($d['pppoe_password'])) {
// only change when pppoe_password empty
Mikrotik::setPpoeUser($client, $c['username'], $npass);
Mikrotik::removePpoeActive($client, $user['username']);
}
}
}
$d->password = $npass;
$d->save();
_msglog('s', Lang::T('Password changed successfully, Please login again'));
_log('[' . $user['username'] . ']: Password changed successfully', 'User', $user['id']);
r2(U . 'login');
} else {
r2(U . 'accounts/change-password', 'e', Lang::T('Incorrect Current Password'));
}
} else {
r2(U . 'accounts/change-password', 'e', Lang::T('Incorrect Current Password'));
}
} else {
r2(U . 'accounts/change-password', 'e', Lang::T('Incorrect Current Password'));
}
break;
case 'profile':
$d = ORM::for_table('tbl_customers')->find_one($user['id']);
if ($d) {
run_hook('customer_view_edit_profile'); #HOOK
$ui->assign('d', $d);
$ui->display('user-profile.tpl');
} else {
r2(U . 'home', 'e', Lang::T('Account Not Found'));
}
break;
case 'edit-profile-post':
$fullname = _post('fullname');
$address = _post('address');
$email = _post('email');
$phonenumber = _post('phonenumber');
run_hook('customer_edit_profile'); #HOOK
$msg = '';
if (Validator::Length($fullname, 31, 2) == false) {
$msg .= 'Full Name should be between 3 to 30 characters' . '<br>';
}
if (Validator::UnsignedNumber($phonenumber) == false) {
$msg .= 'Phone Number must be a number' . '<br>';
}
$d = ORM::for_table('tbl_customers')->find_one($user['id']);
if ($d) {
} else {
$msg .= Lang::T('Data Not Found') . '<br>';
}
if ($msg == '') {
$d->fullname = $fullname;
$d->address = $address;
$d->email = $email;
$d->phonenumber = $phonenumber;
$d->save();
_log('[' . $user['username'] . ']: ' . Lang::T('User Updated Successfully'), 'User', $user['id']);
r2(U . 'accounts/profile', 's', Lang::T('User Updated Successfully'));
} else {
r2(U . 'accounts/profile', 'e', $msg);
}
break;
case 'phone-update':
$d = ORM::for_table('tbl_customers')->find_one($user['id']);
if ($d) {
//run_hook('customer_view_edit_profile'); #HOOK
$ui->assign('d', $d);
$ui->display('user-phone-update.tpl');
} else {
r2(U . 'home', 'e', Lang::T('Account Not Found'));
}
break;
case 'phone-update-otp':
$phone = _post('phone');
$username = $user['username'];
$otpPath = $CACHE_PATH . '/sms/';
// Validate the phone number format
if (!preg_match('/^[0-9]{10,}$/', $phone)) {
r2(U . 'accounts/phone-update', 'e', Lang::T('Invalid phone number format'));
}
if (empty($config['sms_url'])) {
r2(U . 'accounts/phone-update', 'e', Lang::T('SMS server not Available, Please try again later'));
}
if (!empty($config['sms_url'])) {
if (!empty($phone)) {
$d = ORM::for_table('tbl_customers')->where('username', $username)->where('phonenumber', $phone)->find_one();
if ($d) {
r2(U . 'accounts/phone-update', 'e', Lang::T('You cannot use your current phone number'));
}
if (!file_exists($otpPath)) {
mkdir($otpPath);
touch($otpPath . 'index.html');
}
$otpFile = $otpPath . sha1($username . $db_password) . ".txt";
$phoneFile = $otpPath . sha1($username . $db_password) . "_phone.txt";
// expired 10 minutes
if (file_exists($otpFile) && time() - filemtime($otpFile) < 1200) {
r2(U . 'accounts/phone-update', 'e', Lang::T('Please wait ' . (1200 - (time() - filemtime($otpFile))) . ' seconds before sending another SMS'));
} else {
$otp = rand(100000, 999999);
file_put_contents($otpFile, $otp);
file_put_contents($phoneFile, $phone);
// send send OTP to user
if ($config['phone_otp_type'] === 'sms') {
Message::sendSMS($phone, $config['CompanyName'] . "\n Your Verification code is: $otp");
} elseif ($config['phone_otp_type'] === 'whatsapp') {
Message::sendWhatsapp($phone, $config['CompanyName'] . "\n Your Verification code is: $otp");
} elseif ($config['phone_otp_type'] === 'both') {
Message::sendSMS($phone, $config['CompanyName'] . "\n Your Verification code is: $otp");
Message::sendWhatsapp($phone, $config['CompanyName'] . "\n Your Verification code is: $otp");
}
//redirect after sending OTP
r2(U . 'accounts/phone-update', 'e', Lang::T('Verification code has been sent to your phone'));
}
}
}
break;
case 'phone-update-post':
$phone = _post('phone');
$otp_code = _post('otp');
$username = $user['username'];
$otpPath = $CACHE_PATH . '/sms/';
// Validate the phone number format
if (!preg_match('/^[0-9]{10,}$/', $phone)) {
r2(U . 'accounts/phone-update', 'e', Lang::T('Invalid phone number format'));
exit();
}
if (!empty($config['sms_url'])) {
$otpFile = $otpPath . sha1($username . $db_password) . ".txt";
$phoneFile = $otpPath . sha1($username . $db_password) . "_phone.txt";
// Check if OTP file exists
if (!file_exists($otpFile)) {
r2(U . 'accounts/phone-update', 'e', Lang::T('Please request OTP first'));
exit();
}
// expired 10 minutes
if (time() - filemtime($otpFile) > 1200) {
unlink($otpFile);
unlink($phoneFile);
r2(U . 'accounts/phone-update', 'e', Lang::T('Verification code expired'));
exit();
} else {
$code = file_get_contents($otpFile);
// Check if OTP code matches
if ($code != $otp_code) {
r2(U . 'accounts/phone-update', 'e', Lang::T('Wrong Verification code'));
exit();
}
// Check if the phone number matches the one that requested the OTP
$savedPhone = file_get_contents($phoneFile);
if ($savedPhone !== $phone) {
r2(U . 'accounts/phone-update', 'e', Lang::T('The phone number does not match the one that requested the OTP'));
exit();
}
// OTP verification successful, delete OTP and phone number files
unlink($otpFile);
unlink($phoneFile);
}
} else {
r2(U . 'accounts/phone-update', 'e', Lang::T('SMS server not available'));
exit();
}
// Update the phone number in the database
$d = ORM::for_table('tbl_customers')->where('username', $username)->find_one();
if ($d) {
$d->phonenumber = Lang::phoneFormat($phone);
$d->save();
}
r2(U . 'accounts/profile', 's', Lang::T('Phone number updated successfully'));
break;
default:
$ui->display('a404.tpl');
}

View File

@ -0,0 +1,57 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
if(Admin::getID()){
r2(U.'dashboard', "s", Lang::T("You are already logged in"));
}
if (isset($routes['1'])) {
$do = $routes['1'];
} else {
$do = 'login-display';
}
switch ($do) {
case 'post':
$username = _post('username');
$password = _post('password');
run_hook('admin_login'); #HOOK
if ($username != '' and $password != '') {
$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']);
if ($isApi) {
if ($token) {
showResult(true, Lang::T('Login Successful'), ['token' => "a.".$token]);
} else {
showResult(false, Lang::T('Invalid Username or Password'));
}
}
_alert(Lang::T('Login Successful'),'success', "dashboard");
} else {
_log($username . ' ' . Lang::T('Failed Login'), $d['user_type']);
_alert(Lang::T('Invalid Username or Password').".",'danger', "admin");
}
} else {
_alert(Lang::T('Invalid Username or Password')."..",'danger', "admin");
}
} else {
_alert(Lang::T('Invalid Username or Password')."...",'danger', "admin");
}
break;
default:
run_hook('view_login'); #HOOK
$ui->display('admin-login.tpl');
break;
}

View File

@ -0,0 +1,89 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
/**
* used for ajax
**/
_admin();
$ui->assign('_title', Lang::T('Network'));
$ui->assign('_system_menu', 'network');
$action = $routes['1'];
$ui->assign('_admin', $admin);
switch ($action) {
case 'pool':
$routers = _get('routers');
if(empty($routers)){
$d = ORM::for_table('tbl_pool')->find_many();
}else{
$d = ORM::for_table('tbl_pool')->where('routers', $routers)->find_many();
}
$ui->assign('routers', $routers);
$ui->assign('d', $d);
$ui->display('autoload-pool.tpl');
break;
case 'server':
$d = ORM::for_table('tbl_routers')->where('enabled', '1')->find_many();
$ui->assign('d', $d);
$ui->display('autoload-server.tpl');
break;
case 'plan':
$server = _post('server');
$jenis = _post('jenis');
if(in_array($admin['user_type'], array('SuperAdmin', 'Admin'))){
if($server=='radius'){
$d = ORM::for_table('tbl_plans')->where('is_radius', 1)->where('type', $jenis)->find_many();
}else{
$d = ORM::for_table('tbl_plans')->where('routers', $server)->where('type', $jenis)->find_many();
}
}else{
if($server=='radius'){
$d = ORM::for_table('tbl_plans')->where('is_radius', 1)->where('type', $jenis)->where('enabled', '1')->find_many();
}else{
$d = ORM::for_table('tbl_plans')->where('routers', $server)->where('type', $jenis)->where('enabled', '1')->find_many();
}
}
$ui->assign('d', $d);
$ui->display('autoload.tpl');
break;
case 'customer_is_active':
$d = ORM::for_table('tbl_user_recharges')->where('customer_id', $routes['2'])->findOne();
if ($d) {
if ($d['status'] == 'on') {
die('<span class="label label-success" title="Expired ' . Lang::dateAndTimeFormat($d['expiration'], $d['time']) . '">'.$d['namebp'].'</span>');
} else {
die('<span class="label label-danger" title="Expired ' . Lang::dateAndTimeFormat($d['expiration'], $d['time']) . '">'.$d['namebp'].'</span>');
}
} else {
die('<span class="label label-danger">&bull;</span>');
}
break;
case 'customer_select2':
$s = addslashes(_get('s'));
if (empty($s)) {
$c = ORM::for_table('tbl_customers')->limit(30)->find_many();
} else {
$c = ORM::for_table('tbl_customers')->where_raw("(`username` LIKE '%$s%' OR `fullname` LIKE '%$s%' OR `phonenumber` LIKE '%$s%' OR `email` LIKE '%$s%')")->limit(30)->find_many();
}
header('Content-Type: application/json');
foreach ($c as $cust) {
$json[] = [
'id' => $cust['id'],
'text' => $cust['username'] . ' - ' . $cust['fullname'] . ' - ' . $cust['email']
];
}
echo json_encode(['results' => $json]);
die();
default:
$ui->display('a404.tpl');
}

View File

@ -0,0 +1,37 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
/**
* used for ajax
**/
_auth();
$action = $routes['1'];
$user = User::_info();
switch ($action) {
case 'isLogin':
$bill = ORM::for_table('tbl_user_recharges')->where('id', $routes['2'])->where('username', $user['username'])->findOne();
if ($bill['type'] == 'Hotspot' && $bill['status'] == 'on') {
$m = Mikrotik::info($bill['routers']);
$client = Mikrotik::getClient($m['ip_address'], $m['username'], $m['password']);
if (Mikrotik::isUserLogin($client, $user['username'])) {
die('<a href="' . U . 'home&mikrotik=logout&id='.$bill['id'].'" onclick="return confirm(\''.Lang::T('Disconnect Internet?').'\')" class="btn btn-success btn-xs btn-block">'.Lang::T('You are Online, Logout?').'</a>');
} else {
if (!empty($_SESSION['nux-mac']) && !empty($_SESSION['nux-ip'])) {
die('<a href="' . U . 'home&mikrotik=login&id='.$bill['id'].'" onclick="return confirm(\''.Lang::T('Connect to Internet?').'\')" class="btn btn-danger btn-xs btn-block">'.Lang::T('Not Online, Login now?').'</a>');
}else{
die(Lang::T('Your account not connected to internet'));
}
}
} else {
die('--');
}
break;
default:
$ui->display('404.tpl');
}

View File

@ -0,0 +1,190 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_admin();
$ui->assign('_title', Lang::T('Bandwidth Plans'));
$ui->assign('_system_menu', 'services');
$action = $routes['1'];
$ui->assign('_admin', $admin);
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
r2(U . "dashboard", 'e', Lang::T('You do not have permission to access this page'));
}
switch ($action) {
case 'list':
$ui->assign('xfooter', '<script type="text/javascript" src="ui/lib/c/bandwidth.js"></script>');
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' . '<br>';
}
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') . '<br>';
}
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' . '<br>';
}
$id = _post('id');
$d = ORM::for_table('tbl_bandwidth')->find_one($id);
if ($d) {
} else {
$msg .= Lang::T('Data Not Found') . '<br>';
}
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') . '<br>';
}
}
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');
}

View File

@ -0,0 +1,22 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
$action = $routes['1'];
if (file_exists($PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR . $action . '.php')) {
include $PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR . $action . '.php';
if (function_exists($action . '_payment_notification')) {
run_hook('callback_payment_notification'); #HOOK
call_user_func($action . '_payment_notification');
die();
}
}
header('HTTP/1.1 404 Not Found');
echo 'Not Found';

View File

@ -0,0 +1,126 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_admin();
$ui->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', '<a href="' . U . 'settings/app#envato' . '">Envato Personal Access Token</a> 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');
}

View File

@ -0,0 +1,26 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_admin();
$ui->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');
}

View File

@ -0,0 +1,705 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_admin();
$ui->assign('_title', Lang::T('Customer'));
$ui->assign('_system_menu', 'customers');
$action = $routes['1'];
$ui->assign('_admin', $admin);
if (empty($action)) {
$action = 'list';
}
$leafletpickerHeader = <<<EOT
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css">
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' . '<br>';
}
if (Validator::Length($fullname, 36, 2) == false) {
$msg .= 'Full Name should be between 3 to 25 characters' . '<br>';
}
if (!Validator::Length($password, 36, 2)) {
$msg .= 'Password should be between 3 to 35 characters' . '<br>';
}
$d = ORM::for_table('tbl_customers')->where('username', $username)->find_one();
if ($d) {
$msg .= Lang::T('Account already axist') . '<br>';
}
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' . '<br>';
}
if (Validator::Length($fullname, 36, 1) == false) {
$msg .= 'Full Name should be between 2 to 25 characters' . '<br>';
}
if ($password != '') {
if (!Validator::Length($password, 36, 2)) {
$msg .= 'Password should be between 3 to 15 characters' . '<br>';
}
}
$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') . '<br>';
}
$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') . '<br>';
}
$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', '<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.11.3/css/jquery.dataTables.min.css">');
$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;
}

View File

@ -0,0 +1,204 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_admin();
$ui->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');

View File

@ -0,0 +1,13 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
if(Admin::getID()){
r2(U.'dashboard');
}if(User::getID()){
r2(U.'home');
}else{
r2(U.'login');
}

View File

@ -0,0 +1,356 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_admin();
$ui->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 = '
<div id="page-wrap">
<div id="address">
<h3>' . $config['CompanyName'] . '</h3>
' . $config['address'] . '<br>
' . Lang::T('Phone Number') . ': ' . $config['phone'] . '<br>
</div>
<div id="logo"><img id="image" src="' . $logo . '" alt="logo" /></div>
</div>
<div id="header">' . Lang::T('All Transactions at Date') . ': ' . date($config['date_format'], strtotime($mdate)) . '</div>
<table id="customers">
<tr>
<th>' . Lang::T('Username') . '</th>
<th>' . Lang::T('Plan Name') . '</th>
<th>' . Lang::T('Type') . '</th>
<th>' . Lang::T('Plan Price') . '</th>
<th>' . Lang::T('Created On') . '</th>
<th>' . Lang::T('Expires On') . '</th>
<th>' . Lang::T('Method') . '</th>
<th>' . Lang::T('Routers') . '</th>
</tr>';
$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 .= "<tr" . (($c = !$c) ? ' class="alt"' : ' class=""') . ">" . "
<td>$username</td>
<td>$plan_name</td>
<td>$type</td>
<td align='right'>$price</td>
<td>$recharged_on</td>
<td>$expiration $time </td>
<td>$method</td>
<td>$routers</td>
</tr>";
}
$html .= '</table>
<h4 class="text-uppercase text-bold">' . Lang::T('Total Income') . ':</h4>
<h3 class="sum">' . $config['currency_code'] . ' ' . number_format($xy, 2, $config['dec_point'], $config['thousands_sep']) . '</h3>';
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 = '<style>
#page-wrap { width: 100%; margin: 0 auto; }
#header { text-align: center; position: relative; color: black; font: bold 15px Helvetica, Sans-Serif; margin-top: 10px; margin-bottom: 10px;}
#address { width: 300px; float: left; }
#logo { text-align: right; float: right; position: relative; margin-top: 15px; border: 5px solid #fff; overflow: hidden; }
#customers
{
font-family: Helvetica, sans-serif;
width:100%;
border-collapse:collapse;
}
#customers td, #customers th
{
font-size:0.8em;
border:1px solid #98bf21;
padding:3px 5px 2px 5px;
}
#customers th
{
font-size:0.8em;
text-align:left;
padding-top:5px;
padding-bottom:4px;
background-color:#A7C942;
color:#fff;
}
#customers tr.alt td
{
color:#000;
background-color:#EAF2D3;
}
</style>';
$nhtml = <<<EOF
$style
$html
EOF;
$mpdf->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 = '
<div id="page-wrap">
<div id="address">
<h3>' . $config['CompanyName'] . '</h3>
' . $config['address'] . '<br>
' . Lang::T('Phone Number') . ': ' . $config['phone'] . '<br>
</div>
<div id="logo"><img id="image" src="' . $logo . '" alt="logo" /></div>
</div>
<div id="header">' . Lang::T('All Transactions at Date') . ': ' . date($config['date_format'], strtotime($fdate)) . ' - ' . date($config['date_format'], strtotime($tdate)) . '</div>
<table id="customers">
<tr>
<th>' . Lang::T('Username') . '</th>
<th>' . Lang::T('Plan Name') . '</th>
<th>' . Lang::T('Type') . '</th>
<th>' . Lang::T('Plan Price') . '</th>
<th>' . Lang::T('Created On') . '</th>
<th>' . Lang::T('Expires On') . '</th>
<th>' . Lang::T('Method') . '</th>
<th>' . Lang::T('Routers') . '</th>
</tr>';
$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 .= "<tr" . (($c = !$c) ? ' class="alt"' : ' class=""') . ">" . "
<td>$username</td>
<td>$plan_name</td>
<td>$type</td>
<td align='right'>$price</td>
<td>$recharged_on </td>
<td>$expiration $time </td>
<td>$method</td>
<td>$routers</td>
</tr>";
}
$html .= '</table>
<h4 class="text-uppercase text-bold">' . Lang::T('Total Income') . ':</h4>
<h3 class="sum">' . $config['currency_code'] . ' ' . number_format($xy, 2, $config['dec_point'], $config['thousands_sep']) . '</h3>';
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 = '<style>
#page-wrap { width: 100%; margin: 0 auto; }
#header { text-align: center; position: relative; color: black; font: bold 15px Helvetica, Sans-Serif; margin-top: 10px; margin-bottom: 10px;}
#address { width: 300px; float: left; }
#logo { text-align: right; float: right; position: relative; margin-top: 15px; border: 5px solid #fff; overflow: hidden; }
#customers
{
font-family: Helvetica, sans-serif;
width:100%;
border-collapse:collapse;
}
#customers td, #customers th
{
font-size:0.8em;
border:1px solid #98bf21;
padding:3px 5px 2px 5px;
}
#customers th
{
font-size:0.8em;
text-align:left;
padding-top:5px;
padding-bottom:4px;
background-color:#A7C942;
color:#fff;
}
#customers tr.alt td
{
color:#000;
background-color:#EAF2D3;
}
</style>';
$nhtml = <<<EOF
$style
$html
EOF;
$mpdf->WriteHTML($nhtml);
$mpdf->Output(date('Ymd_His') . '.pdf', 'D');
} else {
echo 'No Data';
}
break;
default:
$ui->display('a404.tpl');
}

252
system/controllers/home.php Normal file
View File

@ -0,0 +1,252 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_auth();
$ui->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');

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>

View File

@ -0,0 +1,195 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
$maintenance_mode = $config['maintenance_mode'];
if ($maintenance_mode == true) {
displayMaintenanceMessage();
}
if (Admin::getID()) {
r2(U . 'dashboard');
}
if (User::getID()) {
r2(U . 'home');
}
if (isset($routes['1'])) {
$do = $routes['1'];
} else {
$do = 'login-display';
}
switch ($do) {
case 'post':
$username = _post('username');
$password = _post('password');
run_hook('customer_login'); #HOOK
if ($username != '' and $password != '') {
$d = ORM::for_table('tbl_customers')->where('username', $username)->find_one();
if ($d) {
$d_pass = $d['password'];
if ($d['status'] == 'Banned') {
echo '<div class="alert alert-danger">' . Lang::T('This account status') . ': ' . Lang::T($d['status']) . '</div>';
}
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 '<div class="alert alert-success">' . Lang::T('Login Successful') . '</div>';
r2(U . 'home');
} else {
echo '<div class="alert alert-danger">' . Lang::T('Invalid Username or Password') . '</div>';
_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 '<div class="alert alert-success">' . Lang::T('Login Successful') . '</div>';
r2(U . 'dashboard');
} else {
echo '<div class="alert alert-danger">' . Lang::T('Invalid Username or Password') . '</div>';
_log($username . ' ' . Lang::T('Failed Login'), $d['user_type']);
r2(U . 'login');
}
} else {
echo '<div class="alert alert-danger">' . Lang::T('Invalid Username or Password') . '</div>';
r2(U . 'login');
}
}
} else {
echo '<div class="alert alert-danger">' . Lang::T('Invalid Username or Password') . '</div>';
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;
}

View File

@ -0,0 +1,12 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
run_hook('customer_logout'); #HOOK
if (session_status() == PHP_SESSION_NONE) session_start();
Admin::removeCookie();
User::removeCookie();
session_destroy();
_alert(Lang::T('Logout Successful'),'warning', "login");

125
system/controllers/logs.php Normal file
View File

@ -0,0 +1,125 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_admin();
$ui->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', '');
}

View File

@ -0,0 +1,54 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_admin();
$ui->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', '<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css">');
$ui->assign('_title', Lang::T('Customer Geo Location Information'));
$ui->assign('xfooter', '<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>');
$ui->display('customers-map.tpl');
break;
default:
r2(U . 'map/customer', 'e', 'action not defined');
break;
}

View File

@ -0,0 +1,242 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_admin();
$ui->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 = <<<EOT
<script>
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';
}
}
}
});
});
</script>
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');
}

View File

@ -0,0 +1,22 @@
<?php
/**
* PHP Messaging System
**/
_admin();
$ui->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');
?>

View File

@ -0,0 +1,130 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_admin();
$ui->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');
?>

View File

@ -0,0 +1,347 @@
<?php
// Include necessary files and functions here
// ...
_admin();
$ui->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();
}
}
}
}
?>

View File

@ -0,0 +1,493 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_auth();
$action = $routes['1'];
$user = User::_info();
$ui->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.") . " [ <b>$active[namebp]</b> ]");
}
$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', '');
}

View File

@ -0,0 +1,21 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_auth();
$ui->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');

View File

@ -0,0 +1,71 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_admin();
$ui->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(["<div","</div>"],"",$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(["<div","</div>"],"",$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, <i>chmod 664 pages/*.html<i>"));
}
}else
$ui->display('a404.tpl');
}

View File

@ -0,0 +1,86 @@
<?php
function stkQuery()
{
header('Content-Type: application/json');
$rawData = file_get_contents('php://input');
$postData = json_decode($rawData, true);
// Check if CheckoutRequestID is set
if (!isset($postData['CheckoutRequestID'])) {
echo json_encode(['status' => 'error', 'code' => 400, 'message' => 'missing CheckoutRequestID fields']);
return;
}
$CheckoutRequestID = $postData['CheckoutRequestID'];
$consumerKey = '3AmVP1WFDQn7GrDH8GcSSKxcAvnJdZGC'; // Fill with your app Consumer Key
$consumerSecret = '71Lybl6jUtxM0F35'; // Fill with your app Secret
$headers = ['Content-Type:application/json; charset=utf8'];
$access_token_url = 'https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials';
$curl = curl_init($access_token_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_HEADER, FALSE);
curl_setopt($curl, CURLOPT_USERPWD, $consumerKey.':'.$consumerSecret);
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$result = json_decode($result);
$access_token = $result->access_token;
date_default_timezone_set('Africa/Nairobi');
$query_url = 'https://api.safaricom.co.ke/mpesa/stkpushquery/v1/query';
$BusinessShortCode = '4122323';
$Passkey = 'aaebecea73082fa56af852606106b1316d5b4dfa2f12d0088800b0b88e4bb6e3';
$Timestamp = date('YmdHis');
// ENCRYPT DATA TO GET PASSWORD
$Password = base64_encode($BusinessShortCode . $Passkey . $Timestamp);
// THIS IS THE UNIQUE ID THAT WAS GENERATED WHEN STK REQUEST INITIATED SUCCESSFULLY
$queryheader = ['Content-Type:application/json', 'Authorization:Bearer ' . $access_token];
// Initiating the transaction
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $query_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $queryheader); // Setting custom header
$curl_post_data = array(
'BusinessShortCode' => $BusinessShortCode,
'Password' => $Password,
'Timestamp' => $Timestamp,
'CheckoutRequestID' => $CheckoutRequestID
);
$data_string = json_encode($curl_post_data);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string);
$curl_response = curl_exec($curl);
$data_to = json_decode($curl_response, true);
// Handle response
if (isset($data_to['ResultCode'])) {
$ResultCode = $data_to['ResultCode'];
if ($ResultCode == '1037') {
$message = "1037 Timeout in completing transaction";
} elseif ($ResultCode == '1032') {
$message = "1032 Transaction has been cancelled by user";
} elseif ($ResultCode == '1') {
$message = "1 The balance is insufficient for the transaction";
} elseif ($ResultCode == '0') {
$message = "0 The transaction is successful";
} else {
$message = "Unknown Result Code: $ResultCode";
}
} else {
$message = "Error in the response received from the M-Pesa API";
}
// Sending the response back
echo json_encode([
'message' => $message,
'result' => $data_to
]);
}
?>

BIN
system/plugin/.DS_Store vendored Normal file

Binary file not shown.

2
system/plugin/.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

View File

@ -0,0 +1,420 @@
<?php
function Alloworigins()
{
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit;
}
$requestUri = $_SERVER['REQUEST_URI'];
$queryString = parse_url($requestUri, PHP_URL_QUERY);
$type = null;
if ($queryString) {
parse_str($queryString, $queryParameters);
if (isset($queryParameters['type'])) {
$type = $queryParameters['type'];
if ($type === "grant") {
CreateHostspotUser();
exit;
} elseif ($type === "verify") {
VerifyHotspot();
exit;
} elseif ($type === "reconnect") {
ReconnectUser();
exit;
} elseif ($type === "voucher") {
ReconnectVoucher();
exit;
} else {
echo json_encode(['status' => 'error', 'code' => 400, 'message' => 'The parameter is not present in the URL.']);
}
}
}
}
function ReconnectVoucher() {
header('Content-Type: application/json');
$rawData = file_get_contents('php://input');
$postData = json_decode($rawData, true);
if (!isset($postData['voucher_code'], $postData['account_id'])) {
echo json_encode(['status' => 'error', 'code' => 400, 'message' => 'Missing accountId or voucherCode field']);
return;
}
$accountId = $postData['account_id'];
$voucherCode = $postData['voucher_code'];
$voucher = ORM::for_table('tbl_voucher')
->where('code', $voucherCode)
->where('status', '0')
->find_one();
if (!$voucher) {
echo json_encode([
'status' => 'error',
'Resultcode' => '1',
'voucher' => 'Not Found',
'message' => 'Invalid Voucher code'
]);
exit();
}
if ($voucher['status'] == '1') {
echo json_encode([
'status' => 'error',
'Resultcode' => '3',
'voucher' => 'Used',
'message' => 'Voucher code is already used'
]);
exit();
}
$planId = $voucher['id_plan'];
$routername = $voucher['routers'];
$router = ORM::for_table('tbl_routers')
->where('name', $routername)
->find_one();
if (!$router) {
echo json_encode([
'status' => 'error',
'message' => 'Router not found'
]);
exit();
}
$routerId = $router['id'];
if (!ORM::for_table('tbl_plans')->where('id', $planId)->count() || !ORM::for_table('tbl_routers')->where('id', $routerId)->count()) {
echo json_encode([
'status' => 'error',
'message' => 'Unable to process your request, please refresh the page'
]);
exit();
}
$user = ORM::for_table('tbl_customers')->where('username', $accountId)->find_one();
if (!$user) {
// Create a new user if not exists
$user = ORM::for_table('tbl_customers')->create();
$user->username = $accountId;
$user->password = '1234';
$user->fullname = $accountId;
$user->email = $accountId . '@gmail.com';
$user->phonenumber = $accountId;
$user->pppoe_password = '1234';
$user->address = '';
$user->service_type = 'Hotspot';
}
$user->router_id = $routerId;
$user->save();
// Update the voucher with the user ID
$voucher->user = $user->id;
$voucher->status = '1'; // Mark as used
$voucher->save();
if (Package::rechargeUser($user->id, $routername, $planId, 'Voucher', $voucherCode)) {
echo json_encode([
'status' => 'success',
'Resultcode' => '2',
'voucher' => 'activated',
'message' => 'Voucher code has been activated',
'username' => $user->username
]);
} else {
echo json_encode([
'status' => 'error',
'message' => 'Failed to recharge user package'
]);
}
}
function ReconnectUser()
{
header('Content-Type: application/json');
$rawData = file_get_contents('php://input');
$postData = json_decode($rawData, true);
if (!$postData) {
echo json_encode(['status' => 'error', 'code' => 400, 'message' => 'Invalid JSON DATA']);
exit();
}
if (!isset($postData['mpesa_code'])) {
echo json_encode(['status' => 'error', 'code' => 400, 'message' => 'missing required fields']);
exit();
}
$mpesaCode = $postData['mpesa_code'];
// Query the payment gateway table
$payment = ORM::for_table('tbl_payment_gateway')
->where('gateway_trx_id', $mpesaCode)
->find_one();
if (!$payment) {
$data = array(['status' => 'error', "Resultcode" => "1", 'user' => "Not Found", 'message' => 'Invalid Mpesa Transaction code']);
echo json_encode($data);
exit();
}
$username = $payment['username'];
// Query the user recharges table
$recharge = ORM::for_table('tbl_user_recharges')
->where('username', $username)
->order_by_desc('id')
->find_one();
if ($recharge) {
$status = $recharge['status'];
if ($status == 'on') {
$data = array(
"Resultcode" => "2",
"user" => "Active User",
"username" => $username,
"tyhK" => "1234", // Replace with the actual password or token
"Message" => "We have verified your transaction under the Mpesa Transaction $mpesaCode. Please don't leave this page as we are redirecting you.",
"Status" => "success"
);
} elseif ($status == "off") {
$data = array(
"Resultcode" => "3",
"user" => "Expired User",
"Message" => "We have verified your transaction under the Mpesa Transaction $mpesaCode. But your Package is already Expired. Please buy a new Package.",
"Status" => "danger"
);
} else {
$data = array(
"Message" => "Unexpected status value",
"Status" => "error"
);
}
} else {
$data = array(
"Message" => "Recharge information not found",
"Status" => "error"
);
}
echo json_encode($data);
exit();
}
function VerifyHotspot() {
header('Content-Type: application/json');
$rawData = file_get_contents('php://input');
$postData = json_decode($rawData, true);
if (!$postData) {
echo json_encode(['Resultcode' => 'error', 'Message' => 'Invalid JSON data']);
return;
}
if (!isset($postData['account_id'])) {
echo json_encode(['Resultcode' => 'error', 'Message' => 'Missing required fields']);
return;
}
$accountId = $postData['account_id'];
$user = ORM::for_table('tbl_payment_gateway')
->where('username', $accountId)
->order_by_desc('id')
->find_one();
if ($user) {
$status = $user->status;
$mpesacode = $user->gateway_trx_id;
$res = $user->pg_paid_response;
if ($status == 2 && !empty($mpesacode)) {
echo json_encode([
"Resultcode" => "3",
"Message" => "We have received your transaction under the Mpesa Transaction $mpesacode. Please do not leave this page as we are redirecting you.",
"Status" => "success"
]);
} elseif ($res == "Not enough balance") {
echo json_encode([
"Resultcode" => "2",
"Message" => "Insufficient Balance for the transaction",
"Status" => "danger"
]);
} elseif ($res == "Wrong Mpesa pin") {
echo json_encode([
"Resultcode" => "2",
"Message" => "You entered Wrong Mpesa pin, please resubmit",
"Status" => "danger"
]);
} elseif ($status == 4) {
echo json_encode([
"Resultcode" => "2",
"Message" => "You cancelled the transaction, you can enter phone number again to activate",
"Status" => "info"
]);
} elseif (empty($mpesacode)) {
echo json_encode([
"Resultcode" => "1",
"Message" => "A payment pop up has been sent to your phone. Please enter PIN to continue (Please do not leave or reload the page until redirected).",
"Status" => "primary"
]);
}
} else {
echo json_encode([
"Resultcode" => "error",
"Message" => "User not found"
]);
}
}
function CreateHostspotUser()
{
header('Content-Type: application/json');
$rawData = file_get_contents('php://input');
$postData = json_decode($rawData, true);
if (!$postData) {
echo json_encode(['status' => 'error', 'code' => 400, 'message' => 'Invalid JSON DATA' . $postData . ' n tes ']);
} else {
$phone = $postData['phone_number'];
$planId = $postData['plan_id'];
$routerId = $postData['router_id'];
$accountId = $postData['account_id'];
if (!isset( $postData['phone_number'], $postData['plan_id'], $postData['router_id'], $postData['account_id'])) {
echo json_encode(['status' => 'error', 'code' => 400, 'message' => 'missing required fields' . $postData, 'phone' => $phone, 'planId' => $planId, 'routerId' => $routerId, 'accountId' => $accountId]);
} else {
$phone = (substr($phone, 0, 1) == '+') ? str_replace('+', '', $phone) : $phone;
$phone = (substr($phone, 0, 1) == '0') ? preg_replace('/^0/', '254', $phone) : $phone;
$phone = (substr($phone, 0, 1) == '7') ? preg_replace('/^7/', '2547', $phone) : $phone; //cater for phone number prefix 2547XXXX
$phone = (substr($phone, 0, 1) == '1') ? preg_replace('/^1/', '2541', $phone) : $phone; //cater for phone number prefix 2541XXXX
$phone = (substr($phone, 0, 1) == '0') ? preg_replace('/^01/', '2541', $phone) : $phone;
$phone = (substr($phone, 0, 1) == '0') ? preg_replace('/^07/', '2547', $phone) : $phone;
if (strlen($phone) !== 12) {
echo json_encode(['status' => 'error', 'code' => 1, 'message' => 'Phone number ' . $phone . ' is invalid. Please confirm.']);
}
if (strlen($phone) == 12 && !empty($planId) && !empty($routerId)) {
$PlanExist = ORM::for_table('tbl_plans')->where('id', $planId)->count() > 0;
$RouterExist = ORM::for_table('tbl_routers')->where('id', $routerId)->count() > 0;
if (!$PlanExist || !$RouterExist)
echo json_encode(["status" => "error", "message" => "Unable to process your request, please refresh the page."]);
}
$Userexist = ORM::for_table('tbl_customers')->where('username', $accountId)->find_one();
if ($Userexist) {
$Userexist->router_id = $routerId;
$Userexist->save();
InitiateStkpush($phone, $planId, $accountId, $routerId);
} else {
try {
$defpass = '1234';
$defaddr = 'netXtreme';
$defmail = $phone . '@gmail.com';
$createUser = ORM::for_table('tbl_customers')->create();
$createUser->username = $accountId;
$createUser->password = $defpass;
$createUser->fullname = $phone;
$createUser->router_id = $routerId;
$createUser->phonenumber = $phone;
$createUser->pppoe_password = $defpass;
$createUser->address = $defaddr;
$createUser->email = $defmail;
$createUser->service_type = 'Hotspot';
if ($createUser->save()) {
InitiateStkpush($phone, $planId, $accountId, $routerId);
} else {
echo json_encode(["status" => "error", "message" => "There was a system error when registering user, please contact support."]);
}
} catch (Exception $e) {
echo json_encode(["status" => "error", "message" => "Error creating user: " . $e->getMessage()]);
}
}
}
}
}
function InitiateStkpush($phone, $planId, $accountId, $routerId)
{
$gateway = ORM::for_table('tbl_appconfig')
->where('setting', 'payment_gateway')
->find_one();
$gateway = ($gateway) ? $gateway->value : null;
if ($gateway == "MpesatillStk") {
$url = U . "plugin/initiatetillstk";
} elseif ($gateway == "BankStkPush") {
$url = U . "plugin/initiatebankstk";
} elseif ($gateway == "mpesa") {
$url = U . "plugin/initiatempesa";
} else {
$url = null; // or handle the default case appropriately
}
$Planname = ORM::for_table('tbl_plans')
->where('id', $planId)
->order_by_desc('id')
->find_one();
$Findrouter = ORM::for_table('tbl_routers')
->where('id', $routerId)
->order_by_desc('id')
->find_one();
$rname = $Findrouter->name;
$price = $Planname->price;
$Planname = $Planname->name_plan;
$Checkorders = ORM::for_table('tbl_payment_gateway')
->where('username', $accountId)
->where('status', 1)
->order_by_desc('id')
->find_many();
if ($Checkorders) {
foreach ($Checkorders as $Dorder) {
$Dorder->delete();
}
}
try {
$d = ORM::for_table('tbl_payment_gateway')->create();
$d->username = $accountId;
$d->gateway = $gateway;
$d->plan_id = $planId;
$d->plan_name = $Planname;
$d->routers_id = $routerId;
$d->routers = $rname;
$d->price = $price;
$d->payment_method = $gateway;
$d->payment_channel = $gateway;
$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 = $url;
$d->status = 1;
$d->save();
} catch (Exception $e) {
error_log('Error saving payment gateway record: ' . $e->getMessage());
throw $e;
}
SendSTKcred($phone, $url, $accountId);
}
function SendSTKcred($phone, $url, $accountId )
{
$link = $url;
$fields = array(
'username' => $accountId,
'phone' => $phone,
'channel' => 'Yes',
);
$postvars = http_build_query($fields);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $link);
curl_setopt($ch, CURLOPT_POST, count($fields));
curl_setopt($ch, CURLOPT_POSTFIELDS, $postvars);
$result = curl_exec($ch);
}
Alloworigins();

636
system/plugin/c2b.php Normal file
View File

@ -0,0 +1,636 @@
<?php
register_menu("Mpesa C2B Settings", true, "c2b_settings", 'SETTINGS', '', '', "");
register_menu("Mpesa Transactions", true, "c2b_overview", 'AFTER_MESSAGE', 'fa fa-paypal', '', "");
try {
$db = ORM::get_db();
$tableCheckQuery = "CREATE TABLE IF NOT EXISTS tbl_mpesa_transactions (
id INT AUTO_INCREMENT PRIMARY KEY,
TransID VARCHAR(255) NOT NULL,
TransactionType VARCHAR(255) NOT NULL,
TransTime VARCHAR(255) NOT NULL,
TransAmount DECIMAL(10, 2) NOT NULL,
BusinessShortCode VARCHAR(255) NOT NULL,
BillRefNumber VARCHAR(255) NOT NULL,
OrgAccountBalance DECIMAL(10, 2) NOT NULL,
MSISDN VARCHAR(255) NOT NULL,
FirstName VARCHAR(255) NOT NULL,
CustomerID VARCHAR(255) NOT NULL,
PackageName VARCHAR(255) NOT NULL,
PackagePrice VARCHAR(255) NOT NULL,
TransactionStatus VARCHAR(255) NOT NULL,
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)";
$db->exec($tableCheckQuery);
} catch (PDOException $e) {
echo "Error creating the table: " . $e->getMessage();
} catch (Exception $e) {
echo "An unexpected error occurred: " . $e->getMessage();
}
function c2b_overview()
{
global $ui, $config;
_admin();
$ui->assign('_title', 'Mpesa C2B Payment Overview');
$ui->assign('_system_menu', '');
$admin = Admin::_info();
$ui->assign('_admin', $admin);
// Check user type for access
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Sales'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
exit;
}
$query = ORM::for_table('tbl_mpesa_transactions')->order_by_desc('TransTime');
$payments = $query->find_many();
if (
(empty($config['mpesa_c2b_consumer_key']) || empty($config['mpesa_c2b_consumer_secret']) || empty($config['mpesa_c2b_business_code']))
&& !$config['c2b_registered']
) {
$ui->assign('message', '<em>' . Lang::T("You haven't registered your validation and verification URLs. Please register URLs by clicking ") . ' <a href="' . APP_URL . '/index.php?_route=plugin/c2b_settings"> Register URL </a>' . '</em>');
}
$ui->assign('payments', $payments);
$ui->assign('xheader', '<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.11.5/css/jquery.dataTables.css">');
$ui->display('c2b_overview.tpl');
}
function c2b_settings()
{
global $ui, $admin, $config;
$ui->assign('_title', Lang::T("Mpesa C2B Settings [Offline Payment]"));
$ui->assign('_system_menu', 'settings');
$admin = Admin::_info();
$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");
}
if (_post('save') == 'save') {
$mpesa_c2b_consumer_key = _post('mpesa_c2b_consumer_key');
$mpesa_c2b_consumer_secret = _post('mpesa_c2b_consumer_secret');
$mpesa_c2b_business_code = _post('mpesa_c2b_business_code');
$mpesa_c2b_env = _post('mpesa_c2b_env');
$mpesa_c2b_api = _post('mpesa_c2b_api');
$mpesa_c2b_low_fee = _post('mpesa_c2b_low_fee') ? 1 : 0;
$mpesa_c2b_bill_ref = _post('mpesa_c2b_bill_ref');
$errors = [];
if (empty($mpesa_c2b_consumer_key)) {
$errors[] = Lang::T('Mpesa C2B Consumer Key is required.');
}
if (empty($mpesa_c2b_consumer_secret)) {
$errors[] = Lang::T('Mpesa C2B Consumer Secret is required.');
}
if (empty($mpesa_c2b_business_code)) {
$errors[] = Lang::T('Mpesa C2B Business Code is required.');
}
if (empty($mpesa_c2b_env)) {
$errors[] = Lang::T('Mpesa C2B Environment is required.');
}
if (empty($mpesa_c2b_api)) {
$errors[] = Lang::T('Mpesa C2B API URL is required.');
}
if (empty($mpesa_c2b_bill_ref)) {
$errors[] = Lang::T('Mpesa Bill Ref Number Type is required.');
}
if (!empty($errors)) {
$ui->assign('message', implode('<br>', $errors));
$ui->display('c2b_settings.tpl');
return;
}
$settings = [
'mpesa_c2b_consumer_key' => $mpesa_c2b_consumer_key,
'mpesa_c2b_consumer_secret' => $mpesa_c2b_consumer_secret,
'mpesa_c2b_business_code' => $mpesa_c2b_business_code,
'mpesa_c2b_env' => $mpesa_c2b_env,
'mpesa_c2b_api' => $mpesa_c2b_api,
'mpesa_c2b_low_fee' => $mpesa_c2b_low_fee,
'mpesa_c2b_bill_ref' => $mpesa_c2b_bill_ref,
];
// Update or insert settings in the database
foreach ($settings as $key => $value) {
$d = ORM::for_table('tbl_appconfig')->where('setting', $key)->find_one();
if ($d) {
$d->value = $value;
$d->save();
} else {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = $key;
$d->value = $value;
$d->save();
}
}
if ($admin) {
_log('[' . $admin['username'] . ']: ' . Lang::T('Settings Saved Successfully'));
}
r2(U . 'plugin/c2b_settings', 's', Lang::T('Settings Saved Successfully'));
}
if (!empty($config['mpesa_c2b_consumer_key'] && $config['mpesa_c2b_consumer_secret'] && $config['mpesa_c2b_business_code']) && !$config['c2b_registered']) {
$ui->assign('message', '<em>' . Lang::T("You haven't registered your validation and verification URLs, Please register URLs by clicking ") . ' <a href="' . APP_URL . '/index.php?_route=plugin/c2b_settings"> Register URL </a>' . '</em>');
}
$ui->assign('_c', $config);
$ui->assign('companyName', $config['CompanyName']);
$ui->display('c2b_settings.tpl');
}
function c2b_generateAccessToken()
{
global $config;
$mpesa_c2b_env = $config['mpesa_c2b_env'] ?? null;
$mpesa_c2b_consumer_key = $config['mpesa_c2b_consumer_key'] ?? null;
$mpesa_c2b_consumer_secret = $config['mpesa_c2b_consumer_secret'] ?? null;
$access_token_url = match ($mpesa_c2b_env) {
"live" => 'https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials',
"sandbox" => 'https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials',
};
$headers = ['Content-Type:application/json; charset=utf8'];
$curl = curl_init($access_token_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_HEADER, FALSE);
curl_setopt($curl, CURLOPT_USERPWD, "$mpesa_c2b_consumer_key:$mpesa_c2b_consumer_secret");
$result = curl_exec($curl);
$result = json_decode($result);
if (isset($result->access_token)) {
return $result->access_token;
} else {
return null;
}
}
function c2b_registerUrl()
{
global $config;
if (
(empty($config['mpesa_c2b_consumer_key']) || empty($config['mpesa_c2b_consumer_secret']) || empty($config['mpesa_c2b_business_code']))
&& !$config['c2b_registered']
) {
r2(U . 'plugin/c2b_settings', 'e', Lang::T('Please setup your M-Pesa C2B settings first'));
exit;
}
$access_token = c2b_generateAccessToken();
switch ($access_token) {
case null:
r2(U . 'plugin/c2b_settings', 'e', Lang::T('Failed to generate access token'));
exit;
default:
$BusinessShortCode = $config['mpesa_c2b_business_code'] ?? null;
$mpesa_c2b_env = $config['mpesa_c2b_env'] ?? null;
$confirmationUrl = U . 'plugin/c2b_confirmation';
$validationUrl = U . 'plugin/c2b_validation';
$mpesa_c2b_api = $config['mpesa_c2b_api'] ?? null;
$registerurl = match ($mpesa_c2b_env) {
"live" => match ($mpesa_c2b_api) {
"v1" => 'https://api.safaricom.co.ke/mpesa/c2b/v1/registerurl',
"v2" => 'https://api.safaricom.co.ke/mpesa/c2b/v2/registerurl',
},
"sandbox" => 'https://sandbox.safaricom.co.ke/mpesa/c2b/v1/registerurl',
};
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $registerurl);
curl_setopt($curl, CURLOPT_HTTPHEADER, [
'Content-Type:application/json',
"Authorization:Bearer $access_token"
]);
$data = [
'ShortCode' => $BusinessShortCode,
'ResponseType' => 'Completed',
'ConfirmationURL' => $confirmationUrl,
'ValidationURL' => $validationUrl
];
$data_string = json_encode($data);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string);
$curl_response = curl_exec($curl);
$data = json_decode($curl_response);
if (isset($data->ResponseCode) && $data->ResponseCode == 0) {
try {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = 'c2b_registered';
$d->value = '1';
$d->save();
} catch (Exception $e) {
_log("Failed to save M-Pesa C2B URL to database.\n\n" . $e->getMessage());
sendTelegram("Failed to save M-Pesa C2B URL to database.\n\n" . $e->getMessage());
}
sendTelegram("M-Pesa C2B URL registered successfully");
r2(U . 'plugin/c2b_settings', 's', "M-Pesa C2B URL registered successfully");
} else {
$errorMessage = $data->errorMessage;
sendTelegram("Resister M-Pesa C2B URL Failed\n\n" . json_encode($curl_response, JSON_PRETTY_PRINT));
r2(U . 'plugin/c2b_settings', 'e', "Failed to register M-Pesa C2B URL Error $errorMessage");
}
break;
}
}
function c2b_webhook_log($data)
{
$logFile = 'pages/mpesa-webhook.html';
$logEntry = date('Y-m-d H:i:s') . "<pre>" . htmlspecialchars($data, ENT_QUOTES, 'UTF-8') . "</pre>\n";
if (file_put_contents($logFile, $logEntry, FILE_APPEND) === false) {
sendTelegram("Failed to write to log file: $logFile");
}
}
function c2b_isValidSafaricomIP($ip)
{
$config = c2b_config();
$safaricomIPs = [
'196.201.214.0/24',
'196.201.213.0/24',
'196.201.212.0/24',
'172.69.79.0/24',
'172.69.0.0/24',
'0.0.0.0/0',
];
if ($config['mpesa_c2b_env'] == 'sandbox') {
$safaricomIPs[] = '::1';
}
foreach ($safaricomIPs as $range) {
if (c2b_ipInRange($ip, $range)) {
return true;
}
}
return false;
}
function c2b_ipInRange($ip, $range)
{
list($subnet, $bits) = explode('/', $range);
$ip = ip2long($ip);
$subnet = ip2long($subnet);
$mask = -1 << (32 - $bits);
$subnet &= $mask;
return ($ip & $mask) == $subnet;
}
function c2b_confirmation()
{
global $config;
header("Content-Type: application/json");
$clientIP = $_SERVER['REMOTE_ADDR'];
if (!c2b_isValidSafaricomIP($clientIP)) {
c2b_logAndNotify("Unauthorized request from IP: {$clientIP}");
http_response_code(403);
echo json_encode(["ResultCode" => 1, "ResultDesc" => "Unauthorized"]);
return;
}
$mpesaResponse = file_get_contents('php://input');
if ($mpesaResponse === false) {
c2b_logAndNotify("Failed to get input stream.");
return;
}
c2b_webhook_log('Received webhook request');
c2b_webhook_log($mpesaResponse);
$content = json_decode($mpesaResponse);
if (json_last_error() !== JSON_ERROR_NONE) {
c2b_logAndNotify("Failed to decode JSON response: " . json_last_error_msg());
return;
}
c2b_webhook_log('Decoded JSON data successfully');
if (!class_exists('Package')) {
c2b_logAndNotify("Error: Package class does not exist.");
return;
}
if (isset($config['mpesa_c2b_bill_ref'])) {
switch ($config['mpesa_c2b_bill_ref']) {
case 'phone':
$customer = ORM::for_table('tbl_customers')
->where('phonenumber', $content->BillRefNumber)
->find_one();
break;
case 'username':
$customer = ORM::for_table('tbl_customers')
->where('username', $content->BillRefNumber)
->find_one();
break;
case 'id':
$customer = ORM::for_table('tbl_customers')
->where('id', $content->BillRefNumber)
->find_one();
break;
default:
$customer = null;
break;
}
if (!$customer) {
sendTelegram("Validation failed: No account found for BillRefNumber: $content->BillRefNumber");
_log("Validation failed: No account found for BillRefNumber: $content->BillRefNumber");
echo json_encode(["ResultCode" => "C2B00012", "ResultDesc" => "Invalid Account Number"]);
return;
}
} else {
_log("Configuration error: mpesa_c2b_bill_ref not set.");
sendTelegram("Configuration error: mpesa_c2b_bill_ref not set.");
}
$bills = c2b_billing($customer->id);
if (!$bills) {
c2b_logAndNotify("No matching bill found for BillRefNumber: {$content->BillRefNumber}");
return;
}
foreach ($bills as $bill) {
c2b_handleBillPayment($content, $customer, $bill);
}
echo json_encode(["ResultCode" => 0, "ResultDesc" => "Accepted"]);
}
function c2b_handleBillPayment($content, $customer, $bill)
{
$amountToPay = $bill['price'];
$amountPaid = $content->TransAmount;
$channel_mode = "Mpesa C2B - {$content->TransID}";
$customerBalance = $customer->balance;
$currentBalance = $customerBalance + $amountPaid;
$customerID = $customer->id;
try {
$transaction = c2b_storeTransaction($content, $bill['namebp'], $amountToPay, $customerID);
} catch (Exception $e) {
c2b_handleException("Failed to save transaction", $e);
exit;
}
if ($currentBalance >= $amountToPay) {
$excessAmount = $currentBalance - $amountToPay;
try {
$result = Package::rechargeUser($customer->id, $bill['routers'], $bill['plan_id'], 'mpesa', $channel_mode);
if (!$result) {
c2b_logAndNotify("Mpesa Payment Successful, but failed to activate the package for customer {$customer->username}.");
} else {
if ($excessAmount > 0) {
$customer->balance = $excessAmount;
$customer->save();
} else {
$customer->balance = 0;
$customer->save();
}
c2b_sendPaymentSuccessMessage($customer, $amountPaid, $bill['namebp']);
$transaction->transactionStatus = 'Completed';
$transaction->save();
}
} catch (Exception $e) {
c2b_handleException("Error during package activation", $e);
}
} else {
c2b_updateCustomerBalance($customer, $currentBalance, $amountPaid);
$neededToActivate = $amountToPay - $currentBalance;
c2b_sendBalanceUpdateMessage($customer, $amountPaid, $currentBalance, $neededToActivate);
$transaction->transactionStatus = 'Completed';
$transaction->save();
}
}
function c2b_storeTransaction($content, $packageName, $packagePrice, $customerID)
{
ORM::get_db()->beginTransaction();
try {
$transaction = ORM::for_table('tbl_mpesa_transactions')
->where('TransID', $content->TransID)
->find_one();
if ($transaction) {
// Update existing transaction
$transaction->TransactionType = $content->TransactionType;
$transaction->TransTime = $content->TransTime;
$transaction->TransAmount = $content->TransAmount;
$transaction->BusinessShortCode = $content->BusinessShortCode;
$transaction->BillRefNumber = $content->BillRefNumber;
$transaction->OrgAccountBalance = $content->OrgAccountBalance;
$transaction->MSISDN = $content->MSISDN;
$transaction->FirstName = $content->FirstName;
$transaction->PackageName = $packageName;
$transaction->PackagePrice = $packagePrice;
$transaction->customerID = $customerID;
$transaction->transactionStatus = 'Pending';
} else {
// Create new transaction
$transaction = ORM::for_table('tbl_mpesa_transactions')->create();
$transaction->TransID = $content->TransID;
$transaction->TransactionType = $content->TransactionType;
$transaction->TransTime = $content->TransTime;
$transaction->TransAmount = $content->TransAmount;
$transaction->BusinessShortCode = $content->BusinessShortCode;
$transaction->BillRefNumber = $content->BillRefNumber;
$transaction->OrgAccountBalance = $content->OrgAccountBalance;
$transaction->MSISDN = $content->MSISDN;
$transaction->FirstName = $content->FirstName;
$transaction->PackageName = $packageName;
$transaction->PackagePrice = $packagePrice;
$transaction->customerID = $customerID;
$transaction->transactionStatus = 'Pending';
}
$transaction->save();
ORM::get_db()->commit();
return $transaction;
} catch (Exception $e) {
ORM::get_db()->rollBack();
throw $e;
}
}
function c2b_logAndNotify($message)
{
_log($message);
sendTelegram($message);
}
function c2b_handleException($message, $e)
{
$fullMessage = "$message: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine();
c2b_logAndNotify($fullMessage);
}
function c2b_updateCustomerBalance($customer, $newBalance, $amountPaid)
{
try {
$customer->balance = $newBalance;
$customer->save();
c2b_logAndNotify("Payment of KES {$amountPaid} has been added to the balance of customer {$customer->username}.");
} catch (Exception $e) {
c2b_handleException("Failed to update customer balance", $e);
}
}
function c2b_sendPaymentSuccessMessage($customer, $amountPaid, $packageName)
{
$config = c2b_config();
$message = "Dear {$customer->fullname}, your payment of KES {$amountPaid} has been received and your plan {$packageName} has been successfully activated. Thank you for choosing {$config['CompanyName']}.";
c2b_sendNotification($customer, $message);
}
function c2b_sendBalanceUpdateMessage($customer, $amountPaid, $currentBalance, $neededToActivate)
{
$config = c2b_config();
$message = "Dear {$customer->fullname}, your payment of KES {$amountPaid} has been received and added to your account balance. Your current balance is KES {$currentBalance}.";
if ($neededToActivate > 0) {
$message .= " To activate your package, you need to add KES {$neededToActivate} more to your account.";
} else {
$message .= " Your current balance is sufficient to activate your package.";
}
$message .= "\n" . $config['CompanyName'];
c2b_sendNotification($customer, $message);
}
function c2b_sendNotification($customer, $message)
{
try {
Message::sendSMS($customer->phonenumber, $message);
Message::sendWhatsapp($customer->phonenumber, $message);
} catch (Exception $e) {
c2b_handleException("Failed to send SMS/WhatsApp message", $e);
}
}
function c2b_validation()
{
header("Content-Type: application/json");
$mpesaResponse = file_get_contents('php://input');
$config = c2b_config();
$content = json_decode($mpesaResponse);
if (json_last_error() !== JSON_ERROR_NONE) {
sendTelegram("Failed to decode JSON response.");
_log("Failed to decode JSON response.");
echo json_encode(["ResultCode" => "C2B00016", "ResultDesc" => "Invalid JSON format"]);
return;
}
$BillRefNumber = $content->BillRefNumber;
$TransAmount = $content->TransAmount;
if (isset($config['mpesa_c2b_bill_ref'])) {
switch ($config['mpesa_c2b_bill_ref']) {
case 'phone':
$customer = ORM::for_table('tbl_customers')
->where('phonenumber', $content->BillRefNumber)
->find_one();
break;
case 'username':
$customer = ORM::for_table('tbl_customers')
->where('username', $content->BillRefNumber)
->find_one();
break;
case 'id':
$customer = ORM::for_table('tbl_customers')
->where('id', $content->BillRefNumber)
->find_one();
break;
default:
$customer = null;
break;
}
if (!$customer) {
sendTelegram("Validation failed: No account found for BillRefNumber: $BillRefNumber");
_log("Validation failed: No account found for BillRefNumber: $BillRefNumber");
echo json_encode(["ResultCode" => "C2B00012", "ResultDesc" => "Invalid Account Number"]);
return;
}
} else {
_log("Configuration error: mpesa_c2b_bill_ref not set.");
sendTelegram("Configuration error: mpesa_c2b_bill_ref not set.");
}
$bills = c2b_billing($customer->id);
if (!$bills) {
sendTelegram("Validation failed: No bill found for BillRefNumber: $BillRefNumber");
_log("Validation failed: No bill found for BillRefNumber: $BillRefNumber");
echo json_encode(["ResultCode" => "C2B00012", "ResultDesc" => "Invalid Bill Reference"]);
return;
}
foreach ($bills as $bill) {
}
$billAmount = $bill['price'];
if (!$config['mpesa_c2b_low_fee']) {
if ($TransAmount < $billAmount) {
sendTelegram("Validation failed: Insufficient amount. Transferred: $TransAmount, Required: $billAmount");
_log("Validation failed: Insufficient amount. Transferred: $TransAmount, Required: $billAmount");
echo json_encode(["ResultCode" => "C2B00013", "ResultDesc" => "Invalid or Insufficient Amount"]);
return;
}
}
sendTelegram("Validation successful for BillRefNumber: $BillRefNumber with amount: $TransAmount");
_log("Validation successful for BillRefNumber: $BillRefNumber with amount: $TransAmount");
echo json_encode(["ResultCode" => 0, "ResultDesc" => "Accepted"]);
}
function c2b_billing($id)
{
$d = ORM::for_table('tbl_user_recharges')
->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'
])
->select('tbl_plans.price', 'price')
->left_outer_join('tbl_plans', array('tbl_plans.id', '=', 'tbl_user_recharges.plan_id'))
->where('customer_id', $id)
->find_many();
return $d;
}
function c2b_config()
{
$result = ORM::for_table('tbl_appconfig')->find_many();
foreach ($result as $value) {
$config[$value['setting']] = $value['value'];
}
return $config;
}

View File

@ -0,0 +1,48 @@
<?php
register_menu("Clear System Cache", true, "clear_cache", 'SETTINGS', '');
function clear_cache()
{
global $ui;
_admin();
$ui->assign('_title', 'Clear Cache');
$ui->assign('_system_menu', 'settings');
$admin = Admin::_info();
$ui->assign('_admin', $admin);
// Check user type for access
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
exit;
}
$compiledCacheDir = 'ui/compiled';
$templateCacheDir = 'system/cache';
try {
// Clear the compiled cache
$files = scandir($compiledCacheDir);
foreach ($files as $file) {
if ($file !== '.' && $file !== '..' && is_file($compiledCacheDir . '/' . $file)) {
unlink($compiledCacheDir . '/' . $file);
}
}
// Clear the template cache
$templateCacheFiles = glob($templateCacheDir . '/*.{json,temp}', GLOB_BRACE);
foreach ($templateCacheFiles as $file) {
if (is_file($file)) {
unlink($file);
}
}
// Cache cleared successfully
_log('[' . ($admin['fullname'] ?? 'Unknown Admin') . ']: ' . Lang::T(' Cleared the system cache '), $admin['user_type']);
r2(U . 'dashboard', 's', Lang::T("Cache cleared successfully!"));
} catch (Exception $e) {
// Error occurred while clearing the cache
_log('[' . ($admin['fullname'] ?? 'Unknown Admin') . ']: ' . Lang::T(' Error occurred while clearing the cache: ' . $e->getMessage()), $admin['user_type']);
r2(U . 'dashboard', 'e', Lang::T("Error occurred while clearing the cache: ") . $e->getMessage());
}
}

755
system/plugin/download.php Normal file
View File

@ -0,0 +1,755 @@
<?php
include '../../config.php';
$mysqli = new mysqli($db_host, $db_user, $db_password, $db_name);
if ($mysqli->connect_error) {
die("Connection failed: " . $mysqli->connect_error);
}
// Function to get a setting value
function getSettingValue($mysqli, $setting) {
$query = $mysqli->prepare("SELECT value FROM tbl_appconfig WHERE setting = ?");
$query->bind_param("s", $setting);
$query->execute();
$result = $query->get_result();
if ($row = $result->fetch_assoc()) {
return $row['value'];
}
return '';
}
// Fetch hotspot title and description from tbl_appconfig
$hotspotTitle = getSettingValue($mysqli, 'hotspot_title');
$description = getSettingValue($mysqli, 'description');
$phone = getSettingValue($mysqli, 'phone');
$company = getSettingValue($mysqli, 'CompanyName');
// Fetch router name and router ID from tbl_appconfig
$routerName = getSettingValue($mysqli, 'router_name');
$routerId = getSettingValue($mysqli, 'router_id');
// Fetch available plans
$planQuery = "SELECT id, type, name_plan, price, validity, validity_unit FROM tbl_plans WHERE routers = ? AND type = 'Hotspot'";
$planStmt = $mysqli->prepare($planQuery);
$planStmt->bind_param("s", $routerName);
$planStmt->execute();
$planResult = $planStmt->get_result();
$htmlContent = "";
$htmlContent .= "<!DOCTYPE html>\n";
$htmlContent .= "<html lang=\"en\">\n";
$htmlContent .= "<head>\n";
$htmlContent .= " <meta charset=\"UTF-8\">\n";
$htmlContent .= " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n";
$htmlContent .= " <title>$company</title>\n";
$htmlContent .= " <script src=\"https://cdn.tailwindcss.com\"></script>\n";
$htmlContent .= " <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css\">\n";
$htmlContent .= " <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/glider-js@1.7.7/glider.min.css\" />\n";
$htmlContent .= " <script src=\"https://cdn.jsdelivr.net/npm/glider-js@1.7.7/glider.min.js\"></script>\n";
$htmlContent .= " <link rel=\"preconnect\" href=\"https://cdn.jsdelivr.net\">\n";
$htmlContent .= " <link rel=\"preconnect\" href=\"https://cdnjs.cloudflare.com\" crossorigin>\n";
$htmlContent .= " <link rel=\"stylesheet\" href=\"https://rsms.me/inter/inter.css\">\n";
$htmlContent .= " <!-- <link rel=\"stylesheet\" type=\"text/css\" href=\"styles.css\"> -->\n";
$htmlContent .= "</head>\n";
$htmlContent .= "<body class=\"font-sans antialiased text-gray-900 bg-gray-900 font-inter\">\n";
$htmlContent .= " <!-- Main Content -->\n";
$htmlContent .= " <div class=\"mx-auto max-w-screen-2xl px-4 md:px-4\">\n";
$htmlContent .= " <div class=\"max-h-34 relative mx-auto mt-4 flex max-w-lg flex-1 shrink-0 items-center justify-center overflow-hidden shadow-lg rounded-lg bg-blue-100\">\n";
$htmlContent .= " <!-- overlay - start -->\n";
$htmlContent .= " <!-- <div class=\"absolute inset-0 mix-blend-multiply\"></div> -->\n";
$htmlContent .= " <!-- overlay - end -->\n";
$htmlContent .= " <!-- text start -->\n";
$htmlContent .= " <div class=\"relative flex flex-col items-center p-4 sm:max-w-xl\">\n";
$htmlContent .= " <p class=\"mb-4 text-center text-2xl font-bold text-gray-800 sm:text-xl md:mb-2 \">$company HOTSPOT LOGIN </p>\n";
$htmlContent .= " <ol class=\"text-base text-left text-gray-800 mb-1 list-decimal pl-6\">\n";
$htmlContent .= " <li>Click on your preferred package</li>\n";
$htmlContent .= " <li>Enter Mpesa No.</li>\n";
$htmlContent .= " <li>Enter pin</li>\n";
$htmlContent .= " <li>Wait to be connected</li>\n";
$htmlContent .= " </ol>\n";
$htmlContent .= " <p class=\"mb-4 text-center text-lg font-medium text-gray-700 sm:text-1xl md:mb-1 md:text-xl\"> For any enquiries contact : $phone</p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <!-- text end -->\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"py-2 sm:py-4 lg:py-4\">\n";
$htmlContent .= " <div class=\"mx-auto max-w-screen-2xl px-4 md:px-4\">\n";
$htmlContent .= " <div class=\"mx-auto max-w-lg\">\n";
$htmlContent .= " <div class=\"flex flex-col gap-4\">\n";
$htmlContent .= " <button type=\"button\" class=\"flex items-center justify-center gap-2 rounded-lg bg-blue-500 px-8 py-3 text-center text-sm font-semibold text-white outline-none ring-blue-300 transition duration-100 hover:bg-blue-600 focus-visible:ring active:bg-blue-700 md:text-base\" onclick=\"redeemVoucher()\">\n";
$htmlContent .= " <svg class=\"w-5 h-5 mr-2\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n";
$htmlContent .= " <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 8v13m0-13V6a2 2 0 112 2h-2zm0 0V5.5A2.5 2.5 0 109.5 8H12zm-7 4h14M5 12a2 2 0 110-4h14a2 2 0 110 4M5 12v7a2 2 0 002 2h10a2 2 0 002-2v-7\"></path>\n";
$htmlContent .= " </svg>\n";
$htmlContent .= " Click here to Redeem Voucher\n";
$htmlContent .= " </button>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"py-2 sm:py-4 lg:py-6\">\n";
$htmlContent .= " <div class=\"mx-auto max-w-screen-2xl px-4 md:px-8\">\n";
$htmlContent .= " <div class=\"mx-auto max-w-lg grid grid-cols-2 sm:grid-cols-3 gap-1 p-1\" id=\"cards-container\">\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
// New HTML content added
$htmlContent .= " <div class=\"container mx-auto px-4 mb-2\">\n";
$htmlContent .= " <div class=\"max-w-md mx-auto bg-white rounded-lg overflow-hidden md:max-w-lg\">\n";
$htmlContent .= " <div class=\"p-3\">\n";
$htmlContent .= " <h3 class=\"text-2xl font-semibold text-gray-900 mb-3 text-center\">Enter code to reconnect</h3>\n";
$htmlContent .= " <div class=\"mb-6\">\n";
$htmlContent .= " <label for=\"mpesaCodeInput\" class=\"block text-gray-700 text-sm font-bold mb-2\">Code or
message:</label>\n";
$htmlContent .= " <input type=\"text\" id=\"mpesaCodeInput\" name=\"mpesa_code\" placeholder=\"Enter Code or Full Message\" class=\"w-full rounded-lg border bg-gray-50 px-3 py-2 text-gray-800 outline-none ring-indigo-300 transition duration-100 focus:ring\">\n";
$htmlContent .= " <button id=\"reconnectBtn\" class=\"w-full mt-3 rounded-lg bg-blue-500 px-4 py-2 text-white font-semibold hover:bg-red-600 transition duration-100\">Reconnect</button>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"p-1\">\n";
$htmlContent .= " <div class=\"w-full p-3\">\n";
$htmlContent .= " <div class=\"text-center\">\n";
$htmlContent .= " <h3 class=\"text-2xl text-gray-900\">Already Have an Active Package?</h3>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <form id=\"loginForm\" class=\"form\" name=\"login\" action=\"$(link-login-only)\" method=\"post\" $(if chap-id)onSubmit=\"return doLogin()\" $(endif)>\n";
$htmlContent .= " <input type=\"hidden\" name=\"dst\" value=\"$(link-orig)\" />\n";
$htmlContent .= " <input type=\"hidden\" name=\"popup\" value=\"true\" />\n";
$htmlContent .= " <div class=\"mb-4\">\n";
$htmlContent .= " <label class=\"block text-gray-700 text-sm font-bold mb-2\" for=\"username\">enter username or account number.</label>\n";
$htmlContent .= " <div>\n";
$htmlContent .= " <input id=\"usernameInput\" name=\"username\" type=\"text\" value=\"\" placeholder=\"eg. ACC123456\" class=\"w-full rounded-lg border bg-gray-50 px-3 py-2 text-gray-800 outline-none ring-indigo-300 transition duration-100 focus:ring\" />\n";
$htmlContent .= " <button id=\"submitBtn\" class=\"w-full mt-3 flex items-center justify-center gap-2 rounded-lg bg-blue-500 px-8 py-3 text-center text-sm font-semibold text-white outline-none ring-red-300 transition duration-100 hover:bg-red-600 focus-visible:ring active:bg-red-700 md:text-base\" type=\"button\" onclick=\"submitLogin()\">\n";
$htmlContent .= " Connect\n";
$htmlContent .= " </button>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <input type=\"hidden\" name=\"password\" value=\"1234\">\n";
$htmlContent .= " </form>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"mx-auto max-w-screen-2xl px-4 md:px-8\">\n";
$htmlContent .= " <div class=\"mx-auto mb-4 max-w-lg\">\n";
$htmlContent .= " <div class=\"border-t py-4\">\n";
$htmlContent .= " <p class=\"text-xs text-red-700 text-center\">&copy; " . date("Y") . ". Powered by NestICT</p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= "</body>\n";
// Add the closing script section as well, if necessary
$htmlContent .= "<script>\n";
// Add any required JavaScript here
$htmlContent .= "</script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= "function fetchData() {\n";
$htmlContent .= " let domain = '" . APP_URL . "/';\n";
$htmlContent .= " let siteUrl = domain + \"/index.php?_route=plugin/hotspot_plan\";\n";
$htmlContent .= " let request = new XMLHttpRequest();\n";
$htmlContent .= " const routerName = encodeURIComponent(\"$routerName\");\n";
$htmlContent .= " const dataparams = `routername=\${routerName}`;\n";
$htmlContent .= " request.open(\"POST\", siteUrl, true);\n";
$htmlContent .= " request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');\n";
$htmlContent .= " request.onload = () => {\n";
$htmlContent .= " if (request.readyState === XMLHttpRequest.DONE) {\n";
$htmlContent .= " if (request.status === 200) {\n";
$htmlContent .= " let fetchedData = JSON.parse(request.responseText);\n";
$htmlContent .= " populateCards(fetchedData);\n";
$htmlContent .= " } else {\n";
$htmlContent .= " console.log(`Error \${request.status}: \${request.statusText}`);\n";
$htmlContent .= " }\n";
$htmlContent .= " }\n";
$htmlContent .= " };\n";
$htmlContent .= " request.onerror = () => {\n";
$htmlContent .= " console.error(\"Network error\");\n";
$htmlContent .= " };\n";
$htmlContent .= " request.send(dataparams);\n";
$htmlContent .= "}\n";
$htmlContent .= "function populateCards(data) {\n";
$htmlContent .= " var cardsContainer = document.getElementById('cards-container');\n";
$htmlContent .= " cardsContainer.innerHTML = ''; // Clear existing content\n";
$htmlContent .= " // Sort the plans by price in ascending order\n";
$htmlContent .= " data.data.forEach(router => {\n";
$htmlContent .= " // Sort hotspot plans by price\n";
$htmlContent .= " router.plans_hotspot.sort((a, b) => parseFloat(a.price) - parseFloat(b.price));\n";
$htmlContent .= " router.plans_hotspot.forEach(item => {\n";
$htmlContent .= " var cardDiv = document.createElement('div');\n";
$htmlContent .= " cardDiv.className = 'bg-white border border-black rounded-lg shadow-md overflow-hidden transition duration-300 hover:shadow-lg flex flex-col items-center justify-between mx-auto mb-4 w-40';\n";
$htmlContent .= " cardDiv.innerHTML = `\n";
$htmlContent .= " <div class=\"bg-red-500 text-white w-full py-1\">\n";
$htmlContent .= " <h2 class=\"text-sm font-medium uppercase text-center\" style=\"font-size: clamp(0.75rem, 1.5vw, 1rem); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\">\n";
$htmlContent .= " \${item.planname}\n";
$htmlContent .= " </h2>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"px-4 py-2 flex-grow\">\n";
$htmlContent .= " <p class=\"text-2xl font-bold text-red-600 mb-1\">\n";
$htmlContent .= " <span class=\"text-lg font-medium text-black\">\${item.currency}</span>\n";
$htmlContent .= " \${item.price}\n";
$htmlContent .= " </p>\n";
$htmlContent .= " <p class=\"text-sm text-black mb-2\">\n";
$htmlContent .= " Valid for \${item.validity} \${item.timelimit}\n";
$htmlContent .= " </p>\n";
$htmlContent .= " <hr class=\"border-black mb-2\">\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"px-4 py-2 flex-shrink-0\">\n";
$htmlContent .= " <a href=\"#\" class=\"inline-block bg-gray-900 text-white hover:bg-red-600 font-semibold py-1 px-4 rounded-lg transition duration-300 text-md\"\n";
$htmlContent .= " onclick=\"handlePhoneNumberSubmission('\${item.planId}', '\${item.routerId}'); return false;\"\n";
$htmlContent .= " data-plan-id=\"\${item.planId}\"\n";
$htmlContent .= " data-router-id=\"\${item.routerId}\">\n";
$htmlContent .= " Buy\n";
$htmlContent .= " </a>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " `;\n";
$htmlContent .= " cardsContainer.appendChild(cardDiv);\n";
$htmlContent .= " });\n";
$htmlContent .= " });\n";
$htmlContent .= "}\n";
$htmlContent .= "fetchData();\n";
$htmlContent .= "</script>\n";
$htmlContent .= "<script src=\"https://cdn.jsdelivr.net/npm/sweetalert2@11\"></script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= " function formatPhoneNumber(phoneNumber) {\n";
$htmlContent .= " if (phoneNumber.startsWith('+')) {\n";
$htmlContent .= " phoneNumber = phoneNumber.substring(1);\n";
$htmlContent .= " }\n";
$htmlContent .= " if (phoneNumber.startsWith('0')) {\n";
$htmlContent .= " phoneNumber = '254' + phoneNumber.substring(1);\n";
$htmlContent .= " }\n";
$htmlContent .= " if (phoneNumber.match(/^(7|1)/)) {\n";
$htmlContent .= " phoneNumber = '254' + phoneNumber;\n";
$htmlContent .= " }\n";
$htmlContent .= " return phoneNumber;\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= " function setCookie(name, value, days) {\n";
$htmlContent .= " var expires = \"\";\n";
$htmlContent .= " if (days) {\n";
$htmlContent .= " var date = new Date();\n";
$htmlContent .= " date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));\n";
$htmlContent .= " expires = \"; expires=\" + date.toUTCString();\n";
$htmlContent .= " }\n";
$htmlContent .= " document.cookie = name + \"=\" + (value || \"\") + expires + \"; path=/\";\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= " function getCookie(name) {\n";
$htmlContent .= " var nameEQ = name + \"=\";\n";
$htmlContent .= " var ca = document.cookie.split(';');\n";
$htmlContent .= " for (var i = 0; i < ca.length; i++) {\n";
$htmlContent .= " var c = ca[i];\n";
$htmlContent .= " while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n";
$htmlContent .= " if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n";
$htmlContent .= " }\n";
$htmlContent .= " return null;\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= " function generateAccountId() {\n";
$htmlContent .= " return 'ACN' + Math.floor(10000 + Math.random() * 90000); // Generate a random number between 10000 and 99999\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= "var loginTimeout; // Variable to store the timeout ID\n";
$htmlContent .= "function handlePhoneNumberSubmission(planId, routerId, price) {\n";
$htmlContent .= ' var msg = "You are about to pay Kshs: ' . $item['price'] . '. Enter phone number below and click pay now to initialize payment";' . "\n";
$htmlContent .= " const regexp = /\\\${([^{}]+)}/g;\n";
$htmlContent .= " let result = msg.replace(regexp, function(ignore, key) {\n";
$htmlContent .= " return eval(key);\n";
$htmlContent .= " });\n";
$htmlContent .= " swal.fire({\n";
$htmlContent .= " title: 'Enter Your Number',\n";
$htmlContent .= " input: 'number',\n";
$htmlContent .= " inputAttributes: {\n";
$htmlContent .= " required: 'true'\n";
$htmlContent .= " },\n";
$htmlContent .= " inputValidator: function(value) {\n";
$htmlContent .= " if (value === '') {\n";
$htmlContent .= " return 'You need to write your phonenumber!';\n";
$htmlContent .= " }\n";
$htmlContent .= " },\n";
$htmlContent .= " text: result,\n";
$htmlContent .= " showCancelButton: true,\n";
$htmlContent .= " confirmButtonColor: '#3085d6',\n";
$htmlContent .= " cancelButtonColor: '#d33',\n";
$htmlContent .= " confirmButtonText: 'Pay Now',\n";
$htmlContent .= " showLoaderOnConfirm: true,\n";
$htmlContent .= " preConfirm: (phoneNumber) => {\n";
$htmlContent .= " var formattedPhoneNumber = formatPhoneNumber(phoneNumber);\n";
$htmlContent .= " var accountId = getCookie('accountId');\n";
$htmlContent .= " if (!accountId) {\n";
$htmlContent .= " accountId = generateAccountId(); // Generate a new account ID\n";
$htmlContent .= " setCookie('accountId', accountId, 7); // Set account ID as a cookie\n";
$htmlContent .= " }\n";
$htmlContent .= " document.getElementById('usernameInput').value = accountId; // Use account ID as the new username\n";
$htmlContent .= " console.log(\"Phone number for autofill:\", formattedPhoneNumber);\n";
$htmlContent .= "\n";
$htmlContent .= " return fetch('" . APP_URL . "/index.php?_route=plugin/CreateHotspotuser&type=grant', {\n";
$htmlContent .= " method: 'POST',\n";
$htmlContent .= " headers: {'Content-Type': 'application/json'},\n";
$htmlContent .= " body: JSON.stringify({phone_number: formattedPhoneNumber, plan_id: planId, router_id: routerId, account_id: accountId}),\n";
$htmlContent .= " })\n";
$htmlContent .= " .then(response => {\n";
$htmlContent .= " if (!response.ok) throw new Error('Network response was not ok');\n";
$htmlContent .= " return response.json();\n";
$htmlContent .= " })\n";
$htmlContent .= " .then(data => {\n";
$htmlContent .= " if (data.status === 'error') throw new Error(data.message);\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'info',\n";
$htmlContent .= " title: 'Processing..',\n";
$htmlContent .= " html: `A payment request has been sent to your phone. Please wait while we process your payment.`,\n";
$htmlContent .= " showConfirmButton: false,\n";
$htmlContent .= " allowOutsideClick: false,\n";
$htmlContent .= " didOpen: () => {\n";
$htmlContent .= " Swal.showLoading();\n";
$htmlContent .= " checkPaymentStatus(formattedPhoneNumber);\n";
$htmlContent .= " }\n";
$htmlContent .= " });\n";
$htmlContent .= " return formattedPhoneNumber;\n";
$htmlContent .= " })\n";
$htmlContent .= " .catch(error => {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'error',\n";
$htmlContent .= " title: 'Oops...',\n";
$htmlContent .= " text: error.message,\n";
$htmlContent .= " });\n";
$htmlContent .= " });\n";
$htmlContent .= " },\n";
$htmlContent .= " allowOutsideClick: () => !Swal.isLoading()\n";
$htmlContent .= " });\n";
$htmlContent .= "}\n";
$htmlContent .= "\n";
$htmlContent .= "function checkPaymentStatus(phoneNumber) {\n";
$htmlContent .= " let checkInterval = setInterval(() => {\n";
$htmlContent .= " $.ajax({\n";
$htmlContent .= " url: '" . APP_URL . "/index.php?_route=plugin/CreateHotspotuser&type=verify',\n";
$htmlContent .= " method: 'POST',\n";
$htmlContent .= " data: JSON.stringify({account_id: document.getElementById('usernameInput').value}),\n";
$htmlContent .= " contentType: 'application/json',\n";
$htmlContent .= " dataType: 'json',\n";
$htmlContent .= " success: function(data) {\n";
$htmlContent .= " console.log('Raw Response:', data); // Debugging\n";
$htmlContent .= " if (data.Resultcode === '3') { // Success\n";
$htmlContent .= " clearInterval(checkInterval);\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'success',\n";
$htmlContent .= " title: 'Payment Successful',\n";
$htmlContent .= " text: data.Message,\n";
$htmlContent .= " showConfirmButton: false\n";
$htmlContent .= " });\n";
$htmlContent .= " if (loginTimeout) {\n";
$htmlContent .= " clearTimeout(loginTimeout);\n";
$htmlContent .= " }\n";
$htmlContent .= " loginTimeout = setTimeout(function() {\n";
$htmlContent .= " document.getElementById('loginForm').submit();\n";
$htmlContent .= " }, 2000);\n";
$htmlContent .= " } else if (data.Resultcode === '2') { // Error\n";
$htmlContent .= " clearInterval(checkInterval);\n";
$htmlContent .= " let iconType = data.Status === 'danger' ? 'error' : data.Status;\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: iconType,\n";
$htmlContent .= " title: 'Payment Issue',\n";
$htmlContent .= " text: data.Message,\n";
$htmlContent .= " });\n";
$htmlContent .= " } else if (data.Resultcode === '1') { // Primary\n";
$htmlContent .= " // Continue checking\n";
$htmlContent .= " }\n";
$htmlContent .= " },\n";
$htmlContent .= " error: function(xhr, textStatus, errorThrown) {\n";
$htmlContent .= " console.log('Error: ' + errorThrown);\n";
$htmlContent .= " }\n";
$htmlContent .= " });\n";
$htmlContent .= " }, 2000);\n";
$htmlContent .= "\n";
$htmlContent .= " setTimeout(() => {\n";
$htmlContent .= " clearInterval(checkInterval);\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'warning',\n";
$htmlContent .= " title: 'Timeout',\n";
$htmlContent .= " text: 'Payment verification timed out. Please try again.',\n";
$htmlContent .= " });\n";
$htmlContent .= " }, 600000); // Stop checking after 60 seconds\n";
$htmlContent .= "}\n";
$htmlContent .= "</script>\n";
$htmlContent .= "</script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= "var loginTimeout; // Variable to store the timeout ID\n";
$htmlContent .= "function redeemVoucher() {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " title: 'Redeem Voucher',\n";
$htmlContent .= " input: 'text',\n";
$htmlContent .= " inputPlaceholder: 'Enter voucher code',\n";
$htmlContent .= " inputValidator: function(value) {\n";
$htmlContent .= " if (!value) {\n";
$htmlContent .= " return 'You need to enter a voucher code!';\n";
$htmlContent .= " }\n";
$htmlContent .= " },\n";
$htmlContent .= " confirmButtonColor: '#3085d6',\n";
$htmlContent .= " cancelButtonColor: '#d33',\n";
$htmlContent .= " confirmButtonText: 'Redeem',\n";
$htmlContent .= " showLoaderOnConfirm: true,\n";
$htmlContent .= " preConfirm: (voucherCode) => {\n";
$htmlContent .= " var accountId = voucherCode;\n";
$htmlContent .= " if (!accountId) {\n";
$htmlContent .= " accountId = voucherCode;\n";
$htmlContent .= " setCookie('accountId', accountId, 7);\n";
$htmlContent .= " }\n";
$htmlContent .= " return fetch('" . APP_URL . "/index.php?_route=plugin/CreateHotspotuser&type=voucher', {\n";
$htmlContent .= " method: 'POST',\n";
$htmlContent .= " headers: {'Content-Type': 'application/json'},\n";
$htmlContent .= " body: JSON.stringify({voucher_code: voucherCode, account_id: accountId}),\n";
$htmlContent .= " })\n";
$htmlContent .= " .then(response => {\n";
$htmlContent .= " if (!response.ok) throw new Error('Network response was not ok');\n";
$htmlContent .= " return response.json();\n";
$htmlContent .= " })\n";
$htmlContent .= " .then(data => {\n";
$htmlContent .= " if (data.status === 'error') throw new Error(data.message);\n";
$htmlContent .= " return data;\n";
$htmlContent .= " });\n";
$htmlContent .= " },\n";
$htmlContent .= " allowOutsideClick: () => !Swal.isLoading()\n";
$htmlContent .= " }).then((result) => {\n";
$htmlContent .= " if (result.isConfirmed) {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'success',\n";
$htmlContent .= " title: 'Voucher Redeemed',\n";
$htmlContent .= " text: result.value.message,\n";
$htmlContent .= " showConfirmButton: false,\n";
$htmlContent .= " allowOutsideClick: false,\n";
$htmlContent .= " didOpen: () => {\n";
$htmlContent .= " Swal.showLoading();\n";
$htmlContent .= " var username = result.value.username;\n";
$htmlContent .= " console.log('Received username from server:', username);\n";
$htmlContent .= " var usernameInput = document.querySelector('input[name=\"username\"]');\n";
$htmlContent .= " if (usernameInput) {\n";
$htmlContent .= " console.log('Found username input element.');\n";
$htmlContent .= " usernameInput.value = username;\n";
$htmlContent .= " loginTimeout = setTimeout(function() {\n";
$htmlContent .= " var loginForm = document.getElementById('loginForm');\n";
$htmlContent .= " if (loginForm) {\n";
$htmlContent .= " loginForm.submit();\n";
$htmlContent .= " } else {\n";
$htmlContent .= " console.error('Login form not found.');\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'error',\n";
$htmlContent .= " title: 'Error',\n";
$htmlContent .= " text: 'Login form not found. Please try again.',\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " }, 2000);\n";
$htmlContent .= " } else {\n";
$htmlContent .= " console.error('Username input element not found.');\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'error',\n";
$htmlContent .= " title: 'Error',\n";
$htmlContent .= " text: 'Username input not found. Please try again.',\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " }\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " }).catch(error => {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'error',\n";
$htmlContent .= " title: 'Oops...',\n";
$htmlContent .= " text: error.message,\n";
$htmlContent .= " });\n";
$htmlContent .= " });\n";
$htmlContent .= "}\n";
$htmlContent .= "</script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= "var loginTimeout; // Variable to store the timeout ID\n";
$htmlContent .= "document.addEventListener('DOMContentLoaded', function() {\n";
$htmlContent .= " document.getElementById('reconnectBtn').addEventListener('click', function() {\n";
$htmlContent .= " var mpesaCode = document.getElementById('mpesaCodeInput').value;\n";
$htmlContent .= " var firstWord = mpesaCode.split(' ')[0]; // Get the first word in the MPESA code\n";
$htmlContent .= " fetch('" . APP_URL . "/index.php?_route=plugin/CreateHotspotuser&type=reconnect', {\n";
$htmlContent .= " method: 'POST',\n";
$htmlContent .= " headers: {'Content-Type': 'application/json'},\n";
$htmlContent .= " body: JSON.stringify({mpesa_code: firstWord}),\n"; // Sending only the first word of the MPESA code\n";
$htmlContent .= " })\n";
$htmlContent .= " .then(response => response.json())\n";
$htmlContent .= " .then(data => {\n";
$htmlContent .= " if (data.Status === 'success') {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'success',\n";
$htmlContent .= " title: 'Reconnection Successful',\n";
$htmlContent .= " text: data.Message,\n";
$htmlContent .= " showConfirmButton: false,\n";
$htmlContent .= " allowOutsideClick: false,\n";
$htmlContent .= " didOpen: () => {\n";
$htmlContent .= " Swal.showLoading();\n";
$htmlContent .= " var username = data.username; // Replace with actual JSON field name\n";
$htmlContent .= " console.log('Received username from server:', username);\n";
$htmlContent .= " var usernameInput = document.querySelector('input[name=\"username\"]');\n";
$htmlContent .= " if (usernameInput) {\n";
$htmlContent .= " console.log('Found username input element.');\n";
$htmlContent .= " usernameInput.value = username;\n";
$htmlContent .= " loginTimeout = setTimeout(function() {\n";
$htmlContent .= " var loginForm = document.getElementById('loginForm');\n";
$htmlContent .= " if (loginForm) {\n";
$htmlContent .= " loginForm.submit();\n";
$htmlContent .= " } else {\n";
$htmlContent .= " console.error('Login form not found.');\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'error',\n";
$htmlContent .= " title: 'Error',\n";
$htmlContent .= " text: 'Login form not found. Please try again.',\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " }, 2000);\n";
$htmlContent .= " } else {\n";
$htmlContent .= " console.error('Username input element not found.');\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'error',\n";
$htmlContent .= " title: 'Error',\n";
$htmlContent .= " text: 'Username input not found. Please try again.',\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " }\n";
$htmlContent .= " });\n";
$htmlContent .= " } else {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'error',\n";
$htmlContent .= " title: 'Reconnection Failed',\n";
$htmlContent .= " text: data.Message,\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " })\n";
$htmlContent .= " .catch(error => {\n";
$htmlContent .= " console.error('Error:', error);\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'error',\n";
$htmlContent .= " title: 'Error',\n";
$htmlContent .= " text: 'Failed to reconnect. Please try again later.',\n";
$htmlContent .= " });\n";
$htmlContent .= " });\n";
$htmlContent .= " });\n";
$htmlContent .= "});\n";
$htmlContent .= "</script>\n";
$htmlContent .= "<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js\"></script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= "document.addEventListener('DOMContentLoaded', function() {\n";
$htmlContent .= " // Ensure the button is correctly targeted by its ID.\n";
$htmlContent .= " var submitBtn = document.getElementById('submitBtn');\n";
$htmlContent .= " \n";
$htmlContent .= " // Add a click event listener to the \"Login Now\" button.\n";
$htmlContent .= " submitBtn.addEventListener('click', function(event) {\n";
$htmlContent .= " event.preventDefault(); // Prevent the default button action.\n";
$htmlContent .= " \n";
$htmlContent .= " // Optional: Log to console for debugging purposes.\n";
$htmlContent .= " console.log(\"Login Now button clicked.\");\n";
$htmlContent .= " \n";
$htmlContent .= " // Direct form submission, bypassing the doLogin function for simplicity.\n";
$htmlContent .= " var form = document.getElementById('loginForm');\n";
$htmlContent .= " form.submit(); // Submit the form directly.\n";
$htmlContent .= " });\n";
$htmlContent .= "});\n";
$htmlContent .= "</script>\n";
$htmlContent .= "</html>\n";
$planStmt->close();
$mysqli->close();
// Check if the download parameter is set
if (isset($_GET['download']) && $_GET['download'] == '1') {
// Prepare the HTML content for download
// ... build your HTML content ...
// Specify the filename for the download
$filename = "login.html";
// Send headers to force download
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($filename));
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . strlen($htmlContent));
// Output the content
echo $htmlContent;
// Prevent any further output
exit;
}
// Regular page content goes here
// ... HTML and PHP code to display the page ...

12
system/plugin/error_log Normal file
View File

@ -0,0 +1,12 @@
[06-Jul-2024 15:05:25 UTC] PHP Fatal error: Uncaught Error: Undefined constant "request" in /home/codevibe/kejos.codevibeisp.co.ke/system/plugin/download.php:154
Stack trace:
#0 {main}
thrown in /home/codevibe/kejos.codevibeisp.co.ke/system/plugin/download.php on line 154
[06-Jul-2024 15:05:28 UTC] PHP Fatal error: Uncaught Error: Undefined constant "request" in /home/codevibe/kejos.codevibeisp.co.ke/system/plugin/download.php:154
Stack trace:
#0 {main}
thrown in /home/codevibe/kejos.codevibeisp.co.ke/system/plugin/download.php on line 154
[06-Jul-2024 17:35:47 UTC] PHP Fatal error: Uncaught Error: Undefined constant "request" in /home/codevibe/kejos.codevibeisp.co.ke/system/plugin/download.php:154
Stack trace:
#0 {main}
thrown in /home/codevibe/kejos.codevibeisp.co.ke/system/plugin/download.php on line 154

View File

@ -0,0 +1,79 @@
<?php
// Assuming you have ORM or database access configured correctly
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['routername'])) {
// Example of fetching data (simplified)
$routerName = $_POST['routername'];
// Fetch routers and plans from database (replace with your actual ORM or database queries)
$routers = ORM::for_table('tbl_routers')->find_many();
$plans_hotspot = ORM::for_table('tbl_plans')->where('type', 'Hotspot')->find_many();
// Fetch bandwidth limits for all plans
$bandwidth_limits = ORM::for_table('tbl_bandwidth')->find_many();
$bandwidth_map = [];
foreach ($bandwidth_limits as $limit) {
$bandwidth_map[$limit['plan_id']] = [
'downlimit' => $limit['rate_down'],
'uplimit' => $limit['rate_up'],
];
}
// Fetch currency from tbl_appconfig using the correct column names
$currency_config = ORM::for_table('tbl_appconfig')->where('setting', 'currency_code')->find_one();
$currency = $currency_config ? $currency_config->value : 'Ksh'; // Default to 'Ksh' if not found
// Initialize empty data array to store router-specific plans
$data = [];
// Process each router to filter and collect hotspot plans
foreach ($routers as $router) {
if ($router['name'] === $routerName) { // Check if router name matches POSTed routername
$routerData = [
'name' => $router['name'],
'router_id' => $router['id'],
'description' => $router['description'],
'plans_hotspot' => [],
];
// Filter and collect hotspot plans associated with the router
foreach ($plans_hotspot as $plan) {
if ($router['name'] == $plan['routers']) {
$plan_id = $plan['id'];
$bandwidth_data = isset($bandwidth_map[$plan_id]) ? $bandwidth_map[$plan_id] : [];
// Construct payment link using $_url
$paymentlink = "https://codevibeisp.co.ke/index.php?_route=plugin/hotspot_pay&routerName={$router['name']}&planId={$plan['id']}&routerId={$router['id']}";
// Prepare plan data to be sent in JSON response
$routerData['plans_hotspot'][] = [
'plantype' => $plan['type'],
'planname' => $plan['name_plan'],
'currency' => $currency,
'price' => $plan['price'],
'validity' => $plan['validity'],
'device' => $plan['shared_users'],
'datalimit' => $plan['data_limit'],
'timelimit' => $plan['validity_unit'] ?? null,
'downlimit' => $bandwidth_data['downlimit'] ?? null,
'uplimit' => $bandwidth_data['uplimit'] ?? null,
'paymentlink' => $paymentlink,
'planId' => $plan['id'],
'routerName' => $router['name'],
'routerId' => $router['id']
];
}
}
// Add router data to $data array
$data[] = $routerData;
}
}
// Respond with JSON data
// header('Content-Type: application/json');
// header('Access-Control-Allow-Origin: *'); // Adjust this based on your CORS requirements
echo json_encode(['data' => $data], JSON_PRETTY_PRINT);
exit();
}
?>

View File

@ -0,0 +1,229 @@
<?php
$conn = new PDO("mysql:host=$db_host;dbname=$db_name", $db_user, $db_password);
function hotspot_settings() {
global $ui, $conn;
_admin();
$admin = Admin::_info();
$ui->assign('_title', 'Hotspot Dashboard');
$ui->assign('_admin', $admin);
// Check if form is submitted
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Update Hotspot Title
$newHotspotTitle = isset($_POST['hotspot_title']) ? trim($_POST['hotspot_title']) : '';
if (!empty($newHotspotTitle)) {
$updateStmt = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'hotspot_title'");
$updateStmt->execute([$newHotspotTitle]);
}
// Add similar logic for FAQ fields here
// FAQ Headline 1 Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['frequently_asked_questions_headline1']) ? trim($_POST['frequently_asked_questions_headline1']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'frequently_asked_questions_headline1'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
// FAQ Headline 2 Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['frequently_asked_questions_headline2']) ? trim($_POST['frequently_asked_questions_headline2']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'frequently_asked_questions_headline2'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
// FAQ Headline 3 Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['frequently_asked_questions_headline3']) ? trim($_POST['frequently_asked_questions_headline3']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'frequently_asked_questions_headline3'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
// FAQ Answer 1 Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['frequently_asked_questions_answer1']) ? trim($_POST['frequently_asked_questions_answer1']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'frequently_asked_questions_answer1'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
// FAQ Answer 2 Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['frequently_asked_questions_answer2']) ? trim($_POST['frequently_asked_questions_answer2']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'frequently_asked_questions_answer2'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
// FAQ Answer 3 Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['frequently_asked_questions_answer3']) ? trim($_POST['frequently_asked_questions_answer3']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'frequently_asked_questions_answer3'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
// FAQ Description Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['description']) ? trim($_POST['description']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'description'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Get router name from user input
$routerName = isset($_POST['router_name']) ? trim($_POST['router_name']) : '';
if (!empty($routerName)) {
// Fetch the router ID based on the router name
$routerStmt = $conn->prepare("SELECT id FROM tbl_routers WHERE name = :router_name");
$routerStmt->execute(['router_name' => $routerName]);
$router = $routerStmt->fetch(PDO::FETCH_ASSOC);
if ($router) {
// Update router_id in tbl_appconfig
$updateRouterIdStmt = $conn->prepare("UPDATE tbl_appconfig SET value = :router_id WHERE setting = 'router_id'");
$updateRouterIdStmt->execute(['router_id' => $router['id']]);
// Update router_name in tbl_appconfig
$updateRouterNameStmt = $conn->prepare("UPDATE tbl_appconfig SET value = :router_name WHERE setting = 'router_name'");
$updateRouterNameStmt->execute(['router_name' => $routerName]);
} else {
// Handle the case where no matching router is found
// For example, you can set an error message or take any other appropriate action
}
}
// Other form handling code (if any)
}
// Redirect with a success message
r2(U . "plugin/hotspot_settings", 's', "Settings Saved");
}
// Fetch the current hotspot title from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'hotspot_title'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$hotspotTitle = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('hotspot_title', $hotspotTitle);
// Fetch the current faq headline 1 from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'frequently_asked_questions_headline1'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$headline1 = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('frequently_asked_questions_headline1', $headline1);
// Fetch the current faq headline 2 from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'frequently_asked_questions_headline2'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$headline2 = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('frequently_asked_questions_headline2', $headline2);
// Fetch the current faq headline 3 from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'frequently_asked_questions_headline3'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$headline3 = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('frequently_asked_questions_headline3', $headline3);
// Fetch the current faq Answer1 from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'frequently_asked_questions_answer1'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$answer1 = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('frequently_asked_questions_answer1', $answer1);
// Fetch the current faq Answer2 from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'frequently_asked_questions_answer2'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$answer2 = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('frequently_asked_questions_answer2', $answer2);
// Fetch the current faq Answer 3 from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'frequently_asked_questions_answer3'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$answer3 = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('frequently_asked_questions_answer3', $answer3);
// Fetch the current faq description from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'description'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$description = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('description', $description);
/// Fetch the current router name from the database for display in the form
$routerIdStmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'router_id'");
$routerIdStmt->execute();
$routerIdResult = $routerIdStmt->fetch(PDO::FETCH_ASSOC);
if ($routerIdResult) {
$routerStmt = $conn->prepare("SELECT name FROM tbl_routers WHERE id = :router_id");
$routerStmt->execute(['router_id' => $routerIdResult['value']]);
$router = $routerStmt->fetch(PDO::FETCH_ASSOC);
if ($router) {
$ui->assign('router_name', $router['name']);
}
}
// Render the template
$ui->display('hotspot_settings.tpl');
}

BIN
system/plugin/index.html Normal file

Binary file not shown.

View File

@ -0,0 +1,137 @@
<?php
function initiatePaybillPayment()
{
// Ensure POST variables are set and sanitize input
$username = isset($_POST['username']) ? filter_var($_POST['username'], FILTER_SANITIZE_STRING) : null;
$phone = isset($_POST['phone']) ? filter_var($_POST['phone'], FILTER_SANITIZE_STRING) : null;
if (!$username || !$phone) {
echo "<script>toastr.error('Invalid input data');</script>";
return;
}
// Normalize phone number
$phone = preg_replace(['/^\+/', '/^0/', '/^7/', '/^1/'], ['', '254', '2547', '2541'], $phone);
// Retrieve bank details from the database
$bankaccount = ORM::for_table('tbl_appconfig')->where('setting', 'PaybillAcc')->find_one();
$bankname = ORM::for_table('tbl_appconfig')->where('setting', 'PaybillName')->find_one();
$bankaccount = $bankaccount ? $bankaccount->value : null;
$bankname = $bankname ? $bankname->value : null;
if (!$bankaccount || !$bankname) {
echo "<script>toastr.error('Could not complete the payment req, please contact admin');</script>";
return;
}
// Check for existing user details
$CheckId = ORM::for_table('tbl_customers')->where('username', $username)->order_by_desc('id')->find_one();
$CheckUser = ORM::for_table('tbl_customers')->where('phonenumber', $phone)->find_many();
$UserId = $CheckId ? $CheckId->id : null;
if ($CheckUser) {
ORM::for_table('tbl_customers')->where('phonenumber', $phone)->where_not_equal('id', $UserId)->delete_many();
}
// Retrieve payment gateway record
$PaymentGatewayRecord = ORM::for_table('tbl_payment_gateway')
->where('username', $username)
->where('status', 1)
->order_by_desc('id')
->find_one();
if (!$PaymentGatewayRecord) {
echo "<script>toastr.error('Could not complete the payment req, please contact administrator');</script>";
return;
}
// Update user phone number
$ThisUser = ORM::for_table('tbl_customers')->where('username', $username)->order_by_desc('id')->find_one();
if ($ThisUser) {
$ThisUser->phonenumber = $phone;
$ThisUser->save();
}
$amount = $PaymentGatewayRecord->price;
// Safaricom API credentials
$consumerKey = 'YOUR_CONSUMER_KEY';
$consumerSecret = 'YOUR_CONSUMER_SECRET';
// Get access token
$access_token_url = 'https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials';
$curl = curl_init($access_token_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type:application/json; charset=utf8']);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_USERPWD, "$consumerKey:$consumerSecret");
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($status !== 200) {
echo "<script>toastr.error('Failed to get access token');</script>";
return;
}
$result = json_decode($result);
$access_token = $result->access_token;
// Initiate Paybill payment
$paybill_url = 'https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest';
$Timestamp = date("YmdHis");
$BusinessShortCode = 'YOUR_BUSINESS_SHORTCODE';
$Passkey = 'YOUR_PASSKEY';
$Password = base64_encode($BusinessShortCode . $Passkey . $Timestamp);
$CallBackURL = U . 'callback/PaybillCallback';
$curl_post_data = [
'BusinessShortCode' => $BusinessShortCode,
'Password' => $Password,
'Timestamp' => $Timestamp,
'TransactionType' => 'CustomerPayBillOnline',
'Amount' => $amount,
'PartyA' => $phone,
'PartyB' => $BusinessShortCode,
'PhoneNumber' => $phone,
'CallBackURL' => $CallBackURL,
'AccountReference' => $bankaccount,
'TransactionDesc' => 'PayBill Payment'
];
$curl = curl_init($paybill_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type:application/json', 'Authorization:Bearer ' . $access_token]);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($curl_post_data));
$curl_response = curl_exec($curl);
curl_close($curl);
$mpesaResponse = json_decode($curl_response);
$responseCode = $mpesaResponse->ResponseCode;
$resultDesc = $mpesaResponse->resultDesc;
$MerchantRequestID = $mpesaResponse->MerchantRequestID;
$CheckoutRequestID = $mpesaResponse->CheckoutRequestID;
if ($responseCode == "0") {
date_default_timezone_set('Africa/Nairobi');
$now = date("Y-m-d H:i:s");
$PaymentGatewayRecord->pg_paid_response = $resultDesc;
$PaymentGatewayRecord->username = $username;
$PaymentGatewayRecord->checkout = $CheckoutRequestID;
$PaymentGatewayRecord->payment_method = 'Mpesa PayBill';
$PaymentGatewayRecord->payment_channel = 'Mpesa PayBill';
$PaymentGatewayRecord->save();
if (!empty($_POST['channel'])) {
echo json_encode(["status" => "success", "message" => "Enter Pin to complete"]);
} else {
echo "<script>toastr.success('Enter Mpesa Pin to complete');</script>";
}
} else {
echo "<script>toastr.error('We could not complete the payment for you, please contact administrator');</script>";
}
}
?>

View File

@ -0,0 +1,304 @@
<?php
function initiatebankstk()
{
$username=$_POST['username'];
$phone=$_POST['phone'];
$phone = (substr($phone, 0,1) == '+') ? str_replace('+', '', $phone) : $phone;
$phone = (substr($phone, 0,1) == '0') ? preg_replace('/^0/', '254', $phone) : $phone;
$phone = (substr($phone, 0,1) == '7') ? preg_replace('/^7/', '2547', $phone) : $phone; //cater for phone number prefix 2547XXXX
$phone = (substr($phone, 0,1) == '1') ? preg_replace('/^1/', '2541', $phone) : $phone; //cater for phone number prefix 2541XXXX
$phone = (substr($phone, 0,1) == '0') ? preg_replace('/^01/', '2541', $phone) : $phone;
$phone = (substr($phone, 0,1) == '0') ? preg_replace('/^07/', '2547', $phone) : $phone;
$bankaccount = ORM::for_table('tbl_appconfig')
->where('setting', 'Stkbankacc')
->find_one();
$bankname = ORM::for_table('tbl_appconfig')
->where('setting', 'Stkbankname')
->find_one();
$bankaccount = ($bankaccount) ? $bankaccount->value : null;
$bankname = ($bankname) ? $bankname->value : null;
// echo $bankname;
$CheckId = ORM::for_table('tbl_customers')
->where('username', $username)
->order_by_desc('id')
->find_one();
$CheckUser = ORM::for_table('tbl_customers')
->where('phonenumber', $phone)
->find_many();
$UserId=$CheckId->id;
if (empty($bankaccount) || empty($bankname)) {
echo $error="<script>toastr.error('Could not complete the payment req, please contact admin');</script>";
die();
}
$getpaybill = ORM::for_table('tbl_banks')
->where('name', $bankname)
->find_one();
$paybill=$getpaybill->paybill;
// echo $paybill;
$cburl = U . 'callback/BankStkPush' ;
$PaymentGatewayRecord = ORM::for_table('tbl_payment_gateway')
->where('username', $username)
->where('status', 1) // Add this line to filter by status
->order_by_desc('id')
->find_one();
$ThisUser= ORM::for_table('tbl_customers')
->where('username', $username)
->order_by_desc('id')
->find_one();
$ThisUser->phonenumber=$phone;
// $ThisUser->username=$phone;
$ThisUser->save();
$amount=$PaymentGatewayRecord->price;
if(!$PaymentGatewayRecord){
echo $error="<script>toastr.error('Could not complete the payment req, please contact administrator');</script>";
die();
}
$consumerKey = '3AmVP1WFDQn7GrDH8GcSSKxcAvnJdZGC'; //Fill with your app Consumer Key
$consumerSecret = '71Lybl6jUtxM0F35'; // Fill with your app Secret
$headers = ['Content-Type:application/json; charset=utf8'];
$access_token_url = 'https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials';
$curl = curl_init($access_token_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_HEADER, FALSE);('');
curl_setopt($curl, CURLOPT_USERPWD, $consumerKey.':'.$consumerSecret);
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$result = json_decode($result);
$access_token = $result->access_token;
// echo $access_token;
curl_close($curl);
// Initiate Stk push
$stk_url = 'https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest';
$PartyA = $phone; // This is your phone number,
$AccountReference = $bankaccount;
$TransactionDesc = 'TestMapayment';
$Amount = $amount;
$BusinessShortCode='4122323';
$Passkey='aaebecea73082fa56af852606106b1316d5b4dfa2f12d0088800b0b88e4bb6e3';
$Timestamp = date("YmdHis",time());
$Password = base64_encode($BusinessShortCode.$Passkey.$Timestamp);
$CallBackURL = $cburl;
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $stk_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type:application/json','Authorization:Bearer '.$access_token)); //setting custom header
$curl_post_data = array(
//Fill in the request parameters with valid values
'BusinessShortCode' => $BusinessShortCode,
'Password' => $Password,
'Timestamp' => $Timestamp,
'TransactionType' => 'CustomerPayBillOnline',
'Amount' => $Amount,
'PartyA' => $PartyA,
'PartyB' => $paybill,
'PhoneNumber' => $PartyA,
'CallBackURL' => $CallBackURL,
'AccountReference' => $AccountReference,
'TransactionDesc' => $TransactionDesc
);
$data_string = json_encode($curl_post_data);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string);
$curl_response = curl_exec($curl);
//print_r($curl_response);
// echo $curl_response;
// die;
$mpesaResponse = json_decode($curl_response);
$responseCode = $mpesaResponse->ResponseCode;
$resultDesc = $mpesaResponse->resultDesc;
$MerchantRequestID = $mpesaResponse->MerchantRequestID;
$CheckoutRequestID = $mpesaResponse->CheckoutRequestID;
if($responseCode=="0"){
date_default_timezone_set('Africa/Nairobi');
$now=date("Y-m-d H:i:s");
// $username=$phone;
$PaymentGatewayRecord->pg_paid_response = $resultDesc;
$PaymentGatewayRecord->username = $username;
$PaymentGatewayRecord->checkout = $CheckoutRequestID;
$PaymentGatewayRecord->payment_method = 'Mpesa Stk Push';
$PaymentGatewayRecord->payment_channel = 'Mpesa Stk Push';
$PaymentGatewayRecord->save();
if(!empty($_POST['channel'])){
echo json_encode(["status" => "success", "message" => "Enter Pin to complete"]);
}else{
echo $error="<script>toastr.success('Enter Mpesa Pin to complete');</script>";
}
}else{
echo $error="<script>toastr.error('We could not complete the payment for you, please contact administrator');</script>";
}
}
?>

View File

@ -0,0 +1,148 @@
<?php
function initiatempesa()
{
$username = $_POST['username'];
$phone = $_POST['phone'];
$phone = (substr($phone, 0, 1) == '+') ? str_replace('+', '', $phone) : $phone;
$phone = (substr($phone, 0, 1) == '0') ? preg_replace('/^0/', '254', $phone) : $phone;
$phone = (substr($phone, 0, 1) == '7') ? preg_replace('/^7/', '2547', $phone) : $phone; //cater for phone number prefix 2547XXXX
$phone = (substr($phone, 0, 1) == '1') ? preg_replace('/^1/', '2541', $phone) : $phone; //cater for phone number prefix 2541XXXX
$phone = (substr($phone, 0, 1) == '0') ? preg_replace('/^01/', '2541', $phone) : $phone;
$phone = (substr($phone, 0, 1) == '0') ? preg_replace('/^07/', '2547', $phone) : $phone;
$CheckId = ORM::for_table('tbl_customers')
->where('username', $username)
->order_by_desc('id')
->find_one();
$CheckUser = ORM::for_table('tbl_customers')
->where('phonenumber', $phone)
->find_many();
$UserId = $CheckId->id;
$CallBackURL = U . 'callback/mpesa';
$PaymentGatewayRecord = ORM::for_table('tbl_payment_gateway')
->where('username', $username)
->where('status', 1) // Add this line to filter by status
->order_by_desc('id')
->find_one();
$ThisUser = ORM::for_table('tbl_customers')
->where('username', $username)
->order_by_desc('id')
->find_one();
$ThisUser->phonenumber = $phone;
$ThisUser->save();
$amount = $PaymentGatewayRecord->price;
if (!$PaymentGatewayRecord) {
echo json_encode(["status" => "error", "message" => "Could not complete the payment req, please contact administrator"]);
}
// Get the M-Pesa mpesa_env
$mpesa_env = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_env')
->find_one();
$mpesa_env = ($mpesa_env) ? $mpesa_env->value : null;
// Get the M-Pesa consumer key
$mpesa_consumer_key = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_consumer_key')
->find_one();
$mpesa_consumer_key = ($mpesa_consumer_key) ? $mpesa_consumer_key->value : null;
// Get the M-Pesa consumer secret
$mpesa_consumer_secret = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_consumer_secret')
->find_one();
$mpesa_consumer_secret = ($mpesa_consumer_secret) ? $mpesa_consumer_secret->value : null;
$mpesa_business_code = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_business_code')
->find_one();
$mpesa_business_code = ($mpesa_business_code) ? $mpesa_business_code->value : null;
$mpesa_shortcode_type = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_shortcode_type')
->find_one();
if ($mpesa_shortcode_type == 'BuyGoods') {
$mpesa_buygoods_till_number = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_buygoods_till_number')
->find_one();
$mpesa_buygoods_till_number = ($mpesa_buygoods_till_number) ? $mpesa_buygoods_till_number->value : null;
$PartyB = $mpesa_buygoods_till_number;
$Type_of_Transaction = 'CustomerBuyGoodsOnline';
} else {
$PartyB = $mpesa_business_code;
$Type_of_Transaction = 'CustomerPayBillOnline';
}
$Passkey = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_pass_key')
->find_one();
$Passkey = ($Passkey) ? $Passkey->value : null;
$Time_Stamp = date("Ymdhis");
$password = base64_encode($mpesa_business_code . $Passkey . $Time_Stamp);
if ($mpesa_env == "live") {
$OnlinePayment = 'https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest';
$Token_URL = 'https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials';
} elseif ($mpesa_env == "sandbox") {
$OnlinePayment = 'https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest';
$Token_URL = 'https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials';
} else {
return json_encode(["Message" => "invalid application status"]);
};
$headers = ['Content-Type:application/json; charset=utf8'];
$curl = curl_init($Token_URL);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_HEADER, FALSE);
curl_setopt($curl, CURLOPT_USERPWD, $mpesa_consumer_key . ':' . $mpesa_consumer_secret);
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$result = json_decode($result);
$access_token = $result->access_token;
curl_close($curl);
$password = base64_encode($mpesa_business_code . $Passkey . $Time_Stamp);
$stkpushheader = ['Content-Type:application/json', 'Authorization:Bearer ' . $access_token];
//INITIATE CURL
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $OnlinePayment);
curl_setopt($curl, CURLOPT_HTTPHEADER, $stkpushheader); //setting custom header
$curl_post_data = array(
//Fill in the request parameters with valid values
'BusinessShortCode' => $mpesa_business_code,
'Password' => $password,
'Timestamp' => $Time_Stamp,
'TransactionType' => $Type_of_Transaction,
'Amount' => $amount,
'PartyA' => $phone,
'PartyB' => $PartyB,
'PhoneNumber' => $phone,
'CallBackURL' => $CallBackURL,
'AccountReference' => $username,
'TransactionDesc' => 'Payment for ' . $username
);
$data_string = json_encode($curl_post_data);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string);
$curl_response = curl_exec($curl);
$curl_Tranfer2_response = json_decode($curl_response);
if (isset($curl_Tranfer2_response->ResponseCode) && $curl_Tranfer2_response->ResponseCode == "0") {
$resultDesc = $curl_Tranfer2_response->resultDesc;
$CheckoutRequestID = $curl_Tranfer2_response->CheckoutRequestID;
date_default_timezone_set('Africa/Nairobi');
$now = date("Y-m-d H:i:s");
// $username=$phone;
$PaymentGatewayRecord->pg_paid_response = $resultDesc;
$PaymentGatewayRecord->username = $username;
$PaymentGatewayRecord->checkout = $CheckoutRequestID;
$PaymentGatewayRecord->payment_method = 'Mpesa Stk Push';
$PaymentGatewayRecord->payment_channel = 'Mpesa Stk Push';
$saveGateway = $PaymentGatewayRecord->save();
if ($saveGateway) {
if (!empty($_POST['channel'])) {
echo json_encode(["status" => "success", "message" => "Enter Mpesa Pin to complete $mpesa_business_code $Type_of_Transaction , Party B: $PartyB, Amount: $amount, Phone: $phone, CheckoutRequestID: $CheckoutRequestID"]);
} else {
echo "<script>toastr.success('Enter Mpesa Pin to complete');</script>";
}
} else {
echo json_encode(["status" => "error", "message" => "Failed to save the payment gateway record"]);
}
} else {
$errorMessage = $curl_Tranfer2_response->errorMessage;
echo json_encode(["status" => "error", "message" => $errorMessage]);
}
}

View File

@ -0,0 +1,232 @@
<?php
function initiatetillstk()
{
$username=$_POST['username'];
$phone=$_POST['phone'];
$phone = (substr($phone, 0,1) == '+') ? str_replace('+', '', $phone) : $phone;
$phone = (substr($phone, 0,1) == '0') ? preg_replace('/^0/', '254', $phone) : $phone;
$phone = (substr($phone, 0,1) == '7') ? preg_replace('/^7/', '2547', $phone) : $phone; //cater for phone number prefix 2547XXXX
$phone = (substr($phone, 0,1) == '1') ? preg_replace('/^1/', '2541', $phone) : $phone; //cater for phone number prefix 2541XXXX
$phone = (substr($phone, 0,1) == '0') ? preg_replace('/^01/', '2541', $phone) : $phone;
$phone = (substr($phone, 0,1) == '0') ? preg_replace('/^07/', '2547', $phone) : $phone;
$consumer_key = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_till_consumer_key')
->find_one();
$consumer_secret = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_till_consumer_secret')
->find_one();
$consumer_secret = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_till_consumer_secret')
->find_one();
$BusinessShortCode= ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_till_shortcode_code')
->find_one();
$PartyB= ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_till_partyb')
->find_one();
$LipaNaMpesaPasskey= ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_till_pass_key')
->find_one();
$consumer_key = ($consumer_key) ? $consumer_key->value : null;
$consumer_secret = ($consumer_secret) ? $consumer_secret->value : null;
$BusinessShortCode = ($BusinessShortCode) ? $BusinessShortCode->value : null;
$PartyB = ($PartyB) ? $PartyB->value : null;
$LipaNaMpesaPasskey = ($LipaNaMpesaPasskey) ? $LipaNaMpesaPasskey->value : null;
$cburl = U . 'callback/MpesatillStk' ;
//
$CheckId = ORM::for_table('tbl_customers')
->where('username', $username)
->order_by_desc('id')
->find_one();
$CheckUser = ORM::for_table('tbl_customers')
->where('phonenumber', $phone)
->find_many();
$UserId=$CheckId->id;
$PaymentGatewayRecord = ORM::for_table('tbl_payment_gateway')
->where('username', $username)
->where('status', 1) // Add this line to filter by status
->order_by_desc('id')
->find_one();
$ThisUser= ORM::for_table('tbl_customers')
->where('username', $username)
->order_by_desc('id')
->find_one();
$ThisUser->phonenumber=$phone;
// $ThisUser->username=$phone;
$ThisUser->save();
$amount=$PaymentGatewayRecord->price;
if(!$PaymentGatewayRecord){
echo $error="<script>toastr.success('Unable to proess payment, please reload the page');</script>";
die();
}
$TransactionType = 'CustomerBuyGoodsOnline';
$tokenUrl = 'https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials';
$phone= $phone;
$lipaOnlineUrl = 'https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest';
// $amount= '1';
$CallBackURL = $cburl;
date_default_timezone_set('Africa/Nairobi');
$timestamp = date("YmdHis");
$password = base64_encode($BusinessShortCode . $LipaNaMpesaPasskey . $timestamp);
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $tokenUrl);
$credentials = base64_encode($consumer_key . ':' . $consumer_secret);
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Authorization: Basic ' . $credentials));
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
$curl_response = curl_exec($curl);
$token = json_decode($curl_response)->access_token;
$curl2 = curl_init();
curl_setopt($curl2, CURLOPT_URL, $lipaOnlineUrl);
curl_setopt($curl2, CURLOPT_HTTPHEADER, array('Content-Type:application/json', 'Authorization:Bearer ' . $token));
$curl2_post_data = [
'BusinessShortCode' => $BusinessShortCode,
'Password' => $password,
'Timestamp' => $timestamp,
'TransactionType' => $TransactionType,
'Amount' => $amount,
'PartyA' => $phone,
'PartyB' => $PartyB,
'PhoneNumber' => $phone,
'CallBackURL' => $CallBackURL,
'AccountReference' => 'Payment For Goods',
'TransactionDesc' => 'Payment for goods',
];
$data2_string = json_encode($curl2_post_data);
curl_setopt($curl2, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl2, CURLOPT_POST, true);
curl_setopt($curl2, CURLOPT_POSTFIELDS, $data2_string);
curl_setopt($curl2, CURLOPT_HEADER, false);
curl_setopt($curl2, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($curl2, CURLOPT_SSL_VERIFYHOST, 0);
$curl_response = curl_exec($curl2);
$curl_response1 = curl_exec($curl);
//($curl_response);
//echo $curl_response;
$mpesaResponse = json_decode($curl_response);
//echo $phone;
$responseCode = $mpesaResponse->ResponseCode;
$MerchantRequestID = $mpesaResponse->MerchantRequestID;
$CheckoutRequestID = $mpesaResponse->CheckoutRequestID;
$resultDesc = $mpesaResponse->CustomerMessage;
// file_put_contents('stk.log',$curl_response,FILE_APPEND);
// echo $cburl;
$responseCode = $responseCode;
if($responseCode=="0"){
date_default_timezone_set('Africa/Nairobi');
$now=date("Y-m-d H:i:s");
// $username=$phone;
$PaymentGatewayRecord->pg_paid_response = $resultDesc;
$PaymentGatewayRecord->checkout = $CheckoutRequestID;
$PaymentGatewayRecord->username = $username;
$PaymentGatewayRecord->payment_method = 'Mpesa Stk Push';
$PaymentGatewayRecord->payment_channel = 'Mpesa Stk Push';
$PaymentGatewayRecord->save();
if(!empty($_POST['channel'])){
echo json_encode(["status" => "success", "message" => "Enter Pin to complete","phone"=> $phone]);
}else{
echo $error="<script>toastr.success('Enter Mpesa Pin to complete');</script>";
}
}else{
echo "There is an issue with the transaction, please wait for 0 seconds then try again";
}
}

46
system/plugin/log.php Normal file
View File

@ -0,0 +1,46 @@
<?php
use PEAR2\Net\RouterOS;
use PEAR2\Net\RouterOS\Client;
use PEAR2\Net\RouterOS\Request;
// Fungsi untuk menampilkan log monitor
register_menu("Router Logs", true, "log_ui", 'NETWORK');
function log_ui() {
global $ui, $routes;
_admin();
$ui->assign('_title', 'Log Mikrotik');
$ui->assign('_system_menu', 'Log Mikrotik');
$admin = Admin::_info();
$ui->assign('_admin', $admin);
$routers = ORM::for_table('tbl_routers')->where('enabled', '1')->find_many();
$routerId = $routes['2'] ?? ($routers ? $routers[0]['id'] : null); // Memastikan ada router yang aktif
$logs = fetchLogs($routerId); // Mengambil log dari router yang dipilih
$ui->assign('logs', $logs);
$ui->display('log.tpl');
}
// Fungsi untuk mengambil logs dari MikroTik
function fetchLogs($routerId) {
if (!$routerId) {
return []; // Mengembalikan array kosong jika router tidak tersedia
}
$mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routerId);
if (!$mikrotik) {
return []; // Mengembalikan array kosong jika router tidak ditemukan
}
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$request = new Request('/log/print');
$response = $client->sendSync($request);
$logs = [];
foreach ($response as $entry) {
$logs[] = $entry->getIterator()->getArrayCopy(); // Mengumpulkan data dari setiap entry
}
return $logs;
}

View File

@ -0,0 +1,418 @@
<?php
use PEAR2\Net\RouterOS;
function pppoe()
{
global $ui, $routes;
_admin();
$ui->assign('_title', 'PPPoE Monitor');
$ui->assign('_system_menu', 'PPPoE Monitor');
$admin = Admin::_info();
$ui->assign('_admin', $admin);
$routers = ORM::for_table('tbl_routers')->where('enabled', '1')->find_many();
$router = $routes['2'] ?? $routers[0]['id'];
$ui->assign('routers', $routers);
$ui->assign('router', $router);
$ui->assign('interfaces', pppoe_monitor_router_getInterface());
$ui->display('pppoe_monitor.tpl');
}
function pppoe_monitor_router_getInterface()
{
global $routes;
$routerId = $routes['2'] ?? null;
if (!$routerId) {
return [];
}
$mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routerId);
if (!$mikrotik) {
return [];
}
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$interfaces = $client->sendSync(new RouterOS\Request('/interface/print'));
$interfaceList = [];
foreach ($interfaces as $interface) {
$name = $interface->getProperty('name');
$interfaceList[] = $name; // Jangan menghapus karakter < dan > dari nama interface
}
return $interfaceList;
}
function pppoe_get_combined_users() {
global $routes;
$router = $routes['2'];
$mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($router);
if (!$mikrotik) {
header('Content-Type: application/json');
echo json_encode(['error' => 'Router not found']);
return;
}
try {
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
// Fetch PPP online users
$pppUsers = $client->sendSync(new RouterOS\Request('/ppp/active/print'));
$interfaceTraffic = $client->sendSync(new RouterOS\Request('/interface/print'));
$pppSecrets = $client->sendSync(new RouterOS\Request('/ppp/secret/print'));
$interfaceData = [];
foreach ($interfaceTraffic as $interface) {
$name = $interface->getProperty('name');
if (empty($name)) {
continue;
}
$interfaceData[$name] = [
'status' => $interface->getProperty('running') === 'true' ? 'Connected' : 'Disconnected',
'txBytes' => intval($interface->getProperty('tx-byte')),
'rxBytes' => intval($interface->getProperty('rx-byte')),
];
}
$pppUserList = [];
foreach ($pppUsers as $pppUser) {
$username = $pppUser->getProperty('name');
if (empty($username)) {
continue;
}
$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');
$id = $pppUser->getProperty('.id');
$interfaceName = "<pppoe-$username>";
if (isset($interfaceData[$interfaceName])) {
$trafficData = $interfaceData[$interfaceName];
$txBytes = $trafficData['txBytes'];
$rxBytes = $trafficData['rxBytes'];
$status = $trafficData['status'];
} else {
$txBytes = 0;
$rxBytes = 0;
$status = 'Disconnected';
}
// Get device information
$manufacturer = "Unknown";
if ($callerid) {
$manufacturer = get_manufacturer_from_mac($callerid);
}
// Check if MAC is bound in ppp secrets
$isBound = false;
foreach ($pppSecrets as $secret) {
if ($secret->getProperty('name') === $username && $secret->getProperty('caller-id') === $callerid) {
$isBound = true;
break;
}
}
$pppUserList[$username] = [
'id' => $id,
'username' => $username,
'address' => $address,
'uptime' => $uptime,
'service' => $service,
'caller_id' => $callerid,
'bytes_in' => $bytes_in,
'bytes_out' => $bytes_out,
'tx' => pppoe_monitor_router_formatBytes($txBytes),
'rx' => pppoe_monitor_router_formatBytes($rxBytes),
'total' => pppoe_monitor_router_formatBytes($txBytes + $rxBytes),
'status' => $status,
'manufacturer' => $manufacturer,
'is_bound' => $isBound
];
}
// Convert the user list to a regular array for JSON encoding
$userList = array_values($pppUserList);
// Return the combined user list as JSON
header('Content-Type: application/json');
echo json_encode($userList);
} catch (Exception $e) {
header('Content-Type: application/json');
echo json_encode(['error' => $e->getMessage()]);
}
}
function get_manufacturer_from_mac($mac_address) {
// Normalize the MAC address
$mac_address = strtoupper(preg_replace('/[^A-F0-9]/', '', $mac_address));
// Check if MAC address is valid (at least 6 hex characters required)
if (strlen($mac_address) < 6) {
return 'Invalid MAC address';
}
// Construct the API URL
$url = "https://www.macvendorlookup.com/api/v2/{$mac_address}";
// Initialize cURL session
$ch = curl_init();
// Set cURL options
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // For testing purposes, handle SSL properly in production
// Execute cURL request
$response = curl_exec($ch);
// Check for cURL errors
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
return "Error: $error";
}
// Get HTTP response code
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Check if API returned a valid response
if ($http_code === 204) {
return 'Unknown';
}
// Decode JSON response
$data = json_decode($response, true);
// Check if the response contains manufacturer information
if (isset($data[0]['company'])) {
return trim($data[0]['company']);
} else {
return 'Unknown';
}
}
function pppoe_monitor_router_formatMaxLimit($max_limit) {
$limits = explode('/', $max_limit);
if (count($limits) == 2) {
$downloadLimit = intval($limits[0]);
$uploadLimit = intval($limits[1]);
$formattedDownloadLimit = ceil($downloadLimit / (1024 * 1024)) . ' MB';
$formattedUploadLimit = ceil($uploadLimit / (1024 * 1024)) . ' MB';
return $formattedDownloadLimit . '/' . $formattedUploadLimit;
}
return 'N/A';
}
// Fungsi untuk menghitung total data yang digunakan per harinya
function pppoe_monitor_router_formatBytes($bytes, $precision = 2)
{
$units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
$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 pppoe_monitor_router_traffic()
{
$interface = $_GET["interface"]; // Ambil interface dari parameter GET
// Contoh koneksi ke MikroTik menggunakan library tertentu (misalnya menggunakan ORM dan MikroTik API wrapper)
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']);
try {
$results = $client->sendSync(
(new RouterOS\Request('/interface/monitor-traffic'))
->setArgument('interface', $interface)
->setArgument('once', '')
);
$rows = array();
$rows2 = array();
$labels = array();
foreach ($results as $result) {
$ftx = $result->getProperty('tx-bits-per-second');
$frx = $result->getProperty('rx-bits-per-second');
// Timestamp dalam milidetik (millisecond)
$timestamp = time() * 1000;
$rows[] = $ftx;
$rows2[] = $frx;
$labels[] = $timestamp; // Tambahkan timestamp ke dalam array labels
}
$result = array(
'labels' => $labels,
'rows' => array(
'tx' => $rows,
'rx' => $rows2
)
);
} catch (Exception $e) {
$result = array('error' => $e->getMessage());
}
// Set header untuk respons JSON
header('Content-Type: application/json');
echo json_encode($result);
}
function pppoe_monitor_router_online()
{
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']);
$pppUsers = $client->sendSync(new RouterOS\Request('/ppp/active/print'));
$pppoeInterfaces = [];
foreach ($pppUsers as $pppUser) {
$username = $pppUser->getProperty('name');
$interfaceName = "<pppoe-$username>"; // Tambahkan karakter < dan >
// Ensure interface name is not empty and it's not already in the list
if (!empty($interfaceName) && !in_array($interfaceName, $pppoeInterfaces)) {
$pppoeInterfaces[] = $interfaceName;
}
}
// Return the list of PPPoE interfaces
return $pppoeInterfaces;
}
function pppoe_monitor_router_delete_ppp_user()
{
global $routes;
$router = $routes['2'];
$id = $_POST['id']; // Ambil .id dari POST data
// Cek apakah ID ada di POST data
if (empty($id)) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'message' => 'ID is missing.']);
return;
}
// Ambil detail router dari database
$mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($router);
if (!$mikrotik) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'message' => 'Router not found.']);
return;
}
// Dapatkan klien MikroTik
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
if (!$client) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'message' => 'Failed to connect to the router.']);
return;
}
try {
// Buat permintaan untuk menghapus koneksi aktif PPPoE
$request = new RouterOS\Request('/ppp/active/remove');
$request->setArgument('.id', $id); // Gunakan .id yang sesuai
$client->sendSync($request);
header('Content-Type: application/json');
echo json_encode(['success' => true, 'message' => 'PPPoE user successfully deleted.']);
} catch (Exception $e) {
// Log error untuk debugging
error_log('Failed to delete PPPoE user: ' . $e->getMessage());
header('Content-Type: application/json');
echo json_encode(['success' => false, 'message' => 'Failed to delete PPPoE user: ' . $e->getMessage()]);
}
}
// ======================================================================
// NEW FUNCTIONS:
// Fungsi untuk menghitung total data yang digunakan per harinya
function pppoe_monitor_router_daily_data_usage()
{
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']);
// Ambil semua pengguna aktif PPPoE
$pppUsers = $client->sendSync(new RouterOS\Request('/ppp/active/print'));
$interfaceTraffic = $client->sendSync(new RouterOS\Request('/interface/print'));
// Array untuk menyimpan data penggunaan harian
$daily_usage = [];
// Looping untuk setiap pengguna PPPoE
foreach ($pppUsers as $pppUser) {
$username = $pppUser->getProperty('name');
$interfaceName = "<pppoe-$username>"; // Nama interface sesuai format PPPoE
// Ambil data traffic untuk interface ini
$interfaceData = [];
foreach ($interfaceTraffic as $interface) {
$name = $interface->getProperty('name');
if ($name === $interfaceName) {
$interfaceData = [
'txBytes' => intval($interface->getProperty('tx-byte')),
'rxBytes' => intval($interface->getProperty('rx-byte'))
];
break;
}
}
// Hitung total penggunaan harian
$txBytes = $interfaceData['txBytes'] ?? 0;
$rxBytes = $interfaceData['rxBytes'] ?? 0;
$totalDataMB = ($txBytes + $rxBytes) / (1024 * 1024); // Konversi ke MB
// Ambil tanggal dari waktu saat ini
$date = date('Y-m-d', time());
// Jika belum ada data untuk tanggal ini, inisialisasi
if (!isset($daily_usage[$date])) {
$daily_usage[$date] = [
'total' => 0,
'users' => []
];
}
// Tambahkan penggunaan harian untuk pengguna ini
$daily_usage[$date]['total'] += $totalDataMB;
$daily_usage[$date]['users'][] = [
'username' => $username,
'tx' => pppoe_monitor_router_formatBytes($txBytes),
'rx' => pppoe_monitor_router_formatBytes($rxBytes),
'total' => pppoe_monitor_router_formatBytes($txBytes + $rxBytes)
];
}
// Kembalikan hasil dalam format JSON
header('Content-Type: application/json');
echo json_encode($daily_usage); // $daily_usage adalah array yang berisi data harian dalam format yang sesuai
}
// Fungsi untuk mendapatkan pengguna terbatas pada MikroTik

BIN
system/plugin/ui/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,162 @@
{include file="sections/header.tpl"}
<style>
/* Styles for overall layout and responsiveness */
body {
background-color: #f8f9fa;
font-family: 'Arial', sans-serif;
}
.container {
margin-top: 20px;
background-color: #d8dfe5;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 20px;
max-width: 98%;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
}
/* Styles for table and pagination */
.table {
width: 100%;
margin-bottom: 1rem;
background-color: #fff;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.table th {
vertical-align: middle;
border-color: #dee2e6;
background-color: #343a40;
color: #fff;
}
.table td {
vertical-align: middle;
border-color: #dee2e6;
}
.table-striped tbody tr:nth-of-type(odd) {
background-color: rgba(0, 0, 0, 0.05);
}
.table-hover tbody tr:hover {
background-color: rgba(0, 0, 0, 0.075);
color: #333;
font-weight: bold;
transition: background-color 0.3s, color 0.3s;
}
.pagination .page-item .page-link {
color: #007bff;
background-color: #fff;
border: 1px solid #dee2e6;
margin: 0 2px;
padding: 6px 12px;
transition: background-color 0.3s, color 0.3s;
}
.pagination .page-item .page-link:hover {
background-color: #e9ecef;
color: #0056b3;
}
.pagination .page-item.active .page-link {
z-index: 1;
color: #fff;
background-color: #007bff;
border-color: #007bff;
}
.dataTables_wrapper .dataTables_paginate .paginate_button {
display: inline-block;
padding: 5px 10px;
margin-right: 5px;
border: 1px solid #ccc;
background-color: #fff;
color: #333;
cursor: pointer;
}
</style>
{if isset($message)}
<div class="alert alert-{if $notify_t == 's'}success{else}danger{/if}">
<button type="button" class="close" data-dismiss="alert">
<span aria-hidden="true">×</span>
</button>
<div>{$message}</div>
</div>
{/if}
<div class="col-md-14">
<!-- LINE CHART -->
<div class="box box-info">
<div class="box-header with-border">
<h3 class="box-title">{Lang::T('Payment History')}</h3>
<div class="box-tools pull-right">
<button type="button" class="btn bg-teal btn-sm" data-widget="collapse"><i class="fa fa-refresh"></i>
</button>
<a href="{$app_url}/pages/mpesa-webhook.html" class="btn bg-teal btn-sm"><i class="fa fa-file"></i>
</a>
</div>
</div>
<div class="box-body">
<div class="container">
<div class="table-responsive">
<table class="table table-bordered table-striped" id="payments-table">
<thead>
<tr>
<th>{Lang::T('Customer Name')}</th>
<th>{Lang::T('Transaction Type')}</th>
<th>{Lang::T('Transaction Time')}</th>
<th>{Lang::T('Amount Paid')}</th>
<th>{Lang::T('Package Name')}</th>
<th>{Lang::T('Package Price')}</th>
<th>{Lang::T('Status')}</th>
<th>{Lang::T('Bill Ref Number')}</th>
<th>{Lang::T('Company Balance')}</th>
<th>{Lang::T('Date')}</th>
</tr>
</thead>
<tbody>
{foreach $payments as $payment}
<tr>
<td><a href="{$app_url}/index.php?_route=customers/view/{$payment.CustomerID}">{$payment.FirstName}</a></td>
<td>{$payment.TransactionType}</td>
<td>{$payment.TransTime}</td>
<td>{$payment.TransAmount}</td>
<td>{$payment.PackageName}</td>
<td>{$payment.PackagePrice}</td>
<td><span
class="label {if $payment.TransactionStatus == Completed}label-success {elseif $payment.TransactionStatus == Pending}label-warning {/if}">{$payment.TransactionStatus}</span>
</td>
<td>{$payment.BillRefNumber}</td>
<td>{$payment.OrgAccountBalance}</td>
<td>{$payment.CreatedAt}</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
</div>
</div>
<!-- /.box-body -->
</div>
<!-- /.box -->
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.datatables.net/1.11.3/js/jquery.dataTables.min.js"></script>
<script>
var $j = jQuery.noConflict();
$j(document).ready(function () {
$j('#payments-table').DataTable({
"pagingType": "full_numbers"
});
});
</script>
{include file="sections/footer.tpl"}

View File

@ -0,0 +1,234 @@
{include file="sections/header.tpl"}
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<style>
.styled-form-group {
margin-bottom: 20px;
}
.styled-btn {
color: #28a745;
border: 1px solid #28a745;
background-color: #fff;
padding: 10px 20px;
font-size: 16px;
text-align: center;
text-decoration: none;
display: inline-block;
transition: all 0.3s ease;
}
.styled-btn:hover {
background-color: #28a745;
color: #fff;
}
.styled-small-text {
color: blue;
margin-top: 10px;
display: block;
font-size: 14px;
}
.switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
/* Hidden checkbox */
.switch input {
opacity: 0;
width: 0;
height: 0;
}
/* Slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
border-radius: 24px;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
border-radius: 50%;
}
input:checked+.slider {
background-color: #2196F3;
}
input:focus+.slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked+.slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
</style>
{if isset($message)}
<div class="alert alert-{if $notify_t == 's'}success{else}danger{/if}">
<button type="button" class="close" data-dismiss="alert">
<span aria-hidden="true">×</span>
</button>
<div>{$message}</div>
</div>
{/if}
<form class="form-horizontal" method="post" role="form" action="{$_url}plugin/c2b_settings">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="panel-heading">{Lang::T('M-Pesa C2B Payment Gateway')}</div>
<div class="panel-body">
<div class="form-group col-6">
<label class="col-md-3 control-label">{Lang::T('M-Pesa C2B Environment')}</label>
<div class="col-md-6">
<select class="form-control" name="mpesa_c2b_env" id="mpesa_c2b_env">
<option value="sandbox" {if $_c['mpesa_c2b_env']=='sandbox' }selected{/if}>
{Lang::T('SandBox or
Testing')}</option>
<option value="live" {if $_c['mpesa_c2b_env']=='live' }selected{/if}>{Lang::T('Live or
Production')}
</option>
</select>
<small class="form-text text-muted">
<font color="red"><b>{Lang::T('Sandbox')}</b></font> {Lang::T('is for testing purpose,
please switch to')} <font color="green"><b>{Lang::T('Live')}</b></font> {Lang::T('in
production.')}
</small>
</div>
</div>
<div class="form-group col-6">
<label class="col-md-3 control-label">M-Pesa C2B Consumer Key</label>
<div class="col-md-6">
<input type="text" class="form-control" id="mpesa_c2b_consumer_key"
name="mpesa_c2b_consumer_key" placeholder="xxxxxxxxxxxxxxxxx"
value="{$_c['mpesa_c2b_consumer_key']}">
<small class="form-text text-muted"><a href="https://developer.safaricom.co.ke/MyApps"
target="_blank">https://developer.safaricom.co.ke/MyApps</a></small>
</div>
</div>
<div class="form-group col-6">
<label class="col-md-3 control-label">M-Pesa C2B Consumer Secret</label>
<div class="col-md-6">
<input type="password" class="form-control" id="mpesa_c2b_consumer_secret"
name="mpesa_c2b_consumer_secret" placeholder="xxxxxxxxxxxxxxxxx"
value="{$_c['mpesa_c2b_consumer_secret']}">
</div>
</div>
<div class="form-group col-6">
<label class="col-md-3 control-label">M-Pesa C2B Business Shortcode</label>
<div class="col-md-6">
<input type="text" class="form-control" id="mpesa_c2b_business_code"
name="mpesa_c2b_business_code" placeholder="xxxxxxx" maxlength="7"
value="{$_c['mpesa_c2b_business_code']}">
</div>
</div>
<div class="form-group col-6">
<label class="col-md-3 control-label">M-Pesa C2B Version</label>
<div class="col-md-6">
<select class="form-control" name="mpesa_c2b_api">
<option value="v1" {if $_c['mpesa_c2b_api']=='v1' }selected{/if}>v1</option>
<option value="v2" {if $_c['mpesa_c2b_api']=='v2' }selected{/if}>v2</option>
</select>
<small class="form-text text-muted">Select the version of the API you want to
use.</small>
</div>
</div>
<div class="form-group col-6">
<label class="col-md-3 control-label">Bill Ref Number Type</label>
<div class="col-md-6">
<select class="form-control" name="mpesa_c2b_bill_ref">
<option value="phone" {if $_c['mpesa_c2b_bill_ref']=='phone' }selected{/if}>Phone Number</option>
<option value="username" {if $_c['mpesa_c2b_bill_ref']=='username' }selected{/if}>Username</option>
<option value="id" {if $_c['mpesa_c2b_bill_ref']=='id' }selected{/if}>Account ID</option>
</select>
<small class="form-text text-muted">How will the system identify your customers. BillRefNumber must be a unique identity</small>
</div>
</div>
<div class="form-group col-6">
<label class="col-md-3 control-label">{Lang::T('Accept Insufficient Fee')}</label>
<div class="col-md-6">
<label class="switch">
<input type="checkbox" id="mpesa_c2b_low_fee" value="1"
name="mpesa_c2b_low_fee" {if $_c['mpesa_c2b_low_fee']==1}checked{/if}>
<span class="slider"></span>
</label>
</div>
</div>
{if $_c['c2b_registered'] && $_c['mpesa_c2b_env']!='sandbox'}
<div class="form-group col-12 styled-form-group">
<label class="col-md-3 control-label">Register C2B URL</label>
<div class="col-md-6">
<button class="btn styled-btn">URLs Already Registered</button>
</div>
</div>
{else}
<div class="form-group col-12 styled-form-group">
<label class="col-md-3 control-label">Register C2B URL</label>
<div class="col-md-6">
<a href="{$_url}plugin/c2b_registerUrl" class="btn styled-btn">Click to Register Mpesa
C2B URL</a>
<small class="form-text text-muted styled-small-text">Click only after you have saved
the changes.</small>
</div>
</div>
{/if}
<div class="form-group col-6">
<div class="col-lg-offset-3 col-lg-10">
<button class="btn btn-primary waves-effect waves-light" name="save" value="save"
type="submit">Save Changes</button>
</div>
</div>
<div class="bs-callout bs-callout-info" id="callout-navbar-role">
<h4><b>Accept Insufficient Fee</b>:</h4>
<p> If Enable the money customer sent will be convert to customer balance, but if disabled the system will reject it. <br> It requires Validation URL</p>
<h4><b>Note</b>:</h4>
<p> Before click on Register URL <br>
Make sure you have fill the required fields <br>
</p>
</div>
</div>
</div>
</div>
</div>
</form>
<script>
$(document).ready(function () {
toggleTillNumberInput();
$('#mpesa_c2b_transaction').on('change', function () {
toggleTillNumberInput();
});
function toggleTillNumberInput() {
if ($('#mpesa_c2b_transaction').val() === 'BuyGoods') {
$('#tillNumberContainer').show();
} else {
$('#tillNumberContainer').hide();
}
}
});
</script>
{include file="sections/footer.tpl"}

View File

@ -0,0 +1,193 @@
{include file="sections/header.tpl"}
<section class="content-header">
<h1>
<div class="btn-group">
<button type="button" class="btn btn-success">
Captive Portal Settings
</button>
<button
type="button"
class="btn btn-success dropdown-toggle"
data-toggle="dropdown"
>
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="{$_url}plugin/captive_portal_settings">{Lang::T('General Settings')}</a></li>
<li>
<a href="{$_url}plugin/captive_portal_slider"
>{Lang::T('Manage Sliders')}</a
>
</li>
<li><a href="#">{Lang::T('Manage Advertisements')}</a></li>
<li><a href="#">{Lang::T('Manage Authorizations')}</a></li>
<li><a href="#">{Lang::T('Reports')}</a></li>
<li class="divider"></li>
<li>
<a
href="{$_url}plugin/captive_portal_login"
target="”_blank”"
>Preview Member Landing Page</a
>
</li>
<li>
<a
href="{$_url}plugin/captive_portal_download_login"
target="”_blank”"
> Download Login Page </a
>
</li>
</ul>
</div>
</h1>
<ol class="breadcrumb">
<li>
<a href="{$_url}plugin/captive_portal_overview"><i class="fa fa-dashboard"></i> Captive Portal</a>
</li>
<li class="active"> General Settings</li>
</ol>
</section>
<section class="content">
<div class="table-responsive">
<div class="nav-tabs-custom">
<ul class="nav nav-tabs">
<li class="active">
<a href="#tab_1" data-toggle="tab">{Lang::T('General Settings')}</a>
</li>
<li>
<a href="#tab_2" data-toggle="tab">{Lang::T('Customization')}</a>
</li>
<li>
<a href="#tab_3" data-toggle="tab">{Lang::T('Slider Settings')}</a>
</li>
<li>
<a href="#tab_4" data-toggle="tab">{Lang::T('Advertisement Settings')}</a>
</li>
<li>
<a href="#tab_5" data-toggle="tab">{Lang::T('Trial Authorization Settings')}</a>
</li>
<li>
<a href="#tab_6" data-toggle="tab">{Lang::T('Pages Settings')}</a>
</li>
</ul>
<div class="tab-content">
<div style="overflow-x:auto;" class="tab-pane active" id="tab_1">
<div class="box-body no-padding" id="">
<form method="POST" action="" enctype="multipart/form-data">
<div class="box-body">
<div class="form-group">
<label for="">Hotspot Page Title</label>
<input type="text" class="form-control" name="title" id="title" value="{$settings.hotspot_title}" required>
<small class="form-text text-muted">Hotspot Title will be display on Login Page Head Tag</small>
</div>
<div class="form-group">
<label for="">Hotspot Name</label>
<input type="text" class="form-control" name="name" id="name" value="{$settings.hotspot_name}" required>
<small class="form-text text-muted">Hotspot Name will be display on Login Page Nav Bar if Logo is not available</small>
</div>
<div class="form-group">
<label for="favicon">Favicon</label>
<input type="file" class="form-control" name="favicon" id="favicon" accept="image/x-icon, image/png, image/jpeg, image/gif" onchange="previewImage('favicon', 'favicon-preview')">
<small class="form-text text-muted">Favicon will be display on Login Page browser tab, its placed in head section</small>
<br>
<img id="favicon-preview" src="{$settings.favicon}" alt="Favicon Preview" style="max-width: 32px; max-height: 32px;">
</div>
<div class="form-group">
<label for="logo">Logo</label>
<input type="file" class="form-control" name="logo" id="logo" accept="image/png, image/jpeg, image/svg+xml" onchange="previewImage('logo', 'logo-preview')">
<small class="form-text text-muted">Logo will be display on Login Page Nav Bar section</small>
<br>
<img id="logo-preview" src="{$settings.logo}" alt="Logo Preview" style="max-width: 200px; max-height: 200px;">
</div>
<div class="form-group">
<label class="">{Lang::T('Allow Free Trial')}</label>
<div class="form-group">
<select name="trial" id="trial" class="form-control">
<option value="no" {if {$settings.hotspot_trial}=='no' }selected="selected" {/if}>No
</option>
<option value="yes" {if {$settings.hotspot_trial}=='yes' }selected="selected" {/if}>Yes
</option>
</select>
<small class="form-text text-muted"><ul>
<li>Choose No if you dont want to allow Free Trial </li>
<li>Make sure you enable free trial in Mikrotik Router</li>
<li>free trial button won't display
on captive portal preview, but will work if you connect from hotspot</li>
</ul></small>
</div>
</div>
<div class="form-group">
<label class="">{Lang::T('Allow Member Login')}</label>
<div class="form-group">
<select name="member" id="member" class="form-control">
<option value="no" {if {$settings.hotspot_member}=='no' }selected="selected" {/if}>No
</option>
<option value="yes" {if {$settings.hotspot_member}=='yes' }selected="selected" {/if}>Yes
</option>
</select>
<small class="form-text text-muted">Choose No If you want to disable Member Login</small>
</div>
</div>
</div>
<div class="box-footer">
<a href="{$_url}plugin/captive_portal_overview" class="btn btn-default">Cancel</a>
<button type="submit" class="btn btn-info pull-right">Save Changes</button>
</div>
</form>
</div>
</div>
<!-- /.tab-pane -->
<div class="tab-pane" style="overflow-x:auto;" id="tab_2">
<div class="box-body no-padding" id="">
This feature will be available on Pro Version
</div>
</div>
<!-- /.tab-pane -->
<div style="overflow-x:auto;" class="tab-pane" id="tab_3">
<div class="box-body no-padding" id="">
This feature will be available on Pro Version
</div>
</div>
<div style="overflow-x:auto;" class="tab-pane" id="tab_4">
<div class="box-body no-padding" id="">
This feature will be available on Pro Version
</div>
</div>
<div style="overflow-x:auto;" class="tab-pane" id="tab_5">
<div class="box-body no-padding" id="">
This feature will be available on Pro Version
</div>
</div>
<div style="overflow-x:auto;" class="tab-pane" id="tab_6">
<div class="box-body no-padding" id="">
This feature will be available on Pro Version
</div>
</div>
</div>
</div>
<div>
<pre><b>USAGE:</b>
<br>Upload your sliders in Slider Setting
<br>Go General Settings and setup as per your requirements
<br>Then download your the login.html by clicking on download login page
<br>Then upload the downloaded login.html file to your mikrotik router
<br>Make sure you add your webiste URL in mikrotik hotspot wall garden
<br>If your website is https i will suggest you to add certificate to your router
</pre>
</div>
</section>
<script>
window.addEventListener('DOMContentLoaded', function() {
var portalLink = "https://github.com/focuslinkstech";
$('#version').html('Captive Portal Plugin by: <a href="' + portalLink + '">Focuslinks Tech</a>');
});
</script>
{include file="sections/footer.tpl"}

View File

@ -0,0 +1,559 @@
<?php
include '../../config.php';
$mysqli = new mysqli($db_host, $db_user, $db_password, $db_name);
if ($mysqli->connect_error) {
die("Connection failed: " . $mysqli->connect_error);
}
// Function to get a setting value
function getSettingValue($mysqli, $setting) {
$query = $mysqli->prepare("SELECT value FROM tbl_appconfig WHERE setting = ?");
$query->bind_param("s", $setting);
$query->execute();
$result = $query->get_result();
if ($row = $result->fetch_assoc()) {
return $row['value'];
}
return '';
}
// Fetch hotspot title and description from tbl_appconfig
$hotspotTitle = getSettingValue($mysqli, 'hotspot_title');
$description = getSettingValue($mysqli, 'description');
$phone = getSettingValue($mysqli, 'phone');
$company = getSettingValue($mysqli, 'CompanyName');
// Fetch settings
$settings = [];
$settings['frequently_asked_questions_headline1'] = getSettingValue($mysqli, 'frequently_asked_questions_headline1');
$settings['frequently_asked_questions_answer1'] = getSettingValue($mysqli, 'frequently_asked_questions_answer1');
$settings['frequently_asked_questions_headline2'] = getSettingValue($mysqli, 'frequently_asked_questions_headline2');
$settings['frequently_asked_questions_answer2'] = getSettingValue($mysqli, 'frequently_asked_questions_answer2');
$settings['frequently_asked_questions_headline3'] = getSettingValue($mysqli, 'frequently_asked_questions_headline3');
$settings['frequently_asked_questions_answer3'] = getSettingValue($mysqli, 'frequently_asked_questions_answer3');
// Fetch router name and router ID from tbl_appconfig
$routerName = getSettingValue($mysqli, 'router_name');
$routerId = getSettingValue($mysqli, 'router_id');
// Fetch available plans
$planQuery = "SELECT id, name_plan, price, validity, validity_unit FROM tbl_plans WHERE routers = ? AND type = 'Hotspot'";
$planStmt = $mysqli->prepare($planQuery);
$planStmt->bind_param("s", $routerName);
$planStmt->execute();
$planResult = $planStmt->get_result();
// Initialize HTML content variable
$htmlContent = "<!DOCTYPE html>\n";
$htmlContent .= "<html lang=\"en\">\n";
$htmlContent .= "<head>\n";
$htmlContent .= " <meta charset=\"UTF-8\">\n";
$htmlContent .= " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n";
$htmlContent .= " <title>" . htmlspecialchars($hotspotTitle) . " Hotspot Template - Index</title>\n";
$htmlContent .= " <script src=\"https://cdn.tailwindcss.com\"></script>\n";
$htmlContent .= " <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css\">\n";
$htmlContent .= " <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/glider-js@1.7.7/glider.min.css\" />\n";
$htmlContent .= " <script src=\"https://cdn.jsdelivr.net/npm/glider-js@1.7.7/glider.min.js\"></script>\n";
$htmlContent .= " <link rel=\"preconnect\" href=\"https://cdn.jsdelivr.net\">\n";
$htmlContent .= " <link rel=\"preconnect\" href=\"https://cdnjs.cloudflare.com\" crossorigin>\n";
$htmlContent .= " <link rel=\"stylesheet\" type=\"text/css\" href=\"styles.css\">\n";
$htmlContent .= "</head>\n";
$htmlContent .= "<body class=\"font-sans antialiased text-gray-900\">\n";
$htmlContent .= " <!-- Sticky Header -->\n";
$htmlContent .= " <header class=\"bg-pink-600 text-white fixed w-full z-10\">\n";
$htmlContent .= " <div class=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-5\">\n";
$htmlContent .= " <div class=\"flex items-center justify-between h-16\">\n";
$htmlContent .= " <!-- Logo and title area -->\n";
$htmlContent .= " <div class=\"flex items-center\">\n";
$htmlContent .= " <img src=\"logo.png\" alt=\"Your Company Logo\" class=\"h-8 w-8 mr-2\">\n";
$htmlContent .= " <h1 class=\"text-xl font-bold\">" . htmlspecialchars($hotspotTitle) . " Hotspot Login Page</h1>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <!-- Navigation Links -->\n";
$htmlContent .= " <div class=\"block\">\n";
$htmlContent .= " <div class=\"ml-10 flex items-baseline space-x-4\">\n";
$htmlContent .= " <a href=\"#\" class=\"text-teal-200 hover:text-white px-3 py-2 rounded-md text-sm font-medium\">Already Have an Account? Login</a>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </header>\n";
$htmlContent .= " <!-- Main content -->\n";
$htmlContent .= " <main class=\"pt-24\">\n";
$htmlContent .= " <section class=\"bg-white\">\n";
$htmlContent .= " <div class=\"max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8\">\n";
$htmlContent .= " <h2 class=\"text-3xl font-extrabold text-gray-900 mb-6\">" . htmlspecialchars($description) . "</h2>\n";
$htmlContent .= " <!-- Pricing Section -->\n";
$htmlContent .= " <div class=\"mt-10\">\n";
$htmlContent .= " <div class=\"text-center\">\n";
$htmlContent .= " <h3 class=\"text-2xl leading-8 font-extrabold tracking-tight text-gray-900 sm:text-3xl sm:leading-9\">\n";
$htmlContent .= " CHECK OUR PRICING\n";
$htmlContent .= " </h3>\n";
$htmlContent .= " <p class=\"mt-4 max-w-2xl text-xl leading-7 text-gray-500 lg:mx-auto\">\n";
$htmlContent .= " Choose the plan that fits your needs.\n";
$htmlContent .= " </p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </section>\n";
$htmlContent .= " </main>\n";
$htmlContent .= "<div class=\"mt-10 max-w-7xl mx-auto grid grid-cols-2 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-4 gap-5\">\n";
while ($plan = $planResult->fetch_assoc()) {
$htmlContent .= " <div class=\"flex flex-col rounded-lg shadow-xl overflow-hidden transform transition duration-500 hover:scale-105\">\n";
$htmlContent .= " <div class=\"px-4 py-5 bg-gradient-to-tr from-pink-50 to-pink-200 text-center\">\n";
$htmlContent .= " <span class=\"inline-flex px-3 py-1 rounded-full text-xs font-semibold tracking-wide uppercase bg-pink-800 text-pink-50\">\n";
$htmlContent .= htmlspecialchars($plan['name_plan']) . "\n";
$htmlContent .= " </span>\n";
$htmlContent .= " <div class=\"mt-4 text-4xl leading-none font-extrabold text-pink-800\">\n";
$htmlContent .= " <span class=\"text-lg font-medium text-pink-600\">ksh</span>\n";
$htmlContent .= htmlspecialchars($plan['price']) . "\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <p class=\"mt-2 text-md leading-5 text-pink-700 text-center\">\n";
$htmlContent .= htmlspecialchars($plan['validity']) . " " . htmlspecialchars($plan['validity_unit']) . " Unlimited\n";
$htmlContent .= " </p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"px-4 pt-4 pb-6 bg-pink-500 text-center\">\n";
$htmlContent .= " <a href=\"#\" class=\"inline-block text-pink-800 bg-pink-50 hover:bg-pink-100 focus:outline-none focus:ring-4 focus:ring-pink-500 focus:ring-opacity-50 transform transition duration-150 ease-in-out rounded-lg font-semibold px-3 py-2 text-xs shadow-lg cursor-pointer\"\n";
$htmlContent .= " onclick=\"handlePhoneNumberSubmission(this.getAttribute('data-plan-id'), this.getAttribute('data-router-id')); return false;\" data-plan-id=\"" . $plan['id'] . "\" data-router-id=\"" . $routerId . "\">\n";
$htmlContent .= " Click Here To Connect\n";
$htmlContent .= " </a>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
}
$htmlContent .= "</div>\n";
$htmlContent .= "<!-- Testimonials Section -->\n";
$htmlContent .= "<div class=\"mt-10 mx-auto px-4 sm:px-6 lg:px-8\">\n";
$htmlContent .= " <h3 class=\"text-center text-2xl leading-8 font-extrabold tracking-tight text-gray-900 sm:text-3xl sm:leading-9\">\n";
$htmlContent .= " What Our Users Say\n";
$htmlContent .= " </h3>\n";
$htmlContent .= " <div class=\"glider-contain mt-6\">\n";
$htmlContent .= " <div class=\"glider\">\n";
// Testimonial 1
$htmlContent .= " <div class=\"bg-white rounded-lg shadow-md overflow-hidden\">\n";
$htmlContent .= " <img class=\"w-full h-48 object-cover object-center\" src=\"assets/img/testimonials/testimonials-3.jpg\" alt=\"Testimonial from Otieno Peter\">\n";
$htmlContent .= " <div class=\"p-4\">\n";
$htmlContent .= " <div class=\"uppercase tracking-wide text-sm text-indigo-500 font-semibold\">Otieno Peter</div>\n";
$htmlContent .= " <p class=\"mt-2 text-gray-500\">\"Switching to this service has been a game changer for me. The connection is reliable and fast, making my online work seamless and efficient.\"</p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
// Testimonial 2
$htmlContent .= " <div class=\"bg-white rounded-lg shadow-md overflow-hidden\">\n";
$htmlContent .= " <img class=\"w-full h-48 object-cover object-center\" src=\"assets/img/testimonials/testimonials-2.jpg\" alt=\"Testimonial from Kiveu\">\n";
$htmlContent .= " <div class=\"p-4\">\n";
$htmlContent .= " <div class=\"uppercase tracking-wide text-sm text-indigo-500 font-semibold\">Kiveu</div>\n";
$htmlContent .= " <p class=\"mt-2 text-gray-500\">\"I've experienced unparalleled support and service. The team goes above and beyond to ensure customer satisfaction. Highly recommend!\"</p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
// Testimonial 3
$htmlContent .= " <div class=\"bg-white rounded-lg shadow-md overflow-hidden\">\n";
$htmlContent .= " <img class=\"w-full h-48 object-cover object-center\" src=\"assets/img/testimonials/testimonials-1.jpg\" alt=\"Testimonial from Anonymous User\">\n";
$htmlContent .= " <div class=\"p-4\">\n";
$htmlContent .= " <div class=\"uppercase tracking-wide text-sm text-indigo-500 font-semibold\">Anonymous User</div>\n";
$htmlContent .= " <p class=\"mt-2 text-gray-500\">\"Their commitment to quality and speed is evident. My internet experience has been fantastic ever since I made the switch.\"</p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <!-- Add Arrows -->\n";
$htmlContent .= " <button aria-label=\"Previous\" class=\"glider-prev\">«</button>\n";
$htmlContent .= " <button aria-label=\"Next\" class=\"glider-next\">»</button>\n";
$htmlContent .= " <div role=\"tablist\" class=\"dots\"></div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= "</div>\n";
// Glider.js script for the Testimonials Section
$htmlContent .= "<script>\n";
$htmlContent .= " new Glider(document.querySelector('.glider'), {\n";
$htmlContent .= " slidesToShow: 1,\n";
$htmlContent .= " slidesToScroll: 1,\n";
$htmlContent .= " draggable: true,\n";
$htmlContent .= " dots: '.dots',\n";
$htmlContent .= " arrows: {\n";
$htmlContent .= " prev: '.glider-prev',\n";
$htmlContent .= " next: '.glider-next'\n";
$htmlContent .= " },\n";
$htmlContent .= " responsive: [\n";
$htmlContent .= " {\n";
$htmlContent .= " breakpoint: 775,\n";
$htmlContent .= " settings: {\n";
$htmlContent .= " slidesToShow: 2,\n";
$htmlContent .= " slidesToScroll: 2,\n";
$htmlContent .= " }\n";
$htmlContent .= " },\n";
$htmlContent .= " {\n";
$htmlContent .= " breakpoint: 1024,\n";
$htmlContent .= " settings: {\n";
$htmlContent .= " slidesToShow: 3,\n";
$htmlContent .= " slidesToScroll: 3,\n";
$htmlContent .= " }\n";
$htmlContent .= " }\n";
$htmlContent .= " ]\n";
$htmlContent .= " });\n";
$htmlContent .= "</script>\n";
$htmlContent .= "<!-- FAQ Section -->\n";
$htmlContent .= "<div class=\"mt-10 mx-auto px-4 sm:px-6 lg:px-8\">\n";
$htmlContent .= " <div class=\"text-center\">\n";
$htmlContent .= " <h3 class=\"text-2xl leading-8 font-extrabold tracking-tight text-gray-900 sm:text-3xl sm:leading-9\">\n";
$htmlContent .= " FREQUENTLY ASKED QUESTIONS Will Be Here\n";
$htmlContent .= " </h3>\n";
$htmlContent .= " <p class=\"mt-4 max-w-2xl text-xl leading-7 text-gray-500 lg:mx-auto\">\n";
$htmlContent .= " Everything you need to know before getting started.\n";
$htmlContent .= " </p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"mt-6\">\n";
$htmlContent .= " <dl class=\"space-y-6\">\n";
// FAQ 1
$htmlContent .= " <div class=\"bg-white rounded-lg shadow-md\">\n";
$htmlContent .= " <dt class=\"p-4 cursor-pointer text-lg leading-6 font-medium text-gray-900\" onclick=\"toggleFAQ('faq1')\">" . htmlspecialchars($settings['frequently_asked_questions_headline1']) . "</dt>\n";
$htmlContent .= " <dd id=\"faq1\" class=\"p-4 hidden text-base text-gray-500\">" . htmlspecialchars($settings['frequently_asked_questions_answer1']) . "</dd>\n";
$htmlContent .= " </div>\n";
// FAQ 2
$htmlContent .= " <div class=\"bg-white rounded-lg shadow-md\">\n";
$htmlContent .= " <dt class=\"p-4 cursor-pointer text-lg leading-6 font-medium text-gray-900\" onclick=\"toggleFAQ('faq2')\">" . htmlspecialchars($settings['frequently_asked_questions_headline2']) . "</dt>\n";
$htmlContent .= " <dd id=\"faq2\" class=\"p-4 hidden text-base text-gray-500\">" . htmlspecialchars($settings['frequently_asked_questions_answer2']) . "</dd>\n";
$htmlContent .= " </div>\n";
// FAQ 3
$htmlContent .= " <div class=\"bg-white rounded-lg shadow-md\">\n";
$htmlContent .= " <dt class=\"p-4 cursor-pointer text-lg leading-6 font-medium text-gray-900\" onclick=\"toggleFAQ('faq3')\">" . htmlspecialchars($settings['frequently_asked_questions_headline3']) . "</dt>\n";
$htmlContent .= " <dd id=\"faq3\" class=\"p-4 hidden text-base text-gray-500\">" . htmlspecialchars($settings['frequently_asked_questions_answer3']) . "</dd>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </dl>\n";
$htmlContent .= " </div>\n";
$htmlContent .= "</div>\n";
$htmlContent .= "<div class=\"container mx-auto px-4\">\n";
$htmlContent .= " <div class=\"max-w-md mx-auto bg-white rounded-lg overflow-hidden md:max-w-lg\">\n";
$htmlContent .= " <div class=\"md:flex\">\n";
$htmlContent .= " <div class=\"w-full p-5\">\n";
$htmlContent .= " <div class=\"text-center\">\n";
$htmlContent .= " <h3 class=\"text-2xl text-gray-900\">Already Have an Active Package?</h3>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <form id=\"loginForm\" class=\"form\" name=\"login\" action=\"$(link-login-only)\" method=\"post\" $(if chap-id)onSubmit=\"return doLogin()\"$(endif)>\n";
$htmlContent .= " <input type=\"hidden\" name=\"dst\" value=\"$(link-orig)\" />\n";
$htmlContent .= " <input type=\"hidden\" name=\"popup\" value=\"true\" />\n";
$htmlContent .= " <div class=\"mb-4\">\n";
$htmlContent .= " <label class=\"block text-gray-700 text-sm font-bold mb-2\" for=\"username\">Username</label>\n";
$htmlContent .= " <input id=\"usernameInput\" class=\"shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline\" name=\"username\" type=\"text\" value=\"\" placeholder=\"Username\">\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"mb-6\">\n";
$htmlContent .= " <label class=\"block text-gray-700 text-sm font-bold mb-2\" for=\"password\">Password</label>\n";
$htmlContent .= " <input id=\"passwordInput\" class=\"shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline\" name=\"password\" type=\"password\" placeholder=\"******************\">\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"flex items-center justify-between\">\n";
$htmlContent .= " <button id=\"submitBtn\" class=\"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline\" type=\"button\">\n";
$htmlContent .= " Click Here To Connect\n";
$htmlContent .= " </button>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </form>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= "</div>\n";
$htmlContent .= "<script>\n";
$htmlContent .= "document.addEventListener('DOMContentLoaded', function() {\n";
$htmlContent .= " function autofillLogin() {\n";
$htmlContent .= " var phoneNumber = '2547xxxxxxx';\n";
$htmlContent .= " var password = '1234';\n";
$htmlContent .= " document.querySelector('input[name=\"username\"]').value = phoneNumber;\n";
$htmlContent .= " document.querySelector('input[name=\"password\"]').value = password;\n";
$htmlContent .= " setTimeout(function() {\n";
$htmlContent .= " document.querySelector('button[type=\"submit\"]').click();\n";
$htmlContent .= " }, 15000);\n";
$htmlContent .= " }\n";
$htmlContent .= " autofillLogin();\n";
$htmlContent .= "});\n";
$htmlContent .= "</script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= "function toggleFAQ(faqId) {\n";
$htmlContent .= " var element = document.getElementById(faqId);\n";
$htmlContent .= " if (element.style.display === \"block\") {\n";
$htmlContent .= " element.style.display = \"none\";\n";
$htmlContent .= " } else {\n";
$htmlContent .= " element.style.display = \"block\";\n";
$htmlContent .= " }\n";
$htmlContent .= "}\n";
$htmlContent .= "</script>\n";
$htmlContent .= "</section>\n";
$htmlContent .= "</main>\n";
$htmlContent .= "<!-- Footer -->\n";
$htmlContent .= "<footer class=\"bg-blue-900 text-white\">\n";
$htmlContent .= " <div class=\"max-w-7xl mx-auto px-4 py-12 sm:px-6 lg:px-8\">\n";
$htmlContent .= " <div class=\"lg:grid lg:grid-cols-3 lg:gap-8\">\n";
$htmlContent .= " <div class=\"lg:col-span-1\">\n";
$htmlContent .= " <h2 class=\"text-sm font-semibold uppercase tracking-wider\">\n";
$htmlContent .= " Contact Us\n";
$htmlContent .= " </h2>\n";
$htmlContent .= " <ul class=\"mt-4 space-y-4\">\n";
$htmlContent .= " <li>\n";
$htmlContent .= " <span class=\"block\">Address</span>\n";
$htmlContent .= " </li>\n";
$htmlContent .= " <li>\n";
$htmlContent .= " <span class=\"block\">Email: contact@" . htmlspecialchars($company) . "</span>\n";
$htmlContent .= " </li>\n";
$htmlContent .= " <li>\n";
$htmlContent .= " <span class=\"block\">Phone: " . htmlspecialchars($phone) . "</span>\n";
$htmlContent .= " </li>\n";
$htmlContent .= " </ul>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"lg:col-span-1\">\n";
$htmlContent .= " <h2 class=\"text-sm font-semibold uppercase tracking-wider\">\n";
$htmlContent .= " Quick Links\n";
$htmlContent .= " </h2>\n";
$htmlContent .= " <ul class=\"mt-4 space-y-4\">\n";
$htmlContent .= " <li><a href=\"#\" class=\"hover:underline\">About Us</a></li>\n";
$htmlContent .= " <li><a href=\"#\" class=\"hover:underline\">Our Services</a></li>\n";
$htmlContent .= " <li><a href=\"#\" class=\"hover:underline\">FAQ</a></li>\n";
$htmlContent .= " <li><a href=\"#\" class=\"hover:underline\">Support</a></li>\n";
$htmlContent .= " </ul>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"lg:col-span-1\">\n";
$htmlContent .= " <h2 class=\"text-sm font-semibold uppercase tracking-wider\">\n";
$htmlContent .= " Follow Us\n";
$htmlContent .= " </h2>\n";
$htmlContent .= " <div class=\"mt-4 space-x-4\">\n";
$htmlContent .= " <a href=\"#\" class=\"hover:text-gray-400\"><i class=\"fab fa-facebook-f\"></i></a>\n";
$htmlContent .= " <a href=\"#\" class=\"hover:text-gray-400\"><i class=\"fab fa-twitter\"></i></a>\n";
$htmlContent .= " <a href=\"#\" class=\"hover:text-gray-400\"><i class=\"fab fa-instagram\"></i></a>\n";
$htmlContent .= " <a href=\"#\" class=\"hover:text-gray-400\"><i class=\"fab fa-linkedin-in\"></i></a>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"mt-8 border-t border-gray-700 pt-8 md:flex md:items-center md:justify-between\">\n";
$htmlContent .= " <div class=\"flex space-x-6 md:order-2\">\n";
$htmlContent .= " <a href=\"#\" class=\"text-gray-400 hover:text-gray-300\"><span class=\"sr-only\">Facebook</span><i class=\"fab fa-facebook-f\"></i></a>\n";
$htmlContent .= " <a href=\"#\" class=\"text-gray-400 hover:text-gray-300\"><span class=\"sr-only\">Instagram</span><i class=\"fab fa-instagram\"></i></a>\n";
$htmlContent .= " <a href=\"#\" class=\"text-gray-400 hover:text-gray-300\"><span class=\"sr-only\">Twitter</span><i class=\"fab fa-twitter\"></i></a>\n";
$htmlContent .= " <a href=\"#\" class=\"text-gray-400 hover:text-gray-300\"><span class=\"sr-only\">LinkedIn</span><i class=\"fab fa-linkedin-in\"></i></a>\n";
$htmlContent .= " </div>\n";
$htmlContent .= "<p class=\"mt-8 text-base leading-6 text-gray-400 md:mt-0 md:order-1\">\n";
$htmlContent .= " &copy; 2024 " . htmlspecialchars($company) . " All rights reserved.\n";
$htmlContent .= " </p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= "</footer>\n";
$htmlContent .= "<script src=\"https://cdn.jsdelivr.net/npm/sweetalert2@11\"></script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= " function formatPhoneNumber(phoneNumber) {\n";
$htmlContent .= " if (phoneNumber.startsWith('+')) {\n";
$htmlContent .= " phoneNumber = phoneNumber.substring(1);\n";
$htmlContent .= " }\n";
$htmlContent .= " if (phoneNumber.startsWith('0')) {\n";
$htmlContent .= " phoneNumber = '254' + phoneNumber.substring(1);\n";
$htmlContent .= " }\n";
$htmlContent .= " if (phoneNumber.match(/^(7|1)/)) {\n";
$htmlContent .= " phoneNumber = '254' + phoneNumber;\n";
$htmlContent .= " }\n";
$htmlContent .= " return phoneNumber;\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= " function handlePhoneNumberSubmission(planId, routerId) {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " title: 'Enter Your Phone Number',\n";
$htmlContent .= " input: 'text',\n";
$htmlContent .= " inputPlaceholder: 'Your phone number here',\n";
$htmlContent .= " inputAttributes: {\n";
$htmlContent .= " autocapitalize: 'off'\n";
$htmlContent .= " },\n";
$htmlContent .= " showCancelButton: true,\n";
$htmlContent .= " confirmButtonColor: '#3085d6',\n";
$htmlContent .= " cancelButtonColor: '#d33',\n";
$htmlContent .= " confirmButtonText: 'Submit',\n";
$htmlContent .= " showLoaderOnConfirm: true,\n";
$htmlContent .= " backdrop: `\n";
$htmlContent .= " rgba(0,0,123,0.4)\n";
$htmlContent .= " url(\"https://sweetalert2.github.io/images/nyan-cat.gif\")\n";
$htmlContent .= " center left\n";
$htmlContent .= " no-repeat\n";
$htmlContent .= " `,\n";
$htmlContent .= " preConfirm: (phoneNumber) => {\n";
$htmlContent .= " var formattedPhoneNumber = formatPhoneNumber(phoneNumber);\n";
$htmlContent .= " document.getElementById('usernameInput').value = formattedPhoneNumber;\n";
$htmlContent .= " console.log(\"Phone number for autofill:\", formattedPhoneNumber);\n";
$htmlContent .= "\n";
$htmlContent .= " return fetch('" . APP_URL . "/index.php?_route=plugin/CreateHotspotuser&type=grant', {\n";
$htmlContent .= " method: 'POST',\n";
$htmlContent .= " headers: {'Content-Type': 'application/json'},\n";
$htmlContent .= " body: JSON.stringify({phone_number: formattedPhoneNumber, plan_id: planId, router_id: routerId}),\n";
$htmlContent .= " })\n";
$htmlContent .= " .then(response => {\n";
$htmlContent .= " if (!response.ok) throw new Error('Network response was not ok');\n";
$htmlContent .= " return response.json();\n";
$htmlContent .= " })\n";
$htmlContent .= " .then(data => {\n";
$htmlContent .= " if (data.status === 'error') throw new Error(data.message);\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " title: 'Connecting in 35 Secs...',\n";
$htmlContent .= " html: `Remaining time is <b>\${formattedPhoneNumber}</b>.<br>A payment request has been sent to <b>\${formattedPhoneNumber}</b>. Dont click anything until you are connected. Still on this page after the timer ended? Scroll down and Click Login Now`,\n";
$htmlContent .= " timer: 35000, // Adjusted for 35 seconds\n";
$htmlContent .= " timerProgressBar: true,\n";
$htmlContent .= " didOpen: () => {\n";
$htmlContent .= " Swal.showLoading();\n";
$htmlContent .= " const timer = Swal.getPopup().querySelector(\"b\");\n";
$htmlContent .= " timerInterval = setInterval(() => {\n";
$htmlContent .= " timer.textContent = `\${Swal.getTimerLeft()}`;\n";
$htmlContent .= " }, 100);\n";
$htmlContent .= " },\n";
$htmlContent .= " willClose: () => {\n";
$htmlContent .= " clearInterval(timerInterval);\n";
$htmlContent .= " }\n";
$htmlContent .= " }).then((result) => {\n";
$htmlContent .= " if (result.dismiss === Swal.DismissReason.timer) {\n";
$htmlContent .= " console.log('I was closed by the timer');\n";
$htmlContent .= " document.getElementById('submitBtn').click();\n";
$htmlContent .= " }\n";
$htmlContent .= " });\n";
$htmlContent .= " return formattedPhoneNumber; \n";
$htmlContent .= " })\n";
$htmlContent .= " .catch(error => {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'error',\n";
$htmlContent .= " title: 'Oops...',\n";
$htmlContent .= " text: error.message,\n";
$htmlContent .= " });\n";
$htmlContent .= " });\n";
$htmlContent .= " },\n";
$htmlContent .= " allowOutsideClick: () => !Swal.isLoading()\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= " function FetchAjax(phoneNumber) {\n";
$htmlContent .= " refreshData();\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= " function refreshData() {\n";
$htmlContent .= " function refreshDataInternal() {\n";
$htmlContent .= " $.ajax({\n";
$htmlContent .= " url: '" . APP_URL . "/index.php?_route=plugin/CreateHotspotuser&type=verify',\n";
$htmlContent .= " method: \"POST\",\n";
$htmlContent .= " data: {phone_number: document.getElementById('usernameInput').value},\n";
$htmlContent .= " dataType: \"json\",\n";
$htmlContent .= " success: function(data) {\n";
$htmlContent .= " // Response handling code\n";
$htmlContent .= " },\n";
$htmlContent .= " error: function(xhr, textStatus, errorThrown) {\n";
$htmlContent .= " console.log(\"Error: \" + errorThrown);\n";
$htmlContent .= " }\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " var refreshInterval = setInterval(refreshDataInternal, 2000);\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= " document.addEventListener('DOMContentLoaded', function() {\n";
$htmlContent .= " var submitBtn = document.getElementById('submitBtn');\n";
$htmlContent .= " if (submitBtn) {\n";
$htmlContent .= " submitBtn.addEventListener('click', function(event) {\n";
$htmlContent .= " event.preventDefault();\n";
$htmlContent .= " document.getElementById('loginForm').submit();\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " });\n";
$htmlContent .= "</script>\n";
$htmlContent .= "<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js\"></script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= "document.addEventListener('DOMContentLoaded', function() {\n";
$htmlContent .= " // Ensure the button is correctly targeted by its ID.\n";
$htmlContent .= " var submitBtn = document.getElementById('submitBtn');\n";
$htmlContent .= " \n";
$htmlContent .= " // Add a click event listener to the \"Login Now\" button.\n";
$htmlContent .= " submitBtn.addEventListener('click', function(event) {\n";
$htmlContent .= " event.preventDefault(); // Prevent the default button action.\n";
$htmlContent .= " \n";
$htmlContent .= " // Optional: Log to console for debugging purposes.\n";
$htmlContent .= " console.log(\"Login Now button clicked.\");\n";
$htmlContent .= " \n";
$htmlContent .= " // Direct form submission, bypassing the doLogin function for simplicity.\n";
$htmlContent .= " var form = document.getElementById('loginForm');\n";
$htmlContent .= " form.submit(); // Submit the form directly.\n";
$htmlContent .= " });\n";
$htmlContent .= "});\n";
$htmlContent .= "</script>\n";
$htmlContent .= "</html>\n";
$planStmt->close();
$mysqli->close();
// Check if the download parameter is set
if (isset($_GET['download']) && $_GET['download'] == '1') {
// Prepare the HTML content for download
// ... build your HTML content ...
// Specify the filename for the download
$filename = "login.html";
// Send headers to force download
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($filename));
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . strlen($htmlContent));
// Output the content
echo $htmlContent;
// Prevent any further output
exit;
}
// Regular page content goes here
// ... HTML and PHP code to display the page ...

View File

@ -0,0 +1,117 @@
{include file="sections/header.tpl"}
<section class="content-header">
<h1>
<div class="btn-group">
<button type="button" class="btn btn-success">
Hotspot Settings
</button>
<button type="button" class="btn btn-success dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="{$_url}plugin/hotspot_settings">{Lang::T('General Settings')}</a></li>
<li class="divider"></li>
<li><a href="{$_url}plugin/captive_portal_login" target="_blank">Preview Hotspot Login Page</a></li>
<li><a href="{$app_url}/system/plugin/download.php?download=1" target="_blank">Download Login Page</a></li>
</ul>
</div>
</h1>
<ol class="breadcrumb">
<li><a href="{$app_url}/system/plugin/download.php?download=1"><i class="fa fa-dashboard"></i> Click Here To Download Login Page</a></li>
<li class="active">Hotspot Settings</li>
</ol>
</section>
<section class="content">
<div class="table-responsive">
<div class="nav-tabs-custom">
<ul class="nav nav-tabs">
<li class="active"><a href="#tab_1" data-toggle="tab">{Lang::T('General Settings')}</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tab_1" style="overflow-x:auto;">
<div class="box-body no-padding" id="">
<form method="POST" action="" enctype="multipart/form-data">
<div class="box-body">
<div class="form-group">
<label for="hotspot_title">Hotspot Page Title</label>
<input type="text" class="form-control" name="hotspot_title" id="hotspot_title" value="{$hotspot_title}" required>
<small class="form-text text-muted">In this field, you can enter the name of your ISP company. It will appear as the main title on the hotspot page.</small>
</div>
<div class="form-group">
<label for="description">Brief Description Of Company/Tagline</label>
<input type="text" class="form-control" name="description" id="description" value="{$description}" required>
</div>
<div class="form-group">
<label for="router_name">Router Name:</label>
<input type="text" class="form-control" name="router_name" id="router_name" value="{$router_name}" required>
<small class="form-text text-muted">This is the most important part of the form. Go to Network and then Routers, and copy the exact router name.</small>
</div>
<!-- FAQ fields -->
<div class="form-group">
<label for="frequently_asked_questions_headline1">FAQ Headline 1</label>
<input type="text" class="form-control" name="frequently_asked_questions_headline1" id="frequently_asked_questions_headline1" value="{$frequently_asked_questions_headline1}" required>
</div>
<div class="form-group">
<label for="frequently_asked_questions_answer1">FAQ Answer 1</label>
<textarea class="form-control" id="frequently_asked_questions_answer1" name="frequently_asked_questions_answer1" required>{$frequently_asked_questions_answer1}</textarea>
</div>
<div class="form-group">
<label for="frequently_asked_questions_headline2">FAQ Headline 2</label>
<input type="text" class="form-control" id="frequently_asked_questions_headline2" name="frequently_asked_questions_headline2" value="{$frequently_asked_questions_headline2}" required>
</div>
<div class="form-group">
<label for="frequently_asked_questions_answer2">FAQ Answer 2</label>
<textarea class="form-control" id="frequently_asked_questions_answer2" name="frequently_asked_questions_answer2" required>{$frequently_asked_questions_answer2}</textarea>
</div>
<div class="form-group">
<label for="frequently_asked_questions_headline3">FAQ Headline 3</label>
<input type="text" class="form-control" name="frequently_asked_questions_headline3" id="frequently_asked_questions_headline3" value="{$frequently_asked_questions_headline3}" required>
</div>
<div class="form-group">
<label for="frequently_asked_questions_answer3">FAQ Answer 3</label>
<textarea class="form-control" id="frequently_asked_questions_answer3" name="frequently_asked_questions_answer3" required>{$frequently_asked_questions_answer3}</textarea>
</div>
<!-- Save Changes button -->
<button type="submit" class="btn btn-info pull-right">Save Changes</button>
</form>
<div class="tab-pane" id="tab_6" style="overflow-x:auto;">
<div class="box-body no-padding" id="">
<!-- Content for Pages Settings tab -->
This feature will be Coming Soon
</div>
</div>
</div>
</div>
<div>
<pre><b>USAGE:</b>
<br>Make sure you change this custom Settings and personalize them.
<br>Then download the <strong style="color: black; background-color: yellow;">login.html</strong> by clicking on download login page.
<br>Then upload the downloaded <strong style="color: black; background-color: yellow;">login.html</strong> file to your Mikrotik router.
<br>Make sure you add your website URL in Mikrotik hotspot wall garden. <strong style="color: black; background-color: yellow;">login.html</strong>
</pre>
</div>
</section>
{include file="sections/footer.tpl"}

BIN
system/plugin/ui/index.html Normal file

Binary file not shown.

154
system/plugin/ui/log.tpl Normal file
View File

@ -0,0 +1,154 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Log UI</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
</head>
{include file="sections/header.tpl"}
<body class="bg-gray-100 font-sans leading-normal tracking-normal">
<div class="container mx-auto mt-8 bg-white rounded-lg shadow-lg p-6">
<form class="mb-4" method="post" role="form" action="{$_url}plugin/log_ui">
<ul class="nav nav-tabs flex border-b">
{foreach $routers as $r}
<li class="mr-1" role="presentation" {if $r['id']==$router}class="active"{/if}>
<a class="bg-white inline-block py-2 px-4 text-blue-500 hover:text-blue-800 font-semibold" href="{$_url}plugin/log_ui/{$r['id']}">{$r['name']}</a>
</li>
{/foreach}
</ul>
</form>
<div class="flex flex-wrap mb-4">
<div class="w-full md:w-8/12">
<label class="block">
Show entries
<select name="data_length" aria-controls="data" class="form-control form-control-sm" onchange="updatePerPage(this.value)">
<option value="5" {if $per_page == 5}selected{/if}>5</option>
<option value="10" {if $per_page == 10}selected{/if}>10</option>
<option value="25" {if $per_page == 25}selected{/if}>25</option>
<option value="50" {if $per_page == 50}selected{/if}>50</option>
<option value="100" {if $per_page == 100}selected{/if}>100</option>
</select>
</label>
</div>
<div class="w-full md:w-4/12">
<label class="block">
Search:
<input type="search" id="logSearch" class="form-control form-control-sm" placeholder="Search logs" aria-controls="data" onkeyup="filterLogs()">
</label>
</div>
</div>
<table class="min-w-full bg-white table-auto">
<thead>
<tr class="bg-gray-200">
<th class="w-1/3 py-2 px-4">Time</th>
<th class="w-1/3 py-2 px-4">Topic</th>
<th class="w-1/3 py-2 px-4">Message</th>
</tr>
</thead>
<tbody id="logTableBody">
{assign var=current_page value=$smarty.get.page|default:1}
{assign var=per_page value=$smarty.get.per_page|default:10}
{assign var=start_index value=($current_page - 1) * $per_page}
{foreach from=$logs|array_reverse item=log name=logLoop}
{if $smarty.foreach.logLoop.index >= $start_index && $smarty.foreach.logLoop.index < ($start_index + $per_page)}
<tr class="log-entry">
<td class="border px-4 py-2">{$log.time}</td>
<td class="border px-4 py-2">{$log.topics}</td>
<td class="border px-4 py-2 log-message">
{if $log.message|lower|strpos:'failed' !== false}
<span class="text-red-700 text-bold">{$log.message}</span>
{elseif $log.message|lower|strpos:'trying' !== false}
<span class="text-yellow-700">{$log.message}</span>
{elseif $log.message|lower|strpos:'logged in' !== false}
<span class="text-green-700">{$log.message}</span>
{elseif $log.message|lower|strpos:'login failed' !== false}
<span class="text-blue-700">{$log.message}</span>
{else}
<span class="text-gray-700">{$log.message}</span>
{/if}
</td>
</tr>
{/if}
{/foreach}
</tbody>
</table>
{assign var=total_logs value=$logs|@count}
{assign var=last_page value=ceil($total_logs / $per_page)}
<nav class="mt-4">
<ul class="pagination flex justify-center">
{if $current_page > 1}
<li>
<a class="page-link border py-2 px-4 mx-1" href="index.php?_route=plugin/log_ui&page=1&per_page={$per_page}" aria-label="First">&laquo;&laquo;</a>
</li>
<li>
<a class="page-link border py-2 px-4 mx-1" href="index.php?_route=plugin/log_ui&page={$current_page-1}&per_page={$per_page}" aria-label="Previous">&laquo;</a>
</li>
{/if}
{assign var=max_links value=5}
{assign var=start_page value=max(1, $current_page - floor($max_links / 2))}
{assign var=end_page value=min($last_page, $start_page + $max_links - 1)}
{if $start_page > 1}
<li>
<a class="page-link border py-2 px-4 mx-1" href="index.php?_route=plugin/log_ui&page={$start_page-1}&per_page={$per_page}" aria-label="Previous">&hellip;</a>
</li>
{/if}
{foreach from=range($start_page, $end_page) item=page}
<li>
<a class="page-link border py-2 px-4 mx-1 {if $page == $current_page}bg-blue-500 text-white{/if}" href="index.php?_route=plugin/log_ui&page={$page}&per_page={$per_page}">{$page}</a>
</li>
{/foreach}
{if $end_page < $last_page}
<li>
<a class="page-link border py-2 px-4 mx-1" href="index.php?_route=plugin/log_ui&page={$end_page+1}&per_page={$per_page}" aria-label="Next">&hellip;</a>
</li>
{/if}
{if $current_page < $last_page}
<li>
<a class="page-link border py-2 px-4 mx-1" href="index.php?_route=plugin/log_ui&page={$current_page+1}&per_page={$per_page}" aria-label="Next">&raquo;</a>
</li>
<li>
<a class="page-link border py-2 px-4 mx-1" href="index.php?_route=plugin/log_ui&page={$last_page}&per_page={$per_page}" aria-label="Last">&raquo;&raquo;</a>
</li>
{/if}
</ul>
</nav>
</div>
<script>
function updatePerPage(value) {
var urlParams = new URLSearchParams(window.location.search);
urlParams.set('per_page', value);
urlParams.set('page', 1); // Reset to first page
window.location.search = urlParams.toString();
}
function filterLogs() {
var input = document.getElementById('logSearch').value.toLowerCase();
var table = document.getElementById('logTableBody');
var tr = table.getElementsByClassName('log-entry');
for (var i = 0; i < tr.length; i++) {
var logMessage = tr[i].getElementsByClassName('log-message')[0].textContent || tr[i].getElementsByClassName('log-message')[0].innerText;
if (logMessage.toLowerCase().indexOf(input) > -1) {
tr[i].style.display = '';
} else {
tr[i].style.display = 'none';
}
}
}
</script>
{include file="sections/footer.tpl"}
</body>
</html>

View File

@ -0,0 +1,34 @@
{include file="sections/header.tpl"}
<form class="form-horizontal" method="post" role="form" action="{$_url}plugin/port_tester">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="panel-heading">Testing port external</div>
<div class="panel-body">
<div class="form-group">
<label class="col-md-2 control-label">Port</label>
<div class="col-md-6">
<input type="text" id="port" name="port" value="{$port}" placeholder="8728" class="form-control">
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-success waves-effect waves-light" type="submit">Test It</button>
</div>
</div>
</div>
</div>
{if $result != ''}
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="panel-heading">Result</div>
<div class="panel-body">
{Lang::nl2br($result)}
</div>
</div>
{/if}
</div>
</div>
</form>
{include file="sections/footer.tpl"}

View File

@ -0,0 +1,943 @@
{include file="sections/header.tpl"}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdn.datatables.net/1.10.23/js/jquery.dataTables.min.js"></script>
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.23/css/jquery.dataTables.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/buttons/1.7.1/css/buttons.dataTables.min.css">
<script src="https://cdn.datatables.net/buttons/1.7.1/js/dataTables.buttons.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.datatables.net/1.11.3/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.11.3/js/dataTables.bootstrap5.min.js"></script>
<style>
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.4);
}
.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 0px;
border: 1px solid #888;
width: 80%;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
max-width: 600px;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
}
.card-body {
padding: 1rem;
}
.card-header {
padding: .75rem 1.25rem;
margin-bottom: 0;
background-color: none;
border-bottom: 1px solid rgba(0,0,0,.125);
}
.card-title {
margin-bottom: .75rem;
}
.form-group {
margin-bottom: 1rem;
}
.table-responsive {
display: block;
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
-ms-overflow-style: -ms-autohiding-scrollbar;
}
.table {
width: 100%;
margin-bottom: 1rem;
color: #212529;
}
.container {
padding-top: 20px;
}
#ppp-table_wrapper {
padding: 15px;
}
#ppp-table th, #ppp-table td {
text-align: center;
padding: 6px;
}
.table-striped tbody tr:nth-of-type(odd) {
background-color: #f9f9f9;
}
.panel-default {
border-color: #ddd;
}
.panel-heading {
background-color: #f5f5f5;
border-color: #ddd;
}
.nav-tabs {
margin-bottom: 15px;
}
.nav-tabs > li > a {
border-radius: 0;
color: #555;
background-color: #f9f9f9;
border-color: #ddd;
}
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:focus,
.nav-tabs > li.active > a:hover {
background-color: #fff;
color: #333;
border: 1px solid #ddd;
border-bottom-color: transparent;
cursor: default;
}
.table-striped tbody tr:nth-of-type(odd) {
background-color: #f9f9f9;
}
.table th {
background-color: #f5f5f5;
color: #333;
font-weight: bold;
padding: 8px;
}
.table-striped > tbody > tr > td {
background-color: #fff;
}
.status-connect {
color: #5cb85c;
}
.status-disconnect {
color: #d9534f;
}
.modalsupport {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.4);
justify-content: center;
align-items: center;
}
.modalsupport-content {
background-color: #fefefe;
margin: auto;
padding: 0px;
border: 1px solid #888;
width: 80%;
max-width: 500px;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
text-align: center;
}
.modalsupport-close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.modalsupport-close:hover,
.modalsupport-close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
.card {
border: none;
}
.card-header {
background-color: none;
border-bottom: none;
}
.card-body {
padding: 20px;
}
.donate-button {
margin-top: 10px;
}
.modalsupport img {
width: 100px;
height: auto;
margin-top: 15px;
}
.dataSize {
white-space: nowrap;
}
.action-icons i {
cursor: pointer;
margin-right: 10px;
color: #007bff;
}
.action-icons i:hover {
color: #0056b3;
}
.modal-title {
text-align: center;
width: 100%;
display: block;
font-size: 20px;
font-weight: bold;
margin-top: 20px;
}
.table-bordered {
width: 100%;
max-width: 100%;
table-layout: fixed;
}
.table-bordered th, .table-bordered td {
width: auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
background: none;
border: none;
padding: 5px;
vertical-align: middle;
text-align: center;
}
.advanced-search-container {
margin-bottom: 20px;
padding: 15px;
background-color: #f9f9f9;
border: 1px solid #ddd;
}
.form-inline .form-group {
margin-right: 10px;
}
.dataTables_filter {
display: none;
}
@media (max-width: 768px) {
.panel-default {
padding: 10px;
margin: 0;
}
.panel-heading {
padding: 5px 15px;
}
.panel-body {
padding: 5px 10px;
}
.table th, .table td {
font-size: 15px; /* Mengurangi ukuran font pada tabel */
}
}
.traffic-icon {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 5px;
vertical-align: middle;
}
.traffic-icon-green {
background-color: green;
}
.traffic-icon-yellow {
background-color: yellow;
}
.traffic-icon-red {
background-color: red;
}
.text-left {
text-align: left !important;
}
</style>
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<!-- Form dan navigasi tabs -->
<form class="form-horizontal" method="post" role="form" action="{$_url}plugin/pppoe">
<div class="form-group">
<label for="routerSelect" class="col-sm-2 control-label">Select Router</label>
<div class="col-sm-10">
<select id="routerSelect" name="router" class="form-control" onchange="window.location.href=this.value;">
{foreach $routers as $r}
<option value="{$_url}plugin/pppoe/{$r['id']}" {if $r['id'] == $router}selected{/if}>
{$r['name']}
</option>
{/foreach}
</select>
</div>
</div>
</form>
<div class="advanced-search-container">
<form id="advancedSearchForm" class="form-inline">
<div class="form-group">
<label for="searchUsername">Username:</label>
<input type="text" class="form-control" id="searchUsername" placeholder="Enter username">
</div>
<div class="form-group">
<label for="searchStatus">Status:</label>
<select class="form-control" id="searchStatus">
<option value="">Any</option>
<option value="Connected">Connected</option>
<option value="Disconnected">Disconnected</option>
</select>
</div>
</form>
</div>
<div class="panel panel-default">
<div class="table-responsive">
<div class="panel-body">
<table class="table table-striped" id="ppp-table">
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>IP Address</th>
<th>Uptime</th>
<th>Service</th>
<th>Caller ID</th>
<th>Device</th>
<th>Download</th>
<th>Upload</th>
<th>Total Usage</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<!-- Isi tabel akan dimasukkan melalui JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="detailsModal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<div class="container-fluid mt-5">
<div class="card">
<div class="card-header">
<h5 class="modal-title">Data Usage for <span id="modalUsername"></span></h5>
</div>
<div class="card-body">
<div class="table-responsive mt-4">
<table class="table table-bordered">
<thead>
<tr>
<input type="hidden" id="interface" value="">
<th id="tabletx"><i class="fa fa-download"></i></th>
<th id="tablerx"><i class="fa fa-upload"></i></th>
</tr>
</thead>
</table>
</div>
<div id="chart" class="mt-3"></div>
<div id="dailyChart" class="mt-3"></div>
</div>
</div>
</div>
</div>
</div>
<script>
var $j = jQuery.noConflict();
$j(document).ready(function() {
var table = $j('#ppp-table').DataTable({
responsive: true,
columns: [
{ data: 'id', visible: false },
{
data: 'username',
className: 'text-left',
render: function(data, type, row) {
return '<div style="width: 150px;"><i class="traffic-icon traffic-icon-green"></i> ' + data + '</div>';
}
},
{ data: 'address' },
{ data: 'uptime' },
{ data: 'service' },
{ data: 'caller_id' },
{ data: 'manufacturer' }, // New column for device/manufacturer
{ data: 'tx', className: 'dataSize' },
{ data: 'rx', className: 'dataSize' },
{ data: 'total', className: 'dataSize' },
{
data: 'status',
render: function(data) {
if (data === 'Connected') {
return '<small class="label bg-green">Connected</small>';
} else if (data === 'Disconnected') {
return '<small class="label bg-red">Disconnected</small>';
} else {
return '';
}
}
},
{
data: null,
render: function(data, type, row) {
return '<div class="action-icons" style="display: flex; align-items: center;">' +
'<i class="fa fa-area-chart view-details" style="color: blue; cursor: pointer;" title="View Traffic" data-username="' + row.username + '" data-id="' + row.id + '"></i> ' +
'<i class="fa fa-retweet reconnect-button" style="color: red; cursor: pointer;" title="Reconnect" data-username="' + row.username + '" data-id="' + row.id + '"></i> ' +
'<button class="btn btn-sm ' + (row.is_bound ? 'btn-danger' : 'btn-success') + ' bind-button" data-username="' + row.username + '" data-caller-id="' + row.caller_id + '" data-is-bound="' + row.is_bound + '">' +
(row.is_bound ? 'Unbind' : 'Bind') + '</button>' +
'</div>';
}
}
],
order: [[0, 'asc']],
pageLength: 10,
lengthMenu: [[10, 25, 50, -1], [10, 25, 50, 'All']],
dom: 'Bfrtip',
buttons: ['reset', 'pageLength'],
paging: true,
info: true,
searching: true,
ajax: {
url: '{$_url}plugin/pppoe_get_combined_users/{$router}',
dataSrc: ''
}
});
// Fungsi untuk mendapatkan batas maksimum
function getMaxLimit(data) {
if (data.hasOwnProperty('max_limit')) {
return data.max_limit.toString();
} else {
return 'N/A';
}
}
// Handle view details icon clicks
$j('#ppp-table tbody').on('click', '.view-details', function(e) {
e.preventDefault();
var username = $j(this).data('username');
var id = $j(this).data('id');
viewDetails(id, username);
});
// Handle reconnect icon clicks
$j('#ppp-table tbody').on('click', '.reconnect-button', function(e) {
e.preventDefault();
var username = $j(this).data('username');
var id = $j(this).data('id');
reconnect(id, username);
});
// Function to handle view details
function viewDetails(id, username) {
console.log("Viewing details for:", username);
$j('#modalUsername').text(username);
$j.ajax({
url: '{$_url}plugin/pppoe_get_combined_users',
method: 'GET',
dataType: 'json',
success: function(response) {
var user = response.find(function(item) {
return (item.username && item.username.toString().toLowerCase() === username.toString().toLowerCase());
});
if (username !== null && user !== null && user.username !== null) {
var interfaceValue = '<pppoe-' + user.username + '>';
$j('#interface').val(interfaceValue);
$j('#selectedInterface').text(interfaceValue);
$j('#detailsModal').css('display', 'block');
createChart();
createDailyChart(username); // Pass the username to createDailyChart
} else {
alert('User not found.');
}
},
error: function(xhr, textStatus, errorThrown) {
alert('Failed to retrieve user data.');
console.error('AJAX error:', textStatus, errorThrown);
}
});
}
// Function to handle reconnect
function reconnect(id, username) {
if (confirm('Are you sure you want to disconnect user ' + username + '?')) {
$.ajax({
url: '{$_url}plugin/pppoe_monitor_router_delete_ppp_user/{$router}', // Perbaiki URL AJAX
method: 'POST',
data: { id: id, username: username },
success: function(response) {
if (response.success) {
alert('User ' + username + ' has been disconnected.');
setTimeout(function() {
table.ajax.reload();
}, 2000);
} else {
alert('Failed to disconnect user ' + username + ': ' + (response.message || 'Unknown error'));
}
},
error: function(xhr, textStatus, errorThrown) {
alert('Failed to disconnect user ' + username + ': ' + (errorThrown || 'Unknown error'));
console.error('AJAX error:', textStatus, errorThrown);
}
});
}
}
// Close modal on click of close button
$j('.close').click(function() {
$j('#detailsModal').css('display', 'none');
});
// Close modal on click outside the modal
$j(window).click(function(event) {
if (event.target == document.getElementById('detailsModal')) {
$j('#detailsModal').css('display', 'none');
}
});
// Handle advanced search form submission
$j(document).ready(function() {
$j('#advancedSearchForm').on('submit', function(e) {
e.preventDefault(); // Mencegah pengiriman form secara default
// Mendapatkan nilai dari input
var username = $j('#searchUsername').val();
var status = $j('#searchStatus').val();
// Melakukan pencarian dan menggambar ulang tabel
table.column(1).search(username).draw(); // Kolom 1 untuk username
table.column(9).search(status).draw(); // Kolom 9 untuk status
});
// Menambahkan ikon search ke dalam tombol
var searchButton = $j('<button type="submit" class="btn btn-primary"><i class="fas fa-search"></i></button>');
$j('#advancedSearchForm').append(searchButton);
});
});
var chart;
var chartData = {
txData: [],
rxData: []
};
function createChart() {
var options = {
chart: {
height: 350,
type: 'area',
animations: {
enabled: true,
easing: 'linear',
speed: 200,
animateGradually: {
enabled: true,
delay: 150
},
dynamicAnimation: {
enabled: true,
speed: 200
}
},
events: {
mounted: function() {
updateTrafficValues();
setInterval(updateTrafficValues, 3000);
}
}
},
stroke: {
curve: 'smooth'
},
series: [
{ name: 'Download', data: chartData.txData },
{ name: 'Upload', data: chartData.rxData }
],
xaxis: {
type: 'datetime',
labels: {
formatter: function(value) {
return new Date(value).toLocaleTimeString();
}
}
},
yaxis: {
title: {
text: 'Real Time Data Usage'
},
labels: {
formatter: function(value) {
return formatBytes(value);
}
}
},
tooltip: {
x: {
format: 'HH:mm:ss'
},
y: {
formatter: function(value) {
return formatBytes(value) + 'ps';
}
}
},
dataLabels: {
enabled: false,
formatter: function(value) {
return formatBytes(value);
}
}
};
chart = new ApexCharts(document.querySelector("#chart"), options);
chart.render();
}
var dailyChart; // Declare dailyChart variable globally
function createDailyChart(username) {
var currentDate = new Date();
var startOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1).getTime();
var endOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0).getTime();
generateDailyData(username)
.then(dailyData => {
var dailyTotals = dailyData.download.map((item, index) => ({
x: item.x,
y: item.y + dailyData.upload[index].y
}));
if (dailyChart) {
dailyChart.destroy();
}
var options = {
chart: {
height: 350,
type: 'bar',
animations: {
enabled: true,
easing: 'linear',
speed: 800,
animateGradually: {
enabled: true,
delay: 150
},
dynamicAnimation: {
enabled: true,
speed: 200
}
},
toolbar: {
show: true,
}
},
plotOptions: {
bar: {
horizontal: false,
columnWidth: '15%',
endingShape: 'rounded'
},
},
dataLabels: {
enabled: false
},
stroke: {
show: true,
width: 2,
colors: ['transparent']
},
series: [{
name: 'Download',
data: dailyData.upload
}, {
name: 'Upload',
data: dailyData.download
}, {
name: 'Daily Totals',
data: dailyTotals
}],
xaxis: {
type: 'datetime',
min: startOfMonth,
max: endOfMonth,
labels: {
formatter: function(value) {
return new Date(value).toLocaleDateString();
}
}
},
yaxis: {
title: {
text: 'Total Usage'
},
labels: {
formatter: function(value) {
return formatBytesPerSecond(value);
}
}
},
fill: {
opacity: 1
},
tooltip: {
y: {
formatter: function(val) {
return formatBytes(val);
}
}
},
responsive: [
{
breakpoint: 480,
options: {
plotOptions: {
bar: {
columnWidth: '100%'
}
}
}
}
]
};
dailyChart = new ApexCharts(document.querySelector("#dailyChart"), options);
dailyChart.render();
})
.catch(error => {
console.error("Failed to fetch daily usage data:", error);
});
}
// ========================================== NEW FITUR ==========================================//
function generateDailyData(username, startDate, endDate) {
return new Promise((resolve, reject) => {
$j.ajax({
url: '{$_url}plugin/pppoe_monitor_router_daily_data_usage/{$router}',
data: {
username: username,
start_date: startDate,
end_date: endDate
},
dataType: 'json',
success: function(data) {
console.log("Raw data from server for username", username, ":", data);
var dailyData = {
download: [],
upload: []
};
// Iterate over dates in data and find the correct user data
for (var date in data) {
var users = data[date].users;
// Handle username as number case
var userData = users.find(user => user.username === username || user.username == parseInt(username));
if (userData) {
var rxBytes = convertToBytes(userData.rx);
var txBytes = convertToBytes(userData.tx);
// Store data in dailyData based on date
dailyData.download.push({ x: new Date(date).getTime(), y: rxBytes });
dailyData.upload.push({ x: new Date(date).getTime(), y: txBytes });
}
}
console.log("Filtered daily data for username", username, ":", dailyData);
resolve(dailyData);
},
error: function(xhr, textStatus, errorThrown) {
console.error("AJAX Error in generateDailyData:", textStatus, errorThrown);
console.log("Status:", xhr.status);
console.log("Response Text:", xhr.responseText);
reject(errorThrown);
}
});
});
}
function convertToBytes(value) {
let [number, unit] = value.split(' ');
number = parseFloat(number);
switch (unit) {
case 'GB':
return number * 1024 * 1024 * 1024;
case 'MB':
return number * 1024 * 1024;
case 'KB':
return number * 1024;
default:
return number;
}
}
// ========================================== NEW FITUR ==========================================//
function formatBytesPerSecond(bytes) {
if (bytes === 0) {
return '0 Bps';
}
var k = 1024;
var sizes = ['Bps', 'KBps', 'MBps', 'GBps', 'TBps', 'PBps', 'EBps', 'ZBps', 'YBps'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
var formattedValue = parseFloat((bytes / Math.pow(k, i)).toFixed(2));
return formattedValue + ' ' + sizes[i];
}
// Fungsi untuk mengubah ukuran dalam byte menjadi format yang lebih mudah dibaca
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
// Function to update traffic values and icons
function updateTrafficValues() {
var username = $j('#modalUsername').text().trim();
var interfaceValue = $j('#interface').val();
if (!username || !interfaceValue) {
console.error("Username or interface is undefined or empty.");
return;
}
$j.ajax({
url: '{$_url}plugin/pppoe_monitor_router_traffic/{$router}',
dataType: 'json',
data: { username: username, interface: interfaceValue },
success: function(data) {
var timestamp = new Date().getTime();
var txData = parseInt(data.rows.tx[0]) || 0;
var rxData = parseInt(data.rows.rx[0]) || 0;
// Log data tx dan rx untuk debugging
console.log('txData:', txData, 'rxData:', rxData);
// Update chart data
chartData.txData.push({ x: timestamp, y: txData });
chartData.rxData.push({ x: timestamp, y: rxData });
var maxDataPoints = 10;
if (chartData.txData.length > maxDataPoints) {
chartData.txData.shift();
chartData.rxData.shift();
}
// Update series on the chart
chart.updateSeries([
{ name: 'Download', data: chartData.txData },
{ name: 'Upload', data: chartData.rxData }
]);
// Find the icon element for the specific user based on username
var userRow = $j('#ppp-table tbody tr').filter(function() {
return $j(this).find('td').eq(1).text().trim() === username;
});
var iconElement = userRow.find('.traffic-icon');
// Define thresholds for traffic levels
var thresholdHigh = 2000; // Adjust these values as needed
var thresholdMedium = 1500; // Adjust these values as needed
// Adjust icon color based on traffic levels
if (txData > thresholdHigh || rxData > thresholdHigh) {
iconElement.removeClass('traffic-icon-green traffic-icon-yellow').addClass('traffic-icon-red');
} else if (txData > thresholdMedium || rxData > thresholdMedium) {
iconElement.removeClass('traffic-icon-green traffic-icon-red').addClass('traffic-icon-yellow');
} else {
iconElement.removeClass('traffic-icon-yellow traffic-icon-red').addClass('traffic-icon-green');
}
},
error: function(xhr, textStatus, errorThrown) {
console.error("Status: " + textStatus);
console.error("Error: " + errorThrown);
}
});
}
// Function to update traffic icons based on table data
function updateTrafficIcons(response) {
$j('#ppp-table tbody tr').each(function(index) {
var row = table.row(this).data();
if (row) {
var txValue = parseInt(row.tx, 10);
var rxValue = parseInt(row.rx, 10);
var iconElement = $j(this).find('.traffic-icon');
var maxLimit = row.max_limit;
if (maxLimit === '1M/2M') {
if (txValue >= 2 * 1024 * 1024 || rxValue >= 2 * 1024 * 1024) {
iconElement.removeClass().addClass('traffic-icon traffic-icon-red');
} else if (txValue >= 1.5 * 1024 * 1024 || rxValue >= 1.5 * 1024 * 1024) {
iconElement.removeClass().addClass('traffic-icon traffic-icon-yellow');
} else {
iconElement.removeClass().addClass('traffic-icon traffic-icon-green');
}
} else {
// Default logic for other max limits
if (txValue >= 2 * 1024 * 1024 || rxValue >= 2 * 1024 * 1024) {
iconElement.removeClass().addClass('traffic-icon traffic-icon-red');
} else if (txValue >= 1.5 * 1024 * 1024 || rxValue >= 1.5 * 1024 * 1024) {
iconElement.removeClass().addClass('traffic-icon traffic-icon-yellow');
} else {
iconElement.removeClass().addClass('traffic-icon traffic-icon-green');
}
}
}
});
}
// Donation Popup
document.addEventListener('DOMContentLoaded', function() {
setTimeout(function() {
document.getElementById('donationPopup').style.display = 'flex';
}, 1000);
});
document.getElementById('donationPopup').querySelector('.modalsupport-close').addEventListener('click', function() {
document.getElementById('donationPopup').style.display = 'none';
});
window.addEventListener('click', function(event) {
if (event.target === document.getElementById('donationPopup')) {
document.getElementById('donationPopup').style.display = 'none';
}
});
document.getElementById('donationPopup').querySelector('.donate-button').addEventListener('click', function() {
window.open('https://buymeacoffee.com/kevindonisaputra', '_blank');
});
</script>
{include file="sections/footer.tpl"}

BIN
system/plugin/user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
system/uploads/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,10 @@
{
"expired": "Hello [[name]], your internet package [[package]] has been expired.",
"balance_send": "You sent [[balance]] to [[name]].",
"balance_received": "You have received [[balance]] from [[name]].",
"reminder_7_day": "Hello *[[name]]*, \r\nyour internet package *[[package]]* will be expired in 7 days.",
"reminder_3_day": "Hello *[[name]]*, \r\nyour internet package *[[package]]* will be expired in 3 days.",
"reminder_1_day": "Hello *[[name]]*,\r\n your internet package *[[package]]* will be expired tomorrow.",
"invoice_paid": "*[[company_name]]*\r\n[[address]]\r\n[[phone]]\r\n\r\n\r\nINVOICE: *[[invoice]]*\r\nDate : [[date]]\r\n[[payment_gateway]] [[payment_channel]]\r\n\r\n\r\nType : *[[type]]*\r\nPackage : *[[plan_name]]*\r\nPrice : *[[plan_price]]*\r\n\r\nUsername : *[[user_name]]*\r\nPassword : ***********\r\n\r\nExpired : *[[expired_date]]*\r\n\r\n====================\r\n[[footer]]",
"invoice_balance": "*[[company_name]]*\r\n[[address]]\r\n[[phone]]\r\n\r\n\r\nINVOICE: *[[invoice]]*\r\nDate : [[date]]\r\n[[payment_gateway]] [[payment_channel]]\r\n\r\n\r\nType : *[[type]]*\r\nPackage : *[[plan_name]]*\r\nPrice : *[[plan_price]]*\r\n\r\n====================\r\n[[footer]]"
}

View File

@ -0,0 +1 @@
{"expired":"Dear Customer, your subscription of [[package]] has expired.\r\nONLY Home\/Office Users pay using:\r\nPay Bill:4355580\r\nAcc. No:[[username]]\r\nPrice:[[price]]","reminder_7_day":"","reminder_3_day":"","reminder_1_day":"Hello, Your Internet will Expire in 1 Day, Kindly make subscribe to continue using our Services. \r\n","invoice_paid":"Successfully Purchased [[plan_name]]. Expiry: [[expired_date]]. \r\nIf not connected use MPESA message or Account No.:[[user_name]] on the login page.","invoice_balance":""}

View File

@ -0,0 +1,205 @@
<?php
function BankStkPush_validate_config()
{
global $config;
if (empty($config['Stkbankacc']) || empty($config['Stkbankname']) ) {
sendTelegram("Bank Stk payment gateway not configured");
r2(U . 'order/balance', 'w', Lang::T("Admin has not yet setup the payment gateway, please tell admin"));
}
}
function BankStkPush_show_config()
{
global $ui, $config;
$ui->assign('_title', 'Bank Stk Push - ' . $config['CompanyName']);
$ui->display('bankstkpush.tpl');
}
function BankStkPush_save_config()
{
global $admin, $_L;
$bankacc = _post('account');
$bankname = _post('bankname');
$d = ORM::for_table('tbl_appconfig')->where('setting', 'Stkbankacc')->find_one();
if ($d) {
$d->value = $bankacc;
$d->save();
} else {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = 'Stkbankacc';
$d->value = $bankacc;
$d->save();
}
$d = ORM::for_table('tbl_appconfig')->where('setting', 'Stkbankname')->find_one();
if ($d) {
$d->value = $bankname;
$d->save();
} else {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = 'Stkbankname';
$d->value = $bankname;
$d->save();
}
_log('[' . $admin['username'] . ']: Stk Bank details ' . $_L['Settings_Saved_Successfully'], 'Admin', $admin['id']);
r2(U . 'paymentgateway/BankStkPush', 's', $_L['Settings_Saved_Successfully']);
}
function BankStkPush_create_transaction($trx, $user )
{
$url=(U. "plugin/initiatebankstk");
$d = ORM::for_table('tbl_payment_gateway')
->where('username', $user['username'])
->where('status', 1)
->find_one();
$d->gateway_trx_id = '';
$d->payment_method = 'Bank Stk Push';
$d->pg_url_payment = $url;
$d->pg_request = '';
$d->expired_date = date('Y-m-d H:i:s', strtotime("+5 minutes"));
$d->save();
r2(U . "order/view/" . $d['id'], 's', Lang::T("Create Transaction Success, Please click pay now to process payment"));
die();
}
function BankStkPush_payment_notification()
{
$captureLogs = file_get_contents("php://input");
$analizzare = json_decode($captureLogs);
/// sleep(10);
file_put_contents('back.log',$captureLogs,FILE_APPEND);
$response_code = $analizzare->Body->stkCallback->ResultCode;
$resultDesc = ($analizzare->Body->stkCallback->ResultDesc);
$merchant_req_id = ($analizzare->Body->stkCallback->MerchantRequestID);
$checkout_req_id = ($analizzare->Body->stkCallback->CheckoutRequestID);
$amount_paid = ($analizzare->Body->stkCallback->CallbackMetadata->Item['0']->Value);//get the amount value
$mpesa_code = ($analizzare->Body->stkCallback->CallbackMetadata->Item['1']->Value);//mpesa transaction code..
$sender_phone = ($analizzare->Body->stkCallback->CallbackMetadata->Item['4']->Value);//Telephone Number
$PaymentGatewayRecord = ORM::for_table('tbl_payment_gateway')
->where('checkout', $checkout_req_id)
->where('status', 1) // Add this line to filter by status
->order_by_desc('id')
->find_one();
$uname=$PaymentGatewayRecord->username;
$plan_id=$PaymentGatewayRecord->plan_id;
$mac_address=$PaymentGatewayRecord->mac_address;
$user=$PaymentGatewayRecord;
$userid = ORM::for_table('tbl_customers')
->where('username', $uname)
->order_by_desc('id')
->find_one();
$userid->username=$uname;
$userid->save();
$plans = ORM::for_table('tbl_plans')
->where('id', $plan_id)
->order_by_desc('id')
->find_one();
if ($response_code=="1032")
{
$now = date('Y-m-d H:i:s');
$PaymentGatewayRecord->paid_date = $now;
$PaymentGatewayRecord->status = 4;
$PaymentGatewayRecord->save();
exit();
}
if($response_code=="1037"){
$PaymentGatewayRecord->status = 1;
$PaymentGatewayRecord->pg_paid_response = 'User failed to enter pin';
$PaymentGatewayRecord->save();
exit();
}
if($response_code=="1"){
$PaymentGatewayRecord->status = 1;
$PaymentGatewayRecord->pg_paid_response = 'Not enough balance';
$PaymentGatewayRecord->save();
exit();
}
if($response_code=="2001"){
$PaymentGatewayRecord->status = 1;
$PaymentGatewayRecord->pg_paid_response = 'Wrong Mpesa pin';
$PaymentGatewayRecord->save();
exit();
}
if($response_code=="0"){
$now = date('Y-m-d H:i:s');
$date = date('Y-m-d');
$time= date('H:i:s');
$check_mpesa = ORM::for_table('tbl_payment_gateway')
->where('gateway_trx_id', $mpesa_code)
->find_one();
if($check_mpesa){
echo "double callback, ignore one";
die;
}
$plan_type=$plans->type;
$UserId=$userid->id;
if (!Package::rechargeUser($UserId, $user['routers'], $user['plan_id'], $user['gateway'], $mpesa_code)){
$PaymentGatewayRecord->status = 2;
$PaymentGatewayRecord->paid_date = $now;
$PaymentGatewayRecord->gateway_trx_id = $mpesa_code;
$PaymentGatewayRecord->save();
$username = $PaymentGatewayRecord->username;
// Check if a transaction with the same gateway_trx_id already exists
$existingTransaction = ORM::for_table('tbl_transactions')
->where('mpesacode', $mpesa_code)
->find_one();
if (!$existingTransaction) {
// Save transaction data to tbl_transactions
$transaction = ORM::for_table('tbl_transactions')->create();
$transaction->invoice = $PaymentGatewayRecord->gateway_trx_id; // Set invoice to gateway_trx_id value
$transaction->username = $PaymentGatewayRecord->username;
$transaction->plan_name = $PaymentGatewayRecord->plan_name;
$transaction->price = $amount_paid;
$transaction->recharged_on = $date;
$transaction->recharged_time = $time;
$transaction->expiration = $now;
$transaction->time = $now;
$transaction->method = $PaymentGatewayRecord->payment_method;
$transaction->routers = 0;
$transaction->Type = 'Balance';
$transaction->mpesacode = $mpesa_code;
$transaction->save();
} else {
error_log("Duplicate transaction entry detected for gateway_trx_id: " . $PaymentGatewayRecord->gateway_trx_id);
}
} else {
// Update tbl_recharges
$PaymentGatewayRecord->status = 2;
$PaymentGatewayRecord->paid_date = $now;
$PaymentGatewayRecord->gateway_trx_id = $mpesa_code;
$PaymentGatewayRecord->save();
}
}
}

View File

@ -0,0 +1,234 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
*
* Payment Gateway flutterwave.com
*
* created by @foculinkstech
*
**/
function flutterwave_validate_config()
{
global $config;
if (empty($config['flutterwave_secret_key'])) {
Message::sendTelegram("flutterwave payment gateway not configured");
r2(U . 'order/package', 'w', Lang::T("Admin has not yet setup flutterwave payment gateway, please tell admin"));
}
}
function flutterwave_show_config()
{
global $ui;
$ui->assign('_title', 'Flutterwave - Payment Gateway');
$ui->assign('cur', json_decode(file_get_contents('system/paymentgateway/flutterwave_currency.json'), true));
$ui->assign('channel', json_decode(file_get_contents('system/paymentgateway/flutterwave_channel.json'), true));
$ui->display('flutterwave.tpl');
}
function flutterwave_save_config()
{
global $admin, $_L;
$flutterwave_secret_key = _post('flutterwave_secret_key');
$flutterwave_currency = _post('flutterwave_currency');
$d = ORM::for_table('tbl_appconfig')->where('setting', 'flutterwave_secret_key')->find_one();
if ($d) {
$d->value = $flutterwave_secret_key;
$d->save();
} else {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = 'flutterwave_secret_key';
$d->value = $flutterwave_secret_key;
$d->save();
}
$d = ORM::for_table('tbl_appconfig')->where('setting', 'flutterwave_currency')->find_one();
if ($d) {
$d->value = $flutterwave_currency;
$d->save();
} else {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = 'flutterwave_currency';
$d->value = $flutterwave_currency;
$d->save();
}
$d = ORM::for_table('tbl_appconfig')->where('setting', 'flutterwave_channel')->find_one();
if ($d) {
$d->value = implode(',', $_POST['flutterwave_channel']);
$d->save();
} else {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = 'flutterwave_channel';
$d->value = implode(',', $_POST['flutterwave_channel']);
$d->save();
}
_log('[' . $admin['username'] . ']: Flutterwave ' . $_L['Settings_Saved_Successfully'], 'Admin', $admin['id']);
r2(U . 'paymentgateway/flutterwave', 's', $_L['Settings_Saved_Successfully']);
}
function flutterwave_create_transaction($trx, $user)
{
global $config;
$txref = uniqid('trx');
$json = [
'tx_ref' => $txref,
'amount' => $trx['price'],
'currency' => $config['flutterwave_currency'],
'payment_options' => explode(',', $config['flutterwave_channel']),
'customer' => [
'email' => (empty($user['email'])) ? $user['username'] . '@' . $_SERVER['HTTP_HOST'] : $user['email'],
'name' => $user['fullname'],
'phonenumber' => $user['phonenumber']
],
'meta' => [
'price' => $trx['price'],
'username' => $user['username'],
'trxid' => $trx['id']
],
'customizations' => [
'title' => $trx['plan_name'],
'description' => $trx['plan_name'],
],
'redirect_url' => U . 'callback/flutterwave'
];
// die(json_encode($json,JSON_PRETTY_PRINT));
$result = json_decode(Http::postJsonData(flutterwave_get_server() . 'payments', $json,[
'Authorization: Bearer ' . $config['flutterwave_secret_key'],
'Cache-Control: no-cahe'
],
),
true);
//die(json_encode($result,JSON_PRETTY_PRINT));
if ($result['status'] == 'error') {
Message::sendTelegram("Flutterwave payment failed\n\n" . json_encode($result, JSON_PRETTY_PRINT));
r2(U . 'order/package', 'e', Lang::T("Failed to create transaction.\n".$result['message']));
}
$d = ORM::for_table('tbl_payment_gateway')
->where('username', $user['username'])
->where('status', 1)
->find_one();
$d->gateway_trx_id = $txref;
$d->pg_url_payment = $result['data']['link'];
$d->pg_request = json_encode($result);
$d->expired_date = date('Y-m-d H:i:s', strtotime("+ 6 HOUR"));
$d->save();
header('Location: ' . $result['data']['link']);
exit();
r2(U . "order/view/" . $d['id'], 's', Lang::T("Create Transaction Success"));
}
function flutterwave_payment_notification()
{
global $config;
if(isset($_GET['status']))
{
//* check payment status
if($_GET['status'] == 'cancelled')
{
// die(json_encode($txref,JSON_PRETTY_PRINT));
Message::sendTelegram("Flutterwave Payment Cancelled: \n\n");
r2(U . 'order/package', 'e', Lang::T("Flutterwave Payment Cancelled."));
}
elseif($_GET['status'] == 'successful')
{
$txid = $_GET['transaction_id'];
$result = json_decode(Http::getData(flutterwave_get_server() . 'transactions/' . $txid. '/verify', [
'Authorization: Bearer ' . $config['flutterwave_secret_key'],
'Cache-Control: no-cahe'
]), true);
//die(json_encode($result,JSON_PRETTY_PRINT));
{
$id = $result['data']['id'];
$amountPaid = $result['data']['charged_amount'];
$amountToPay = $result['data']['meta']['price'];
$username = $result['data']['meta']['username'];
$trxid = $result['data']['meta']['trxid'];
if($amountPaid >= $amountToPay)
{
// die(json_encode($trxid,JSON_PRETTY_PRINT));
// echo 'Payment successful';
$d = ORM::for_table('tbl_payment_gateway')
->where('username', $username)
->where('status', 1)
->find_one();
$d->gateway_trx_id = $id;
$d->save();
r2(U . 'order/view/'.$trxid.'/check');
// r2(U . 'order/package', 's', Lang::T("Flutterwave Payment Completed."));
exit();
//* Continue to give item to the user
}
else
{
// echo 'Fraud transactio detected';
r2(U . 'order/package', 'e', Lang::T("Fraud transactions detected."));
exit();
}
}
}
}
}
function flutterwave_get_status($trx, $user)
{
global $config;
$trans_id = $trx['gateway_trx_id'];
$result = json_decode(Http::getData(flutterwave_get_server() . 'transactions/' . $trx['gateway_trx_id']. '/verify', [
'Authorization: Bearer ' . $config['flutterwave_secret_key'],
'Cache-Control: no-cahe'
]), true);
//die(json_encode($result,JSON_PRETTY_PRINT));
if ($result['status'] == 'error') {
r2(U . "order/view/" . $trx['id'], 'w', Lang::T("Transaction still unpaid."));
} else if (in_array($result['status'], ['success']) && $trx['status'] != 2) {
if (!Package::rechargeUser($user['id'], $trx['routers'], $trx['plan_id'], $trx['gateway'], 'Flutterwave')) {
r2(U . "order/view/" . $trx['id'], 'd', Lang::T("Failed to activate your Package, please try again later."));
}
$trx->pg_paid_response = json_encode($result);
$trx->payment_method = 'Flutterwave';
$trx->payment_channel = $result['data']['payment_type'];
$trx->paid_date = date('Y-m-d H:i:s', strtotime( $result['data']['created_at']));
$trx->status = 2;
$trx->save();
r2(U . "order/view/" . $trx['id'], 's', Lang::T("Transaction successful."));
} else if ($result['status'] == 'EXPIRED') {
$trx->pg_paid_response = json_encode($result);
$trx->status = 3;
$trx->save();
r2(U . "order/view/" . $trx['id'], 'd', Lang::T("Transaction expired."));
} else if ($trx['status'] == 2) {
r2(U . "order/view/" . $trx['id'], 'd', Lang::T("Transaction has been paid.."));
}else{
Message::sendTelegram("flutterwave_get_status: unknown result\n\n".json_encode($result, JSON_PRETTY_PRINT));
r2(U . "order/view/" . $trx['id'], 'd', Lang::T("Unknown Command."));
}
}
function flutterwave_get_server()
{
global $_app_stage;
if ($_app_stage == 'Live') {
return 'https://api.flutterwave.com/v3/';
} else {
return 'https://api.flutterwave.com/v3/';
}
}

View File

@ -0,0 +1,35 @@
[
{
"id": "card",
"name": "Card Payment"
},
{
"id": "ussd",
"name": "USSD"
},
{
"id": "account",
"name": "Bank Account"
},
{
"id": "banktransfer",
"name": "Bank Transfer"
},
{
"id": "nqr",
"name": "QR payment"
},
{
"id": "mpesa",
"name": "M-Pesa"
},
{
"id": "mobilemoneyghana",
"name": "Mobile money Ghana"
},
{
"id": "credit",
"name": "Credit payment"
}
]

View File

@ -0,0 +1,30 @@
[
{
"id": "NGN",
"name": "Nigerian Naira"
},
{
"id": "GHC",
"name": "Ghana Cedis"
},
{
"id": "KES",
"name": "Kenyan Shilling"
},
{
"id": "ZAR",
"name": "South African Rand"
},
{
"id": "GBP",
"name": "British Pound Sterling"
},
{
"id": "USD",
"name": "United States Dollar"
},
{
"id": "TZS",
"name": "Tanzanian Shilling"
}
]

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
ui/.DS_Store vendored Normal file

Binary file not shown.

8
ui/index.html Normal file
View File

@ -0,0 +1,8 @@
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>

BIN
ui/ui/.DS_Store vendored Normal file

Binary file not shown.

17
ui/ui/404.tpl Normal file
View File

@ -0,0 +1,17 @@
{include file="sections/user-header.tpl"}
<div class="container-fluid">
<div class="col-xl-12 col-xxl-12">
<div class="col-md-12">
<div class="card" style="display: grid; align-content: center;">
<div class="card-body text-center ai-icon text-primary">
<i class="flaticon-381-error"></i>
<h4 class="my-2">404</h4>
<a href="{$_url}home" title="Go to Dashboard" class="btn my-2 btn-primary btn-lg px-4"><i
class="fa fa-usd"></i> Go to Dashboard</a>
</div>
</div>
</div>
</div>
</div>
</div>
{include file="sections/user-footer.tpl"}

BIN
ui/ui/Ass/.DS_Store vendored Normal file

Binary file not shown.

30929
ui/ui/Ass/css/style.css Normal file

File diff suppressed because it is too large Load Diff

BIN
ui/ui/Ass/icons/.DS_Store vendored Normal file

Binary file not shown.

49
ui/ui/a404.tpl Normal file
View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="https://laravel.com/img/favicon/favicon-16x16.png" type='image/x-icon'>
<title>Admin Dashboard</title>
<link rel="stylesheet" href="assets/vendor/chartist/css/chartist.min.css">
<link href="assets/vendor/bootstrap-select/dist/css/bootstrap-select.min.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">
</head>
<body style="background-color:#e9ecef;">
<div class="" style="margin-top: 32px;">
<div class="container-fluid">
<div class="col-xl-12 col-xxl-12">
<div class="col-md-12">
<div class="card" style="display: grid; align-content: center;">
<div class="card-body text-center ai-icon text-primary">
<i class="flaticon-381-error"></i>
<h4 class="my-2">404</h4>
<a href="{$_url}dashboard" title="Go to Dashboard" class="btn my-2 btn-primary btn-lg px-4"><i
class="fa fa-usd"></i> Go to Dashboard</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="assets/vendor/global/global.min.js"></script>
<script src="assets/vendor/bootstrap-select/dist/js/bootstrap-select.min.js"></script>
<script src="assets/vendor/chart.js/Chart.bundle.min.js"></script>
<!-- Chart piety plugin files -->
<script src="assets/vendor/peity/jquery.peity.min.js"></script>
<!-- Apex Chart -->
<script src="assets/vendor/apexchart/apexchart.js"></script>
<!-- Dashboard 1 -->
<script src="assets/js/dashboard/dashboard-1.js"></script>
<script src="assets/js/custom.min.js"></script>
<script src="assets/js/deznav-init.js"></script>
<script src="assets/js/demo.js"></script>
</body>
</html>

53
ui/ui/admin-login.tpl Normal file
View File

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="https://laravel.com/img/favicon/favicon-16x16.png" type='image/x-icon'>
<title>{Lang::T('Login')} - {$_c['CompanyName']}</title>
<link rel="stylesheet" href="ui/ui/Css/lineicons.min.css">
<link rel="stylesheet" href="ui/ui/Css/Style.css">
<meta http-equiv="refresh" content="{$time}; url={$url}">
</head>
<body>
<div class="login-div">
<div class="logo"></div>
<div class="title">{$_c['CompanyName']} - Admin</div>
{if isset($notify)}
{$notify}
{/if}
<form action="{$_url}admin/post" method="post">
<div class="login-in">
<div class="username">
<i class="lni lni-user"></i>
<input type="text" required class="form-control" name="username" placeholder="{Lang::T('Username')}">
</div>
<div class="password">
<i class="lni lni-lock"></i>
<input type="password" required class="form-control" name="password" placeholder="{Lang::T('Password')}">
</div>
</div>
<button type="submit" class="buttons" value="{Lang::T('Login')}">login</button>
</form>
</div>
<script src="assets/vendor/global/global.min.js"></script>
<script src="assets/vendor/bootstrap-select/dist/js/bootstrap-select.min.js"></script>
<script src="assets/vendor/chart.js/Chart.bundle.min.js"></script>
<!-- Chart piety plugin files -->
<script src="assets/vendor/peity/jquery.peity.min.js"></script>
<!-- Apex Chart -->
<script src="assets/vendor/apexchart/apexchart.js"></script>
<!-- Dashboard 1 -->
<script src="assets/js/dashboard/dashboard-1.js"></script>
<script src="assets/js/custom.min.js"></script>
<script src="assets/js/deznav-init.js"></script>
<script src="assets/js/demo.js"></script>
</body>
</html>

70
ui/ui/alert.tpl Normal file
View File

@ -0,0 +1,70 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="https://laravel.com/img/favicon/favicon-16x16.png" type='image/x-icon'>
<title>{Lang::T('Login')} - {$_c['CompanyName']}</title>
<link rel="stylesheet" href="ui/ui/assets/vendor/chartist/css/chartist.min.css">
<link href="ui/ui/assets/vendor/bootstrap-select/dist/css/bootstrap-select.min.css" rel="stylesheet">
<link href="ui/ui/assets/css/style.css" rel="stylesheet">
<link rel="stylesheet" href="ui/ui/styles/sweetalert2.min.css" />
<link rel="stylesheet" href="ui/ui/styles/plugins/pace.css" />
<script src="ui/ui/scripts/sweetalert2.all.min.js"></script>
<meta http-equiv="refresh" content="{$time}; url={$url}">
</head>
<body style="background-color:#e9ecef;">
<div class="" style="margin-top: 72px;">
<div class="container-fluid">
<div class="col-xl-12 col-xxl-12">
<div class="col-md-12">
<div class="card" style="display: grid; align-content: center;">
<div class="card-body text-center ai-icon text-primary">
<h4 class="my-2">{ucwords(Lang::T($type))}</h4>
<div class="my-2">{$text}</div>
<a href="{$url}" id="button" class="btn my-2 btn-primary btn-lg px-4"><i
class="fa fa-usd"></i> {Lang::T('Click Here')} ({$time})</a>
<div class="lockscreen-footer text-center">
{$_c['CompanyName']}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
var time = {$time};
timer();
function timer() {
setTimeout(() => {
time--;
if (time > -1) {
document.getElementById("button").innerHTML = "{Lang::T('Click Here')} (" + time + ")";
timer();
}
}, 1000);
}
</script>
<script src="assets/vendor/global/global.min.js"></script>
<script src="assets/vendor/bootstrap-select/dist/js/bootstrap-select.min.js"></script>
<script src="assets/vendor/chart.js/Chart.bundle.min.js"></script>
<!-- Chart piety plugin files -->
<script src="assets/vendor/peity/jquery.peity.min.js"></script>
<!-- Apex Chart -->
<script src="assets/vendor/apexchart/apexchart.js"></script>
<!-- Dashboard 1 -->
<script src="assets/js/dashboard/dashboard-1.js"></script>
<script src="assets/js/custom.min.js"></script>
<script src="assets/js/deznav-init.js"></script>
<script src="assets/js/demo.js"></script>
</body>
</html>

159
ui/ui/app-localisation.tpl Normal file
View File

@ -0,0 +1,159 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-xxl-6 col-xxl-12">
<div class="col-md-12">
<div class="card">
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<h5 class="card-title">{Lang::T('Localisation')}</h5>
</div>
<div class="card-body">
<form class="form-horizontal" method="post" role="form"
action="{$_url}settings/localisation-post">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Timezone')}</label>
<div class="col-md-6">
<select name="tzone" id="tzone" class="form-control">
{foreach $tlist as $value => $label}
<option value="{$value}" {if $_c['timezone'] eq
$value}selected="selected" {/if}>
{$label}</option>
{/foreach}
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Date Format')}</label>
<div class="col-md-6">
<select class="form-control" name="date_format" id="date_format">
<option value="d/m/Y" {if $_c['date_format'] eq 'd/m/Y' }
selected="selected" {/if}>
{date('d/m/Y')}</option>
<option value="d.m.Y" {if $_c['date_format'] eq 'd.m.Y' }
selected="selected" {/if}>
{date('d.m.Y')}</option>
<option value="d-m-Y" {if $_c['date_format'] eq 'd-m-Y' }
selected="selected" {/if}>
{date('d-m-Y')}</option>
<option value="m/d/Y" {if $_c['date_format'] eq 'm/d/Y' }
selected="selected" {/if}>
{date('m/d/Y')}</option>
<option value="Y/m/d" {if $_c['date_format'] eq 'Y/m/d' }
selected="selected" {/if}>
{date('Y/m/d')}</option>
<option value="Y-m-d" {if $_c['date_format'] eq 'Y-m-d' }
selected="selected" {/if}>
{date('Y-m-d')}</option>
<option value="M d Y" {if $_c['date_format'] eq 'M d Y' }
selected="selected" {/if}>
{date('M d Y')}</option>
<option value="d M Y" {if $_c['date_format'] eq 'd M Y' }
selected="selected" {/if}>
{date('d M Y')}</option>
<option value="jS M y" {if $_c['date_format'] eq 'jS M y' }
selected="selected" {/if}>
{date('jS M y')}</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Default Language')}</label>
<div class="col-md-6">
<select class="form-control" name="lan" id="lan">
{foreach $lani as $lanis}
<option value="{$lanis@key}" {if $_c['language'] eq $lanis@key}
selected="selected" {/if}>
{$lanis@key}
</option>
{/foreach}
<option disabled>_________</option>
{foreach $lan as $lans}
<option value="{$lans@key}" {if $_c['language'] eq $lans@key}
selected="selected" {/if}>
{$lans@key}
</option>
{/foreach}
</select>
</div>
<div class="col-md-4 help-block">
<a href="{$_url}settings/language">{Lang::T('Language Editor')}</a>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Decimal Point')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="dec_point" name="dec_point"
value="{$_c['dec_point']}">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Thousands Separator')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="thousands_sep"
name="thousands_sep" value="{$_c['thousands_sep']}">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Currency Code')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="currency_code"
name="currency_code" value="{$_c['currency_code']}">
</div>
<span class="help-block col-md-4">{Lang::T('Keep it blank if you do not want to
show currency code')}</span>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Country Code Phone')}</label>
<div class="col-md-6">
<div class="input-group">
<span class="input-group-text" id="basic-addon1">+</span>
<input type="text" class="form-control" id="country_code_phone"
placeholder="62" name="country_code_phone"
value="{$_c['country_code_phone']}">
</div>
</div>
</div>
<hr>
<div class="form-group row">
<label class="col-md-2 control-label">Radius Plan</label>
<div class="col-md-6">
<input type="text" class="form-control" id="radius_plan" name="radius_plan"
value="{if $_c['radius_plan']==''}Radius Plan{else}{$_c['radius_plan']}{/if}">
</div>
<span class="help-block col-md-4">{Lang::T('Change title in user Plan
order')}</span>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">Hotspot Plan</label>
<div class="col-md-6">
<input type="text" class="form-control" id="hotspot_plan"
name="hotspot_plan"
value="{if $_c['hotspot_plan']==''}Hotspot Plan{else}{$_c['hotspot_plan']}{/if}">
</div>
<span class="help-block col-md-4">{Lang::T('Change title in user Plan
order')}</span>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">PPPOE Plan</label>
<div class="col-md-6">
<input type="text" class="form-control" id="pppoe_plan" name="pppoe_plan"
value="{if $_c['pppoe_plan']==''}PPPOE Plan{else}{$_c['pppoe_plan']}{/if}">
</div>
<span class="help-block col-md-4">{Lang::T('Change title in user Plan
order')}</span>
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-primary" type="submit">{Lang::T('Save
Changes')}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

192
ui/ui/app-notifications.tpl Normal file
View File

@ -0,0 +1,192 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<form class="form-horizontal" method="post" role="form" action="{$_url}settings/notifications-post">
<div class="row">
<div class="col-xxl-6 col-xxl-12">
<div class="col-md-12">
<div class="card">
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('User Notification')}</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group">
<label class="control-label">{Lang::T('Expired Notification
Message')}</label>
<div class="">
<textarea style="overflow: hidden;" class="form-control"
id="expired" oninput="autoExpand(this)" name="expired"
placeholder="Hello [[name]], your internet package [[package]] has been expired"
rows="6">{if $_json['expired']!=''}{Lang::htmlspecialchars($_json['expired'])}{else}Hello [[name]], your internet package [[package]] has been expired.{/if}</textarea>
</div>
<script>
function autoExpand(element) {
element.style.height = 'auto';
element.style.height = (element.scrollHeight) + 'px';
}
</script>
<p class="help-block">
<b>[[name]]</b> will be replaced with Customer Name.
<b>[[username]]</b> will be replaced with Customer username.
<b>[[package]]</b> will be replaced with Package name.
<b>[[price]]</b> will be replaced with Package price.
<b>[[bills]]</b> aditional bills for customers
</p>
</div>
<div class="form-group row">
<label class="control-label">{Lang::T('Reminder 7 days')}</label>
<div class="">
<textarea class="form-control" id="reminder_7_day"
name="reminder_7_day"
rows="4">{Lang::htmlspecialchars($_json['reminder_7_day'])}</textarea>
</div>
<p class="help-block">
<b>[[name]]</b> will be replaced with Customer Name.
<b>[[username]]</b> will be replaced with Customer username.
<b>[[package]]</b> will be replaced with Package name.
<b>[[price]]</b> will be replaced with Package price.
<b>[[expired_date]]</b> will be replaced with Expiration date.
<b>[[bills]]</b> aditional bills for customers
</p>
</div>
<div class="form-group">
<label class="control-label">{Lang::T('Reminder 3 days')}</label>
<div class="">
<textarea class="form-control" id="reminder_3_day"
name="reminder_3_day"
rows="3">{Lang::htmlspecialchars($_json['reminder_3_day'])}</textarea>
</div>
<p class="help-block">
<b>[[name]]</b> will be replaced with Customer Name.
<b>[[username]]</b> will be replaced with Customer username.
<b>[[package]]</b> will be replaced with Package name.
<b>[[price]]</b> will be replaced with Package price.
<b>[[expired_date]]</b> will be replaced with Expiration date.
<b>[[bills]]</b> aditional bills for customers
</p>
</div>
<div class="form-group">
<label class="control-label">{Lang::T('Reminder 1 day')}</label>
<div class="">
<textarea class="form-control" id="reminder_1_day"
name="reminder_1_day"
rows="3">{Lang::htmlspecialchars($_json['reminder_1_day'])}</textarea>
</div>
<p class="help-block">
<b>[[name]]</b> will be replaced with Customer Name.
<b>[[username]]</b> will be replaced with Customer username.
<b>[[package]]</b> will be replaced with Package name.
<b>[[price]]</b> will be replaced with Package price.
<b>[[expired_date]]</b> will be replaced with Expiration date.
<b>[[bills]]</b> aditional bills for customers
</p>
</div>
<div class="form-group">
<label class="control-label">{Lang::T('Invoice Notification
Payment')}</label>
<div class="">
<textarea class="form-control" id="invoice_paid" name="invoice_paid"
placeholder="Hello [[name]], your internet package [[package]] has been expired"
rows="20">{Lang::htmlspecialchars($_json['invoice_paid'])}</textarea>
</div>
<p class="help-block">
<b>[[company_name]]</b> Your Company Name at Settings.
<b>[[address]]</b> Your Company Address at Settings.
<b>[[phone]]</b> Your Company Phone at Settings.
<b>[[invoice]]</b> invoice number.
<b>[[date]]</b> Date invoice created.
<b>[[payment_gateway]]</b> Payment gateway user paid from.
<b>[[payment_channel]]</b> Payment channel user paid from.
<b>[[type]]</b> is Hotspot/PPPOE.
<b>[[plan_name]]</b> Internet Package.
<b>[[plan_price]]</b> Internet Package Prices.
<b>[[name]]</b> Receiver name.
<b>[[user_name]]</b> Username internet.
<b>[[user_password]]</b> User password.
<b>[[expired_date]]</b> Expired datetime.
<b>[[footer]]</b> Invoice Footer.
<b>[[note]]</b> For Notes by admin.
</p>
</div>
<div class="form-group">
<label class="control-label">{Lang::T('Balance Notification
Payment')}</label>
<div class="">
<textarea class="form-control" id="invoice_balance"
name="invoice_balance"
placeholder="Hello [[name]], your internet package [[package]] has been expired"
rows="20">{Lang::htmlspecialchars($_json['invoice_balance'])}</textarea>
</div>
<p class="help-block">
<b>[[company_name]]</b> Your Company Name at Settings.
<b>[[address]]</b> Your Company Address at Settings.
<b>[[phone]]</b> Your Company Phone at Settings.
<b>[[invoice]]</b> invoice number.
<b>[[date]]</b> Date invoice created.
<b>[[payment_gateway]]</b> Payment gateway user paid from.
<b>[[payment_channel]]</b> Payment channel user paid from.
<b>[[type]]</b> is Hotspot/PPPOE.
<b>[[plan_name]]</b> Internet Package.
<b>[[plan_price]]</b> Internet Package Prices.
<b>[[name]]</b> Receiver name.
<b>[[user_name]]</b> Username internet.
<b>[[user_password]]</b> User password.
<b>[[trx_date]]</b> Transaction datetime.
<b>[[balance_before]]</b> Balance Before.
<b>[[balance]]</b> Balance After.
<b>[[footer]]</b> Invoice Footer.
</p>
</div>
{if $_c['enable_balance'] == 'yes'}
<div class="panel-body">
<div class="form-group">
<label class="control-label">{Lang::T('Send Balance')}</label>
<div class="">
<textarea class="form-control" id="balance_send"
name="balance_send"
rows="3">{if $_json['balance_send']}{Lang::htmlspecialchars($_json['balance_send'])}{else}{Lang::htmlspecialchars($_default['balance_send'])}{/if}</textarea>
</div>
<p class="help-block">
<b>[[name]]</b> Receiver name.
<b>[[balance]]</b> how much balance have been send.
<b>[[current_balance]]</b> Current Balance.
</p>
</div>
</div>
<div class="panel-body">
<div class="form-group">
<label class="control-label">{Lang::T('Received Balance')}</label>
<div class="">
<textarea class="form-control" id="balance_received"
name="balance_received"
rows="3">{if $_json['balance_received']}{Lang::htmlspecialchars($_json['balance_received'])}{else}{Lang::htmlspecialchars($_default['balance_received'])}{/if}</textarea>
</div>
<p class="help-block">
<b>[[name]]</b> Sender name.
<b>[[balance]]</b> how much balance have been received.
<b>[[current_balance]]</b> Current Balance.
</p>
</div>
</div>
{/if}
<hr>
<div class="form-group">
<button class="btn btn-success btn-block" type="submit">{Lang::T('Save
Changes')}</button>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
{include file="sections/footer.tpl"}

831
ui/ui/app-settings.tpl Normal file
View File

@ -0,0 +1,831 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<form class="form-horizontal" method="post" role="form" action="{$_url}settings/app-post"
enctype="multipart/form-data">
<div class="row">
<div class="col-xxl-12">
<div class="col-md-12">
<div class="card">
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('General Settings')}</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Application Name/
Company Name')}</label>
<div class="col-md-6">
<input type="text" required class="form-control" id="CompanyName"
name="CompanyName" value="{$_c['CompanyName']}">
</div>
<span class="help-block col-md-4">{Lang::T('This Name will be shown on
the Title')}</span>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Company Logo')}</label>
<div class="col-md-6">
<input type="file" class="form-control" id="logo" name="logo"
accept="image/*">
<span class="help-block">For PDF Reports | Best size 1078 x 200 |
uploaded image will be
autosize</span>
</div>
<span class="help-block col-md-4">
<a href="./{$logo}" target="_blank"><img src="./{$logo}" height="48"
alt="logo for PDF"></a>
</span>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Company
Footer')}</label>
<div class="col-md-6">
<input type="text" required class="form-control" id="CompanyFooter"
name="CompanyFooter" value="{$_c['CompanyFooter']}">
</div>
<span class="help-block col-md-4">{Lang::T('Will show below user
pages')}</span>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Address')}</label>
<div class="col-md-6">
<textarea class="form-control" id="address" name="address"
rows="3">{Lang::htmlspecialchars($_c['address'])}</textarea>
</div>
<span class="help-block col-md-4">{Lang::T('You can use html
tag')}</span>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Phone Number')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="phone" name="phone"
value="{$_c['phone']}">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Invoice
Footer')}</label>
<div class="col-md-6">
<textarea class="form-control" id="note" name="note"
rows="3">{Lang::htmlspecialchars($_c['note'])}</textarea>
<span class="help-block">{Lang::T('You can use html tag')}</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label"><i
class="glyphicon glyphicon-print"></i> Print Max
Char</label>
<div class="col-md-6">
<input type="number" required class="form-control" id="printer_cols"
placeholder="37" name="printer_cols"
value="{$_c['printer_cols']}">
</div>
<span class="help-block col-md-4">For invoice print using Thermal
Printer</span>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">Theme</label>
<div class="col-md-6">
<select name="theme" id="theme" class="form-select" style="height: 52px; background-color: white;">
<option value="default" {if $_c['theme'] eq 'default'
}selected="selected" {/if}>Default
</option>
{foreach $themes as $theme}
<option value="{$theme}" {if $_c['theme'] eq
$theme}selected="selected" {/if}>
{Lang::ucWords($theme)}</option>
{/foreach}
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">APP URL</label>
<div class="col-md-6">
<input type="text" readonly class="form-control" value="{$app_url}">
</div>
<p class="help-block col-md-4">edit at config.php</p>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">Hide Dashboard Content</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-3 control-label"><input type="checkbox"
name="hide_mrc" value="yes" {if $_c['hide_mrc'] eq 'yes'
}checked{/if}>
{Lang::T('Monthly Registered Customers')}</label>
<label class="col-md-2 control-label"><input type="checkbox"
name="hide_tms" value="yes" {if $_c['hide_tms'] eq 'yes'
}checked{/if}> {Lang::T('Total Monthly Sales')}</label>
<label class="col-md-2 control-label"><input type="checkbox"
name="hide_aui" value="yes" {if $_c['hide_aui'] eq 'yes'
}checked{/if}> {Lang::T('All Users Insights')}</label>
<label class="col-md-2 control-label"><input type="checkbox"
name="hide_al" value="yes" {if $_c['hide_al'] eq 'yes'
}checked{/if}> {Lang::T('Activity Log')}</label>
<label class="col-md-2 control-label"><input type="checkbox"
name="hide_uet" value="yes" {if $_c['hide_uet'] eq 'yes'
}checked{/if}> {Lang::T('User Expired, Today')}</label>
<label class="col-md-2 control-label"><input type="checkbox"
name="hide_vs" value="yes" {if $_c['hide_vs'] eq 'yes'
}checked{/if}> Vouchers Stock</label>
<label class="col-md-2 control-label"><input type="checkbox"
name="hide_pg" value="yes" {if $_c['hide_pg'] eq 'yes'
}checked{/if}> Payment Gateway</label>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">Voucher</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Disable
Voucher')}</label>
<div class="col-md-6">
<select name="disable_voucher" id="disable_voucher"
class="form-select" style="height: 52px; background-color: white;">
<option value="no" {if $_c['disable_voucher']=='no'
}selected="selected" {/if}>No
</option>
<option value="yes" {if $_c['disable_voucher']=='yes'
}selected="selected" {/if}>Yes
</option>
</select>
</div>
<p class="help-block col-md-4">{Lang::T('Voucher activation menu will be
hidden')}</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Voucher
Format')}</label>
<div class="col-md-6">
<select name="voucher_format" id="voucher_format"
class="form-select" style="height: 52px; background-color: white;">
<option value="up" {if $_c['voucher_format']=='up'
}selected="selected" {/if}>UPPERCASE
</option>
<option value="low" {if $_c['voucher_format']=='low'
}selected="selected" {/if}>
lowercase
</option>
<option value="rand" {if $_c['voucher_format']=='rand'
}selected="selected" {/if}>
RaNdoM
</option>
</select>
</div>
<p class="help-block col-md-4">UPPERCASE lowercase RaNdoM</p>
</div>
{if $_c['disable_voucher'] != 'yes'}
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Disable
Registration')}</label>
<div class="col-md-6">
<select name="disable_registration" id="disable_registration"
class="form-select" style="height: 52px; background-color: white;">
<option value="no" {if $_c['disable_registration']=='no'
}selected="selected" {/if}>No
</option>
<option value="yes" {if $_c['disable_registration']=='yes'
}selected="selected" {/if}>
Yes
</option>
</select>
</div>
<p class="help-block col-md-4">
{Lang::T('Customer just Login with Phone number and Voucher Code,
Voucher will be
password')}
</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">Redirect after Activation</label>
<div class="col-md-6">
<input type="text" class="form-control" id="voucher_redirect"
name="voucher_redirect"
placeholder="https://192.168.88.1/status"
value="{$voucher_redirect}">
</div>
<p class="help-block col-md-4">
{Lang::T('After Customer activate voucher or login, customer will be
redirected to this
url')}
</p>
</div>
{/if}
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">FreeRadius</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">Enable Radius</label>
<div class="col-md-6">
<select name="radius_enable" id="radius_enable"
class="form-select" style="height: 52px; background-color: white;" text-muted">
<option value="0">No</option>
<option value="1" {if $_c['radius_enable']}selected="selected"
{/if}>Yes</option>
</select>
</div>
<p class="help-block col-md-4"><a
href="https://github.com/hotspotbilling/phpnuxbill/wiki/FreeRadius"
target="_blank">You can find Free Radius Setup & Configuration
Instructions here.</a></p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">Radius Client</label>
<div class="col-md-6">
<input type="text" class="form-control" name="radius_client"
value="{$_c['radius_client']}">
</div>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('Balance System')}</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Enable System')}</label>
<div class="col-md-6">
<select name="enable_balance" id="enable_balance"
class="form-select" style="height: 52px; background-color: white;">
<option value="no" {if $_c['enable_balance']=='no'
}selected="selected" {/if}>No
</option>
<option value="yes" {if $_c['enable_balance']=='yes'
}selected="selected" {/if}>Yes
</option>
</select>
</div>
<p class="help-block col-md-4">{Lang::T('Customer can deposit money to
buy voucher')}</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Allow
Transfer')}</label>
<div class="col-md-6">
<select name="allow_balance_transfer" id="allow_balance_transfer"
class="form-select" style="height: 52px; background-color: white;">
<option value="no" {if $_c['allow_balance_transfer']=='no'
}selected="selected" {/if}>
No</option>
<option value="yes" {if $_c['allow_balance_transfer']=='yes'
}selected="selected" {/if}>
Yes</option>
</select>
</div>
<p class="help-block col-md-4">{Lang::T('Allow balance transfer between
customers')}</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Minimum Balance
Transfer')}</label>
<div class="col-md-6">
<input type="number" class="form-control" id="minimum_transfer"
name="minimum_transfer" value="{$_c['minimum_transfer']}">
</div>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('Telegram Notification')}</h5>
<div class="btn-group pull-right">
<a class="btn btn-success btn-xs" href="javascript:testTg()">Test
TG</a>
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">Telegram Bot Token</label>
<div class="col-md-6">
<input type="password" class="form-control" id="telegram_bot"
name="telegram_bot" onmouseleave="this.type = 'password'"
onmouseenter="this.type = 'text'" value="{$_c['telegram_bot']}"
placeholder="123456:asdasgdkuasghddlashdashldhalskdklasd">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">Telegram User/Channel/Group
ID</label>
<div class="col-md-6">
<input type="text" class="form-control" id="telegram_target_id"
name="telegram_target_id" value="{$_c['telegram_target_id']}"
placeholder="12345678">
</div>
<small id="emailHelp"
class="form-text text-muted help-block col-md-4">You will get
Payment and
Error
notification on you telegram</small>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('SMS OTP Registration')}</h5>
<div class="btn-group pull-right">
<a class="btn btn-success" href="javascript:testSms()">Test SMS</a>
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">SMS Server URL</label>
<div class="col-md-6">
<input type="text" class="form-control" id="sms_url" name="sms_url"
value="{$_c['sms_url']}"
placeholder="https://domain/?param_number=[number]&param_text=[text]&secret=">
</div>
<p class="help-block col-md-4">Must include <b>[text]</b> &amp;
<b>[number]</b>, it will be
replaced.
</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">Or use Mikrotik SMS</label>
<div class="col-md-6">
<select class="form-control"
onchange="document.getElementById('sms_url').value = this.value">
<option value="">Select Router</option>
{foreach $r as $rs}
<option value="{$rs['name']}" {if
$rs['name']==$_c['sms_url']}selected{/if}>
{$rs['name']}</option>
{/foreach}
</select>
</div>
<p class="help-block col-md-4">Must include <b>[text]</b> &amp;
<b>[number]</b>, it will be
replaced.
</p>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('Whatsapp Notification')}</h5>
<div class="btn-group pull-right">
<a class="btn btn-success" href="javascript:testWa()">Test WA</a>
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">Whatsapp Server URL</label>
<div class="col-md-6">
<input type="text" class="form-control" id="wa_url" name="wa_url"
value="{$_c['wa_url']}"
placeholder="https://domain/?param_number=[number]&param_text=[text]&secret=">
</div>
<p class="help-block col-md-4">Must include <b>[text]</b> &amp;
<b>[number]</b>, it will be
replaced.
</p>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('Email Notification')}</h5>
<div class="btn-group pull-right">
<a class="btn btn-success" href="javascript:testEmail()">Test
Email</a>
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save" aria-hidden="true"></span>
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">SMTP Host : port</label>
<div class="col-md-4">
<input type="text" class="form-control" id="smtp_host"
name="smtp_host" value="{$_c['smtp_host']}"
placeholder="smtp.host.tld">
</div>
<div class="col-md-2">
<input type="number" class="form-control" id="smtp_port"
name="smtp_port" value="{$_c['smtp_port']}"
placeholder="465 587 port">
</div>
<p class="help-block col-md-4">Empty this to use internal mail() PHP</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">SMTP username</label>
<div class="col-md-6">
<input type="text" class="form-control" id="smtp_user"
name="smtp_user" value="{$_c['smtp_user']}"
placeholder="user@host.tld">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">SMTP Password</label>
<div class="col-md-6">
<input type="password" class="form-control" id="smtp_pass"
name="smtp_pass" value="{$_c['smtp_pass']}"
onmouseleave="this.type = 'password'"
onmouseenter="this.type = 'text'">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">SMTP Security</label>
<div class="col-md-6">
<select name="smtp_ssltls" id="smtp_ssltls" class="form-select" style="height: 52px; background-color: white;">
<option value="" {if $_c['smtp_ssltls']=='' }selected="selected"
{/if}>Not Secure</option>
<option value="ssl" {if $_c['smtp_ssltls']=='ssl'
}selected="selected" {/if}>SSL</option>
<option value="tls" {if $_c['smtp_ssltls']=='tls'
}selected="selected" {/if}>TLS</option>
</select>
</div>
<p class="help-block col-md-4">UPPERCASE lowercase RaNdoM</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">Mail From</label>
<div class="col-md-6">
<input type="text" class="form-control" id="mail_from"
name="mail_from" value="{$_c['mail_from']}"
placeholder="noreply@host.tld">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">Mail Reply To</label>
<div class="col-md-6">
<input type="text" class="form-control" id="mail_reply_to"
name="mail_reply_to" value="{$_c['mail_reply_to']}"
placeholder="support@host.tld">
</div>
<p class="help-block col-md-4">Customer will reply email to this
address, empty if you want to use From Address</p>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('User Notification')}</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Expired
Notification')}</label>
<div class="col-md-6">
<select name="user_notification_expired"
id="user_notification_expired" class="form-select" style="height: 52px; background-color: white;">
<option value="none">None</option>
<option value="wa" {if $_c['user_notification_expired']=='wa'
}selected="selected" {/if}>Whatsapp</option>
<option value="sms" {if $_c['user_notification_expired']=='sms'
}selected="selected" {/if}>SMS</option>
</select>
</div>
<p class="help-block col-md-4">{Lang::T('User will get notification when
package expired')}</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Payment
Notification')}</label>
<div class="col-md-6">
<select name="user_notification_payment"
id="user_notification_payment" class="form-select" style="height: 52px; background-color: white;">
<option value="none">None</option>
<option value="wa" {if $_c['user_notification_payment']=='wa'
}selected="selected" {/if}>Whatsapp</option>
<option value="sms" {if $_c['user_notification_payment']=='sms'
}selected="selected" {/if}>SMS</option>
</select>
</div>
<p class="help-block col-md-4">
{Lang::T('User will get invoice notification when buy package or
package refilled')}</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Reminder
Notification')}</label>
<div class="col-md-6">
<select name="user_notification_reminder"
id="user_notification_reminder" class="form-select" style="height: 52px; background-color: white;">
<option value="none">None</option>
<option value="wa" {if $_c['user_notification_reminder']=='wa'
}selected="selected" {/if}>Whatsapp</option>
<option value="sms" {if $_c['user_notification_reminder']=='sms'
}selected="selected" {/if}>SMS</option>
</select>
</div>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('Tawk.to Chat Widget')}</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">https://tawk.to/chat/</label>
<div class="col-md-6">
<input type="text" class="form-control" id="tawkto" name="tawkto"
value="{$_c['tawkto']}"
placeholder="62f1ca7037898912e961f5/1ga07df">
</div>
<p class="help-block col-md-4">For Direct Chat Link with client from
their Dashboard</p>
</div>
<div class="col-md-12">
<div class="card kanbanPreview-bx">
<div class="card-body">
<div class="sub-card">
<span class="text-warning sub-title fs-14">Copy and paste in
Terminal</span>
<p class="font-w500">
<code>/ip hotspot walled-garden add dst-host=tawk.to add dst-host=*.tawk.to</code>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">Generate Your API Key</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">Access Token</label>
<div class="col-md-6">
<input type="password" class="form-control" id="api_key"
name="api_key" value="{$_c['api_key']}"
placeholder="Empty this to randomly created API key"
onmouseleave="this.type = 'password'"
onmouseenter="this.type = 'text'">
</div>
<p class="col-md-4 help-block">{Lang::T('This Token will act as
SuperAdmin/Admin.')}</p>
<p class="col-md-12 help-block">Empty this to randomly created API key
that you can use to link this application to other services</p>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('Proxy')}</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Proxy
Server')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="http_proxy"
name="http_proxy" value="{$_c['http_proxy']}"
placeholder="127.0.0.1:3128">
</div>
<p class="col-md-4 help-block">Enter Your Proxy Server IP Address or DNS IP or URl</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Proxy Server
Login')}</label>
<div class="col-md-6">
<input type="password" class="form-control" id="http_proxyauth"
name="http_proxyauth" autocomplete="off"
value="{$_c['http_proxyauth']}" placeholder="username:password"
onmouseleave="this.type = 'password'"
onmouseenter="this.type = 'text'">
</div>
<p class="col-md-4 help-block">Enter Your Proxy Server Login Password</p>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('Miscellaneous')}</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('OTP
Required')}</label>
<div class="col-md-6">
<select name="allow_phone_otp" id="allow_phone_otp"
class="form-select" style="height: 52px; background-color: white;">
<option value="no" {if $_c['allow_phone_otp']=='no'
}selected="selected" {/if}>
No</option>
<option value="yes" {if $_c['allow_phone_otp']=='yes'
}selected="selected" {/if}>Yes
</option>
</select>
</div>
<p class="help-block col-md-4">{Lang::T('OTP is required
when user want
to change phone
number')}</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('OTP Method')}</label>
<div class="col-md-6">
<select name="phone_otp_type" id="phone_otp_type"
class="form-select" style="height: 52px; background-color: white;">
<option value="sms" {if $_c['phone_otp_type']=='sms'
}selected="selected" {/if}>
{Lang::T('SMS')}
<option value="whatsapp" {if $_c['phone_otp_type']=='whatsapp'
}selected="selected" {/if}>
{Lang::T('WhatsApp')}
<option value="both" {if $_c['phone_otp_type']=='both'
}selected="selected" {/if}>
{Lang::T('SMS and WhatsApp')}
</option>
</select>
</div>
<p class="help-block col-md-4">{Lang::T('The method which
OTP will be
sent to user')}</p>
</div>
</div>
</div>
<hr>
<div class="card-body">
<div class="form-group">
<button class="btn btn-success btn-block" type="submit">{Lang::T('Save
Changes')}</button>
</div>
</div>
<div class="card-body">
<div class="card kanbanPreview-bx">
<div class="card-body">
<div class="sub-card">
<span class="text-warning sub-title fs-14">Copy and paste in
Terminal</span>
<code>/ip hotspot walled-garden add dst-host={$_domain} add dst-host=*.{$_domain}</code>
</div>
</div>
</div>
<div class="card kanbanPreview-bx">
<div class="card-body">
<div class="sub-card">
<span class="text-warning sub-title fs-14">Cron Jobs</span>
<p># Expired Cronjob Every 5 Minutes</p>
<pre>*/5 * * * * cd {$dir} && {$php} cron.php</pre>
<p># Expired Cronjob Every 1 Hour</p>
<pre>0 * * * * cd {$dir} && {$php} cron.php</pre>
<p># Reminder Cronjob Every 7 AM</p>
<pre>0 7 * * * cd {$dir} && {$php} cron_reminder.php</pre>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
<script>
function testWa() {
var target = prompt("Phone number\nSave First before Test", "");
if (target != null) {
window.location.href = '{$_url}settings/app&testWa=' + target;
}
}
function testSms() {
var target = prompt("Phone number\nSave First before Test", "");
if (target != null) {
window.location.href = '{$_url}settings/app&testSms=' + target;
}
}
function testEmail() {
var target = prompt("Email\nSave First before Test", "");
if (target != null) {
window.location.href = '{$_url}settings/app&testEmail=' + target;
}
}
function testTg() {
window.location.href = '{$_url}settings/app&testTg=test';
}
</script>
</div>
{include file="sections/footer.tpl"}

4
ui/ui/autoload-pool.tpl Normal file
View File

@ -0,0 +1,4 @@
<option value=''>{Lang::T('Select Pool')}</option>
{foreach $d as $ds}
<option value="{$ds['pool_name']}">{$ds['pool_name']}{if $routers==''} - {$ds['routers']}{/if}</option>
{/foreach}

View File

@ -0,0 +1,7 @@
<option value=''>{Lang::T('Select Routers')}</option>
{if $_c['radius_enable']}
<option value="radius">Radius</option>
{/if}
{foreach $d as $ds}
<option value="{$ds['name']}">{$ds['name']}</option>
{/foreach}

9
ui/ui/autoload.tpl Normal file
View File

@ -0,0 +1,9 @@
<option value="">Select Plans</option>
{foreach $d as $ds}
<option value="{$ds['id']}">
{if $ds['enabled'] neq 1}DISABLED PLAN &bull; {/if}
{$ds['name_plan']} &bull;
{Lang::moneyFormat($ds['price'])}
{if $ds['prepaid'] neq 'yes'} &bull; POSTPAID {/if}
</option>
{/foreach}

51
ui/ui/balance-add.tpl Normal file
View File

@ -0,0 +1,51 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-xxl-6 col-xxl-12">
<div class="card card-primary card-hovered card-stacked mb30">
<div class="card-header" style="display: grid; align-content: center; justify-content: center;">
<h5 class="card-title">{Lang::T('Add Service Plan')}</h5>
</div>
<div class="card-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}services/balance-add-post">
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Status')}</label>
<div class="col-md-8">
<label class="radio-inline warning">
<input type="radio" checked name="enabled" value="1"> Enable
</label>
<label class="radio-inline">
<input type="radio" name="enabled" value="0"> Disable
</label>
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Plan Name')}</label>
<div class="col-md-8">
<input type="text" required class="form-control" id="name" name="name" maxlength="40"
placeholder="Topup 100">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Plan Price')}</label>
<div class="col-md-8">
<div class="input-group">
<span class="input-group-text" >{$_c['currency_code']}</span>
<input type="number" class="form-control" name="price" required>
</div>
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-success" type="submit">{Lang::T('Save Changes')}</button>
<a class="btn btn-warning" href="{$_url}services/balance">{Lang::T('Cancel')}</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

51
ui/ui/balance-edit.tpl Normal file
View File

@ -0,0 +1,51 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-xxl-6 col-xxl-12">
<div class="card card-primary card-hovered panel-stacked mb30">
<div class="card-header" style="display: grid; align-content: center; justify-content: center;">
<h5 class="card-title">{Lang::T('Edit Service Plan')}</h5>
</div>
<div class="card-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}services/balance-edit-post">
<input type="hidden" name="id" value="{$d['id']}">
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Status')}</label>
<div class="col-md-8">
<label class="radio-inline warning">
<input type="radio" checked name="enabled" value="1"> Enable
</label>
<label class="radio-inline">
<input type="radio" name="enabled" value="0"> Disable
</label>
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Plan Name')}</label>
<div class="col-md-8">
<input type="text" required class="form-control" id="name" value="{$d['name_plan']}" name="name" maxlength="40" placeholder="Topup 100">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Plan Price')}</label>
<div class="col-md-8">
<div class="input-group">
<span class="input-group-addon" style="background-color: white; align-items: center; display: flex; border-radius: 12px 0 0 12px; height: 50px;">{$_c['currency_code']}</span>
<input type="number" class="form-control" name="price" value="{$d['price']}" required>
</div>
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-success" type="submit">{Lang::T('Save Changes')}</button>
Or <a href="{$_url}services/balance">{Lang::T('Cancel')}</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

55
ui/ui/balance.tpl Normal file
View File

@ -0,0 +1,55 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-xxl-6 col-xxl-12">
<div class="card panel-hovered mb20 panel-primary">
<div class="card-header" style="display: grid; align-content: center; justify-content: center;">
<h5 class="card-title">{Lang::T('Balance Plans')}</h5>
</div>
<div class="card-body">
<div class="md-whiteframe-z1 mb20 text-center row" style="padding: 15px">
<div class="col-md-8">
<form id="site-search" method="post" action="{$_url}services/balance/">
<div class="input-group">
<!-- <div class="input-group-addon">
<span class="fa fa-search"></span>
</div> -->
<input type="text" name="name" class="form-control" placeholder="{Lang::T('Search by Name')}...">
<button class="btn btn-success" style="border-radius: 0 12px 12px 0; border: none;" type="submit"><i class="fa fa-search"></i>{Lang::T('Search')}</button>
</div>
</form>
</div>
<div class="col-md-4">
<a href="{$_url}services/balance-add" class="btn btn-primary btn-block"><i class="fa fa-plus"> </i> {Lang::T('New Service Plan')}</a>
</div>&nbsp;
</div>
<div class="table-responsive">
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{Lang::T('Plan Name')}</th>
<th>{Lang::T('Plan Price')}</th>
<th>{Lang::T('Manage')}</th>
</tr>
</thead>
<tbody>
{foreach $d as $ds}
<tr {if $ds['enabled'] != 1}class="danger" title="disabled"{/if}>
<td>{$ds['name_plan']}</td>
<td>{Lang::moneyFormat($ds['price'])}</td>
<td>
<a href="{$_url}services/balance-edit/{$ds['id']}" class="btn btn-info btn-xs"><i class="fa fa-edit"></i>{Lang::T('Edit')}</a>
<a href="{$_url}services/balance-delete/{$ds['id']}" onclick="return confirm('{Lang::T('Delete')}?')" id="{$ds['id']}" class="btn btn-danger btn-xs"><i class="fa fa-trash" aria-hidden="true"></i></a>
</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
{include file="pagination.tpl"}
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

84
ui/ui/bandwidth-add.tpl Normal file
View File

@ -0,0 +1,84 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="card card-primary card-hovered card-stacked mb30">
<div class="card-header" style="display: grid; align-content: center; justify-content: center;">
<h5 class="card-title">{Lang::T('Add New Bandwidth')}</h5></div>
<div class="card-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}bandwidth/add-post">
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Bandwidth Name')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="name" name="name">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Rate Download')}</label>
<div class="col-md-4">
<input type="text" class="form-control" id="rate_down" name="rate_down">
</div>
<div class="col-md-2">
<select class="form-select" style="height: 52px; background-color: white;" id="rate_down_unit" name="rate_down_unit">
<option value="Kbps">Kbps</option>
<option value="Mbps">Mbps</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Rate Upload')}</label>
<div class="col-md-4">
<input type="text" class="form-control" id="rate_up" name="rate_up">
</div>
<div class="col-md-2">
<select class="form-select" style="height: 52px; background-color: white;" id="rate_up_unit" name="rate_up_unit">
<option value="Kbps">Kbps</option>
<option value="Mbps">Mbps</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Burst Limit</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Burst/Limit]">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Burst Threshold</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Burst/Threshold]">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Burst Time</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Burst/Time]">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Priority</label>
<div class="col-md-6">
<input type="number" class="form-control" name="burst[]" placeholder="[Priority]">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Limit At</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Limit/At]">
</div>
</div>
<div class="form-group row">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-primary"
type="submit">{Lang::T('Submit')}</button>
Or <a href="{$_url}bandwidth/list">{Lang::T('Cancel')}</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

97
ui/ui/bandwidth-edit.tpl Normal file
View File

@ -0,0 +1,97 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="card card-primary card-hovered card-stacked mb30">
<div class="card-heading" style="display: grid; align-content: center; justify-content: center;">
<h5 class="card-title">{Lang::T('Edit Bandwidth')}</h5>
</div>
<div class="card-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}bandwidth/edit-post">
<input type="hidden" name="id" value="{$d['id']}">
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Bandwidth Name')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="name" name="name" value="{$d['name_bw']}">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Rate Download')}</label>
<div class="col-md-4">
<input type="text" class="form-control" id="rate_down" name="rate_down"
value="{$d['rate_down']}">
</div>
<div class="col-md-2">
<select class="form-select" style="height: 52px; background-color: white;" id="rate_down_unit" name="rate_down_unit">
<option value="Kbps" {if $d['rate_down_unit'] eq 'Kbps'}selected="selected" {/if}>Kbps
</option>
<option value="Mbps" {if $d['rate_down_unit'] eq 'Mbps'}selected="selected" {/if}>Mbps
</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Rate Upload')}</label>
<div class="col-md-4">
<input type="text" class="form-control" id="rate_up" name="rate_up" value="{$d['rate_up']}">
</div>
<div class="col-md-2">
<select class="form-select" style="height: 52px; background-color: white;" id="rate_up_unit" name="rate_up_unit">
<option value="Kbps" {if $d['rate_up_unit'] eq 'Kbps'}selected="selected" {/if}>Kbps
</option>
<option value="Mbps" {if $d['rate_up_unit'] eq 'Mbps'}selected="selected" {/if}>Mbps
</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Burst Limit</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Burst/Limit]" value="{$burst[0]}">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Burst Threshold</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Burst/Threshold]" value="{$burst[1]}">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Burst Time</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Burst/Time]" value="{$burst[2]}">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Priority</label>
<div class="col-md-6">
<input type="number" class="form-control" name="burst[]" placeholder="[Priority]" value="{$burst[3]}">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Limit At</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Limit/At]" value="{$burst[4]}">
</div>
</div>
<div class="form-group row">
<div class="col-lg-offset-2 col-lg-10">
<small>{Lang::T('Editing Bandwidth will not automatically update the plan, you need to edit the plan then save again')}</small>
</div>
</div>
<div class="form-group row">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-primary"
type="submit">{Lang::T('Submit')}</button>
Or <a href="{$_url}bandwidth/list">{Lang::T('Cancel')}</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

Some files were not shown because too many files have changed in this diff Show More