Refactoring classes into src/ folder, so they will be auto-loaded by composer

This commit is contained in:
slawkens
2024-01-27 00:36:49 +01:00
parent 410d75c882
commit 1a6fb8bee2
78 changed files with 439 additions and 341 deletions

56
system/src/Cache/APC.php Normal file
View File

@@ -0,0 +1,56 @@
<?php
/**
* Cache APC class
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @author Mark Samman (Talaturen) <marksamman@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC\Cache;
class APC
{
private $prefix;
private $enabled;
public function __construct($prefix = '')
{
$this->prefix = $prefix;
$this->enabled = function_exists('apc_fetch');
}
public function set($key, $var, $ttl = 0)
{
$key = $this->prefix . $key;
apc_delete($key);
apc_store($key, $var, $ttl);
}
public function get($key)
{
$tmp = '';
if ($this->fetch($this->prefix . $key, $tmp)) {
return $tmp;
}
return '';
}
public function fetch($key, &$var)
{
return ($var = apc_fetch($this->prefix . $key)) !== false;
}
public function delete($key)
{
apc_delete($this->prefix . $key);
}
public function enabled()
{
return $this->enabled;
}
}

56
system/src/Cache/APCu.php Normal file
View File

@@ -0,0 +1,56 @@
<?php
/**
* Cache APC class
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @author Mark Samman (Talaturen) <marksamman@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC\Cache;
class APCu
{
private $prefix;
private $enabled;
public function __construct($prefix = '')
{
$this->prefix = $prefix;
$this->enabled = function_exists('apcu_fetch');
}
public function set($key, $var, $ttl = 0)
{
$key = $this->prefix . $key;
apcu_delete($key);
apcu_store($key, $var, $ttl);
}
public function get($key)
{
$tmp = '';
if ($this->fetch($this->prefix . $key, $tmp)) {
return $tmp;
}
return '';
}
public function fetch($key, &$var)
{
return ($var = apcu_fetch($this->prefix . $key)) !== false;
}
public function delete($key)
{
apcu_delete($this->prefix . $key);
}
public function enabled()
{
return $this->enabled;
}
}

128
system/src/Cache/Cache.php Normal file
View File

@@ -0,0 +1,128 @@
<?php
/**
* Cache class
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @author Mark Samman (Talaturen) <marksamman@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC\Cache;
/**
* Class Cache
*
* @method set($key, $var, $ttl = 0)
* @method get($key)
* @method fetch($key, &$var)
* @method delete($key)
*/
class Cache
{
static private $instance;
/**
* @return Cache
*/
public static function getInstance()
{
if (!self::$instance) {
return self::generateInstance(config('cache_engine'), config('cache_prefix'));
}
return self::$instance;
}
/**
* @param string $engine
* @param string $prefix
* @return Cache
*/
public static function generateInstance($engine = '', $prefix = '')
{
if (config('env') === 'dev') {
self::$instance = new self();
return self::$instance;
}
switch (strtolower($engine)) {
case 'apc':
self::$instance = new APC($prefix);
break;
case 'apcu':
self::$instance = new APCu($prefix);
break;
case 'eaccelerator':
self::$instance = new eAccelerator($prefix);
break;
case 'xcache':
self::$instance = new XCache($prefix);
break;
case 'file':
self::$instance = new File($prefix, CACHE);
break;
case 'php':
self::$instance = new PHP($prefix, CACHE);
break;
case 'auto':
self::$instance = self::generateInstance(self::detect(), $prefix);
break;
default:
self::$instance = new self();
break;
}
return self::$instance;
}
/**
* @return string
*/
public static function detect()
{
if (function_exists('apc_fetch'))
return 'apc';
else if (function_exists('apcu_fetch'))
return 'apcu';
else if (function_exists('eaccelerator_get'))
return 'eaccelerator';
else if (function_exists('xcache_get') && ini_get('xcache.var_size'))
return 'xcache';
return 'file';
}
/**
* @return bool
*/
public function enabled()
{
return false;
}
public static function remember($key, $ttl, $callback)
{
$cache = self::getInstance();
if (!$cache->enabled()) {
return $callback();
}
$value = null;
if ($cache->fetch($key, $value)) {
return unserialize($value);
}
$value = $callback();
$cache->set($key, serialize($value), $ttl);
return $value;
}
}

View File

@@ -0,0 +1,56 @@
<?php
/**
* Cache eAccelerator class
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @author Mark Samman (Talaturen) <marksamman@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC\Cache;
class EAccelerator
{
private $prefix;
private $enabled;
public function __construct($prefix = '')
{
$this->prefix = $prefix;
$this->enabled = function_exists('eaccelerator_get');
}
public function set($key, $var, $ttl = 0)
{
$key = $this->prefix . $key;
eaccelerator_rm($key);
eaccelerator_put($key, $var, $ttl);
}
public function get($key)
{
$tmp = '';
if ($this->fetch($this->prefix . $key, $tmp)) {
return $tmp;
}
return '';
}
public function fetch($key, &$var)
{
return ($var = eaccelerator_get($this->prefix . $key)) !== null;
}
public function delete($key)
{
eaccelerator_rm($this->prefix . $key);
}
public function enabled()
{
return $this->enabled;
}
}

76
system/src/Cache/File.php Normal file
View File

@@ -0,0 +1,76 @@
<?php
/**
* File cache class
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC\Cache;
class File
{
private $prefix;
private $dir;
private $enabled;
public function __construct($prefix = '', $dir = '')
{
$this->prefix = $prefix;
$this->dir = $dir;
$this->enabled = (file_exists($this->dir) && is_dir($this->dir) && is_writable($this->dir));
}
public function set($key, $var, $ttl = 0)
{
$file = $this->_name($key);
file_put_contents($file, $var);
if ($ttl === 0) {
$ttl = 365 * 24 * 60 * 60; // 365 days
}
touch($file, time() + $ttl);
}
public function get($key)
{
$tmp = '';
if ($this->fetch($key, $tmp)) {
return $tmp;
}
return '';
}
public function fetch($key, &$var)
{
$file = $this->_name($key);
if (!file_exists($file) || filemtime($file) < time()) {
return false;
}
$var = file_get_contents($file);
return true;
}
public function delete($key)
{
$file = $this->_name($key);
if (file_exists($file)) {
unlink($file);
}
}
public function enabled()
{
return $this->enabled;
}
private function _name($key)
{
return sprintf('%s%s%s', $this->dir, $this->prefix, sha1($key));
}
}

83
system/src/Cache/PHP.php Normal file
View File

@@ -0,0 +1,83 @@
<?php
/**
* PHP cache class
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC\Cache;
class PHP
{
private $prefix;
private $dir;
private $enabled;
public function __construct($prefix = '', $dir = '')
{
$this->prefix = $prefix;
$this->dir = $dir;
$this->enabled = (file_exists($this->dir) && is_dir($this->dir) && is_writable($this->dir));
}
public function set($key, $var, $ttl = 0)
{
$var = var_export($var, true);
// Write to temp file first to ensure atomicity
$tmp = $this->dir . "tmp_$key." . uniqid('', true) . '.tmp';
file_put_contents($tmp, '<?php $var = ' . $var . ';', LOCK_EX);
$file = $this->_name($key);
rename($tmp, $file);
if ($ttl === 0) {
$ttl = 365 * 24 * 60 * 60; // 365 days
}
touch($file, time() + $ttl);
}
public function get($key)
{
$tmp = '';
if ($this->fetch($key, $tmp)) {
return $tmp;
}
return '';
}
public function fetch($key, &$var)
{
$file = $this->_name($key);
if (!file_exists($file) || filemtime($file) < time()) {
return false;
}
@include $file;
$var = isset($var) ? $var : null;
return true;
}
public function delete($key)
{
$file = $this->_name($key);
if (file_exists($file)) {
unlink($file);
}
}
public function enabled()
{
return $this->enabled;
}
private function _name($key)
{
return sprintf('%s%s%s', $this->dir, $this->prefix, sha1($key) . '.php');
}
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* XCache class
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @author Mark Samman (Talaturen) <marksamman@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC\Cache;
class XCache
{
private $prefix;
private $enabled;
public function __construct($prefix = '')
{
$this->prefix = $prefix;
$this->enabled = function_exists('xcache_get') && ini_get('xcache.var_size');
}
public function set($key, $var, $ttl = 0)
{
$key = $this->prefix . $key;
xcache_unset($key);
xcache_set($key, $var, $ttl);
}
public function get($key)
{
$tmp = '';
if ($this->fetch($this->prefix . $key, $tmp)) {
return $tmp;
}
return '';
}
public function fetch($key, &$var)
{
$key = $this->prefix . $key;
if (!xcache_isset($key)) {
return false;
}
$var = xcache_get($key);
return true;
}
public function delete($key)
{
xcache_unset($this->prefix . $key);
}
public function enabled()
{
return $this->enabled;
}
}

150
system/src/Changelog.php Normal file
View File

@@ -0,0 +1,150 @@
<?php
namespace MyAAC;
use MyAAC\Cache\Cache;
use MyAAC\Models\Changelog as ModelsChangelog;
class Changelog
{
static public function verify($body,$date, &$errors)
{
if(!isset($date) || !isset($body[0])) {
$errors[] = 'Please fill all inputs.';
return false;
}
if(strlen($body) > CL_LIMIT) {
$errors[] = 'Changelog content cannot be longer than ' . CL_LIMIT . ' characters.';
return false;
}
return true;
}
static public function add($body, $type, $where, $player_id, $cdate, &$errors)
{
if(!self::verify($body,$cdate, $errors))
return false;
$row = new ModelsChangelog;
$row->body = $body;
$row->type = $type;
$row->date = $cdate;
$row->where = $where;
$row->player_id = $player_id ?? 0;
if ($row->save()) {
self::clearCache();
return true;
}
return false;
}
static public function get($id) {
return ModelsChangelog::find($id);
}
static public function update($id, $body, $type, $where, $player_id, $date, &$errors)
{
if(!self::verify($body,$date, $errors))
return false;
if (ModelsChangelog::where('id', '=', $id)->update([
'body' => $body,
'type' => $type,
'where' => $where,
'player_id' => $player_id ?? 0,
'date' => $date
])) {
self::clearCache();
return true;
}
return false;
}
static public function delete($id, &$errors)
{
if(isset($id))
{
$row = ModelsChangelog::find($id);
if ($row) {
if (!$row->delete()) {
$errors[] = 'Fail during delete Changelog.';
}
} else {
$errors[] = 'Changelog with id ' . $id . ' does not exist.';
}
} else {
$errors[] = 'Changelog id not set.';
}
if(count($errors)) {
return false;
}
self::clearCache();
return true;
}
static public function toggleHidden($id, &$errors, &$status)
{
if(isset($id))
{
$row = ModelsChangelog::find($id);
if ($row) {
$row->hidden = $row->hidden == 1 ? 0 : 1;
if (!$row->save()) {
$errors[] = 'Fail during toggle hidden Changelog.';
}
$status = $row->hidden;
} else {
$errors[] = 'Changelog with id ' . $id . ' does not exists.';
}
}
else
$errors[] = 'Changelog id not set.';
if(count($errors)) {
return false;
}
self::clearCache();
return true;
}
static public function getCached($type)
{
global $template_name;
$cache = Cache::getInstance();
if ($cache->enabled())
{
$tmp = '';
if ($cache->fetch('changelog_' . $template_name, $tmp) && isset($tmp[0])) {
return $tmp;
}
}
return false;
}
static public function clearCache()
{
global $template_name;
$cache = Cache::getInstance();
if (!$cache->enabled()) {
return;
}
$tmp = '';
foreach (get_templates() as $template) {
if ($cache->fetch('changelog_' . $template_name, $tmp)) {
$cache->delete('changelog_' . $template_name);
}
}
}
}

View File

@@ -0,0 +1,290 @@
<?php
namespace MyAAC;
use MyAAC\Models\Player;
/**
* CreateCharacter
*
* @package MyAAC
* @author Gesior <jerzyskalski@wp.pl>
* @author Slawkens <slawkens@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
class CreateCharacter
{
/**
* @param $name
* @param $errors
* @return bool
*/
public function checkName($name, &$errors)
{
$minLength = setting('core.create_character_name_min_length');
$maxLength = setting('core.create_character_name_max_length');
if(empty($name)) {
$errors['name'] = 'Please enter a name for your character!';
return false;
}
if(strlen($name) > $maxLength) {
$errors['name'] = 'Name is too long. Max. length <b>' . $maxLength . '</b> letters.';
return false;
}
if(strlen($name) < $minLength) {
$errors['name'] = 'Name is too short. Min. length <b>' . $minLength . '</b> letters.';
return false;
}
$name_length = strlen($name);
if(strspn($name, "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM- '") != $name_length) {
$errors['name'] = 'This name contains invalid letters, words or format. Please use only a-Z, - , \' and space.';
return false;
}
if(!preg_match("/[A-z ']/", $name)) {
$errors['name'] = 'Your name contains illegal characters.';
return false;
}
if(!admin() && !\Validator::newCharacterName($name)) {
$errors['name'] = \Validator::getLastError();
return false;
}
if(Player::where('name', '=', $name)->exists()) {
$errors['name'] = 'Character with this name already exist.';
return false;
}
return empty($errors);
}
/**
* @param string $name
* @param int $sex
* @param int $vocation
* @param int $town
* @param array $errors
* @return bool
*/
public function check($name, $sex, &$vocation, &$town, &$errors)
{
$this->checkName($name, $errors);
if(empty($sex) && $sex != "0") {
$errors['sex'] = 'Please select the sex for your character!';
}
if(count(config('character_samples')) > 1)
{
if(!isset($vocation))
$errors['vocation'] = 'Please select a vocation for your character.';
}
else {
$vocation = config('character_samples')[0];
}
if(count(config('character_towns')) > 1) {
if(!isset($town)) {
$errors['town'] = 'Please select a town for your character.';
}
}
else {
$town = config('character_towns')[0];
}
if(empty($errors)) {
if(!isset(config('genders')[$sex]))
$errors['sex'] = 'Sex is invalid.';
if(!in_array($town, config('character_towns'), false))
$errors['town'] = 'Please select valid town.';
if(count(config('character_samples')) > 1)
{
$newchar_vocation_check = false;
foreach((array)config('character_samples') as $char_vocation_key => $sample_char)
if($vocation === $char_vocation_key)
$newchar_vocation_check = true;
if(!$newchar_vocation_check)
$errors['vocation'] = 'Unknown vocation. Please fill in form again.';
}
else
$vocation = 0;
}
return empty($errors);
}
/**
* @param string $name
* @param int $sex
* @param int $vocation
* @param int $town
* @param \OTS_Account $account
* @param array $errors
* @return bool
* @throws \E_OTS_NotLoaded
* @throws \Twig_Error_Loader
* @throws \Twig_Error_Runtime
* @throws \Twig_Error_Syntax
*/
public function doCreate($name, $sex, $vocation, $town, $account, &$errors)
{
if(!$this->check($name, $sex, $vocation, $town, $errors)) {
return false;
}
if(empty($errors))
{
$number_of_players_on_account = $account->getPlayersList(true)->count();
if($number_of_players_on_account >= setting('core.characters_per_account'))
$errors[] = 'You have too many characters on your account <b>('.$number_of_players_on_account . '/' . setting('core.characters_per_account') . ')</b>!';
}
if(empty($errors))
{
$char_to_copy_name = config('character_samples')[$vocation];
$char_to_copy = new \OTS_Player();
$char_to_copy->find($char_to_copy_name);
if(!$char_to_copy->isLoaded())
$errors[] = 'Wrong characters configuration. Try again or contact with admin. ADMIN: Go to Admin Panel -> Settings -> Create Character and set valid characters to copy names. Character to copy: <b>'.$char_to_copy_name.'</b> doesn\'t exist.';
}
if(!empty($errors)) {
return false;
}
global $db;
if($sex == "0")
$char_to_copy->setLookType(136);
$player = new \OTS_Player();
$player->setName($name);
$player->setAccount($account);
$player->setGroupId(1);
$player->setSex($sex);
$player->setVocation($char_to_copy->getVocation());
if($db->hasColumn('players', 'promotion'))
$player->setPromotion($char_to_copy->getPromotion());
if($db->hasColumn('players', 'direction'))
$player->setDirection($char_to_copy->getDirection());
$player->setConditions($char_to_copy->getConditions());
$rank = $char_to_copy->getRank();
if($rank->isLoaded()) {
$player->setRank($char_to_copy->getRank());
}
if($db->hasColumn('players', 'lookaddons'))
$player->setLookAddons($char_to_copy->getLookAddons());
$player->setTownId($town);
$player->setExperience($char_to_copy->getExperience());
$player->setLevel($char_to_copy->getLevel());
$player->setMagLevel($char_to_copy->getMagLevel());
$player->setHealth($char_to_copy->getHealth());
$player->setHealthMax($char_to_copy->getHealthMax());
$player->setMana($char_to_copy->getMana());
$player->setManaMax($char_to_copy->getManaMax());
$player->setManaSpent($char_to_copy->getManaSpent());
$player->setSoul($char_to_copy->getSoul());
for($skill = \POT::SKILL_FIRST; $skill <= \POT::SKILL_LAST; $skill++) {
$value = 10;
if (setting('core.use_character_sample_skills')) {
$value = $char_to_copy->getSkill($skill);
}
$player->setSkill($skill, $value);
}
$player->setLookBody($char_to_copy->getLookBody());
$player->setLookFeet($char_to_copy->getLookFeet());
$player->setLookHead($char_to_copy->getLookHead());
$player->setLookLegs($char_to_copy->getLookLegs());
$player->setLookType($char_to_copy->getLookType());
$player->setCap($char_to_copy->getCap());
$player->setBalance(0);
$player->setPosX(0);
$player->setPosY(0);
$player->setPosZ(0);
if($db->hasColumn('players', 'stamina')) {
$player->setStamina($char_to_copy->getStamina());
}
if($db->hasColumn('players', 'loss_experience')) {
$player->setLossExperience($char_to_copy->getLossExperience());
$player->setLossMana($char_to_copy->getLossMana());
$player->setLossSkills($char_to_copy->getLossSkills());
}
if($db->hasColumn('players', 'loss_items')) {
$player->setLossItems($char_to_copy->getLossItems());
$player->setLossContainers($char_to_copy->getLossContainers());
}
$player->save();
$player->setCustomField('created', time());
$player = new \OTS_Player();
$player->find($name);
if(!$player->isLoaded()) {
error("Error. Can't create character. Probably problem with database. Please try again later or contact with admin.");
return false;
}
if($db->hasTable('player_skills')) {
for($skill = \POT::SKILL_FIRST; $skill <= \POT::SKILL_LAST; $skill++) {
$value = 10;
if (setting('core.use_character_sample_skills')) {
$value = $char_to_copy->getSkill($skill);
}
$skillExists = $db->query('SELECT `skillid` FROM `player_skills` WHERE `player_id` = ' . $player->getId() . ' AND `skillid` = ' . $skill);
if($skillExists->rowCount() <= 0) {
$db->query('INSERT INTO `player_skills` (`player_id`, `skillid`, `value`, `count`) VALUES ('.$player->getId().', '.$skill.', ' . $value . ', 0)');
}
}
}
if ($db->hasTable('player_items') && $db->hasColumn('player_items', 'pid') && $db->hasColumn('player_items', 'sid') && $db->hasColumn('player_items', 'itemtype')) {
$loaded_items_to_copy = $db->query("SELECT * FROM player_items WHERE player_id = ".$char_to_copy->getId()."");
foreach($loaded_items_to_copy as $save_item) {
$blob = $db->quote($save_item['attributes']);
$db->query("INSERT INTO `player_items` (`player_id` ,`pid` ,`sid` ,`itemtype`, `count`, `attributes`) VALUES ('".$player->getId()."', '".$save_item['pid']."', '".$save_item['sid']."', '".$save_item['itemtype']."', '".$save_item['count']."', $blob);");
}
}
global $hooks;
if (!$hooks->trigger(HOOK_ACCOUNT_CREATE_CHARACTER_AFTER,
[
'account' => $account,
'player' => $player,
'name' => $name,
'sex' => $sex,
'vocation' => $vocation,
'town' => $town,
]
)) {
return false;
}
global $twig;
$twig->display('success.html.twig', array(
'title' => 'Character Created',
'description' => 'The character <b>' . $name . '</b> has been created.<br/>
Please select the outfit when you log in for the first time.<br/><br/>
<b>See you on ' . configLua('serverName') . '!</b>'
));
$account->logAction('Created character <b>' . $name . '</b>.');
return true;
}
}

183
system/src/Creatures.php Normal file
View File

@@ -0,0 +1,183 @@
<?php
/**
* Creatures class
*
* @package MyAAC
* @author Gesior <jerzyskalski@wp.pl>
* @author Slawkens <slawkens@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC;
use MyAAC\Models\Monster;
class Creatures {
/**
* @var \OTS_MonstersList
*/
private static $monstersList;
private static $lastError = '';
public static function loadFromXML($show = false) {
try {
Monster::query()->delete();
} catch(\Exception $error) {}
if($show) {
echo '<h2>Reload monsters.</h2>';
echo "<h2>All records deleted from table '" . TABLE_PREFIX . "monsters' in database.</h2>";
}
try {
self::$monstersList = new \OTS_MonstersList(config('data_path') . 'monster/');
}
catch(\Exception $e) {
self::$lastError = $e->getMessage();
return false;
}
$items = array();
Items::load();
foreach((array)Items::$items as $id => $item) {
$items[$item['name']] = $id;
}
//$names_added must be an array
$names_added[] = '';
//add monsters
foreach(self::$monstersList as $lol) {
$monster = self::$monstersList->current();
if(!$monster->loaded()) {
if($show) {
warning('Error while adding monster: ' . self::$monstersList->currentFile());
}
continue;
}
//load monster mana needed to summon/convince
$mana = $monster->getManaCost();
//load monster name
$name = $monster->getName();
//load monster health
$health = $monster->getHealth();
//load monster speed and calculate "speed level"
$speed_ini = $monster->getSpeed();
if($speed_ini <= 220) {
$speed_lvl = 1;
} else {
$speed_lvl = ($speed_ini - 220) / 2;
}
//check "is monster use haste spell"
$defenses = $monster->getDefenses();
$use_haste = 0;
foreach($defenses as $defense) {
if($defense == 'speed') {
$use_haste = 1;
}
}
//load race
$race = $monster->getRace();
$armor = $monster->getArmor();
$defensev = $monster->getDefense();
//load look
$look = $monster->getLook();
//load monster flags
$flags = $monster->getFlags();
if(!isset($flags['summonable']))
$flags['summonable'] = '0';
if(!isset($flags['convinceable']))
$flags['convinceable'] = '0';
if(!isset($flags['pushable']))
$flags['pushable'] = '0';
if(!isset($flags['canpushitems']))
$flags['canpushitems'] = '0';
if(!isset($flags['canpushcreatures']))
$flags['canpushcreatures'] = '0';
if(!isset($flags['runonhealth']))
$flags['runonhealth'] = '0';
if(!isset($flags['canwalkonenergy']))
$flags['canwalkonenergy'] = '0';
if(!isset($flags['canwalkonpoison']))
$flags['canwalkonpoison'] = '0';
if(!isset($flags['canwalkonfire']))
$flags['canwalkonfire'] = '0';
if(!isset($flags['hostile']))
$flags['hostile'] = '0';
if(!isset($flags['attackable']))
$flags['attackable'] = '0';
if(!isset($flags['rewardboss']))
$flags['rewardboss'] = '0';
$summons = $monster->getSummons();
$loot = $monster->getLoot();
foreach($loot as &$item) {
if(!\Validator::number($item['id'])) {
if(isset($items[$item['id']])) {
$item['id'] = $items[$item['id']];
}
}
}
if(!in_array($name, $names_added)) {
try {
Monster::create(array(
'name' => $name,
'mana' => empty($mana) ? 0 : $mana,
'exp' => $monster->getExperience(),
'health' => $health,
'speed_lvl' => $speed_lvl,
'use_haste' => $use_haste,
'voices' => json_encode($monster->getVoices()),
'immunities' => json_encode($monster->getImmunities()),
'elements' => json_encode($monster->getElements()),
'summonable' => $flags['summonable'] > 0 ? 1 : 0,
'convinceable' => $flags['convinceable'] > 0 ? 1 : 0,
'pushable' => $flags['pushable'] > 0 ? 1 : 0,
'canpushitems' => $flags['canpushitems'] > 0 ? 1 : 0,
'canpushcreatures' => $flags['canpushcreatures'] > 0 ? 1 : 0,
'runonhealth' => $flags['runonhealth'] > 0 ? 1 : 0,
'canwalkonenergy' => $flags['canwalkonenergy'] > 0 ? 1 : 0,
'canwalkonpoison' => $flags['canwalkonpoison'] > 0 ? 1 : 0,
'canwalkonfire' => $flags['canwalkonfire'] > 0 ? 1 : 0,
'hostile' => $flags['hostile'] > 0 ? 1 : 0,
'attackable' => $flags['attackable'] > 0 ? 1 : 0,
'rewardboss' => $flags['rewardboss'] > 0 ? 1 : 0,
'defense' => $defensev,
'armor' => $armor,
'race' => $race,
'loot' => json_encode($loot),
'look' => json_encode($look),
'summons' => json_encode($summons)
));
if($show) {
success('Added: ' . $name . '<br/>');
}
}
catch(\Exception $error) {
if($show) {
warning('Error while adding monster (' . $name . '): ' . $error->getMessage());
}
}
$names_added[] = $name;
}
}
return true;
}
public static function getMonstersList() {
return self::$monstersList;
}
public static function getLastError() {
return self::$lastError;
}
}

44
system/src/Data.php Normal file
View File

@@ -0,0 +1,44 @@
<?php
/**
* Data class
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC;
class Data
{
private $table = '';
public function __construct($table) {
$this->table = $table;
}
public function get($where)
{
global $db;
return $db->select($this->table, $where);
}
public function add($data)
{
global $db;
return $db->insert($this->table, $data);
}
public function delete($data, $where)
{
global $db;
return $db->delete($this->table, $data, $where);
}
public function update($data, $where)
{
global $db;
return $db->update($this->table, $data, $where);
}
}

107
system/src/DataLoader.php Normal file
View File

@@ -0,0 +1,107 @@
<?php
/**
* Project: MyAAC
* Automatic Account Creator for Open Tibia Servers
*
* This is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @copyright 2020 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC;
class DataLoader
{
private static $locale;
private static $startTime;
/**
* Load data from server
*/
public static function load()
{
self::$startTime = microtime(true);
if(Items::loadFromXML()) {
success(self::$locale['step_database_loaded_items'] . self::getLoadedTime());
}
else {
error(Items::getError());
}
self::$startTime = microtime(true);
if(Creatures::loadFromXML()) {
success(self::$locale['step_database_loaded_monsters'] . self::getLoadedTime());
if(Creatures::getMonstersList()->hasErrors()) {
self::$locale['step_database_error_monsters'] = str_replace('$LOG$', 'system/logs/error.log', self::$locale['step_database_error_monsters']);
warning(self::$locale['step_database_error_monsters']);
}
}
else {
error(Creatures::getLastError());
}
self::$startTime = microtime(true);
if(NPCs::loadFromXML()) {
success(self::$locale['step_database_loaded_npcs'] . self::getLoadedTime());
}
else {
error(self::$locale['step_database_error_npcs']);
}
self::$startTime = microtime(true);
if(Spells::loadFromXML()) {
success(self::$locale['step_database_loaded_spells'] . self::getLoadedTime());
}
else {
error(Spells::getLastError());
}
self::$startTime = microtime(true);
if (Towns::save()) {
success(self::$locale['step_database_loaded_towns'] . self::getLoadedTime());
}
else {
warning(self::$locale['step_database_error_towns']);
}
self::$startTime = microtime(true);
if(Weapons::loadFromXML()) {
success(self::$locale['step_database_loaded_weapons'] . self::getLoadedTime());
}
else {
error(Weapons::getError());
}
}
public static function setLocale($locale) {
self::$locale = $locale;
}
private static function getLoadedTime()
{
$endTime = round(microtime(true) - self::$startTime, 3);
return ' (' . str_replace('$TIME$', $endTime, self::$locale['loaded_in_ms']) . ')';
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace MyAAC\Exceptions;
class SensitiveException extends \Exception
{
}

325
system/src/Forum.php Normal file
View File

@@ -0,0 +1,325 @@
<?php
/**
* Forum class
*
* @package MyAAC
* @author Gesior <jerzyskalski@wp.pl>
* @author Slawkens <slawkens@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC;
$settingForumTablePrefix = setting('core.forum_table_prefix');
if(null !== $settingForumTablePrefix && !empty(trim($settingForumTablePrefix))) {
if(!in_array($settingForumTablePrefix, array('myaac_', 'z_'))) {
throw new \RuntimeException('Invalid value for forum_table_prefix in config.php. Can be only: "myaac_" or "z_".');
}
define('FORUM_TABLE_PREFIX', $settingForumTablePrefix);
}
else {
if($db->hasTable('z_forum')) {
define('FORUM_TABLE_PREFIX', 'z_');
}
else {
define('FORUM_TABLE_PREFIX', 'myaac_');
}
}
class Forum
{
/**
* @param \OTS_Account $account
* @return bool
* @throws \E_OTS_NotLoaded
*/
public static function canPost($account)
{
global $db, $config;
if(!$account->isLoaded() || $account->isBanned())
return false;
if(self::isModerator())
return true;
return
$db->query(
'SELECT `id` FROM `players` WHERE `account_id` = ' . $db->quote($account->getId()) .
' AND `level` >= ' . $db->quote(setting('core.forum_level_required')) .
' LIMIT 1')->rowCount() > 0;
}
public static function isModerator() {
return hasFlag(FLAG_CONTENT_FORUM) || superAdmin();
}
public static function add_thread($title, $body, $section_id, $player_id, $account_id, &$errors)
{
global $db;
$thread_id = 0;
if($db->insert(FORUM_TABLE_PREFIX . 'forum', array(
'first_post' => 0,
'last_post' => time(),
'section' => $section_id,
'replies' => 0,
'views' => 0,
'author_aid' => isset($account_id) ? $account_id : 0,
'author_guid' => isset($player_id) ? $player_id : 0,
'post_text' => $body, 'post_topic' => $title,
'post_smile' => 0, 'post_html' => 1,
'post_date' => time(),
'last_edit_aid' => 0, 'edit_date' => 0,
'post_ip' => $_SERVER['REMOTE_ADDR']
))) {
$thread_id = $db->lastInsertId();
$db->query("UPDATE `" . FORUM_TABLE_PREFIX . "forum` SET `first_post`=".(int) $thread_id." WHERE `id` = ".(int) $thread_id);
}
return $thread_id;
}
public static function add_post($thread_id, $section, $author_aid, $author_guid, $post_text, $post_topic, $smile, $html)
{
global $db;
$db->insert(FORUM_TABLE_PREFIX . 'forum', array(
'first_post' => $thread_id,
'section' => $section,
'author_aid' => $author_aid,
'author_guid' => $author_guid,
'post_text' => $post_text,
'post_topic' => $post_topic,
'post_smile' => $smile,
'post_html' => $html,
'post_date' => time(),
'post_ip' => $_SERVER['REMOTE_ADDR']
));
}
public static function add_board($name, $description, $access, $guild, &$errors)
{
global $db;
if(isset($name[0]) && isset($description[0]))
{
$query = $db->select(TABLE_PREFIX . 'forum_boards', array('name' => $name));
if($query === false)
{
$query =
$db->query(
'SELECT ' . $db->fieldName('ordering') .
' FROM ' . $db->tableName(TABLE_PREFIX . 'forum_boards') .
' ORDER BY ' . $db->fieldName('ordering') . ' DESC LIMIT 1'
);
$ordering = 0;
if($query->rowCount() > 0) {
$query = $query->fetch();
$ordering = $query['ordering'] + 1;
}
$db->insert(TABLE_PREFIX . 'forum_boards', array('name' => $name, 'description' => $description, 'access' => $access, 'guild' => $guild, 'ordering' => $ordering));
}
else
$errors[] = 'Forum board with this name already exists.';
}
else
$errors[] = 'Please fill all inputs.';
return !count($errors);
}
public static function get_board($id) {
global $db;
return $db->select(TABLE_PREFIX . 'forum_boards', array('id' => $id));
}
public static function update_board($id, $name, $access, $guild, $description) {
global $db;
$db->update(TABLE_PREFIX . 'forum_boards', array('name' => $name, 'description' => $description, 'access' => $access, 'guild' => $guild), array('id' => $id));
}
public static function delete_board($id, &$errors)
{
global $db;
if(isset($id))
{
if(self::get_board($id) !== false)
$db->delete(TABLE_PREFIX . 'forum_boards', array('id' => $id));
else
$errors[] = 'Forum board with id ' . $id . ' does not exists.';
}
else
$errors[] = 'id not set';
return !count($errors);
}
public static function toggleHidden_board($id, &$errors)
{
global $db;
if(isset($id))
{
$query = self::get_board($id);
if($query !== false)
$db->update(TABLE_PREFIX . 'forum_boards', array('hidden' => ($query['hidden'] == 1 ? 0 : 1)), array('id' => $id));
else
$errors[] = 'Forum board with id ' . $id . ' does not exists.';
}
else
$errors[] = 'id not set';
return !count($errors);
}
public static function move_board($id, $i, &$errors)
{
global $db;
$query = self::get_board($id);
if($query !== false)
{
$ordering = $query['ordering'] + $i;
$old_record = $db->select(TABLE_PREFIX . 'forum_boards', array('ordering' => $ordering));
if($old_record !== false)
$db->update(TABLE_PREFIX . 'forum_boards', array('ordering' => $query['ordering']), array('ordering' => $ordering));
$db->update(TABLE_PREFIX . 'forum_boards', array('ordering' => $ordering), array('id' => $id));
}
else
$errors[] = 'Forum board with id ' . $id . ' does not exists.';
return !count($errors);
}
public static function parseSmiles($text)
{
$smileys = array(
';D' => 1,
':D' => 1,
':cool:' => 2,
';cool;' => 2,
':ekk:' => 3,
';ekk;' => 3,
';o' => 4,
';O' => 4,
':o' => 4,
':O' => 4,
':(' => 5,
';(' => 5,
':mad:' => 6,
';mad;' => 6,
';rolleyes;' => 7,
':rolleyes:' => 7,
':)' => 8,
';d' => 9,
':d' => 9,
';)' => 10
);
foreach($smileys as $search => $replace)
$text = str_replace($search, '<img src="images/forum/smile/'.$replace.'.gif" alt="'. $search .'" title="' . $search . '" />', $text);
return $text;
}
public static function parseBBCode($text, $smiles)
{
$rows = 0;
while(stripos($text, '[code]') !== false && stripos($text, '[/code]') !== false )
{
$code = substr($text, stripos($text, '[code]')+6, stripos($text, '[/code]') - stripos($text, '[code]') - 6);
if(!is_int($rows / 2)) { $bgcolor = 'ABED25'; } else { $bgcolor = '23ED25'; } $rows++;
$text = str_ireplace('[code]'.$code.'[/code]', '<i>Code:</i><br /><table cellpadding="0" style="background-color: #'.$bgcolor.'; width: 480px; border-style: dotted; border-color: #CCCCCC; border-width: 2px"><tr><td>'.$code.'</td></tr></table>', $text);
}
$rows = 0;
while(stripos($text, '[quote]') !== false && stripos($text, '[/quote]') !== false )
{
$quote = substr($text, stripos($text, '[quote]')+7, stripos($text, '[/quote]') - stripos($text, '[quote]') - 7);
if(!is_int($rows / 2)) { $bgcolor = 'AAAAAA'; } else { $bgcolor = 'CCCCCC'; } $rows++;
$text = str_ireplace('[quote]'.$quote.'[/quote]', '<table cellpadding="0" style="background-color: #'.$bgcolor.'; width: 480px; border-style: dotted; border-color: #007900; border-width: 2px"><tr><td>'.$quote.'</td></tr></table>', $text);
}
$rows = 0;
while(stripos($text, '[url]') !== false && stripos($text, '[/url]') !== false )
{
$url = substr($text, stripos($text, '[url]')+5, stripos($text, '[/url]') - stripos($text, '[url]') - 5);
$text = str_ireplace('[url]'.$url.'[/url]', '<a href="'.$url.'" target="_blank">'.$url.'</a>', $text);
}
$xhtml = false;
$tags = array(
'#\[b\](.*?)\[/b\]#si' => ($xhtml ? '<strong>\\1</strong>' : '<b>\\1</b>'),
'#\[i\](.*?)\[/i\]#si' => ($xhtml ? '<em>\\1</em>' : '<i>\\1</i>'),
'#\[u\](.*?)\[/u\]#si' => ($xhtml ? '<span style="text-decoration: underline;">\\1</span>' : '<u>\\1</u>'),
'#\[s\](.*?)\[/s\]#si' => ($xhtml ? '<strike>\\1</strike>' : '<s>\\1</s>'),
'#\[guild\](.*?)\[/guild\]#si' => urldecode(generateLink(getGuildLink('$1', false), '$1', true)),
'#\[house\](.*?)\[/house\]#si' => urldecode(generateLink(getHouseLink('$1', false), '$1', true)),
'#\[player\](.*?)\[/player\]#si' => urldecode(generateLink(getPlayerLink('$1', false), '$1', true)),
// TODO: [poll] tag
'#\[color=(.*?)\](.*?)\[/color\]#si' => ($xhtml ? '<span style="color: \\1;">\\2</span>' : '<span style="color: \\1">\\2</span>'),
'#\[img\](.*?)\[/img\]#si' => ($xhtml ? '<img class="forum-image" style="max-width:550px; max-height; 550px;" src="\\1" border="0" alt="" />' : '<img class="forum-image" style="max-width:550px; max-height; 550px;" src="\\1" border="0" alt="">'),
'#\[url=(.*?)\](.*?)\[/url\]#si' => '<a href="\\1" title="\\2">\\2</a>',
// '#\[email\](.*?)\[/email\]#si' => '<a href="mailto:\\1" title="Email \\1">\\1</a>',
'#\[code\](.*?)\[/code\]#si' => '<code>\\1</code>',
// '#\[align=(.*?)\](.*?)\[/align\]#si' => ($xhtml ? '<div style="text-align: \\1;">\\2</div>' : '<div align="\\1">\\2</div>'),
// '#\[br\]#si' => ($xhtml ? '<br style="clear: both;" />' : '<br>'),
);
foreach($tags as $search => $replace)
$text = preg_replace($search, $replace, $text);
return ($smiles ? Forum::parseSmiles($text) : $text);
}
public static function showPost($topic, $text, $smiles = true, $html = false)
{
if($html) {
return '<b>' . $topic . '</b><hr />' . $text;
}
$post = '';
if(!empty($topic))
$post .= '<b>'.($smiles ? self::parseSmiles($topic) : $topic).'</b><hr />';
$post .= self::parseBBCode(nl2br($text), $smiles);
return $post;
}
public static function hasAccess($board_id) {
global $sections, $logged, $account_logged, $logged_access;
if(!isset($sections[$board_id]))
return false;
$hasAccess = true;
$section = $sections[$board_id];
if($section['guild'] > 0) {
if($logged) {
$guild = new \OTS_Guild();
$guild->load($section['guild']);
$status = false;
if($guild->isLoaded()) {
$account_players = $account_logged->getPlayers();
foreach ($account_players as $player) {
if($guild->hasMember($player)) {
$status = true;
}
}
}
if (!$status) $hasAccess = false;
}
else {
$hasAccess = false;
}
}
if($section['access'] > 0) {
if($logged_access < $section['access']) {
$hasAccess = false;
}
}
return $hasAccess;
}
}

42
system/src/Hook.php Normal file
View File

@@ -0,0 +1,42 @@
<?php
namespace MyAAC;
class Hook
{
private $_name, $_type, $_file;
public function __construct($name, $type, $file) {
$this->_name = $name;
$this->_type = $type;
$this->_file = $file;
}
public function execute($params)
{
global $db, $config, $template_path, $ots, $content, $twig;
if(is_callable($this->_file))
{
$params['db'] = $db;
$params['config'] = $config;
$params['template_path'] = $template_path;
$params['ots'] = $ots;
$params['content'] = $content;
$params['twig'] = $twig;
$tmp = $this->_file;
$ret = $tmp($params);
}
else {
extract($params);
$ret = include BASE . $this->_file;
}
return !isset($ret) || $ret == 1 || $ret;
}
public function name() {return $this->_name;}
public function type() {return $this->_type;}
}

44
system/src/Hooks.php Normal file
View File

@@ -0,0 +1,44 @@
<?php
namespace MyAAC;
class Hooks
{
private static $_hooks = array();
public function register($hook, $type = '', $file = null) {
if(!($hook instanceof Hook))
$hook = new Hook($hook, $type, $file);
self::$_hooks[$hook->type()][] = $hook;
}
public function trigger($type, $params = array())
{
$ret = true;
if(isset(self::$_hooks[$type]))
{
foreach(self::$_hooks[$type] as $name => $hook) {
/** @var $hook Hook */
if (!$hook->execute($params)) {
$ret = false;
}
}
}
return $ret;
}
public function exist($type) {
return isset(self::$_hooks[$type]);
}
public function load()
{
foreach(Plugins::getHooks() as $hook) {
$this->register($hook['name'], $hook['type'], $hook['file']);
}
Plugins::clearWarnings();
}
}

140
system/src/Items.php Normal file
View File

@@ -0,0 +1,140 @@
<?php
/**
* Items class
*
* @package MyAAC
* @author Gesior <jerzyskalski@wp.pl>
* @author Slawkens <slawkens@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC;
use MyAAC\Cache\PHP as CachePHP;
use MyAAC\Models\Spell;
class Items
{
private static $error = '';
public static $items;
public static function loadFromXML($show = false)
{
$file_path = config('data_path') . 'items/items.xml';
if (!file_exists($file_path)) {
self::$error = 'Cannot load file ' . $file_path;
return false;
}
$xml = new \DOMDocument;
$xml->load($file_path);
$items = array();
foreach ($xml->getElementsByTagName('item') as $item) {
if ($item->getAttribute('fromid')) {
for ($id = $item->getAttribute('fromid'); $id <= $item->getAttribute('toid'); $id++) {
$tmp = self::parseNode($id, $item, $show);
$items[$tmp['id']] = $tmp['content'];
}
} else {
$tmp = self::parseNode($item->getAttribute('id'), $item, $show);
$items[$tmp['id']] = $tmp['content'];
}
}
$cache_php = new CachePHP(config('cache_prefix'), CACHE . 'persistent/');
$cache_php->set('items', $items, 5 * 365 * 24 * 60 * 60);
return true;
}
public static function parseNode($id, $node, $show = false) {
$name = $node->getAttribute('name');
$article = $node->getAttribute('article');
$plural = $node->getAttribute('plural');
$attributes = array();
foreach($node->getElementsByTagName('attribute') as $attr) {
$attributes[strtolower($attr->getAttribute('key'))] = $attr->getAttribute('value');
}
return array('id' => $id, 'content' => array('article' => $article, 'name' => $name, 'plural' => $plural, 'attributes' => $attributes));
}
public static function getError() {
return self::$error;
}
public static function load() {
if(self::$items) {
return;
}
$cache_php = new CachePHP(config('cache_prefix'), CACHE . 'persistent/');
self::$items = $cache_php->get('items');
}
public static function get($id) {
self::load();
return isset(self::$items[$id]) ? self::$items[$id] : [];
}
public static function getDescription($id, $count = 1) {
$item = self::get($id);
$attr = $item['attributes'];
$s = '';
if(!empty($item['name'])) {
if($count > 1) {
if($attr['showcount']) {
$s .= $count . ' ';
}
if(!empty($item['plural'])) {
$s .= $item['plural'];
}
else if((int)$attr['showcount'] == 0) {
$s .= $item['name'];
}
else {
$s .= $item['name'] . 's';
}
}
else {
if(!empty($item['aticle'])) {
$s .= $item['article'] . ' ';
}
$s .= $item['name'];
}
}
else
$s .= 'an item of type ' . $item['id'];
if(isset($attr['type']) && strtolower($attr['type']) == 'rune') {
$item = Spell::where('item_id', $id)->first();
if($item) {
if($item->level > 0 && $item->maglevel > 0) {
$s .= '. ' . ($count > 1 ? "They" : "It") . ' can only be used by ';
}
$configVocations = config('vocations');
if(!empty(trim($item->vocations))) {
$vocations = json_decode($item->vocations);
if(count($vocations) > 0) {
foreach($vocations as $voc => $show) {
$vocations[$configVocations[$voc]] = $show;
}
}
}
else {
$s .= 'players';
}
$s .= ' with';
}
}
return $s;
}
}

View File

@@ -1,6 +1,7 @@
<?php
namespace MyAAC\Models;
use Illuminate\Database\Eloquent\Model;
class AccountVipList extends Model {

60
system/src/NPCs.php Normal file
View File

@@ -0,0 +1,60 @@
<?php
/**
* NPC class
*
* @package MyAAC
* @author Gesior <jerzyskalski@wp.pl>
* @author Slawkens <slawkens@gmail.com>
* @author Lee
* @copyright 2021 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC;
use MyAAC\Cache\PHP as CachePHP;
class NPCs
{
public static $npcs;
public static function loadFromXML($show = false)
{
$npc_path = config('data_path') . 'npc/';
if (!file_exists($npc_path))
return false;
$npcs = [];
$xml = new \DOMDocument();
foreach (preg_grep('~\.(xml)$~i', scandir($npc_path)) as $npc) {
$xml->load($npc_path . $npc);
if ($xml) {
$element = $xml->getElementsByTagName('npc')->item(0);
if (isset($element)) {
$name = $element->getAttribute('name');
if (!empty($name) && !in_array($name, $npcs)) {
$npcs[] = strtolower($name);
}
}
}
}
if (count($npcs) == 0) {
return false;
}
$cache_php = new CachePHP(config('cache_prefix'), CACHE . 'persistent/');
$cache_php->set('npcs', $npcs, 5 * 365 * 24 * 60 * 60);
return true;
}
public static function load()
{
if (self::$npcs) {
return;
}
$cache_php = new CachePHP(config('cache_prefix'), CACHE . 'persistent/');
self::$npcs = $cache_php->get('npcs');
}
}

169
system/src/News.php Normal file
View File

@@ -0,0 +1,169 @@
<?php
namespace MyAAC;
use MyAAC\Cache\Cache;
use MyAAC\Models\News as ModelsNews;
class News
{
static public function verify($title, $body, $article_text, $article_image, &$errors)
{
if(!isset($title[0]) || !isset($body[0])) {
$errors[] = 'Please fill all inputs.';
return false;
}
if(strlen($title) > NEWS_TITLE_LIMIT) {
$errors[] = 'News title cannot be longer than ' . NEWS_TITLE_LIMIT . ' characters.';
return false;
}
if(strlen($body) > NEWS_BODY_LIMIT) {
$errors[] = 'News content cannot be longer than ' . NEWS_BODY_LIMIT . ' characters.';
return false;
}
if(strlen($article_text) > ARTICLE_TEXT_LIMIT) {
$errors[] = 'Article text cannot be longer than ' . ARTICLE_TEXT_LIMIT . ' characters.';
return false;
}
if(strlen($article_image) > ARTICLE_IMAGE_LIMIT) {
$errors[] = 'Article image cannot be longer than ' . ARTICLE_IMAGE_LIMIT . ' characters.';
return false;
}
return true;
}
static public function add($title, $body, $type, $category, $player_id, $comments, $article_text, $article_image, &$errors)
{
if(!self::verify($title, $body, $article_text, $article_image, $errors))
return false;
ModelsNews::create([
'title' => $title,
'body' => $body,
'type' => $type,
'date' => time(),
'category' => $category,
'player_id' => isset($player_id) ? $player_id : 0,
'comments' => $comments,
'article_text' => ($type == 3 ? $article_text : ''),
'article_image' => ($type == 3 ? $article_image : '')
]);
self::clearCache();
return true;
}
static public function get($id) {
return ModelsNews::find($id)->toArray();
}
static public function update($id, $title, $body, $type, $category, $player_id, $comments, $article_text, $article_image, &$errors)
{
if(!self::verify($title, $body, $article_text, $article_image, $errors))
return false;
ModelsNews::where('id', $id)->update([
'title' => $title,
'body' => $body,
'type' => $type,
'category' => $category,
'last_modified_by' => isset($player_id) ? $player_id : 0,
'last_modified_date' => time(),
'comments' => $comments,
'article_text' => $article_text,
'article_image' => $article_image
]);
self::clearCache();
return true;
}
static public function delete($id, &$errors)
{
if(isset($id)) {
$row = ModelsNews::find($id);
if($row) {
if (!$row->delete()) {
$errors[] = 'Fail during delete News.';
}
}
else {
$errors[] = 'News with id ' . $id . ' does not exists.';
}
}
else {
$errors[] = 'News id not set.';
}
if(count($errors)) {
return false;
}
self::clearCache();
return true;
}
static public function toggleHidden($id, &$errors, &$status)
{
if(isset($id))
{
$row = ModelsNews::find($id);
if($row)
{
$row->hidden = $row->hidden == 1 ? 0 : 1;
if (!$row->save()) {
$errors[] = 'Fail during toggle hidden News.';
}
$status = $row->hidden;
}
else
$errors[] = 'News with id ' . $id . ' does not exists.';
}
else
$errors[] = 'News id not set.';
if(count($errors)) {
return false;
}
self::clearCache();
return true;
}
static public function getCached($type)
{
global $template_name;
$cache = Cache::getInstance();
if ($cache->enabled())
{
$tmp = '';
if ($cache->fetch('news_' . $template_name . '_' . $type, $tmp) && isset($tmp[0])) {
return $tmp;
}
}
return false;
}
static public function clearCache()
{
$cache = Cache::getInstance();
if (!$cache->enabled()) {
return;
}
$tmp = '';
foreach (get_templates() as $template) {
if ($cache->fetch('news_' . $template . '_' . NEWS, $tmp)) {
$cache->delete('news_' . $template . '_' . NEWS);
}
if ($cache->fetch('news_' . $template . '_' . TICKER, $tmp)) {
$cache->delete('news_' . $template . '_' . TICKER);
}
if ($cache->fetch('news_' . $template . '_' . ARTICLE, $tmp)) {
$cache->delete('news_' . $template . '_' . ARTICLE);
}
}
}
}

731
system/src/Plugins.php Normal file
View File

@@ -0,0 +1,731 @@
<?php
namespace MyAAC;
use Composer\Semver\Semver;
use MyAAC\Cache\Cache;
use MyAAC\Models\Menu;
class Plugins {
private static $warnings = [];
private static $error = null;
private static $plugin_json = [];
public static function getRoutes()
{
$cache = Cache::getInstance();
if ($cache->enabled()) {
$tmp = '';
if ($cache->fetch('plugins_routes', $tmp)) {
return unserialize($tmp);
}
}
$routes = [];
$pluginPages = glob(PLUGINS . '*/pages/*.php');
foreach ($pluginPages as $file) {
$file = str_replace(PLUGINS, 'plugins/', $file);
$name = pathinfo($file, PATHINFO_FILENAME);
$routes[] = [['get', 'post'], $name, $file, 1000];
}
foreach(self::getAllPluginsJson() as $plugin) {
$warningPreTitle = 'Plugin: ' . $plugin['name'] . ' - ';
if (isset($plugin['routes'])) {
foreach ($plugin['routes'] as $_name => $info) {
// default method: get
$method = $info['method'] ?? ['GET'];
if ($method !== '*') {
$methods = is_string($method) ? explode(',', $info['method']) : $method;
foreach ($methods as $method) {
$method = strtolower($method);
if (!in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'head'])) {
self::$warnings[] = $warningPreTitle . 'Not allowed method ' . $method . '... Disabling this route...';
}
}
}
else {
$methods = '*'; // all available methods
}
if (!isset($info['priority'])) {
$info['priority'] = 100; // default priority
}
if (isset($info['redirect_from'])) {
removeIfFirstSlash($info['redirect_from']);
$info['pattern'] = $info['redirect_from'];
if (!isset($info['redirect_to'])) {
self::$warnings[] = $warningPreTitle . 'redirect set without "redirect_to".';
}
else {
removeIfFirstSlash($info['redirect_to']);
$info['file'] = '__redirect__/' . $info['redirect_to'];
}
}
// replace first occurrence of / in pattern if found (will be auto-added later)
removeIfFirstSlash($info['pattern']);
foreach ($routes as $id => &$route) {
if($route[1] == $info['pattern']) {
if($info['priority'] < $route[3]) {
self::$warnings[] = $warningPreTitle . "Duplicated route with lower priority: {$info['pattern']}. Disabling this route...";
continue 2;
}
else {
self::$warnings[] = $warningPreTitle . "Duplicated route with lower priority: {$route[1]} ({$route[3]}). Disabling this route...";
unset($routes[$id]);
}
}
}
$routes[] = [$methods, $info['pattern'], $info['file'], $info['priority']];
}
}
}
/*
usort($routes, function ($a, $b)
{
// key 3 is priority
if ($a[3] == $b[3]) {
return 0;
}
return ($a[3] > $b[3]) ? -1 : 1;
});
*/
// cleanup before passing back
// priority is not needed anymore
foreach ($routes as &$route) {
unset($route[3]);
}
if ($cache->enabled()) {
$cache->set('plugins_routes', serialize($routes), 600);
}
return $routes;
}
public static function getThemes()
{
$cache = Cache::getInstance();
if ($cache->enabled()) {
$tmp = '';
if ($cache->fetch('plugins_themes', $tmp)) {
return unserialize($tmp);
}
}
$themes = [];
$pluginThemes = glob(PLUGINS . '*/themes/*', GLOB_ONLYDIR);
foreach ($pluginThemes as $path) {
$path = str_replace(PLUGINS, 'plugins/', $path);
$name = pathinfo($path, PATHINFO_FILENAME);
$themes[$name] = $path;
}
if ($cache->enabled()) {
$cache->set('plugins_themes', serialize($themes), 600);
}
return $themes;
}
public static function getHooks()
{
$cache = Cache::getInstance();
if ($cache->enabled()) {
$tmp = '';
if ($cache->fetch('plugins_hooks', $tmp)) {
return unserialize($tmp);
}
}
$hooks = [];
foreach(self::getAllPluginsJson() as $plugin) {
if (isset($plugin['hooks'])) {
foreach ($plugin['hooks'] as $_name => $info) {
if (str_contains($info['type'], 'HOOK_')) {
$info['type'] = str_replace('HOOK_', '', $info['type']);
}
if (defined('HOOK_'. $info['type'])) {
$hook = constant('HOOK_'. $info['type']);
$hooks[] = ['name' => $_name, 'type' => $hook, 'file' => $info['file']];
} else {
self::$warnings[] = 'Plugin: ' . $plugin['name'] . '. Unknown event type: ' . $info['type'];
}
}
}
}
if ($cache->enabled()) {
$cache->set('plugins_hooks', serialize($hooks), 600);
}
return $hooks;
}
public static function getAllPluginsSettings()
{
$cache = Cache::getInstance();
if ($cache->enabled()) {
$tmp = '';
if ($cache->fetch('plugins_settings', $tmp)) {
return unserialize($tmp);
}
}
$settings = [];
foreach (self::getAllPluginsJson() as $plugin) {
if (isset($plugin['settings'])) {
$settingsFile = require BASE . $plugin['settings'];
if (!isset($settingsFile['key'])) {
warning("Settings file for plugin - {$plugin['name']} does not contain 'key' field");
continue;
}
$settings[$settingsFile['key']] = ['pluginFilename' => $plugin['filename'], 'settingsFilename' => $plugin['settings']];
}
}
if ($cache->enabled()) {
$cache->set('plugins_settings', serialize($settings), 600); // cache for 10 minutes
}
return $settings;
}
public static function getAllPluginsJson($disabled = false)
{
$cache = Cache::getInstance();
if ($cache->enabled()) {
$tmp = '';
if ($cache->fetch('plugins', $tmp)) {
return unserialize($tmp);
}
}
$plugins = [];
foreach (get_plugins($disabled) as $filename) {
$plugin = self::getPluginJson($filename);
if (!$plugin) {
continue;
}
$plugin['filename'] = $filename;
$plugins[] = $plugin;
}
if ($cache->enabled()) {
$cache->set('plugins', serialize($plugins), 600); // cache for 10 minutes
}
return $plugins;
}
public static function getPluginSettings($filename)
{
$plugin_json = self::getPluginJson($filename);
if (!$plugin_json) {
return false;
}
if (!isset($plugin_json['settings']) || !file_exists(BASE . $plugin_json['settings'])) {
return false;
}
return $plugin_json['settings'];
}
public static function getPluginJson($filename = null)
{
if(!isset($filename)) {
return self::$plugin_json;
}
$pathToPlugin = PLUGINS . $filename . '.json';
if (!file_exists($pathToPlugin)) {
self::$warnings[] = "Cannot load $filename.json. File doesn't exist.";
return false;
}
$string = file_get_contents($pathToPlugin);
$plugin_json = json_decode($string, true);
if ($plugin_json == null) {
self::$warnings[] = "Cannot load $filename.json. File might be not a valid json code.";
return false;
}
if (isset($plugin_json['enabled']) && !getBoolean($plugin_json['enabled'])) {
self::$warnings[] = 'Skipping ' . $filename . '... The plugin is disabled.';
return false;
}
return $plugin_json;
}
public static function install($file): bool
{
global $db;
if(!\class_exists('\ZipArchive')) {
throw new \RuntimeException('Please install PHP zip extension. Plugins upload disabled until then.');
}
$zip = new \ZipArchive();
if($zip->open($file) !== true) {
self::$error = 'There was a problem with opening zip archive.';
return false;
}
for ($i = 0; $i < $zip->numFiles; $i++) {
$tmp = $zip->getNameIndex($i);
if(pathinfo($tmp, PATHINFO_DIRNAME) == 'plugins' && pathinfo($tmp, PATHINFO_EXTENSION) == 'json')
$json_file = $tmp;
}
if(!isset($json_file)) {
self::$error = 'Cannot find plugin info .json file. Installation is discontinued.';
return false;
}
$plugin_temp_dir = CACHE . 'plugins/' . str_replace('.zip', '', basename($file)) . '/';
if(!$zip->extractTo($plugin_temp_dir)) { // place in cache dir
self::$error = 'There was a problem with extracting zip archive to cache directory.';
$zip->close();
return false;
}
self::$error = 'There was a problem with extracting zip archive.';
$file_name = $plugin_temp_dir . $json_file;
if(!file_exists($file_name)) {
self::$error = "Cannot load " . $file_name . ". File doesn't exist.";
$zip->close();
return false;
}
$pluginFilename = str_replace('.json', '', basename($json_file));
if (self::existDisabled($pluginFilename)) {
success('The plugin already existed, but was disabled. It has been enabled again and will be now reinstalled.');
self::enable($pluginFilename);
}
$string = file_get_contents($file_name);
$plugin_json = json_decode($string, true);
self::$plugin_json = $plugin_json;
if ($plugin_json == null) {
self::$warnings[] = 'Cannot load ' . $file_name . '. File might be not a valid json code.';
}
else {
$continue = true;
if(!isset($plugin_json['name']) || empty(trim($plugin_json['name']))) {
self::$warnings[] = 'Plugin "name" tag is not set.';
}
if(!isset($plugin_json['description']) || empty(trim($plugin_json['description']))) {
self::$warnings[] = 'Plugin "description" tag is not set.';
}
if(!isset($plugin_json['version']) || empty(trim($plugin_json['version']))) {
self::$warnings[] = 'Plugin "version" tag is not set.';
}
if(!isset($plugin_json['author']) || empty(trim($plugin_json['author']))) {
self::$warnings[] = 'Plugin "author" tag is not set.';
}
if(!isset($plugin_json['contact']) || empty(trim($plugin_json['contact']))) {
self::$warnings[] = 'Plugin "contact" tag is not set.';
}
if(isset($plugin_json['require'])) {
$require = $plugin_json['require'];
$myaac_satified = true;
if(isset($require['myaac_'])) {
$require_myaac = $require['myaac_'];
if(!Semver::satisfies(MYAAC_VERSION, $require_myaac)) {
$myaac_satified = false;
}
}
else if(isset($require['myaac'])) {
$require_myaac = $require['myaac'];
if(version_compare(MYAAC_VERSION, $require_myaac, '<')) {
$myaac_satified = false;
}
}
if(!$myaac_satified) {
self::$error = "Your AAC version doesn't meet the requirement of this plugin. Required version is: " . $require_myaac . ", and you're using version " . MYAAC_VERSION . ".";
return false;
}
$php_satisfied = true;
if(isset($require['php_'])) {
$require_php = $require['php_'];
if(!Semver::satisfies(phpversion(), $require_php)) {
$php_satisfied = false;
}
}
else if(isset($require['php'])) {
$require_php = $require['php'];
if(version_compare(phpversion(), $require_php, '<')) {
$php_satisfied = false;
}
}
if(!$php_satisfied) {
self::$error = "Your PHP version doesn't meet the requirement of this plugin. Required version is: " . $require_php . ", and you're using version " . phpversion() . ".";
$continue = false;
}
$database_satisfied = true;
if(isset($require['database_'])) {
$require_database = $require['database_'];
if(!Semver::satisfies(DATABASE_VERSION, $require_database)) {
$database_satisfied = false;
}
}
else if(isset($require['database'])) {
$require_database = $require['database'];
if(version_compare(DATABASE_VERSION, $require_database, '<')) {
$database_satisfied = false;
}
}
if(!$database_satisfied) {
self::$error = "Your database version doesn't meet the requirement of this plugin. Required version is: " . $require_database . ", and you're using version " . DATABASE_VERSION . ".";
$continue = false;
}
if($continue) {
foreach($require as $req => $version) {
$req = strtolower(trim($req));
$version = trim($version);
if(in_array($req, array('myaac', 'myaac_', 'php', 'php_', 'database', 'database_'))) {
continue;
}
if(in_array($req, array('php-ext', 'php-extension'))) { // require php extension
$tmpDisplayError = false;
$explode = explode(',', $version);
foreach ($explode as $item) {
if(!extension_loaded($item)) {
$errors[] = "This plugin requires php extension: " . $item . " to be installed.";
$tmpDisplayError = true;
}
}
if ($tmpDisplayError) {
self::$error = implode('<br/>', $errors);
$continue = false;
break;
}
}
else if($req == 'table') {
$tmpDisplayError = false;
$explode = explode(',', $version);
foreach ($explode as $item) {
if(!$db->hasTable($item)) {
$errors[] = "This plugin requires table: " . $item . " to exist in the database.";
$tmpDisplayError = true;
}
}
if ($tmpDisplayError) {
self::$error = implode('<br/>', $errors);
$continue = false;
break;
}
}
else if($req == 'column') {
$tmpDisplayError = false;
$explode = explode(',', $version);
foreach ($explode as $item) {
$tmp = explode('.', $item);
if(count($tmp) == 2) {
if(!$db->hasColumn($tmp[0], $tmp[1])) {
$errors[] = "This plugin requires database column: " . $tmp[0] . "." . $tmp[1] . " to exist in database.";
$tmpDisplayError = true;
}
}
else {
self::$warnings[] = "Invalid plugin require column: " . $item;
}
}
if ($tmpDisplayError) {
self::$error = implode('<br/>', $errors);
$continue = false;
break;
}
}
else if(strpos($req, 'ext-') !== false) {
$tmp = explode('-', $req);
if(count($tmp) == 2) {
if(!extension_loaded($tmp[1]) || !Semver::satisfies(phpversion($tmp[1]), $version)) {
self::$error = "This plugin requires php extension: " . $tmp[1] . ", version " . $version . " to be installed.";
$continue = false;
break;
}
}
}
else if(!self::is_installed($req, $version)) {
self::$error = "This plugin requires another plugin to run correctly. The another plugin is: " . $req . ", with version " . $version . ".";
$continue = false;
break;
}
}
}
}
if($continue) {
if(!$zip->extractTo(BASE)) { // "Real" Install
self::$error = 'There was a problem with extracting zip archive to base directory.';
$zip->close();
return false;
}
if (isset($plugin_json['install'])) {
if (file_exists(BASE . $plugin_json['install'])) {
$db->revalidateCache();
require BASE . $plugin_json['install'];
$db->revalidateCache();
}
else
self::$warnings[] = 'Cannot load install script. Your plugin might be not working correctly.';
}
clearCache();
return true;
}
}
return false;
}
public static function isEnabled($pluginFileName): bool
{
$filenameJson = $pluginFileName . '.json';
return !is_file(PLUGINS . 'disabled.' . $filenameJson) && is_file(PLUGINS . $filenameJson);
}
public static function existDisabled($pluginFileName): bool
{
$filenameJson = $pluginFileName . '.json';
return is_file(PLUGINS . 'disabled.' . $filenameJson);
}
public static function enable($pluginFileName): bool {
return self::enableDisable($pluginFileName, true);
}
public static function disable($pluginFileName): bool {
return self::enableDisable($pluginFileName, false);
}
private static function enableDisable($pluginFileName, $enable): bool
{
$filenameJson = $pluginFileName . '.json';
$fileExist = is_file(PLUGINS . ($enable ? 'disabled.' : '') . $filenameJson);
if (!$fileExist) {
self::$error = 'Cannot ' . ($enable ? 'enable' : 'disable') . ' plugin: ' . $pluginFileName . '. File does not exist.';
return false;
}
$result = rename(PLUGINS . ($enable ? 'disabled.' : '') . $filenameJson, PLUGINS . ($enable ? '' : 'disabled.') . $filenameJson);
if (!$result) {
self::$error = 'Cannot ' . ($enable ? 'enable' : 'disable') . ' plugin: ' . $pluginFileName . '. Permission problem.';
return false;
}
return true;
}
/**
* This function is to execute the "install" part of the plugin
*
* @param $plugin_name
* @return bool
*/
public static function executeInstall($plugin_name): bool
{
$filename = BASE . 'plugins/' . $plugin_name . '.json';
if(!file_exists($filename)) {
self::$error = 'Plugin ' . $plugin_name . ' does not exist.';
return false;
}
$string = file_get_contents($filename);
$plugin_json = json_decode($string, true);
if(!$plugin_json) {
self::$error = 'Cannot load plugin info ' . $plugin_name . '.json';
return false;
}
if(!isset($plugin_json['install'])) {
self::$error = "Plugin doesn't have install options defined. Skipping...";
return false;
}
global $db;
if (file_exists(BASE . $plugin_json['install'])) {
$db->revalidateCache();
require BASE . $plugin_json['install'];
$db->revalidateCache();
}
else {
self::$warnings[] = 'Cannot load install script. Your plugin might be not working correctly.';
}
return true;
}
public static function uninstall($plugin_name): bool
{
$filename = BASE . 'plugins/' . $plugin_name . '.json';
if(!file_exists($filename)) {
self::$error = 'Plugin ' . $plugin_name . ' does not exist.';
return false;
}
$string = file_get_contents($filename);
$plugin_info = json_decode($string, true);
if(!$plugin_info) {
self::$error = 'Cannot load plugin info ' . $plugin_name . '.json';
return false;
}
if(!isset($plugin_info['uninstall'])) {
self::$error = "Plugin doesn't have uninstall options defined. Skipping...";
return false;
}
$success = true;
foreach($plugin_info['uninstall'] as $file) {
if(strpos($file, '/') === 0) {
$success = false;
self::$error = "You cannot use absolute paths (starting with slash - '/'): " . $file;
break;
}
$file = str_replace('\\', '/', BASE . $file);
$realpath = str_replace('\\', '/', realpath(dirname($file)));
if(!is_sub_dir($file, BASE) || $realpath != dirname($file)) {
$success = false;
self::$error = "You don't have rights to delete: " . $file;
break;
}
}
if($success) {
foreach($plugin_info['uninstall'] as $file) {
if(!deleteDirectory(BASE . $file)) {
self::$warnings[] = 'Cannot delete: ' . $file;
}
}
$cache = Cache::getInstance();
if($cache->enabled()) {
$cache->delete('templates');
$cache->delete('hooks');
$cache->delete('template_menus');
}
return true;
}
return false;
}
public static function is_installed($plugin_name, $version): bool
{
$filename = BASE . 'plugins/' . $plugin_name . '.json';
if(!file_exists($filename)) {
return false;
}
$string = file_get_contents($filename);
$plugin_info = json_decode($string, true);
if(!$plugin_info) {
return false;
}
if(!isset($plugin_info['version'])) {
return false;
}
return Semver::satisfies($plugin_info['version'], $version);
}
public static function getWarnings() {
return self::$warnings;
}
public static function clearWarnings() {
self::$warnings = [];
}
public static function getError() {
return self::$error;
}
/**
* Install menus
* Helper function for plugins
*
* @param string $templateName
* @param array $categories
*/
public static function installMenus($templateName, $categories)
{
// check if menus already exist
$menuInstalled = Menu::where('template', $templateName)->select('id')->first();
if ($menuInstalled) {
return;
}
foreach ($categories as $category => $menus) {
$i = 0;
foreach ($menus as $name => $link) {
$color = '';
$blank = 0;
if (is_array($link)) {
if (isset($link['name'])) {
$name = $link['name'];
}
if (isset($link['color'])) {
$color = $link['color'];
}
if (isset($link['blank'])) {
$blank = $link['blank'] ? 1 : 0;
}
$link = $link['link'];
}
$insert_array = [
'template' => $templateName,
'name' => $name,
'link' => $link,
'category' => $category,
'ordering' => $i++,
'blank' => $blank,
'color' => $color,
];
Menu::create($insert_array);
}
}
}
}

607
system/src/Settings.php Normal file
View File

@@ -0,0 +1,607 @@
<?php
namespace MyAAC;
use MyAAC\Cache\Cache;
use MyAAC\Models\Settings as ModelsSettings;
class Settings implements \ArrayAccess
{
static private $instance;
private $settingsFile = [];
private $settingsDatabase = [];
private $cache = [];
private $valuesAsked = [];
private $errors = [];
/**
* @return Settings
*/
public static function getInstance(): Settings
{
if (!self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function load()
{
$cache = Cache::getInstance();
if ($cache->enabled()) {
$tmp = '';
if ($cache->fetch('settings', $tmp)) {
$this->settingsDatabase = unserialize($tmp);
return;
}
}
$settings = ModelsSettings::all();
foreach ($settings as $setting)
{
$this->settingsDatabase[$setting->name][$setting->key] = $setting->value;
}
if ($cache->enabled()) {
$cache->set('settings', serialize($this->settingsDatabase), 600);
}
}
public function save($pluginName, $values)
{
$this->loadPlugin($pluginName);
if (!isset($this->settingsFile[$pluginName])) {
throw new \RuntimeException("Error on save settings: plugin $pluginName does not exist");
}
$settings = $this->settingsFile[$pluginName];
global $hooks;
if (!$hooks->trigger(HOOK_ADMIN_SETTINGS_BEFORE_SAVE, [
'name' => $pluginName,
'values' => $values,
'settings' => $settings,
])) {
return false;
}
if (isset($settings['callbacks']['beforeSave'])) {
if (!$settings['callbacks']['beforeSave']($settings, $values)) {
return false;
}
}
$this->errors = [];
ModelsSettings::where('name', $pluginName)->delete();
foreach ($values as $key => $value) {
$errorMessage = '';
if (isset($settings['settings'][$key]['callbacks']['beforeSave']) && !$settings['settings'][$key]['callbacks']['beforeSave']($key, $value, $errorMessage)) {
$this->errors[] = $errorMessage;
continue;
}
try {
ModelsSettings::create([
'name' => $pluginName,
'key' => $key,
'value' => $value
]);
} catch (\PDOException $error) {
$this->errors[] = 'Error while saving setting (' . $pluginName . ' - ' . $key . '): ' . $error->getMessage();
}
}
$cache = Cache::getInstance();
if ($cache->enabled()) {
$cache->delete('settings');
}
return true;
}
public function updateInDatabase($pluginName, $key, $value)
{
if (ModelsSettings::where(['name' => $pluginName, 'key' => $key])->exists()) {
ModelsSettings::where(['name' => $pluginName, 'key' => $key])->update(['value' => $value]);
}
else {
// insert new
ModelsSettings::create(['name' => $pluginName, 'key' => $key, 'value' => $value]);
}
}
public function deleteFromDatabase($pluginName, $key = null)
{
if (!isset($key)) {
ModelsSettings::where('name', $pluginName)->delete();
}
else {
ModelsSettings::where('name', $pluginName)->where('key', $key)->delete();
}
}
public static function display($plugin, $settings): array
{
$settingsDb = ModelsSettings::where('name', $plugin)->pluck('value', 'key')->toArray();
$config = [];
require BASE . 'config.local.php';
foreach ($config as $key => $value) {
if (is_bool($value)) {
$settingsDb[$key] = $value ? 'true' : 'false';
}
elseif (is_array($value)) {
$settingsDb[$key] = $value;
}
else {
$settingsDb[$key] = (string)$value;
}
}
$javascript = '';
ob_start();
?>
<ul class="nav nav-tabs" id="myTab">
<?php
$i = 0;
foreach($settings as $setting) {
if (isset($setting['script'])) {
$javascript .= $setting['script'] . PHP_EOL;
}
if ($setting['type'] === 'category') {
?>
<li class="nav-item">
<a class="nav-link<?= ($i === 0 ? ' active' : ''); ?>" id="home-tab-<?= $i++; ?>" data-toggle="tab" href="#tab-<?= str_replace(' ', '', $setting['title']); ?>" type="button"><?= $setting['title']; ?></a>
</li>
<?php
}
}
?>
</ul>
<div class="tab-content" id="tab-content">
<?php
$checkbox = function ($key, $type, $value) {
echo '<label><input type="radio" id="' . $key . '_' . ($type ? 'yes' : 'no') . '" name="settings[' . $key . ']" value="' . ($type ? 'true' : 'false') . '" ' . ($value === $type ? 'checked' : '') . '/>' . ($type ? 'Yes' : 'No') . '</label> ';
};
$i = 0;
$j = 0;
foreach($settings as $key => $setting) {
if ($setting['type'] === 'category') {
if ($j++ !== 0) { // close previous category
echo '</tbody></table></div>';
}
?>
<div class="tab-pane fade show<?= ($j === 1 ? ' active' : ''); ?>" id="tab-<?= str_replace(' ', '', $setting['title']); ?>">
<?php
continue;
}
if ($setting['type'] === 'section') {
if ($i++ !== 0) { // close previous section
echo '</tbody></table>';
}
?>
<h3 id="row_<?= $key ?>" style="text-align: center"><strong><?= $setting['title']; ?></strong></h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th style="width: 13%">Name</th>
<th style="width: 30%">Value</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<?php
continue;
}
if (!isset($setting['hidden']) || !$setting['hidden']) {
?>
<tr id="row_<?= $key ?>">
<td><label for="<?= $key ?>" class="control-label"><?= $setting['name'] ?></label></td>
<td>
<?php
}
if (isset($setting['hidden']) && $setting['hidden']) {
$value = '';
if ($setting['type'] === 'boolean') {
$value = ($setting['default'] ? 'true' : 'false');
}
else if (in_array($setting['type'], ['text', 'number', 'email', 'password', 'textarea'])) {
$value = $setting['default'];
}
else if ($setting['type'] === 'options') {
$value = $setting['options'][$setting['default']];
}
echo '<input type="hidden" name="settings[' . $key . ']" value="' . $value . '" id="' . $key . '"';
}
else if ($setting['type'] === 'boolean') {
if(isset($settingsDb[$key])) {
if($settingsDb[$key] === 'true') {
$value = true;
}
else {
$value = false;
}
}
else {
$value = ($setting['default'] ?? false);
}
$checkbox($key, true, $value);
$checkbox($key, false, $value);
}
else if (in_array($setting['type'], ['text', 'number', 'email', 'password'])) {
if ($setting['type'] === 'number') {
$min = (isset($setting['min']) ? ' min="' . $setting['min'] . '"' : '');
$max = (isset($setting['max']) ? ' max="' . $setting['max'] . '"' : '');
$step = (isset($setting['step']) ? ' step="' . $setting['step'] . '"' : '');
}
else {
$min = $max = $step = '';
}
echo '<input class="form-control" type="' . $setting['type'] . '" name="settings[' . $key . ']" value="' . ($settingsDb[$key] ?? ($setting['default'] ?? '')) . '" id="' . $key . '"' . $min . $max . $step . '/>';
}
else if($setting['type'] === 'textarea') {
if (is_array($settingsDb[$key])) {
$settingsDb[$key] = implode(',', $settingsDb[$key]);
}
$value = ($settingsDb[$key] ?? ($setting['default'] ?? ''));
$valueWithSpaces = array_map('trim', preg_split('/\r\n|\r|\n/', trim($value)));
$rows = count($valueWithSpaces);
if ($rows < 2) {
$rows = 2; // always min 2 rows for textarea
}
echo '<textarea class="form-control" rows="' . $rows . '" name="settings[' . $key . ']" id="' . $key . '">' . $value . '</textarea>';
}
else if ($setting['type'] === 'options') {
if ($setting['options'] === '$templates') {
$templates = [];
foreach (get_templates() as $value) {
$templates[$value] = $value;
}
$setting['options'] = $templates;
}
else if($setting['options'] === '$clients') {
$clients = [];
foreach((array)config('clients') as $client) {
$client_version = (string)($client / 100);
if(strpos($client_version, '.') === false)
$client_version .= '.0';
$clients[$client] = $client_version;
}
$setting['options'] = $clients;
}
else if ($setting['options'] == '$timezones') {
$timezones = [];
foreach (\DateTimeZone::listIdentifiers() as $value) {
$timezones[$value] = $value;
}
$setting['options'] = $timezones;
}
else {
if (is_string($setting['options'])) {
$setting['options'] = explode(',', $setting['options']);
foreach ($setting['options'] as &$option) {
$option = trim($option);
}
}
}
echo '<select class="form-control" name="settings[' . $key . ']" id="' . $key . '">';
foreach ($setting['options'] as $value => $option) {
$compareTo = ($settingsDb[$key] ?? ($setting['default'] ?? ''));
if($value === 'true') {
$selected = $compareTo === true;
}
else if($value === 'false') {
$selected = $compareTo === false;
}
else {
$selected = $compareTo == $value;
}
echo '<option value="' . $value . '" ' . ($selected ? 'selected' : '') . '>' . $option . '</option>';
}
echo '</select>';
}
if (!isset($setting['hidden']) || !$setting['hidden']) {
?>
</td>
<td>
<div class="well setting-default"><?php
echo (isset($setting['desc']) ? makeLinksClickable($setting['desc']) : '');
echo '<br/>';
echo '<strong>Default:</strong> ';
if ($setting['type'] === 'boolean') {
echo ($setting['default'] ? 'Yes' : 'No');
}
else if (in_array($setting['type'], ['text', 'number', 'email', 'password', 'textarea'])) {
echo $setting['default'];
}
else if ($setting['type'] === 'options') {
if (is_int($setting['default']) || !empty($setting['default'])) {
echo $setting['options'][$setting['default']];
}
}
?></div>
</td>
</tr>
<?php
}
}
?>
</tbody>
</table>
</div>
</div>
<div class="box-footer">
<button name="save" type="submit" class="btn btn-primary">Save</button>
</div>
<?php
return ['content' => ob_get_clean(), 'script' => $javascript];
}
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
if (is_null($offset)) {
throw new \RuntimeException("Settings: You cannot set empty offset with value: $value!");
}
$this->loadPlugin($offset);
$pluginKeyName = $this->valuesAsked['pluginKeyName'];
$key = $this->valuesAsked['key'];
// remove whole plugin settings
if (!isset($value)) {
$this->offsetUnset($offset);
$this->deleteFromDatabase($pluginKeyName, $key);
return;
}
$this->settingsDatabase[$pluginKeyName][$key] = $value;
}
#[\ReturnTypeWillChange]
public function offsetExists($offset): bool
{
$this->loadPlugin($offset);
$pluginKeyName = $this->valuesAsked['pluginKeyName'];
$key = $this->valuesAsked['key'];
// remove specified plugin settings (all)
if(is_null($key)) {
return isset($this->settingsDatabase[$offset]);
}
return isset($this->settingsDatabase[$pluginKeyName][$key]);
}
#[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
$this->loadPlugin($offset);
$pluginKeyName = $this->valuesAsked['pluginKeyName'];
$key = $this->valuesAsked['key'];
if (isset($this->cache[$offset])) {
unset($this->cache[$offset]);
}
// remove specified plugin settings (all)
if(!isset($key)) {
unset($this->settingsFile[$pluginKeyName]);
unset($this->settingsDatabase[$pluginKeyName]);
$this->deleteFromDatabase($pluginKeyName);
return;
}
unset($this->settingsFile[$pluginKeyName]['settings'][$key]);
unset($this->settingsDatabase[$pluginKeyName][$key]);
}
/**
* Get settings
* Usage: $setting['plugin_name.key']
* Example: $settings['shop_system.paypal_email']
*
* @param mixed $offset
* @return array|mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
// try cache hit
if(isset($this->cache[$offset])) {
return $this->cache[$offset];
}
$this->loadPlugin($offset);
$pluginKeyName = $this->valuesAsked['pluginKeyName'];
$key = $this->valuesAsked['key'];
// return specified plugin settings (all)
if(!isset($key)) {
if (!isset($this->settingsFile[$pluginKeyName]['settings'])) {
throw new \RuntimeException('Unknown plugin settings: ' . $pluginKeyName);
}
return $this->settingsFile[$pluginKeyName]['settings'];
}
$ret = [];
if(isset($this->settingsFile[$pluginKeyName]['settings'][$key])) {
$ret = $this->settingsFile[$pluginKeyName]['settings'][$key];
}
if(isset($this->settingsDatabase[$pluginKeyName][$key])) {
$value = $this->settingsDatabase[$pluginKeyName][$key];
$ret['value'] = $value;
}
else {
$ret['value'] = $this->settingsFile[$pluginKeyName]['settings'][$key]['default'];
}
if(isset($ret['type'])) {
switch($ret['type']) {
case 'boolean':
$ret['value'] = getBoolean($ret['value']);
break;
case 'number':
if (!isset($ret['step']) || (int)$ret['step'] == 1) {
$ret['value'] = (int)$ret['value'];
}
break;
default:
break;
}
}
if (isset($ret['callbacks']['get'])) {
$ret['value'] = $ret['callbacks']['get']($ret['value']);
}
$this->cache[$offset] = $ret;
return $ret;
}
private function updateValuesAsked($offset)
{
$pluginKeyName = $offset;
if (strpos($offset, '.')) {
$explode = explode('.', $offset, 2);
$pluginKeyName = $explode[0];
$key = $explode[1];
$this->valuesAsked = ['pluginKeyName' => $pluginKeyName, 'key' => $key];
}
else {
$this->valuesAsked = ['pluginKeyName' => $pluginKeyName, 'key' => null];
}
}
private function loadPlugin($offset)
{
$this->updateValuesAsked($offset);
$pluginKeyName = $this->valuesAsked['pluginKeyName'];
$key = $this->valuesAsked['key'];
if (!isset($this->settingsFile[$pluginKeyName])) {
if ($pluginKeyName === 'core') {
$settingsFilePath = SYSTEM . 'settings.php';
} else {
//$pluginSettings = Plugins::getPluginSettings($pluginKeyName);
$settings = Plugins::getAllPluginsSettings();
if (!isset($settings[$pluginKeyName])) {
warning("Setting $pluginKeyName does not exist or does not have settings defined.");
return;
}
$settingsFilePath = BASE . $settings[$pluginKeyName]['settingsFilename'];
}
if (!file_exists($settingsFilePath)) {
throw new \RuntimeException('Failed to load settings file for plugin: ' . $pluginKeyName);
}
$this->settingsFile[$pluginKeyName] = require $settingsFilePath;
}
}
public static function saveConfig($config, $filename, &$content = '')
{
$content = "<?php" . PHP_EOL;
unset($config['installed']);
$content .= "\$config['installed'] = true;" . PHP_EOL;
foreach ($config as $key => $value) {
$content .= "\$config['$key'] = ";
$content .= var_export($value, true);
$content .= ';' . PHP_EOL;
}
$success = file_put_contents($filename, $content);
// we saved new config.php, need to revalidate cache (only if opcache is enabled)
if (function_exists('opcache_invalidate')) {
opcache_invalidate($filename);
}
return $success;
}
public static function testDatabaseConnection($config): bool
{
$user = null;
$password = null;
$dns = [];
if( isset($config['database_name']) ) {
$dns[] = 'dbname=' . $config['database_name'];
}
if( isset($config['database_user']) ) {
$user = $config['database_user'];
}
if( isset($config['database_password']) ) {
$password = $config['database_password'];
}
if( isset($config['database_host']) ) {
$dns[] = 'host=' . $config['database_host'];
}
if( isset($config['database_port']) ) {
$dns[] = 'port=' . $config['database_port'];
}
try {
$connectionTest = new \PDO('mysql:' . implode(';', $dns), $user, $password);
$connectionTest->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
}
catch(\PDOException $error) {
error('MySQL connection failed. Settings has been reverted.');
error($error->getMessage());
return false;
}
return true;
}
public function getErrors() {
return $this->errors;
}
}

186
system/src/Spells.php Normal file
View File

@@ -0,0 +1,186 @@
<?php
/**
* Spells class
*
* @package MyAAC
* @author Gesior <jerzyskalski@wp.pl>
* @author Slawkens <slawkens@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC;
use MyAAC\Models\Spell;
class Spells {
private static $spellsList = null;
private static $lastError = '';
// 1 - attack, 2 - healing, 3 - summon, 4 - supply, 5 - support
public static function loadGroup($tGroup) {
switch ($tGroup) {
case "attack":
return 1;
case "healing":
return 2;
case "summon":
return 3;
case "supply":
return 4;
case "support":
return 5;
}
}
public static function loadFromXML($show = false) {
global $config;
try {
Spell::query()->delete();
} catch(\Exception $error) {}
if($show) {
echo '<h2>Reload spells.</h2>';
echo '<h2>All records deleted from table <b>' . TABLE_PREFIX . 'spells</b> in database.</h2>';
}
try {
self::$spellsList = new \OTS_SpellsList($config['data_path'].'spells/spells.xml');
}
catch(\Exception $e) {
self::$lastError = $e->getMessage();
return false;
}
//add conjure spells
$conjurelist = self::$spellsList->getConjuresList();
if($show) {
echo "<h3>Conjure:</h3>";
}
foreach($conjurelist as $spellname) {
$spell = self::$spellsList->getConjure($spellname);
$name = $spell->getName();
$words = $spell->getWords();
if(strpos($words, '#') !== false)
continue;
try {
Spell::create(array(
'name' => $name,
'words' => $words,
'type' => 2,
'mana' => $spell->getMana(),
'level' => $spell->getLevel(),
'maglevel' => $spell->getMagicLevel(),
'soul' => $spell->getSoul(),
'premium' => $spell->isPremium() ? 1 : 0,
'vocations' => json_encode($spell->getVocations()),
'conjure_count' => $spell->getConjureCount(),
'conjure_id' => $spell->getConjureId(),
'reagent' => $spell->getReagentId(),
'hidden' => $spell->isEnabled() ? 0 : 1
));
if($show) {
success('Added: ' . $name . '<br/>');
}
}
catch(\PDOException $error) {
if($show) {
warning('Error while adding spell (' . $name . '): ' . $error->getMessage());
}
}
}
// add instant spells
$instantlist = self::$spellsList->getInstantsList();
if($show) {
echo "<h3>Instant:</h3>";
}
foreach($instantlist as $spellname) {
$spell = self::$spellsList->getInstant($spellname);
$name = $spell->getName();
$words = $spell->getWords();
if(strpos($words, '#') !== false)
continue;
try {
Spell::create(array(
'name' => $name,
'words' => $words,
'type' => 1,
'mana' => $spell->getMana(),
'level' => $spell->getLevel(),
'maglevel' => $spell->getMagicLevel(),
'soul' => $spell->getSoul(),
'premium' => $spell->isPremium() ? 1 : 0,
'vocations' => json_encode($spell->getVocations()),
'conjure_count' => 0,
'hidden' => $spell->isEnabled() ? 0 : 1
));
if($show) {
success('Added: ' . $name . '<br/>');
}
}
catch(\PDOException $error) {
if($show) {
warning('Error while adding spell (' . $name . '): ' . $error->getMessage());
}
}
}
// add runes
$runeslist = self::$spellsList->getRunesList();
if($show) {
echo "<h3>Runes:</h3>";
}
foreach($runeslist as $spellname) {
$spell = self::$spellsList->getRune($spellname);
$name = $spell->getName() . ' Rune';
try {
Spell::create(array(
'name' => $name,
'words' => $spell->getWords(),
'type' => 3,
'mana' => $spell->getMana(),
'level' => $spell->getLevel(),
'maglevel' => $spell->getMagicLevel(),
'soul' => $spell->getSoul(),
'premium' => $spell->isPremium() ? 1 : 0,
'vocations' => json_encode($spell->getVocations()),
'conjure_count' => 0,
'item_id' => $spell->getID(),
'hidden' => $spell->isEnabled() ? 0 : 1
));
if($show) {
success('Added: ' . $name . '<br/>');
}
}
catch(\PDOException $error) {
if($show) {
warning('Error while adding spell (' . $name . '): ' . $error->getMessage());
}
}
}
return true;
}
public static function getSpellsList() {
return self::$spellsList;
}
public static function getLastError() {
return self::$lastError;
}
}

56
system/src/Timer.php Normal file
View File

@@ -0,0 +1,56 @@
<?php
/**
* Timer class
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC;
class Timer
{
private $start = 0;
private $stop = 0;
private $elapsed = 0;
function __construct($start = true) {
if($start) $this->start();
}
function start() {
$this->start = $this->_gettime();
}
function stop()
{
$this->stop = $this->_gettime();
$this->elapsed = $this->_compute();
}
function elapsed()
{
if(!$this->elapsed)
$this->stop();
return $this->elapsed;
}
function reset()
{
$this->start = 0;
$this->stop = 0;
$this->elapsed = 0;
}
private function _gettime() {
return microtime(true);
}
private function _compute() {
return $this->stop - $this->start;
}
}
?>

129
system/src/Towns.php Normal file
View File

@@ -0,0 +1,129 @@
<?php
/**
* Project: MyAAC
* Automatic Account Creator for Open Tibia Servers
*
* This is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @copyright 2020 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC;
use MyAAC\Models\Town;
class Towns
{
/**
* @var string
*/
private static $filename = CACHE . 'persistent/' . 'towns.php';
/**
* Determine towns
*
* @return array
*/
public static function determine()
{
global $db;
if($db->hasTable('towns')) {
return self::getFromDatabase();
}
return self::getFromOTBM();
}
/**
* Load cached towns file
*/
public static function load()
{
$towns = config('towns');
if (file_exists(self::$filename)) {
$towns = require self::$filename;
}
config(['towns', $towns]);
}
/**
* Save into cache file
*
* @return bool
*/
public static function save()
{
$towns = self::determine();
if (count($towns) > 0) {
file_put_contents(self::$filename, '<?php return ' . var_export($towns, true) . ';', LOCK_EX);
return true;
}
return false;
}
/**
* Load from OTBM map file
*
* @return array
*/
public static function getFromOTBM()
{
$mapName = configLua('mapName');
if (!isset($mapName)) {
$mapName = configLua('map');
$mapFile = config('server_path') . $mapName;
}
if (strpos($mapName, '.otbm') === false) {
$mapName .= '.otbm';
}
if (!isset($mapFile)) {
$mapFile = config('data_path') . 'world/' . $mapName;
}
if (strpos($mapFile, '.gz') !== false) {
$mapFile = str_replace('.gz', '', $mapFile);
}
$towns = [];
if (file_exists($mapFile)) {
ini_set('memory_limit', '-1');
$townsReader = new TownsReader($mapFile);
$townsReader->load();
$towns = $townsReader->get();
}
return $towns;
}
/**
* Load from database
*
* @return array
*/
public static function getFromDatabase()
{
return Town::pluck('name', 'id')->toArray();
}
}

View File

@@ -0,0 +1,84 @@
<?php
/*
This file is part of OTSCMS (http://www.otscms.com/) project.
Copyright (C) 2005 - 2007 Wrzasq (wrzasq@gmail.com)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
namespace MyAAC;
/*
This code bases on original OTServ code for .otbm files - file iomapotbm.cpp rev.2141
*/
class TownsReader
{
// node bytes
const ESCAPE_CHAR = 0xFD;
const NODE_START = 0xFE;
// map node types
const OTBM_TOWN = 13;
// file handler
protected $file;
// towns
private $towns = [];
// loads map .otbm file
public function __construct($file)
{
// opens file for reading
$this->file = fopen($file, 'rb');
}
public function load()
{
// checks if file is opened correctly
if ($this->file) {
// skips version
fseek($this->file, 4);
// reads nodes chain
while (!feof($this->file)) {
// reads byte
switch (ord(fgetc($this->file))) {
// maybe a town node
case self::NODE_START:
// reads node type
if (ord(fgetc($this->file)) == self::OTBM_TOWN) {
$id = unpack('L', fread($this->file, 4));
$length = unpack('S', fread($this->file, 2));
// reads town name
$this->towns[$id[1]] = fread($this->file, $length[1]);
}
break;
// escape next character - it might be NODE_START character which is in fact not
case self::ESCAPE_CHAR:
fgetc($this->file);
break;
}
}
}
}
public function get() {
return $this->towns;
}
}

View File

@@ -0,0 +1,123 @@
<?php
/**
* Usage Statistics
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC;
use MyAAC\Cache\Cache;
class UsageStatistics {
private static $report_url = 'https://my-aac.org/report_usage.php';
public static function report() {
$data = json_encode(self::getStats());
$options = array(
'http' => array(
'header' => 'Content-type: application/json' . "\r\n"
. 'Content-Length: ' . strlen($data) . "\r\n",
'content' => $data
)
);
$context = stream_context_create($options);
$result = file_get_contents(self::$report_url, false, $context);
return $result !== false;
}
public static function getStats() {
global $config, $db;
$ret = array();
$ret['unique_id'] = hash('sha1', $config['server_path']);
$ret['server_os'] = php_uname('s') . ' ' . php_uname('r');
$ret['myaac_version'] = MYAAC_VERSION;
$ret['myaac_db_version'] = DATABASE_VERSION;
if($db->hasTable('server_config')) {
$query = $db->query('SELECT `value` FROM `server_config` WHERE `config` = ' . $db->quote('database_version'));
if($query->rowCount() == 1) {
$query = $query->fetch();
$ret['otserv_db_version'] = $query['value'];
}
}
$ret['client_version'] = $config['client'];
$ret['php_version'] = phpversion();
$query = $db->query('SELECT VERSION() as `version`;');
if($query->rowCount() == 1) {
$query = $query->fetch();
$ret['mysql_version'] = $query['version'];
}
$query = $db->query('SELECT SUM(ROUND(((DATA_LENGTH + INDEX_LENGTH) / 1024 ), 0)) AS "size"
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = "' . $config['database_name'] . '";');
if($query->rowCount() == 1) {
$query = $query->fetch();
$ret['database_size'] = $query['size'];
}
$ret['views_counter'] = getDatabaseConfig('views_counter');
$query = $db->query('SELECT COUNT(`id`) as `size` FROM `accounts`;');
if($query->rowCount() == 1) {
$query = $query->fetch();
$ret['accounts_size'] = $query['size'];
}
$query = $db->query('SELECT COUNT(`id`) as `size` FROM `players`;');
if($query->rowCount() == 1) {
$query = $query->fetch();
$ret['players_size'] = $query['size'];
}
$query = $db->query('SELECT COUNT(`id`) as `size` FROM `' . TABLE_PREFIX . 'monsters`;');
if($query->rowCount() == 1) {
$query = $query->fetch();
$ret['monsters_size'] = $query['size'];
}
$query = $db->query('SELECT COUNT(`id`) as `size` FROM `' . TABLE_PREFIX . 'spells`;');
if($query->rowCount() == 1) {
$query = $query->fetch();
$ret['spells_size'] = $query['size'];
}
$ret['locales'] = get_locales();
$ret['plugins'] = array();
foreach(get_plugins() as $plugin) {
$string = file_get_contents(BASE . 'plugins/' . $plugin . '.json');
$plugin_info = json_decode($string, true);
if($plugin_info != false) {
if(isset($plugin_info['version'])) {
$ret['plugins'][$plugin] = $plugin_info['version'];
}
}
}
$ret['templates'] = get_templates();
$ret['date_timezone'] = setting('core.date_timezone');
$ret['backward_support'] = setting('core.backward_support');
$cache_engine = strtolower($config['cache_engine']);
if($cache_engine == 'auto') {
$cache_engine = Cache::detect();
}
$ret['cache_engine'] = $cache_engine;
return $ret;
}
}

442
system/src/Validator.php Normal file
View File

@@ -0,0 +1,442 @@
<?php
/**
* Validator class
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC;
use MyAAC\Models\Monster;
use MyAAC\Models\Spell;
class Validator
{
private static $lastError = '';
public static function number($number) {
if(!preg_match("/^([0-9]+)$/", $number)) {
self::$lastError = 'Invalid number format.';
return false;
}
return true;
}
/**
* Validate account id
* Id lenght must be 6-10 chars
*
* @param string $id Account id to check
* @return bool Is account name valid?
*/
public static function accountId($id)
{
if(!isset($id[0]))
{
self::$lastError = 'Please enter your account number!';
return false;
}
if(!Validator::number($id)) {
self::$lastError = 'Invalid account number format. Please use only numbers 0-9.';
return false;
}
$length = strlen($id);
if($length < 6)
{
self::$lastError = 'Account is too short (min. 6 chars).';
return false;
}
if($length > 10)
{
self::$lastError = 'Account is too long (max. 10 chars).';
return false;
}
return true;
}
/**
* Validate account name
* Name lenght must be 3-32 chars
*
* @param string $name Account name to check
* @return bool Is account name valid?
*/
public static function accountName($name)
{
if(!isset($name[0]))
{
self::$lastError = 'Please enter your account name!';
return false;
}
$length = strlen($name);
if($length < 3)
{
self::$lastError = 'Account name is too short (min. 3 chars).';
return false;
}
if($length > 32)
{
self::$lastError = 'Account name is too long (max. 32 chars).';
return false;
}
if(preg_match('/ {2,}/', $name))
{
self::$lastError = 'Invalid account name format. Use only A-Z and numbers 0-9 and no double spaces.';
return false;
}
if(!preg_match("/^[A-Z0-9]+$/i", $name))
{
self::$lastError = 'Invalid account name format. Use only A-Z and numbers 0-9.';
return false;
}
return true;
}
/**
* Advanced mail validator
*
* @param string $email
* @return bool Is email valid?
*/
public static function email($email) {
if(empty($email)) {
self::$lastError = 'Please enter your new email address.';
return false;
}
if(strlen($email) > 255) {
self::$lastError = 'E-mail is too long (max. 255 chars).';
return false;
}
if(setting('core.account_mail_block_plus_sign')) {
$explode = explode('@', $email);
if(isset($explode[0]) && (strpos($explode[0],'+') !== false)) {
self::$lastError = 'Please do not use plus (+) sign in your e-mail.';
return false;
}
}
if(!preg_match('/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9_](?:[A-z0-9_\-](?!\.)){0,61}[a-zA-Z0-9_]?\.)+[a-zA-Z0-9_](?:[a-zA-Z0-9_\-](?!$)){0,61}[a-zA-Z0-9_]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/', $email)) {
self::$lastError = 'Invalid e-mail format.';
return false;
}
return true;
}
/**
* Validate account password
* Name lenght must be 3-32 chars
*
* @param string $name Account name to check
* @return bool Is account name valid?
*/
public static function password($password)
{
if (!isset($password[0])) {
self::$lastError = 'Please enter the password.';
return false;
}
if (strlen($password) < 8 || strlen($password) > 29) {
self::$lastError = 'The password must have at least 8 and maximum 29 letters!';
return false;
}
if(!preg_match('/[a-zA-Z]/', $password)) {
self::$lastError = 'The password must contain at least one letter A-Z or a-z!';
return false;
}
if(!preg_match('/[0-9]/', $password)) {
self::$lastError = 'The password must contain at least one number!';
return false;
}
return true;
}
/**
* Validate character name.
* Name lenght must be 3-25 chars
*
* @param string $name Name to check
* @return bool Is name valid?
*/
public static function characterName($name)
{
if(!isset($name[0]))
{
self::$lastError = 'Please enter character name.';
return false;
}
// installer doesn't know config.php yet
// that's why we need to ignore the nulls
if(defined('MYAAC_INSTALL')) {
$minLength = 4;
$maxLength = 21;
}
else {
$minLength = setting('core.create_character_name_min_length');
$maxLength = setting('core.create_character_name_max_length');
}
$length = strlen($name);
if($length < $minLength)
{
self::$lastError = "Character name is too short. Min. length <b>$minLength</b> characters.";
return false;
}
if($length > $maxLength)
{
self::$lastError = "Character name is too long. Max. length <b>$maxLength</b> characters.";
return false;
}
if(strspn($name, "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM- [ ] '") != $length)
{
self::$lastError = "Invalid name format. Use only A-Z, spaces and '.";
return false;
}
if(preg_match('/ {2,}/', $name))
{
self::$lastError = 'Invalid character name format. Use only A-Z and no double spaces.';
return false;
}
if(!preg_match("/[A-z ']/", $name))
{
self::$lastError = "Invalid name format. Use only A-Z, spaces and '.";
return false;
}
return true;
}
/**
* Validate new character name.
* Name lenght must be 3-25 chars
*
* @param string $name Name to check
* @return bool Is name valid?
*/
public static function newCharacterName($name)
{
global $db, $config;
$name_lower = strtolower($name);
$first_words_blocked = array_merge(["'", '-'], setting('core.create_character_name_blocked_prefix'));
foreach($first_words_blocked as $word) {
if($word == substr($name_lower, 0, strlen($word))) {
self::$lastError = 'Your name contains blocked words.';
return false;
}
}
if(substr($name_lower, -1) == "'" || substr($name_lower, -1) == "-") {
self::$lastError = 'Your name contains illegal characters.';
return false;
}
if(substr($name_lower, 1, 1) == ' ') {
self::$lastError = 'Your name contains illegal space.';
return false;
}
if(substr($name_lower, -2, 1) == " ") {
self::$lastError = 'Your name contains illegal space.';
return false;
}
if(preg_match('/ {2,}/', $name)) {
self::$lastError = 'Invalid character name format. Use only A-Z and numbers 0-9 and no double spaces.';
return false;
}
if(strtolower($config['lua']['serverName']) == $name_lower) {
self::$lastError = 'Your name cannot be same as server name.';
return false;
}
$names_blocked = setting('core.create_character_name_blocked_names');
foreach($names_blocked as $word) {
if($word == $name_lower) {
self::$lastError = 'Your name contains blocked words.';
return false;
}
}
$words_blocked = array_merge(['--', "''","' ", " '", '- ', ' -', "-'", "'-"], setting('core.create_character_name_blocked_words'));
foreach($words_blocked as $word) {
if(!(strpos($name_lower, $word) === false)) {
self::$lastError = 'Your name contains illegal words.';
return false;
}
}
$name_length = strlen($name_lower);
for($i = 0; $i < $name_length; $i++)
{
if(isset($name_lower[$i]) && isset($name_lower[$i + 1]) && $name_lower[$i] == $name_lower[$i + 1] && isset($name_lower[$i + 2]) && $name_lower[$i] == $name_lower[$i + 2]) {
self::$lastError = 'Your name is invalid.';
return false;
}
}
// check if was namelocked previously
if($db->hasTable('player_namelocks') && $db->hasColumn('player_namelocks', 'name')) {
$namelock = $db->query('SELECT `player_id` FROM `player_namelocks` WHERE `name` = ' . $db->quote($name));
if($namelock->rowCount() > 0) {
self::$lastError = 'Character with this name has been namelocked.';
return false;
}
}
$monstersCheck = setting('core.create_character_name_monsters_check');
if ($monstersCheck) {
if (Monster::where('name', 'like', $name_lower)->exists()) {
self::$lastError = 'Your name cannot contains monster name.';
return false;
}
}
$spellsCheck = setting('core.create_character_name_spells_check');
if ($spellsCheck) {
if (Spell::where('name', 'like', $name_lower)->exists()) {
self::$lastError = 'Your name cannot contains spell name.';
return false;
}
if (Spell::where('words', $name_lower)->exists()) {
self::$lastError = 'Your name cannot contains spell name.';
return false;
}
}
$npcCheck = setting('core.create_character_name_npc_check');
if ($npcCheck) {
NPCs::load();
if(NPCs::$npcs) {
foreach (NPCs::$npcs as $npc) {
if(strpos($name_lower, $npc) !== false) {
self::$lastError = 'Your name cannot contains NPC name.';
return false;
}
}
}
}
return true;
}
/**
* Validate guild name
* Name lenght must be 3-32 chars
*
* @param string $name Name to check
* @return bool Is name valid?
*/
public static function guildName($name)
{
if(empty($name)) {
self::$lastError = 'Please enter guild name.';
return false;
}
if(strspn($name, "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0123456789- ") != strlen($name)) {
self::$lastError = 'Invalid guild name format.';
return false;
}
if(!preg_match("/[A-z ]{3,32}/", $name)) {
self::$lastError = 'Invalid guild name format.';
return false;
}
return true;
}
/**
* Validate guild nick
* Nick lenght must be 3-40 chars
*
* @param string $name Name to check
* @return bool Is name valid?
*/
public static function guildNick($name)
{
if(empty($name)) {
self::$lastError = 'Please enter guild nick.';
return false;
}
if(strspn($name, "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0123456789- ") != strlen($name)) {
self::$lastError = 'Invalid guild nick format.';
return false;
}
if(!preg_match("/[A-z ]{3,40}/", $name)) {
self::$lastError = 'Invalid guild nick format.';
return false;
}
return true;
}
/**
* Validate rank name
* Rank lenght must be 1-32 chars
*
* @param string $name Name to check
* @return bool Is name valid?
*/
public static function rankName($name)
{
if(empty($name)) {
self::$lastError = 'Please enter rank name.';
return false;
}
if(strspn($name, "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0123456789-[ ] ") != strlen($name)) {
self::$lastError = 'Invalid rank name. Please use only a-Z, 0-9 and spaces.';
return false;
}
if(!preg_match("/[A-z ]{1,32}/", $name)) {
self::$lastError = 'Invalid rank name. Please use only a-Z, 0-9 and spaces.';
return false;
}
return true;
}
/**
* Simple string validator, checks if string contains valid characters
*
* @param string $str String to validate
* @param boolean $numbers Numbers should be allowed?
*/
public static function str($str, $numbers = false) {
return preg_match('/^[a-z0-9\ ]*$/i', $str);
}
public static function getLastError() {
return self::$lastError;
}
}

126
system/src/Visitors.php Normal file
View File

@@ -0,0 +1,126 @@
<?php
/**
* Visitors class
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC;
use MyAAC\Cache\Cache;
use MyAAC\Models\Visitor;
class Visitors
{
private $sessionTime; // time session will live
private $data; // cached data
private $cacheEnabled;
private $cache;
public function __construct($sessionTime = 10)
{
$this->cache = Cache::getInstance();
$this->cacheEnabled = $this->cache->enabled();
if($this->cacheEnabled)
{
$tmp = '';
if($this->cache->fetch('visitors', $tmp))
$this->data = unserialize($tmp);
else
$this->data = array();
}
$this->sessionTime = $sessionTime;
$this->cleanVisitors();
$ip = $_SERVER['REMOTE_ADDR'];
$userAgentShortened = substr($_SERVER['HTTP_USER_AGENT'] ?? 'unknown', 0, 255);
if($this->visitorExists($ip))
$this->updateVisitor($ip, $_SERVER['REQUEST_URI'], $userAgentShortened);
else
$this->addVisitor($ip, $_SERVER['REQUEST_URI'], $userAgentShortened);
}
public function __destruct()
{
if($this->cacheEnabled)
$this->cache->set('visitors', serialize($this->data), 120);
}
public function visitorExists($ip)
{
if($this->cacheEnabled) {
return isset($this->data[$ip]);
}
return Visitor::where('ip', $ip)->exists();
}
private function cleanVisitors()
{
if($this->cacheEnabled)
{
$timeNow = time();
foreach($this->data as $ip => $details)
{
if($timeNow - (int)$details['lastvisit'] > $this->sessionTime * 60)
unset($this->data[$ip]);
}
return;
}
Visitor::where('lastvisit', '<', (time() - $this->sessionTime * 60))->delete();
}
private function updateVisitor($ip, $page, $userAgent)
{
if($this->cacheEnabled) {
$this->data[$ip] = array('page' => $page, 'lastvisit' => time(), 'user_agent' => $userAgent);
return;
}
Visitor::where('ip', $ip)->update(['lastvisit' => time(), 'page' => $page, 'user_agent' => $userAgent]);
}
private function addVisitor($ip, $page, $userAgent)
{
if($this->cacheEnabled) {
$this->data[$ip] = array('page' => $page, 'lastvisit' => time(), 'user_agent' => $userAgent);
return;
}
Visitor::create(['ip' => $ip, 'lastvisit' => time(), 'page' => $page, 'user_agent' => $userAgent]);
}
public function getVisitors()
{
if($this->cacheEnabled) {
foreach($this->data as $ip => &$details)
$details['ip'] = $ip;
return $this->data;
}
return Visitor::orderByDesc('lastvisit')->get()->toArray();
}
public function getAmountVisitors()
{
if($this->cacheEnabled) {
return count($this->data);
}
return Visitor::count();
}
public function show() {
echo $this->getAmountVisitors();
}
}
?>

87
system/src/Weapons.php Normal file
View File

@@ -0,0 +1,87 @@
<?php
/**
* Weapons class
*
* @package MyAAC
* @author Gesior <jerzyskalski@wp.pl>
* @author Slawkens <slawkens@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC;
use MyAAC\Models\Weapon;
defined('MYAAC') or die('Direct access not allowed!');
class Weapons {
private static $error = '';
public static function loadFromXML($show = false)
{
global $config;
try {
Weapon::query()->delete();
} catch (\PDOException $error) {
}
$file_path = $config['data_path'] . 'weapons/weapons.xml';
if (!file_exists($file_path)) {
self::$error = 'Cannot load file ' . $file_path;
return false;
}
$xml = new \DOMDocument;
$xml->load($file_path);
foreach ($xml->getElementsByTagName('wand') as $weapon) {
self::parseNode($weapon, $show);
}
foreach ($xml->getElementsByTagName('melee') as $weapon) {
self::parseNode($weapon, $show);
}
foreach ($xml->getElementsByTagName('distance') as $weapon) {
self::parseNode($weapon, $show);
}
return true;
}
public static function parseNode($node, $show = false) {
global $config;
$id = (int)$node->getAttribute('id');
$vocations_ids = array_flip($config['vocations']);
$level = (int)$node->getAttribute('level');
$maglevel = (int)$node->getAttribute('maglevel');
$vocations = array();
foreach($node->getElementsByTagName('vocation') as $vocation) {
$show = $vocation->getAttribute('showInDescription');
if(!empty($vocation->getAttribute('id')))
$voc_id = $vocation->getAttribute('id');
else {
$voc_id = $vocations_ids[$vocation->getAttribute('name')];
}
$vocations[$voc_id] = strlen($show) == 0 || $show != '0';
}
if(Weapon::find($id)) {
if($show) {
warning('Duplicated weapon with id: ' . $id);
}
}
else {
Weapon::create([
'id' => $id, 'level' => $level, 'maglevel' => $maglevel, 'vocations' => json_encode($vocations)
]);
}
}
public static function getError() {
return self::$error;
}
}

103
system/src/global.php Normal file
View File

@@ -0,0 +1,103 @@
<?php
$i = 0;
define('HOOK_STARTUP', ++$i);
define('HOOK_BEFORE_PAGE', ++$i);
define('HOOK_AFTER_PAGE', ++$i);
define('HOOK_FINISH', ++$i);
define('HOOK_TIBIACOM_ARTICLE', ++$i);
define('HOOK_TIBIACOM_BORDER_3', ++$i);
define('HOOK_CHARACTERS_BEFORE_INFORMATIONS', ++$i);
define('HOOK_CHARACTERS_AFTER_INFORMATIONS', ++$i);
define('HOOK_CHARACTERS_BEFORE_SKILLS', ++$i);
define('HOOK_CHARACTERS_AFTER_SKILLS', ++$i);
define('HOOK_CHARACTERS_AFTER_QUESTS', ++$i);
define('HOOK_CHARACTERS_AFTER_EQUIPMENT', ++$i);
define('HOOK_CHARACTERS_BEFORE_DEATHS', ++$i);
define('HOOK_CHARACTERS_BEFORE_SIGNATURE', ++$i);
define('HOOK_CHARACTERS_AFTER_SIGNATURE', ++$i);
define('HOOK_CHARACTERS_AFTER_ACCOUNT', ++$i);
define('HOOK_CHARACTERS_AFTER_CHARACTERS', ++$i);
define('HOOK_LOGIN', ++$i);
define('HOOK_LOGIN_ATTEMPT', ++$i);
define('HOOK_LOGOUT', ++$i);
define('HOOK_ACCOUNT_CHANGE_PASSWORD_POST', ++$i);
define('HOOK_ACCOUNT_CREATE_BEFORE_FORM', ++$i);
define('HOOK_ACCOUNT_CREATE_BEFORE_BOXES', ++$i);
define('HOOK_ACCOUNT_CREATE_BETWEEN_BOXES_1', ++$i);
define('HOOK_ACCOUNT_CREATE_BETWEEN_BOXES_2', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_BOXES', ++$i);
define('HOOK_ACCOUNT_CREATE_BEFORE_ACCOUNT', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_ACCOUNT', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_EMAIL', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_COUNTRY', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_PASSWORD', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_PASSWORDS', ++$i);
define('HOOK_ACCOUNT_CREATE_BEFORE_CHARACTER_NAME', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_CHARACTER_NAME', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_SEX', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_VOCATION', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_TOWNS', ++$i);
define('HOOK_ACCOUNT_CREATE_BEFORE_SUBMIT_BUTTON', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_FORM', ++$i);
define('HOOK_ACCOUNT_CREATE_POST', ++$i);
define('HOOK_ACCOUNT_LOGIN_BEFORE_PAGE', ++$i);
define('HOOK_ACCOUNT_LOGIN_BEFORE_ACCOUNT', ++$i);
define('HOOK_ACCOUNT_LOGIN_AFTER_ACCOUNT', ++$i);
define('HOOK_ACCOUNT_LOGIN_BEFORE_PASSWORD', ++$i);
define('HOOK_ACCOUNT_LOGIN_AFTER_PASSWORD', ++$i);
define('HOOK_ACCOUNT_LOGIN_AFTER_REMEMBER_ME', ++$i);
define('HOOK_ACCOUNT_LOGIN_AFTER_PAGE', ++$i);
define('HOOK_ACCOUNT_LOGIN_POST', ++$i);
define('HOOK_ACCOUNT_CREATE_CHARACTER_AFTER', ++$i);
define('HOOK_ADMIN_HEAD_END', ++$i);
define('HOOK_ADMIN_HEAD_START', ++$i);
define('HOOK_ADMIN_BODY_START', ++$i);
define('HOOK_ADMIN_BODY_END', ++$i);
define('HOOK_ADMIN_BEFORE_PAGE', ++$i);
define('HOOK_ADMIN_MENU', ++$i);
define('HOOK_ADMIN_LOGIN_AFTER_ACCOUNT', ++$i);
define('HOOK_ADMIN_LOGIN_AFTER_PASSWORD', ++$i);
define('HOOK_ADMIN_LOGIN_AFTER_SIGN_IN', ++$i);
define('HOOK_ADMIN_ACCOUNTS_SAVE_POST', ++$i);
define('HOOK_ADMIN_SETTINGS_BEFORE_SAVE', ++$i);
define('HOOK_CRONJOB', ++$i);
define('HOOK_EMAIL_CONFIRMED', ++$i);
define('HOOK_GUILDS_BEFORE_GUILD_HEADER', ++$i);
define('HOOK_GUILDS_AFTER_GUILD_HEADER', ++$i);
define('HOOK_GUILDS_AFTER_GUILD_INFORMATION', ++$i);
define('HOOK_GUILDS_AFTER_GUILD_MEMBERS', ++$i);
define('HOOK_GUILDS_AFTER_INVITED_CHARACTERS', ++$i);
define('HOOK_TWIG', ++$i);
const HOOK_FIRST = HOOK_STARTUP;
define('HOOK_LAST', $i);
function is_sub_dir($path = NULL, $parent_folder = BASE): bool|string
{
//Get directory path minus last folder
$dir = dirname($path);
$folder = substr($path, strlen($dir));
//Check the base dir is valid
$dir = realpath($dir);
//Only allow valid filename characters
$folder = preg_replace('/[^a-z0-9\.\-_]/i', '', $folder);
//If this is a bad path or a bad end folder name
if( !$dir OR !$folder OR $folder === '.') {
return false;
}
//Rebuild path
$path = $dir. '/' . $folder;
//If this path is higher than the parent folder
if( strcasecmp($path, $parent_folder) > 0 ) {
return $path;
}
return false;
}