Merge branch 'develop' into feature/refactor-account-lost

This commit is contained in:
slawkens
2026-01-01 08:16:03 +01:00
26 changed files with 271 additions and 2991 deletions

View File

@@ -22,7 +22,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
php-versions: [ '8.1', '8.2', '8.3', '8.4' ] php-versions: [ '8.1', '8.2', '8.3', '8.4', '8.5' ]
ots: ['tfs-1.4', 'canary-3.1.2'] # TODO: add 'tfs-master' (actually doesn't work cause AAC doesn't support reading .env configuration) ots: ['tfs-1.4', 'canary-3.1.2'] # TODO: add 'tfs-master' (actually doesn't work cause AAC doesn't support reading .env configuration)
name: Cypress (PHP ${{ matrix.php-versions }}, ${{ matrix.ots }}) name: Cypress (PHP ${{ matrix.php-versions }}, ${{ matrix.ots }})
steps: steps:

View File

@@ -1,5 +1,18 @@
# Changelog # Changelog
## [1.8.6 - 14.12.2025]
### Added
* Added hook for adding custom rules to validate new character name (https://github.com/slawkens/myaac/commit/8e6749c59984631288e8e9803819b2f0ff389761)
### Fixed
* Highscores: Fix ordering by different skills (Adjust order by desc: skill_tries, manaspent, experience) - More exact results (https://github.com/slawkens/myaac/commit/c86257e6dacbad773aa09c0958eeaa106a967f2d)
* Fix exception shown on first install, when there is no vendor - Before it displayed 500 white page, now it display the exception (https://github.com/slawkens/myaac/commit/18a1178e4b93607a350259679e0366cb83fb4126)
* Fix typo $up -> $down, in migration nr 7, was failing due that (https://github.com/slawkens/myaac/commit/fd74f01291d0e9cdb92ee1b95021c9d7b591ad7c)
### Changed
* Ini set html_errors = 0, to show html code in exceptions (https://github.com/slawkens/myaac/commit/9ed06782e67772826d927ad847a077b99df5060d)
## [1.8.5 - 21.11.2025] ## [1.8.5 - 21.11.2025]
### Added ### Added

View File

@@ -23,6 +23,7 @@ if (!hasFlag(FLAG_CONTENT_MENUS) && !superAdmin()) {
} }
$pluginThemes = Plugins::getThemes(); $pluginThemes = Plugins::getThemes();
$groups = new OTS_Groups_List();
if (isset($_POST['template'])) { if (isset($_POST['template'])) {
$template = $_POST['template']; $template = $_POST['template'];
@@ -32,6 +33,8 @@ if (isset($_POST['template'])) {
$post_menu_link = $_POST['menu_link'] ?? []; $post_menu_link = $_POST['menu_link'] ?? [];
$post_menu_blank = $_POST['menu_blank'] ?? []; $post_menu_blank = $_POST['menu_blank'] ?? [];
$post_menu_color = $_POST['menu_color'] ?? []; $post_menu_color = $_POST['menu_color'] ?? [];
$post_menu_access = $_POST['menu_access'] ?? [];
if (count($post_menu) != count($post_menu_link)) { if (count($post_menu) != count($post_menu_link)) {
echo 'Menu count is not equal menu links. Something went wrong when sending form.'; echo 'Menu count is not equal menu links. Something went wrong when sending form.';
return; return;
@@ -50,6 +53,7 @@ if (isset($_POST['template'])) {
'link' => $post_menu_link[$category][$i], 'link' => $post_menu_link[$category][$i],
'blank' => $post_menu_blank[$category][$i] == 'on' ? 1 : 0, 'blank' => $post_menu_blank[$category][$i] == 'on' ? 1 : 0,
'color' => str_replace('#', '', $post_menu_color[$category][$i]), 'color' => str_replace('#', '', $post_menu_color[$category][$i]),
'access' => $post_menu_access[$category][$i],
'category' => $category, 'category' => $category,
'ordering' => $i 'ordering' => $i
]); ]);
@@ -122,7 +126,7 @@ if (isset($_POST['template'])) {
?> ?>
<?php <?php
$menus = Menu::query() $menus = Menu::query()
->select('name', 'link', 'blank', 'color', 'category', 'ordering') ->select('name', 'link', 'access', 'blank', 'color', 'category', 'ordering')
->where('enabled', 1) ->where('enabled', 1)
->where('template', $template) ->where('template', $template)
->orderBy('ordering') ->orderBy('ordering')
@@ -151,11 +155,34 @@ if (isset($_POST['template'])) {
foreach ($menus[$id] as $menu): foreach ($menus[$id] as $menu):
$color = (empty($menu['color']) ? ($cat['default_links_color'] ?? ($config['menu_default_links_color'] ?? ($config['menu_default_color'] ?? '#ffffff'))) : '#' . $menu['color']); $color = (empty($menu['color']) ? ($cat['default_links_color'] ?? ($config['menu_default_links_color'] ?? ($config['menu_default_color'] ?? '#ffffff'))) : '#' . $menu['color']);
?> ?>
<li class="ui-state-default" id="list-<?php echo $id ?>-<?php echo $i ?>"><label>Name:</label> <input type="text" name="menu[<?php echo $id ?>][]" value="<?php echo escapeHtml($menu['name']); ?>"/> <li class="ui-state-default" id="list-<?php echo $id ?>-<?php echo $i ?>">
<label>Link:</label> <input type="text" name="menu_link[<?php echo $id ?>][]" value="<?php echo $menu['link'] ?>"/> <label class="label_menu_name">Name: <input type="text" name="menu[<?php echo $id ?>][]" class="form-control menu-name" value="<?php echo escapeHtml($menu['name']); ?>"/>
<input type="hidden" name="menu_blank[<?php echo $id ?>][]" value="0"/> </label>
<label><input class="blank-checkbox" type="checkbox" <?php echo($menu['blank'] == 1 ? 'checked' : '') ?>/><span title="Open in New Window">New Window</span></label>
<input class="color-picker" type="text" name="menu_color[<?php echo $id ?>][]" value="<?php echo $color; ?>"/> <label class="label_menu_link">Link: <input type="text" name="menu_link[<?= $id ?>][]" class="form-control menu-link" value="<?php echo $menu['link'] ?>"/>
</label>
<br/>
<div class="menu-options-row">
<label>Access:
<select name="menu_access[<?= $id ?>][]" class="form-control menu-access">
<option value="0" <?= ($menu['access'] == 0 ? 'selected' : ''); ?>>Guest*</option>
<?php foreach ($groups->getGroups() as $group): ?>
<option value="<?= $group->getId(); ?>" <?= ($menu['access'] == $group->getId() ? 'selected' : ''); ?>><?= ucfirst($group->getName()); ?></option>
<?php endforeach; ?>
</select>
</label>
<label>Color: <input class="menu-color" type="color" name="menu_color[<?php echo $id ?>][]" value="<?php echo $color; ?>"/>
</label>
<input type="hidden" name="menu_blank[<?php echo $id ?>][]" class="menu-blank" value="0"/>
<label><input type="checkbox" class="menu-blank-checkbox" <?php echo($menu['blank'] == 1 ? 'checked' : '') ?>/><span title="Open in New Window">New Window</span></label>
</div>
<a class="remove-button" id="remove-button-<?php echo $id ?>-<?php echo $i ?>"><i class="fas fa-trash"></a></i></li> <a class="remove-button" id="remove-button-<?php echo $id ?>-<?php echo $i ?>"><i class="fas fa-trash"></a></i></li>
<?php $i++; $last_id[$id] = $i; <?php $i++; $last_id[$id] = $i;
endforeach; endforeach;

View File

@@ -27,7 +27,7 @@ if (version_compare(phpversion(), '8.1', '<')) die('PHP version 8.1 or higher is
const MYAAC = true; const MYAAC = true;
const MYAAC_VERSION = '2.0-dev'; const MYAAC_VERSION = '2.0-dev';
const DATABASE_VERSION = 47; const DATABASE_VERSION = 48;
const TABLE_PREFIX = 'myaac_'; const TABLE_PREFIX = 'myaac_';
define('START_TIME', microtime(true)); define('START_TIME', microtime(true));
define('MYAAC_OS', stripos(PHP_OS, 'WIN') === 0 ? 'WINDOWS' : (strtoupper(PHP_OS) === 'DARWIN' ? 'MAC' : 'LINUX')); define('MYAAC_OS', stripos(PHP_OS, 'WIN') === 0 ? 'WINDOWS' : (strtoupper(PHP_OS) === 'DARWIN' ? 'MAC' : 'LINUX'));
@@ -148,16 +148,17 @@ if(!IS_CLI) {
/** @var array $config */ /** @var array $config */
ini_set('log_errors', 1); ini_set('log_errors', 1);
if(@$config['env'] === 'dev' || defined('MYAAC_INSTALL')) { if(isset($config['env']) && $config['env'] !== 'dev' && !defined('MYAAC_INSTALL')) {
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
}
else {
ini_set('display_errors', 0); ini_set('display_errors', 0);
ini_set('display_startup_errors', 0); ini_set('display_startup_errors', 0);
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT); error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);
} }
else {
ini_set('html_errors', 0);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
}
$autoloadFile = VENDOR . 'autoload.php'; $autoloadFile = VENDOR . 'autoload.php';
if (!is_file($autoloadFile)) { if (!is_file($autoloadFile)) {

View File

@@ -102,6 +102,7 @@ CREATE TABLE IF NOT EXISTS `myaac_menu`
`template` varchar(255) NOT NULL, `template` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL, `name` varchar(255) NOT NULL,
`link` varchar(255) NOT NULL, `link` varchar(255) NOT NULL,
`access` tinyint NOT NULL DEFAULT 0,
`blank` tinyint NOT NULL DEFAULT 0, `blank` tinyint NOT NULL DEFAULT 0,
`color` varchar(6) NOT NULL DEFAULT '', `color` varchar(6) NOT NULL DEFAULT '',
`category` int NOT NULL DEFAULT 1, `category` int NOT NULL DEFAULT 1,

View File

@@ -26,6 +26,7 @@ use MyAAC\Cache\Cache;
*/ */
class OTS_DB_MySQL extends OTS_Base_DB class OTS_DB_MySQL extends OTS_Base_DB
{ {
private bool $hasCacheChanged = false;
private array $has_table_cache = []; private array $has_table_cache = [];
private array $has_column_cache = []; private array $has_column_cache = [];
private array $get_column_info_cache = []; private array $get_column_info_cache = [];
@@ -164,7 +165,7 @@ class OTS_DB_MySQL extends OTS_Base_DB
$cache->delete('database_columns_info'); $cache->delete('database_columns_info');
$cache->delete('database_checksum'); $cache->delete('database_checksum');
} }
else { else if ($this->hasCacheChanged) {
$cache->set('database_tables', serialize($this->has_table_cache), 3600); $cache->set('database_tables', serialize($this->has_table_cache), 3600);
$cache->set('database_columns', serialize($this->has_column_cache), 3600); $cache->set('database_columns', serialize($this->has_column_cache), 3600);
$cache->set('database_columns_info', serialize($this->get_column_info_cache), 3600); $cache->set('database_columns_info', serialize($this->get_column_info_cache), 3600);
@@ -228,6 +229,8 @@ class OTS_DB_MySQL extends OTS_Base_DB
private function hasTableInternal($name): bool private function hasTableInternal($name): bool
{ {
$this->hasCacheChanged = true;
return ($this->has_table_cache[$name] = $this->query('SELECT `TABLE_NAME` FROM `information_schema`.`tables` WHERE `TABLE_SCHEMA` = ' . $this->quote(config('database_name')) . ' AND `TABLE_NAME` = ' . $this->quote($name) . ' LIMIT 1;')->rowCount() > 0); return ($this->has_table_cache[$name] = $this->query('SELECT `TABLE_NAME` FROM `information_schema`.`tables` WHERE `TABLE_SCHEMA` = ' . $this->quote(config('database_name')) . ' AND `TABLE_NAME` = ' . $this->quote($name) . ' LIMIT 1;')->rowCount() > 0);
} }
@@ -241,6 +244,8 @@ class OTS_DB_MySQL extends OTS_Base_DB
} }
private function hasColumnInternal($table, $column): bool { private function hasColumnInternal($table, $column): bool {
$this->hasCacheChanged = true;
return $this->hasTable($table) && ($this->has_column_cache[$table . '.' . $column] = count($this->query('SHOW COLUMNS FROM `' . $table . "` LIKE " . $this->quote($column))->fetchAll()) > 0); return $this->hasTable($table) && ($this->has_column_cache[$table . '.' . $column] = count($this->query('SHOW COLUMNS FROM `' . $table . "` LIKE " . $this->quote($column))->fetchAll()) > 0);
} }
@@ -272,6 +277,8 @@ class OTS_DB_MySQL extends OTS_Base_DB
return false; return false;
} }
$this->hasCacheChanged = true;
$formatResult = function ($result) { $formatResult = function ($result) {
return [ return [
'field' => $result['Field'], 'field' => $result['Field'],

16
system/migrations/48.php Normal file
View File

@@ -0,0 +1,16 @@
<?php
/**
* @var OTS_DB_MySQL $db
*/
$up = function () use ($db) {
if (!$db->hasColumn(TABLE_PREFIX . 'menu', 'access')) {
$db->addColumn(TABLE_PREFIX . 'menu', 'access', 'TINYINT NOT NULL DEFAULT 0 AFTER `link`');
}
};
$down = function () use ($db) {
if ($db->hasColumn(TABLE_PREFIX . 'menu', 'access')) {
$db->dropColumn(TABLE_PREFIX . 'menu', 'access');
}
};

View File

@@ -9,7 +9,7 @@ $up = function () use ($db) {
} }
}; };
$up = function () use ($db) { $down = function () use ($db) {
if (!$db->hasColumn(TABLE_PREFIX . 'screenshots', 'name')) { if (!$db->hasColumn(TABLE_PREFIX . 'screenshots', 'name')) {
$db->addColumn(TABLE_PREFIX . 'screenshots', 'name', 'VARCHAR(30) NOT NULL'); $db->addColumn(TABLE_PREFIX . 'screenshots', 'name', 'VARCHAR(30) NOT NULL');
} }

View File

@@ -176,7 +176,9 @@ if (empty($highscores)) {
POT::SKILL_FISH => 'skill_fishing', POT::SKILL_FISH => 'skill_fishing',
); );
$query->addSelect($skill_ids[$skill] . ' as value'); $query
->addSelect($skill_ids[$skill] . ' as value')
->orderByDesc($skill_ids[$skill] . '_tries');
} else { } else {
$query $query
->join('player_skills', 'player_skills.player_id', '=', 'players.id') ->join('player_skills', 'player_skills.player_id', '=', 'players.id')
@@ -198,11 +200,11 @@ if (empty($highscores)) {
if ($skill == POT::SKILL__MAGLEVEL) { if ($skill == POT::SKILL__MAGLEVEL) {
$query $query
->addSelect('players.maglevel as value', 'players.maglevel') ->addSelect('players.maglevel as value', 'players.maglevel')
->orderBy('manaspent'); ->orderByDesc('manaspent');
} else { // level } else { // level
$query $query
->addSelect('players.level as value', 'players.experience') ->addSelect('players.level as value', 'players.experience')
->orderBy('experience'); ->orderByDesc('experience');
$list = 'experience'; $list = 'experience';
} }
} }

View File

@@ -8,6 +8,7 @@
* @link https://my-aac.org * @link https://my-aac.org
*/ */
use MyAAC\Models\Menu;
use MyAAC\Models\Pages; use MyAAC\Models\Pages;
use MyAAC\Plugins; use MyAAC\Plugins;
@@ -331,7 +332,20 @@ else {
} }
} }
if (!$found) { $tmpPageOriginal = $page;
$pagesWithDynamicPart = ['characters', 'forum', 'highscores', 'monsters'];
foreach ($pagesWithDynamicPart as $_page) {
if (str_contains($page, $_page)) {
$tmpPageOriginal = $_page;
}
}
$themeMenu = Menu::select(['name'])
->where('template', $template_name)
->where('link', $tmpPageOriginal)
->where('access', '>', $logged_access);
if (!$found || $themeMenu->count() >= 1) {
$page = '404'; $page = '404';
$file = SYSTEM . 'pages/404.php'; $file = SYSTEM . 'pages/404.php';
} }

View File

@@ -219,7 +219,14 @@ return [
'cache_engine' => [ 'cache_engine' => [
'name' => 'Cache Engine', 'name' => 'Cache Engine',
'type' => 'options', 'type' => 'options',
'options' => ['auto' => 'Auto', 'file' => 'Files', 'apc' => 'APC', 'apcu' => 'APCu', 'disable' => 'Disable'], 'options' => [
'auto' => 'Auto',
'file' => 'Files',
'apc' => 'APC',
'apcu' => 'APCu',
'php' => 'PHP',
'disable' => 'Disable',
],
'desc' => 'Auto is most reasonable. It will detect the best cache engine', 'desc' => 'Auto is most reasonable. It will detect the best cache engine',
'default' => 'auto', 'default' => 'auto',
'is_config' => true, 'is_config' => true,

View File

@@ -13,8 +13,8 @@ namespace MyAAC\Cache;
class APC class APC
{ {
private $prefix; private string $prefix;
private $enabled; private bool $enabled;
public function __construct($prefix = '') public function __construct($prefix = '')
{ {
@@ -22,14 +22,14 @@ class APC
$this->enabled = function_exists('apc_fetch'); $this->enabled = function_exists('apc_fetch');
} }
public function set($key, $var, $ttl = 0) public function set($key, $var, $ttl = 0): void
{ {
$key = $this->prefix . $key; $key = $this->prefix . $key;
apc_delete($key); apc_delete($key);
apc_store($key, $var, $ttl); apc_store($key, $var, $ttl);
} }
public function get($key) public function get($key): string
{ {
$tmp = ''; $tmp = '';
if ($this->fetch($this->prefix . $key, $tmp)) { if ($this->fetch($this->prefix . $key, $tmp)) {
@@ -39,18 +39,15 @@ class APC
return ''; return '';
} }
public function fetch($key, &$var) public function fetch($key, &$var): bool {
{
return ($var = apc_fetch($this->prefix . $key)) !== false; return ($var = apc_fetch($this->prefix . $key)) !== false;
} }
public function delete($key) public function delete($key): void {
{
apc_delete($this->prefix . $key); apc_delete($this->prefix . $key);
} }
public function enabled() public function enabled(): bool {
{
return $this->enabled; return $this->enabled;
} }
} }

View File

@@ -13,8 +13,8 @@ namespace MyAAC\Cache;
class APCu class APCu
{ {
private $prefix; private string $prefix;
private $enabled; private bool $enabled;
public function __construct($prefix = '') public function __construct($prefix = '')
{ {
@@ -22,14 +22,14 @@ class APCu
$this->enabled = function_exists('apcu_fetch'); $this->enabled = function_exists('apcu_fetch');
} }
public function set($key, $var, $ttl = 0) public function set($key, $var, $ttl = 0): void
{ {
$key = $this->prefix . $key; $key = $this->prefix . $key;
apcu_delete($key); apcu_delete($key);
apcu_store($key, $var, $ttl); apcu_store($key, $var, $ttl);
} }
public function get($key) public function get($key): string
{ {
$tmp = ''; $tmp = '';
if ($this->fetch($this->prefix . $key, $tmp)) { if ($this->fetch($this->prefix . $key, $tmp)) {
@@ -39,18 +39,15 @@ class APCu
return ''; return '';
} }
public function fetch($key, &$var) public function fetch($key, &$var): bool {
{
return ($var = apcu_fetch($this->prefix . $key)) !== false; return ($var = apcu_fetch($this->prefix . $key)) !== false;
} }
public function delete($key) public function delete($key): void {
{
apcu_delete($this->prefix . $key); apcu_delete($this->prefix . $key);
} }
public function enabled() public function enabled(): bool {
{
return $this->enabled; return $this->enabled;
} }
} }

View File

@@ -47,35 +47,15 @@ class Cache
return self::$instance; return self::$instance;
} }
switch (strtolower($engine)) { self::$instance = match (strtolower($engine)) {
case 'apc': 'apc' => new APC($prefix),
self::$instance = new APC($prefix); 'apcu' => new APCu($prefix),
break; 'xcache' => new XCache($prefix),
'file' => new File($prefix, CACHE),
case 'apcu': 'php' => new PHP($prefix, CACHE),
self::$instance = new APCu($prefix); 'auto' => self::generateInstance(self::detect(), $prefix),
break; default => new self(),
};
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 self::$instance;
} }
@@ -83,7 +63,7 @@ class Cache
/** /**
* @return string * @return string
*/ */
public static function detect() public static function detect(): string
{ {
if (function_exists('apc_fetch')) if (function_exists('apc_fetch'))
return 'apc'; return 'apc';
@@ -98,8 +78,7 @@ class Cache
/** /**
* @return bool * @return bool
*/ */
public function enabled() public function enabled(): bool {
{
return false; return false;
} }

View File

@@ -12,18 +12,22 @@ namespace MyAAC\Cache;
class File class File
{ {
private $prefix; private string $prefix;
private $dir; private string $dir;
private $enabled; private bool $enabled;
public function __construct($prefix = '', $dir = '') public function __construct($prefix = '', $dir = '')
{ {
$this->prefix = $prefix; $this->prefix = $prefix;
$this->dir = $dir; $this->dir = $dir;
ensureFolderExists($this->dir);
ensureIndexExists($this->dir);
$this->enabled = (file_exists($this->dir) && is_dir($this->dir) && is_writable($this->dir)); $this->enabled = (file_exists($this->dir) && is_dir($this->dir) && is_writable($this->dir));
} }
public function set($key, $var, $ttl = 0) public function set($key, $var, $ttl = 0): void
{ {
$file = $this->_name($key); $file = $this->_name($key);
file_put_contents($file, $var); file_put_contents($file, $var);
@@ -35,7 +39,7 @@ class File
touch($file, time() + $ttl); touch($file, time() + $ttl);
} }
public function get($key) public function get($key): string
{ {
$tmp = ''; $tmp = '';
if ($this->fetch($key, $tmp)) { if ($this->fetch($key, $tmp)) {
@@ -45,7 +49,7 @@ class File
return ''; return '';
} }
public function fetch($key, &$var) public function fetch($key, &$var): bool
{ {
$file = $this->_name($key); $file = $this->_name($key);
if (!file_exists($file) || filemtime($file) < time()) { if (!file_exists($file) || filemtime($file) < time()) {
@@ -56,7 +60,7 @@ class File
return true; return true;
} }
public function delete($key) public function delete($key): void
{ {
$file = $this->_name($key); $file = $this->_name($key);
if (file_exists($file)) { if (file_exists($file)) {
@@ -64,13 +68,11 @@ class File
} }
} }
public function enabled() public function enabled(): bool {
{
return $this->enabled; return $this->enabled;
} }
private function _name($key) private function _name($key): string {
{
return sprintf('%s%s%s', $this->dir, $this->prefix, sha1($key)); return sprintf('%s%s%s', $this->dir, $this->prefix, sha1($key));
} }
} }

View File

@@ -12,39 +12,40 @@ namespace MyAAC\Cache;
class PHP class PHP
{ {
private $prefix; private string $prefix;
private $dir; private string $dir;
private $enabled; private bool $enabled;
public function __construct($prefix = '', $dir = '') public function __construct($prefix = '', $dir = '')
{ {
$this->prefix = $prefix; $this->prefix = $prefix;
$this->dir = $dir; $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);
ensureFolderExists($this->dir); ensureFolderExists($this->dir);
ensureIndexExists($this->dir); ensureIndexExists($this->dir);
// Write to temp file first to ensure atomicity $this->enabled = (file_exists($this->dir) && is_dir($this->dir) && is_writable($this->dir));
$tmp = $this->dir . "tmp_$key." . uniqid('', true) . '.tmp'; }
file_put_contents($tmp, '<?php $var = ' . $var . ';', LOCK_EX);
$file = $this->_name($key); public function set($key, $var, $ttl = 0): void
rename($tmp, $file); {
$var = var_export($var, true);
if ($ttl === 0) { if ($ttl === 0) {
$ttl = 365 * 24 * 60 * 60; // 365 days $ttl = 365 * 24 * 60 * 60; // 365 days
} }
touch($file, time() + $ttl); $expires = time() + $ttl;
// Write to temp file first to ensure atomicity
$tmp = $this->dir . "tmp_$key." . uniqid('', true) . '.tmp';
file_put_contents($tmp, "<?php return ['expires' => $expires, 'var' => $var];", LOCK_EX);
$file = $this->_name($key);
rename($tmp, $file);
} }
public function get($key) public function get($key): string
{ {
$tmp = ''; $tmp = '';
if ($this->fetch($key, $tmp)) { if ($this->fetch($key, $tmp)) {
@@ -54,19 +55,23 @@ class PHP
return ''; return '';
} }
public function fetch($key, &$var) public function fetch($key, &$var): bool
{ {
$file = $this->_name($key); $file = $this->_name($key);
if (!file_exists($file) || filemtime($file) < time()) { if (!file_exists($file)) {
return false; return false;
} }
@include $file; $content = include $file;
$var = isset($var) ? $var : null; if (!isset($content) || $content['expires'] < time()) {
return false;
}
$var = $content['var'];
return true; return true;
} }
public function delete($key) public function delete($key): void
{ {
$file = $this->_name($key); $file = $this->_name($key);
if (file_exists($file)) { if (file_exists($file)) {
@@ -74,13 +79,11 @@ class PHP
} }
} }
public function enabled() public function enabled(): bool {
{
return $this->enabled; return $this->enabled;
} }
private function _name($key) private function _name($key): string {
{
return sprintf('%s%s%s', $this->dir, $this->prefix, sha1($key) . '.php'); return sprintf('%s%s%s', $this->dir, $this->prefix, sha1($key) . '.php');
} }
} }

View File

@@ -13,8 +13,8 @@ namespace MyAAC\Cache;
class XCache class XCache
{ {
private $prefix; private string $prefix;
private $enabled; private bool $enabled;
public function __construct($prefix = '') public function __construct($prefix = '')
{ {
@@ -22,14 +22,14 @@ class XCache
$this->enabled = function_exists('xcache_get') && ini_get('xcache.var_size'); $this->enabled = function_exists('xcache_get') && ini_get('xcache.var_size');
} }
public function set($key, $var, $ttl = 0) public function set($key, $var, $ttl = 0): void
{ {
$key = $this->prefix . $key; $key = $this->prefix . $key;
xcache_unset($key); xcache_unset($key);
xcache_set($key, $var, $ttl); xcache_set($key, $var, $ttl);
} }
public function get($key) public function get($key): string
{ {
$tmp = ''; $tmp = '';
if ($this->fetch($this->prefix . $key, $tmp)) { if ($this->fetch($this->prefix . $key, $tmp)) {
@@ -39,7 +39,7 @@ class XCache
return ''; return '';
} }
public function fetch($key, &$var) public function fetch($key, &$var): bool
{ {
$key = $this->prefix . $key; $key = $this->prefix . $key;
if (!xcache_isset($key)) { if (!xcache_isset($key)) {
@@ -50,13 +50,11 @@ class XCache
return true; return true;
} }
public function delete($key) public function delete($key): void {
{
xcache_unset($this->prefix . $key); xcache_unset($this->prefix . $key);
} }
public function enabled() public function enabled(): bool {
{
return $this->enabled; return $this->enabled;
} }
} }

View File

@@ -231,6 +231,7 @@ class Forum
if(!is_int($rows / 2)) { $bgcolor = 'ABED25'; } else { $bgcolor = '23ED25'; } $rows++; 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); $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; $rows = 0;
while(stripos($text, '[quote]') !== false && stripos($text, '[/quote]') !== false ) while(stripos($text, '[quote]') !== false && stripos($text, '[/quote]') !== false )
{ {
@@ -238,11 +239,31 @@ class Forum
if(!is_int($rows / 2)) { $bgcolor = 'AAAAAA'; } else { $bgcolor = 'CCCCCC'; } $rows++; 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); $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 ) $tagsToParse = [
'url' => function ($str) {
return '<a href="'.$str.'" target="_blank">'.$str.'</a>';
},
'player' => function ($str) {
return generateLink(getPlayerLink($str, false), $str, true);
},
'guild' => function ($str) {
return generateLink(getGuildLink($str, false), $str, true);
},
'house' => function ($str) {
return generateLink(getHouseLink($str, false), $str, true);
}
];
foreach ($tagsToParse as $tag => $callback) {
while(stripos($text, "[$tag]") !== false && stripos($text, "[/$tag]") !== false
&& stripos($text, "[$tag]") < stripos($text, "[/$tag]"))
{ {
$url = substr($text, stripos($text, '[url]')+5, stripos($text, '[/url]') - stripos($text, '[url]') - 5); $length = strlen("[$tag]");
$text = str_ireplace('[url]'.$url.'[/url]', '<a href="'.$url.'" target="_blank">'.$url.'</a>', $text); $substr = substr($text, stripos($text, "[$tag]") + $length, stripos($text, "[/$tag]") - stripos($text, "[$tag]") - $length);
$text = str_ireplace('[' . $tag . ']' . $substr . '[/' . $tag . ']', $callback($substr), $text);
}
} }
$xhtml = false; $xhtml = false;
@@ -252,9 +273,6 @@ class Forum
'#\[u\](.*?)\[/u\]#si' => ($xhtml ? '<span style="text-decoration: underline;">\\1</span>' : '<u>\\1</u>'), '#\[u\](.*?)\[/u\]#si' => ($xhtml ? '<span style="text-decoration: underline;">\\1</span>' : '<u>\\1</u>'),
'#\[s\](.*?)\[/s\]#si' => ($xhtml ? '<strike>\\1</strike>' : '<s>\\1</s>'), '#\[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 // TODO: [poll] tag
'#\[color=(.*?)\](.*?)\[/color\]#si' => ($xhtml ? '<span style="color: \\1;">\\2</span>' : '<span style="color: \\1">\\2</span>'), '#\[color=(.*?)\](.*?)\[/color\]#si' => ($xhtml ? '<span style="color: \\1;">\\2</span>' : '<span style="color: \\1">\\2</span>'),

View File

@@ -9,6 +9,6 @@ class Menu extends Model {
public $timestamps = false; public $timestamps = false;
protected $fillable = ['template', 'name', 'link', 'blank', 'color', 'category', 'ordering', 'enabled']; protected $fillable = ['template', 'name', 'link', 'access', 'blank', 'color', 'category', 'ordering', 'enabled'];
} }

View File

@@ -342,6 +342,16 @@ class Validator
} }
} }
global $hooks;
$params = ['name' => $name, 'error' => ''];
$hooks->triggerFilter(HOOK_FILTER_VALIDATE_CHARACTER_NEW_NAME, $params);
if (!empty($params['error'])) {
self::$lastError = $params['error'];
return false;
}
return true; return true;
} }

View File

@@ -108,6 +108,7 @@ define('HOOK_FILTER_ROUTES', ++$i);
define('HOOK_FILTER_TWIG_DISPLAY', ++$i); define('HOOK_FILTER_TWIG_DISPLAY', ++$i);
define('HOOK_FILTER_TWIG_RENDER', ++$i); define('HOOK_FILTER_TWIG_RENDER', ++$i);
define('HOOK_FILTER_THEME_FOOTER', ++$i); define('HOOK_FILTER_THEME_FOOTER', ++$i);
define('HOOK_FILTER_VALIDATE_CHARACTER_NEW_NAME', ++$i);
const HOOK_FIRST = HOOK_INIT; const HOOK_FIRST = HOOK_INIT;
define('HOOK_LAST', $i); define('HOOK_LAST', $i);

View File

@@ -146,10 +146,10 @@ if($twig_loader) {
function get_template_menus(): array function get_template_menus(): array
{ {
global $template_name; global $template_name, $logged_access;
$result = Cache::remember('template_menus_' . $template_name, 10 * 60, function () use ($template_name) { $result = Cache::remember('template_menus_' . $template_name, 10 * 60, function () use ($template_name) {
$result = Menu::select(['name', 'link', 'blank', 'color', 'category']) $result = Menu::select(['name', 'link', 'access', 'blank', 'color', 'category'])
->where('template', $template_name) ->where('template', $template_name)
->orderBy('category') ->orderBy('category')
->orderBy('ordering') ->orderBy('ordering')
@@ -163,6 +163,10 @@ function get_template_menus(): array
$menus = []; $menus = [];
foreach($result as $menu) { foreach($result as $menu) {
if ($menu['access'] > $logged_access) {
continue;
}
if (empty($menu['link'])) { if (empty($menu['link'])) {
$menu['link'] = 'news'; $menu['link'] = 'news';
} }

View File

@@ -2,7 +2,8 @@
<p class="note">You are editing: {{ template }}<br/><br/> <p class="note">You are editing: {{ template }}<br/><br/>
Hint: You can drag menu items.<br/> Hint: You can drag menu items.<br/>
Hint: Add links to external sites using: <b>http://</b> or <b>https://</b> prefix.<br/> Hint: Add links to external sites using: <b>http://</b> or <b>https://</b> prefix.<br/>
Not all templates support blank and colorful links. Not all templates support blank and colorful links.<br/>
* Guest means everyone will see the link. Player means registered and logged-in user.
</p> </p>
<div class="row text-center"> <div class="row text-center">
<div class="col-md-2 col-sm-1"></div> <div class="col-md-2 col-sm-1"></div>

View File

@@ -14,42 +14,62 @@
colors[{{ cat }}] = '{{ options['default_links_color'] ?? (menuDefaultLinksColor ?? config('menu_default_color')) }}'; colors[{{ cat }}] = '{{ options['default_links_color'] ?? (menuDefaultLinksColor ?? config('menu_default_color')) }}';
{% endfor %} {% endfor %}
function confirmRemoveMenuItem(that)
{
let id = $(that).attr("id");
if (confirm('Are you sure, that you want to remove this element?')) {
$('#list-' + id.replace('remove-button-', '')).remove();
}
}
$(function () { $(function () {
const $sortable = $(".sortable"); const $sortable = $(".sortable");
$sortable.sortable(); $sortable.sortable();
$sortable.disableSelection(); $sortable.disableSelection();
$(".remove-button").on('click', function () { $(".remove-button").on('click', function () {
var id = $(this).attr("id"); confirmRemoveMenuItem(this);
$('#list-' + id.replace('remove-button-', '')).remove();
}); });
$(".add-button").on('click', function () { $(".add-button").on('click', function () {
var cat = $(this).attr("id").replace('add-button-', ''); let cat = $(this).attr("id").replace('add-button-', '');
var id = last_id[cat]; let id = last_id[cat];
last_id[cat]++; last_id[cat]++;
const color = colors[cat]; const color = colors[cat];
$('#sortable-' + cat).append('<li class="ui-state-default" id="list-' + cat + '-' + id + '"><label>Name:</label> <input type="text" name="menu[' + cat + '][]" value=""/> <label>Link:</label> <input type="text" name="menu_link[' + cat + '][]" value=""/><input type="hidden" name="menu_blank[' + cat + '][]" value="0" /> <label><input class="blank-checkbox" type="checkbox"/><span title="Open in New Window">New Window</span></label> <input class="color-picker" type="text" name="menu_color[' + cat + '][]" value="#' + color + '" /> <a class="remove-button" id="remove-button-' + cat + '-' + id + '"><i class="fas fa-trash"></i></a></li>'); //add input bo
$('#remove-button-' + cat + '-' + id).on('click', function () {
$('#list-' + $(this).attr("id").replace('remove-button-', '')).remove();
});
initializeSpectrum(); let copy = $('.ui-state-default:first').clone();
copy.attr('id', 'list-' + cat + '-' + id);
copy.find('.menu-name').val('').attr('name', 'menu[' + cat + '][]');
copy.find('.menu-link').val('').attr('name', 'menu_link[' + cat + '][]');
copy.find('.menu-access').val('0').attr('name', 'menu_access[' + cat + '][]');
copy.find('.menu-color').val(color).attr('name', 'menu_color[' + cat + '][]');
copy.find('.menu-blank').attr('name', 'menu_blank[' + cat + '][]');
copy.find('.menu-blank-checkbox').prop('checked', false);
copy.find('.remove-button').attr('id', 'remove-button-' + cat + '-' + id);
$('#sortable-' + cat).append(copy);
$('#remove-button-' + cat + '-' + id).on('click', function () {
confirmRemoveMenuItem(this);
});
}); });
$("#menus-form").on('submit', function (e) { $("#menus-form").on('submit', function (e) {
$('.blank-checkbox:not(:checked)').each(function (i, obj) { $('.menu-blank-checkbox:not(:checked)').each(function (i, obj) {
$(obj).parent().prev().val("off"); $(obj).parent().prev().val("off");
}); });
$('.blank-checkbox:checked').each(function (i, obj) { $('.menu-blank-checkbox:checked').each(function (i, obj) {
$(obj).parent().prev().val("on"); $(obj).parent().prev().val("on");
}); });
}); });
}); });
</script> </script>
<style type="text/css"> <style>
.sortable { .sortable {
list-style-type: none; list-style-type: none;
margin: 0; margin: 0;
@@ -60,24 +80,16 @@
.remove-button, .add-button { .remove-button, .add-button {
cursor: pointer; cursor: pointer;
} }
</style>
<script type="text/javascript" src="{{ constant('BASE_URL') }}tools/js/spectrum.js"></script>
<link type="text/css" rel="stylesheet" href="{{ constant('BASE_URL') }}tools/css/spectrum.css"/>
<script type="text/javascript">
$(function () {
initializeSpectrum();
});
function initializeSpectrum() { .ui-sortable-handle {
$(".color-picker").spectrum({ padding: 10px;
preferredFormat: "hex",
showInput: true,
showPalette: true,
palette: [
['black', 'white', 'blanchedalmond',
'rgb(255, 128, 0);', 'hsv 100 70 50'],
['red', 'yellow', 'green', 'blue', 'violet']
]
});
} }
</script>
.label_menu_name, .label_menu_link {
width: 45%;
}
.menu-name, .menu-link {
width: 100%;
}
</style>

View File

@@ -1,507 +0,0 @@
/***
Spectrum Colorpicker v1.8.0
https://github.com/bgrins/spectrum
Author: Brian Grinstead
License: MIT
***/
.sp-container {
position:absolute;
top:0;
left:0;
display:inline-block;
*display: inline;
*zoom: 1;
/* https://github.com/bgrins/spectrum/issues/40 */
z-index: 9999994;
overflow: hidden;
}
.sp-container.sp-flat {
position: relative;
}
/* Fix for * { box-sizing: border-box; } */
.sp-container,
.sp-container * {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
/* http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */
.sp-top {
position:relative;
width: 100%;
display:inline-block;
}
.sp-top-inner {
position:absolute;
top:0;
left:0;
bottom:0;
right:0;
}
.sp-color {
position: absolute;
top:0;
left:0;
bottom:0;
right:20%;
}
.sp-hue {
position: absolute;
top:0;
right:0;
bottom:0;
left:84%;
height: 100%;
}
.sp-clear-enabled .sp-hue {
top:33px;
height: 77.5%;
}
.sp-fill {
padding-top: 80%;
}
.sp-sat, .sp-val {
position: absolute;
top:0;
left:0;
right:0;
bottom:0;
}
.sp-alpha-enabled .sp-top {
margin-bottom: 18px;
}
.sp-alpha-enabled .sp-alpha {
display: block;
}
.sp-alpha-handle {
position:absolute;
top:-4px;
bottom: -4px;
width: 6px;
left: 50%;
cursor: pointer;
border: 1px solid black;
background: white;
opacity: .8;
}
.sp-alpha {
display: none;
position: absolute;
bottom: -14px;
right: 0;
left: 0;
height: 8px;
}
.sp-alpha-inner {
border: solid 1px #333;
}
.sp-clear {
display: none;
}
.sp-clear.sp-clear-display {
background-position: center;
}
.sp-clear-enabled .sp-clear {
display: block;
position:absolute;
top:0px;
right:0;
bottom:0;
left:84%;
height: 28px;
}
/* Don't allow text selection */
.sp-container, .sp-replacer, .sp-preview, .sp-dragger, .sp-slider, .sp-alpha, .sp-clear, .sp-alpha-handle, .sp-container.sp-dragging .sp-input, .sp-container button {
-webkit-user-select:none;
-moz-user-select: -moz-none;
-o-user-select:none;
user-select: none;
}
.sp-container.sp-input-disabled .sp-input-container {
display: none;
}
.sp-container.sp-buttons-disabled .sp-button-container {
display: none;
}
.sp-container.sp-palette-buttons-disabled .sp-palette-button-container {
display: none;
}
.sp-palette-only .sp-picker-container {
display: none;
}
.sp-palette-disabled .sp-palette-container {
display: none;
}
.sp-initial-disabled .sp-initial {
display: none;
}
/* Gradients for hue, saturation and value instead of images. Not pretty... but it works */
.sp-sat {
background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#FFF), to(rgba(204, 154, 129, 0)));
background-image: -webkit-linear-gradient(left, #FFF, rgba(204, 154, 129, 0));
background-image: -moz-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
background-image: -o-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
background-image: -ms-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
background-image: linear-gradient(to right, #fff, rgba(204, 154, 129, 0));
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr=#FFFFFFFF, endColorstr=#00CC9A81)";
filter : progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr='#FFFFFFFF', endColorstr='#00CC9A81');
}
.sp-val {
background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#000000), to(rgba(204, 154, 129, 0)));
background-image: -webkit-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0));
background-image: -moz-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
background-image: -o-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
background-image: -ms-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
background-image: linear-gradient(to top, #000, rgba(204, 154, 129, 0));
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#00CC9A81, endColorstr=#FF000000)";
filter : progid:DXImageTransform.Microsoft.gradient(startColorstr='#00CC9A81', endColorstr='#FF000000');
}
.sp-hue {
background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
background: -ms-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
background: -o-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
background: -webkit-gradient(linear, left top, left bottom, from(#ff0000), color-stop(0.17, #ffff00), color-stop(0.33, #00ff00), color-stop(0.5, #00ffff), color-stop(0.67, #0000ff), color-stop(0.83, #ff00ff), to(#ff0000));
background: -webkit-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
background: linear-gradient(to bottom, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
}
/* IE filters do not support multiple color stops.
Generate 6 divs, line them up, and do two color gradients for each.
Yes, really.
*/
.sp-1 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0000', endColorstr='#ffff00');
}
.sp-2 {
height:16%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff00', endColorstr='#00ff00');
}
.sp-3 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ff00', endColorstr='#00ffff');
}
.sp-4 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffff', endColorstr='#0000ff');
}
.sp-5 {
height:16%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0000ff', endColorstr='#ff00ff');
}
.sp-6 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff00ff', endColorstr='#ff0000');
}
.sp-hidden {
display: none !important;
}
/* Clearfix hack */
.sp-cf:before, .sp-cf:after { content: ""; display: table; }
.sp-cf:after { clear: both; }
.sp-cf { *zoom: 1; }
/* Mobile devices, make hue slider bigger so it is easier to slide */
@media (max-device-width: 480px) {
.sp-color { right: 40%; }
.sp-hue { left: 63%; }
.sp-fill { padding-top: 60%; }
}
.sp-dragger {
border-radius: 5px;
height: 5px;
width: 5px;
border: 1px solid #fff;
background: #000;
cursor: pointer;
position:absolute;
top:0;
left: 0;
}
.sp-slider {
position: absolute;
top:0;
cursor:pointer;
height: 3px;
left: -1px;
right: -1px;
border: 1px solid #000;
background: white;
opacity: .8;
}
/*
Theme authors:
Here are the basic themeable display options (colors, fonts, global widths).
See http://bgrins.github.io/spectrum/themes/ for instructions.
*/
.sp-container {
border-radius: 0;
background-color: #ECECEC;
border: solid 1px #f0c49B;
padding: 0;
}
.sp-container, .sp-container button, .sp-container input, .sp-color, .sp-hue, .sp-clear {
font: normal 12px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.sp-top {
margin-bottom: 3px;
}
.sp-color, .sp-hue, .sp-clear {
border: solid 1px #666;
}
/* Input */
.sp-input-container {
float:right;
width: 100px;
margin-bottom: 4px;
}
.sp-initial-disabled .sp-input-container {
width: 100%;
}
.sp-input {
font-size: 12px !important;
border: 1px inset;
padding: 4px 5px;
margin: 0;
width: 100%;
background:transparent;
border-radius: 3px;
color: #222;
}
.sp-input:focus {
border: 1px solid orange;
}
.sp-input.sp-validation-error {
border: 1px solid red;
background: #fdd;
}
.sp-picker-container , .sp-palette-container {
float:left;
position: relative;
padding: 10px;
padding-bottom: 300px;
margin-bottom: -290px;
}
.sp-picker-container {
width: 172px;
border-left: solid 1px #fff;
}
/* Palettes */
.sp-palette-container {
border-right: solid 1px #ccc;
}
.sp-palette-only .sp-palette-container {
border: 0;
}
.sp-palette .sp-thumb-el {
display: block;
position:relative;
float:left;
width: 24px;
height: 15px;
margin: 3px;
cursor: pointer;
border:solid 2px transparent;
}
.sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active {
border-color: orange;
}
.sp-thumb-el {
position:relative;
}
/* Initial */
.sp-initial {
float: left;
border: solid 1px #333;
}
.sp-initial span {
width: 30px;
height: 25px;
border:none;
display:block;
float:left;
margin:0;
}
.sp-initial .sp-clear-display {
background-position: center;
}
/* Buttons */
.sp-palette-button-container,
.sp-button-container {
float: right;
}
/* Replacer (the little preview div that shows up instead of the <input>) */
.sp-replacer {
margin:0;
overflow:hidden;
cursor:pointer;
padding: 4px;
display:inline-block;
*zoom: 1;
*display: inline;
border: solid 1px #91765d;
background: #eee;
color: #333;
vertical-align: middle;
}
.sp-replacer:hover, .sp-replacer.sp-active {
border-color: #F0C49B;
color: #111;
}
.sp-replacer.sp-disabled {
cursor:default;
border-color: silver;
color: silver;
}
.sp-dd {
padding: 2px 0;
height: 16px;
line-height: 16px;
float:left;
font-size:10px;
}
.sp-preview {
position:relative;
width:25px;
height: 20px;
border: solid 1px #222;
margin-right: 5px;
float:left;
z-index: 0;
}
.sp-palette {
*width: 220px;
max-width: 220px;
}
.sp-palette .sp-thumb-el {
width:16px;
height: 16px;
margin:2px 1px;
border: solid 1px #d0d0d0;
}
.sp-container {
padding-bottom:0;
}
/* Buttons: http://hellohappy.org/css3-buttons/ */
.sp-container button {
background-color: #eeeeee;
background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc);
background-image: -moz-linear-gradient(top, #eeeeee, #cccccc);
background-image: -ms-linear-gradient(top, #eeeeee, #cccccc);
background-image: -o-linear-gradient(top, #eeeeee, #cccccc);
background-image: linear-gradient(to bottom, #eeeeee, #cccccc);
border: 1px solid #ccc;
border-bottom: 1px solid #bbb;
border-radius: 3px;
color: #333;
font-size: 14px;
line-height: 1;
padding: 5px 4px;
text-align: center;
text-shadow: 0 1px 0 #eee;
vertical-align: middle;
}
.sp-container button:hover {
background-color: #dddddd;
background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb);
background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb);
background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb);
background-image: -o-linear-gradient(top, #dddddd, #bbbbbb);
background-image: linear-gradient(to bottom, #dddddd, #bbbbbb);
border: 1px solid #bbb;
border-bottom: 1px solid #999;
cursor: pointer;
text-shadow: 0 1px 0 #ddd;
}
.sp-container button:active {
border: 1px solid #aaa;
border-bottom: 1px solid #888;
-webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-moz-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-ms-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-o-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
}
.sp-cancel {
font-size: 11px;
color: #d93f3f !important;
margin:0;
padding:2px;
margin-right: 5px;
vertical-align: middle;
text-decoration:none;
}
.sp-cancel:hover {
color: #d93f3f !important;
text-decoration: underline;
}
.sp-palette span:hover, .sp-palette span.sp-thumb-active {
border-color: #000;
}
.sp-preview, .sp-alpha, .sp-thumb-el {
position:relative;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
}
.sp-preview-inner, .sp-alpha-inner, .sp-thumb-inner {
display:block;
position:absolute;
top:0;left:0;bottom:0;right:0;
}
.sp-palette .sp-thumb-inner {
background-position: 50% 50%;
background-repeat: no-repeat;
}
.sp-palette .sp-thumb-light.sp-thumb-active .sp-thumb-inner {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUeNpiYBhsgJFMffxAXABlN5JruT4Q3wfi/0DsT64h8UD8HmpIPCWG/KemIfOJCUB+Aoacx6EGBZyHBqI+WsDCwuQ9mhxeg2A210Ntfo8klk9sOMijaURm7yc1UP2RNCMbKE9ODK1HM6iegYLkfx8pligC9lCD7KmRof0ZhjQACDAAceovrtpVBRkAAAAASUVORK5CYII=);
}
.sp-palette .sp-thumb-dark.sp-thumb-active .sp-thumb-inner {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMdJREFUOE+tkgsNwzAMRMugEAahEAahEAZhEAqlEAZhEAohEAYh81X2dIm8fKpEspLGvudPOsUYpxE2BIJCroJmEW9qJ+MKaBFhEMNabSy9oIcIPwrB+afvAUFoK4H0tMaQ3XtlrggDhOVVMuT4E5MMG0FBbCEYzjYT7OxLEvIHQLY2zWwQ3D+9luyOQTfKDiFD3iUIfPk8VqrKjgAiSfGFPecrg6HN6m/iBcwiDAo7WiBeawa+Kwh7tZoSCGLMqwlSAzVDhoK+6vH4G0P5wdkAAAAASUVORK5CYII=);
}
.sp-clear-display {
background-repeat:no-repeat;
background-position: center;
background-image: url(data:image/gif;base64,R0lGODlhFAAUAPcAAAAAAJmZmZ2dnZ6enqKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq/Hx8fLy8vT09PX19ff39/j4+Pn5+fr6+vv7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAP8ALAAAAAAUABQAAAihAP9FoPCvoMGDBy08+EdhQAIJCCMybCDAAYUEARBAlFiQQoMABQhKUJBxY0SPICEYHBnggEmDKAuoPMjS5cGYMxHW3IiT478JJA8M/CjTZ0GgLRekNGpwAsYABHIypcAgQMsITDtWJYBR6NSqMico9cqR6tKfY7GeBCuVwlipDNmefAtTrkSzB1RaIAoXodsABiZAEFB06gIBWC1mLVgBa0AAOw==);
}

File diff suppressed because it is too large Load Diff