diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..e04fec1 --- /dev/null +++ b/.htaccess @@ -0,0 +1,23 @@ +# Prevent directory listings +Options -Indexes + +# Prevent visitors from viewing files directly + + + Order allow,deny + Deny from all + Satisfy All + + + Require all denied + + + +# URL rewrites + + RewriteEngine On + RewriteRule ^(inc/|themes/|tmp/).*\.(php|html)$ - [F,L] + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule ^ index.php [L] + \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..1eef15b --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,99 @@ +Batflat Freeware License + +1. Preamble + +This License applies to the computer program which with this licence is distributed (now +the "Program"). The Program is intellectual property of Paweł Klockiewicz and +Wojciech Król (the "Authors") and is placed under the protection of copyright laws, +including Polish legislation and international treaties such as the Berne Convention. + +Please note that this License is not a free software licence as defined by the Free +Software Foundation. Usage of the software covered by this licence is allowed free of +charge, but its COMMERCIAL DISTRIBUTION IS PROHIBITED. While the authors of this software +support the open-source philosophy and also distributes other GPL-licenced software, this +licence states that no profit could be made by third parties from the covered software. + +BY USING OR DISTRIBUTING THE PROGRAM (OR ANY WORK BASED ON THE PROGRAM), YOU INDICATE YOUR +ACCEPTANCE OF THIS LICENSE TO DO SO, AND ALL ITS TERMS AND CONDITIONS FOR USING AND +DISTRIBUTING THE PROGRAM OR WORKS BASED ON IT. NOTHING OTHER THAN THIS LICENSE GRANTS YOU +PERMISSION TO DISTRIBUTE THE PROGRAM OR ITS DERIVATIVE WORKS. THESE ACTIONS ARE PROHIBITED +BY LAW. IF YOU DO NOT ACCEPT THESE TERMS AND CONDITIONS, DO NOT DISTRIBUTE THE PROGRAM. + +2. Licenses + +Licensor hereby grants you the following rights, provided that you comply with all of the +restrictions set forth in this License and provided, further, that you distribute an +unmodified copy of this License with the Program: + + (a) Permission is granted to use the Program for non-commercial, non-military + purposes. + + (b) You may copy and distribute literal (i.e. verbatim) copies of the Program as you + receive it throughout the world, in any medium. + + (c) You may create works based on the Program and distribute copies of such throughout + the world, in any medium. + +3. Restrictions + +This license is subject to the following restrictions: + + (a) The Program may not be used for commercial or milatary purposes. + + (b) Every disribution of the Program must retain a copy of this Licence. + + (c) The Program may not be sold or incorporated into any product which is sold without + prior permission from the Authors. Distribution of the Program or any work based on the + Program by a commercial organization to any third party without permission is prohibited + if any payment is made in connection with such distribution, whether directly (as in + payment for a copy of the Program) or indirectly (as in payment for some service related + to the Program, or payment for some product or service that includes a copy of the Program + "without charge"). + + (d) The Program, especially the name of the Program, logo, a backlink to the official + website and the names of the Authors must remain unmodified. + + (e) Any output produced through the use of the Program, or derived from, is subject to + this license, i.e. content made by running the Program may be freely distributed provided + that no direct or indirect payment is required by the distributor in exchange of this + content. Distribution of produced content must also follow the rules that may govern + distribution and exploitation of the input material and/or the software that generated. + + (f) You must meet all of the following conditions with respect to any work that you + distribute or publish that in whole or in part contains or is derived from the Program + (the "Work"): + + (i) You must cause the Work to be licensed as a whole and at no charge to all + third parties under the terms of this License; + + (ii) If the Work normally reads commands interactively when run, you must cause + it, at each time the Work commences operation, to print or display an announcement + including an appropriate copyright notice. Such notice must also state that users may + redistribute the Work only under the conditions of this License and tell the user how + to view the copy of this License included with the Work; + + (iii) If you distribute any written or printed material at all with the Work, such + material must include either a written copy of this License, or a prominent written + indication that the Work is covered by this License and written instructions for + printing and/or displaying the copy of the License on the distribution medium; + + (iv) You may not impose any further restrictions on the recipient's exercise of + the rights granted herein. + +4. Reservation of Rights + +No rights are granted to the Program except as expressly set forth herein. You may not +copy, modify, sublicense, or distribute the Program except as expressly provided under +this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program +is void, and will automatically terminate your rights under this License. However, parties +who have received copies, or rights, from you under this License will not have their +licenses terminated so long as such parties remain in full compliance. + +5. Limitations + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/ReadMe.md b/ReadMe.md index d6715cf..276c441 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,7 +1,15 @@ Batflat ======= -This repository is used to report bugs and to list available languages, themes and modules. To download the CMS go to the project page. +Batflat was created as a lightweight alternative to heavy and outdated CMS'. Many people use complex solutions for simple pages, unnecessarily. Building this content management system, we focused on simplicity - even novice webmaster adapt his template and writes his own module. To achieve this, we implemented a simple template system and trivial application architecture. + +Batflat does not require MySQL database, because all the data are collected in a single file. This provides perfect portability when changing your hosting provider. Just copy all the files from one account to another. That's all. There's nothing to configure or to change. However, if you SQLite does not meet your requirements, you can quickly change the database type thanks to PDO. + +What's more, Batflat does not have installation wizard, because there is no such need. Right after uploading a package to an FTP server, Batflat is ready for action! Therefore, the installation process takes as much time as it takes to transfer files ;-) + +Each page can have it's own individual name and URL, that makes Batflat SEO friendly. Your site may be available in multiple languages. Currently Bootflat supports translation to Polish, English, French, Turkish, Swedish, Italian and Russian. + +Control panel and the default template is fully responsive, which makes it accessible from any mobile device, even on the phone thanks to used CSS framework - Bootstrap. Each of our module is adapted to it. ## Project page diff --git a/admin/.htaccess b/admin/.htaccess new file mode 100644 index 0000000..f8b0d81 --- /dev/null +++ b/admin/.htaccess @@ -0,0 +1,10 @@ +# Prevent directory listings +Options -Indexes + +# URL rewrites + + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule ^ index.php [L] + \ No newline at end of file diff --git a/admin/index.php b/admin/index.php new file mode 100644 index 0000000..c65eddd --- /dev/null +++ b/admin/index.php @@ -0,0 +1,58 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +ob_start(); +header('Content-Type:text/html;charset=utf-8'); +define('BASE_DIR', __DIR__.'/..'); +require_once('../inc/core/defines.php'); + +if (DEV_MODE) { + error_reporting(E_ALL); + ini_set('display_errors', 1); +} else { + error_reporting(0); +} + +require_once('../inc/core/lib/Autoloader.php'); + +// Admin core init +$core = new Inc\Core\Admin; + +if ($core->loginCheck()) { + $core->loadModules(); + + // Modules routing + $core->router->set('(:str)/(:str)(:any)', function ($module, $method, $params) use ($core) { + $core->createNav($module, $method); + if ($params) { + $core->loadModule($module, $method, explode('/', trim($params, '/'))); + } else { + $core->loadModule($module, $method); + } + }); + + $core->router->execute(); + $core->drawTheme('index.html'); + $core->module->finishLoop(); +} else { + if (isset($_POST['login'])) { + if ($core->login($_POST['username'], $_POST['password'], isset($_POST['remember_me']))) { + if (count($arrayURL = parseURL()) > 1) { + $url = array_merge([ADMIN], $arrayURL); + redirect(url($url)); + } + redirect(url([ADMIN, 'dashboard', 'main'])); + } + } + $core->drawTheme('login.html'); +} + +ob_end_flush(); diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..10bb3c3 Binary files /dev/null and b/favicon.ico differ diff --git a/inc/core/Admin.php b/inc/core/Admin.php new file mode 100644 index 0000000..8081fe2 --- /dev/null +++ b/inc/core/Admin.php @@ -0,0 +1,352 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Core; + +/** + * Core Admin class + */ +class Admin extends Main +{ + /** + * Assigned variables for templates + * + * @var array + */ + private $assign = []; + + /** + * Registered module pages + * + * @var array + */ + private $registerPage = []; + + /** + * Instance of Modules Collection + * + * @var \Inc\Core\Lib\ModulesCollection + */ + public $module = null; + + /** + * Admin constructor + */ + public function __construct() + { + parent::__construct(); + + $this->router->set('logout', function () { + $this->logout(); + }); + $this->loadLanguage($this->settings->get('settings.lang_admin')); + } + + /** + * set variables to template core and display them + * @param string $file + * @return void + */ + public function drawTheme($file) + { + $username = $this->getUserInfo('fullname'); + + $this->assign['username'] = !empty($username) ? $username : $this->getUserInfo('username'); + $this->assign['notify'] = $this->getNotify(); + $this->assign['path'] = url(); + $this->assign['version'] = $this->settings->get('settings.version'); + $this->assign['has_update'] = $this->module ? $this->module->settings->_checkUpdate() : false; + + $this->assign['header'] = isset_or($this->appends['header'], ['']); + $this->assign['footer'] = isset_or($this->appends['footer'], ['']); + + $this->tpl->set('bat', $this->assign); + echo $this->tpl->draw(THEMES.'/admin/'.$file, true); + } + + /** + * load language files + * @param string $lang + * @return void + */ + private function loadLanguage($language) + { + $this->lang['name'] = $language; + + foreach (glob(MODULES.'/*/lang/admin/'.$language.'.ini') as $file) { + $base = str_replace($language, 'en_english', $file); + $module = str_replace([MODULES.'/', '/lang/admin/'.$language.'.ini'], null, $file); + $this->lang[$module] = array_merge(parse_ini_file($base), parse_ini_file($file)); + } + + foreach (glob('../inc/lang/'.$language.'/admin/*.ini') as $glob) { + $base = str_replace($language, 'en_english', $glob); + $file = pathinfo($glob); + $this->lang[$file['filename']] = array_merge(parse_ini_file($base), parse_ini_file($glob)); + } + $this->tpl->set('lang', $this->lang); + } + + /** + * load module and set variables + * @param string $name + * @param string $feature + * @param array $params (optional) + * @return void + */ + public function loadModule($name, $method, $params = []) + { + $row = $this->module->{$name}; + + if ($row && ($details = $this->getModuleInfo($name))) { + if (($this->getUserInfo('access') == 'all') || in_array($name, explode(',', $this->getUserInfo('access')))) { + $anyMethod = 'any'.ucfirst($method); + $method = strtolower($_SERVER['REQUEST_METHOD']).ucfirst($method); + + if (method_exists($this->module->{$name}, $method)) { + $details['content'] = call_user_func_array([$this->module->{$name}, $method], array_values($params)); + } elseif (method_exists($this->module->{$name}, $anyMethod)) { + $details['content'] = call_user_func_array([$this->module->{$name}, $anyMethod], array_values($params)); + } else { + http_response_code(404); + $this->setNotify('failure', "[@{$method}] ".$this->lang['general']['unknown_method']); + $details['content'] = null; + } + + $this->tpl->set('module', $details); + } else { + exit; + } + } else { + exit; + } + } + + /** + * create list of modules + * @param string $activeModile + * @param string $activeFeature + * @return void + */ + public function createNav($activeModule, $activeMethod) + { + $nav = []; + $modules = $this->module->getArray(); + + if ($this->getUserInfo('access') != 'all') { + $modules = array_intersect_key($modules, array_fill_keys(explode(',', $this->getUserInfo('access')), null)); + } + + foreach ($modules as $dir => $module) { + $subnav = $this->getModuleNav($dir); + $details = $this->getModuleInfo($dir); + + if (isset($details['pages'])) { + foreach ($details['pages'] as $pageName => $pageSlug) { + $this->registerPage($pageName, $pageSlug); + } + } + if ($subnav) { + if ($activeModule == $dir) { + $activeElement = 'active'; + } else { + $activeElement = null; + } + + $subnavURLs = []; + foreach ($subnav as $key => $val) { + if (($activeModule == $dir) && isset($activeMethod) && ($activeMethod == $val)) { + $activeSubElement = 'active'; + } else { + $activeSubElement = null; + } + + $subnavURLs[] = [ + 'name' => $key, + 'url' => url([ADMIN, $dir, $val]), + 'active' => $activeSubElement, + ]; + } + + if (count($subnavURLs) == 1) { + $moduleURL = $subnavURLs[0]['url']; + $subnavURLs = []; + } else { + $moduleURL = '#'; + } + + $nav[] = [ + 'dir' => $dir, + 'name' => $details['name'], + 'icon' => $details['icon'], + 'url' => $moduleURL, + 'active' => $activeElement, + 'subnav' => $subnavURLs, + ]; + } + } + $this->assign['nav'] = $nav; + } + + /** + * get module informations + * @param string $dir + * @return array + */ + public function getModuleInfo($dir) + { + $file = MODULES.'/'.$dir.'/Info.php'; + $core = $this; + + if (file_exists($file)) { + return include($file); + } else { + return false; + } + } + + /** + * get module's methods + * @param string $dir + * @return array + */ + public function getModuleNav($dir) + { + if ($this->module->has($dir)) { + return $this->module->{$dir}->navigation(); + } + + return false; + } + + /** + * get module method + * @param string $dir + * @param string $feature + * @param array $params (optional) + * @return array + */ + public function getModuleMethod($name, $method, $params = []) + { + if (method_exists($this->module->{$name}, $method)) { + return call_user_func_array([$this->module->{$name}, $method], array_values($params)); + } + + $this->setNotify('failure', $this->lang['general']['unknown_method']); + return false; + } + + /** + * user login + * @param string $username + * @param string $password + * @return bool + */ + public function login($username, $password, $remember_me = false) + { + // Check attempt + $attempt = $this->db('login_attempts')->where('ip', $_SERVER['REMOTE_ADDR'])->oneArray(); + + // Create attempt if does not exist + if (!$attempt) { + $this->db('login_attempts')->save(['ip' => $_SERVER['REMOTE_ADDR'], 'attempts' => 0]); + $attempt = ['ip' => $_SERVER['REMOTE_ADDR'], 'attempts' => 0, 'expires' => 0]; + } else { + $attempt['attempts'] = intval($attempt['attempts']); + $attempt['expires'] = intval($attempt['expires']); + } + + // Is IP blocked? + if ((time() - $attempt['expires']) < 0) { + $this->setNotify('failure', sprintf($this->lang['general']['login_attempts'], ceil(($attempt['expires']-time())/60))); + return false; + } + + $row = $this->db('users')->where('username', $username)->oneArray(); + + if (count($row) && password_verify(trim($password), $row['password'])) { + // Reset fail attempts for this IP + $this->db('login_attempts')->where('ip', $_SERVER['REMOTE_ADDR'])->save(['attempts' => 0]); + + $_SESSION['bat_user'] = $row['id']; + $_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(6)); + $_SESSION['userAgent'] = $_SERVER['HTTP_USER_AGENT']; + $_SESSION['IPaddress'] = $_SERVER['REMOTE_ADDR']; + + if ($remember_me) { + $token = str_gen(64, "1234567890qwertyuiop[]asdfghjkl;zxcvbnm,./"); + + $this->db('remember_me')->save(['user_id' => $row['id'], 'token' => $token, 'expiry' => time()+60*60*24*30]); + + setcookie('batflat_remember', $row['id'].':'.$token, time()+60*60*24*365, '/'); + } + return true; + } else { + // Increase attempt + $this->db('login_attempts')->where('ip', $_SERVER['REMOTE_ADDR'])->save(['attempts' => $attempt['attempts']+1]); + $attempt['attempts'] += 1; + + // ... and block if reached maximum attempts + if ($attempt['attempts'] % 3 == 0) { + $this->db('login_attempts')->where('ip', $_SERVER['REMOTE_ADDR'])->save(['expires' => strtotime("+10 minutes")]); + $attempt['expires'] = strtotime("+10 minutes"); + + $this->setNotify('failure', sprintf($this->lang['general']['login_attempts'], ceil(($attempt['expires']-time())/60))); + } else { + $this->setNotify('failure', $this->lang['general']['login_failure']); + } + + return false; + } + } + + /** + * user logout + * @return void + */ + private function logout() + { + $_SESSION = []; + + // Delete remember_me token from database and cookie + if (isset($_COOKIE['batflat_remember'])) { + $token = explode(':', $_COOKIE['batflat_remember']); + $this->db('remember_me')->where('user_id', $token[0])->where('token', $token[1])->delete(); + setcookie('batflat_remember', null, -1, '/'); + } + + session_unset(); + session_destroy(); + redirect(url(ADMIN.'/')); + } + + /** + * Register module page + * + * @param string $name + * @param string $path + * @return void + */ + private function registerPage($name, $path) + { + $this->registerPage[] = ['id' => null, 'title' => $name, 'slug' => $path]; + } + + /** + * Get registered pages + * + * @return array + */ + public function getRegisteredPages() + { + return $this->registerPage; + } +} diff --git a/inc/core/AdminModule.php b/inc/core/AdminModule.php new file mode 100644 index 0000000..94b4073 --- /dev/null +++ b/inc/core/AdminModule.php @@ -0,0 +1,28 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Core; + +/** + * Admin class for administration panel + */ +abstract class AdminModule extends BaseModule +{ + /** + * Module navigation + * + * @return array + */ + public function navigation() + { + return []; + } +} diff --git a/inc/core/BaseModule.php b/inc/core/BaseModule.php new file mode 100644 index 0000000..94b98f3 --- /dev/null +++ b/inc/core/BaseModule.php @@ -0,0 +1,203 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Core; + +/** + * Base class for each module functionality + */ +class BaseModule +{ + /** + * Reference to Core instance + * + * @var \Inc\Core\Main + */ + protected $core; + + /** + * Reference to Template instance + * + * @var \Inc\Core\Lib\Templates + */ + protected $tpl; + + /** + * Reference to Router instance + * + * @var \Inc\Core\Lib\Router + */ + protected $route; + + /** + * Reference to Settings instance + * + * @var \Inc\Core\Lib\Settings + */ + protected $settings; + + /** + * Module dir name + * + * @var string + */ + protected $name; + + /** + * Reference to language array + * + * @var array + */ + protected $lang; + + /** + * Module constructor + * + * @param Inc\Core\Main $core + * @return void + */ + public function __construct(Main $core) + { + $this->core = $core; + $this->tpl = $core->tpl; + $this->router = $core->router; + $this->settings = $core->settings; + $this->lang = $core->lang; + $this->name = strtolower(str_replace(['Inc\Modules\\', '\\Admin', '\\Site'], null, static::class)); + } + + /** + * Module initialization + * + * @return void + */ + public function init() + { + } + + /** + * Procedures before destroy + * + * @return void + */ + public function finish() + { + } + + /** + * Languages list + * @param string $selected + * @param string $active ('active' or 'selected') + * @return array + */ + protected function _getLanguages($selected = null, $active = 'active') + { + $langs = glob(BASE_DIR.'/inc/lang/*', GLOB_ONLYDIR); + + $result = []; + foreach ($langs as $lang) { + if ($selected == basename($lang)) { + $attr = $active; + } else { + $attr = null; + } + $result[] = ['name' => basename($lang), 'attr' => $attr]; + } + return $result; + } + + /** + * Hook to draw template with set variables + * + * @param string $file + * @param array $variables + * @return string + */ + protected function draw($file, array $variables = []) + { + if (!empty($variables)) { + foreach ($variables as $key => $value) { + $this->tpl->set($key, $value); + } + } + + if (strpos($file, BASE_DIR) !== 0) { + if ($this instanceof AdminModule) { + $file = MODULES.'/'.$this->name.'/view/admin/'.$file; + } else { + $file = MODULES.'/'.$this->name.'/view/'.$file; + } + } + + return $this->tpl->draw($file); + } + + /** + * Get current module language value + * + * @param string $key + * @param string $module + * @return string + */ + protected function lang($key, $module = null) + { + if (empty($module)) { + $module = $this->name; + } + + return isset_or($this->lang[$module][$key], null); + } + + /** + * Get or set module settings + * + * @param string $module Example 'module' or shorter 'module.field' + * @param mixed $field If module has field it contains value + * @param mixed $value OPTIONAL + * @return mixed + */ + protected function settings($module, $field = false, $value = false) + { + if (substr_count($module, '.') == 1) { + $value = $field; + list($module, $field) = explode('.', $module); + } + + if ($value === false) { + return $this->settings->get($module, $field); + } else { + return $this->settings->set($module, $field, $value); + } + } + + /** + * Database QueryBuilder + * + * @param string $table + * @return \Inc\Core\Lib\QueryBuilder + */ + protected function db($table = null) + { + return $this->core->db($table); + } + + /** + * Create notification + * @param string $type ('success' or 'failure') + * @param string $text + * @param mixed $args [, mixed $... ]] + * @return void + */ + protected function notify() + { + call_user_func_array([$this->core, 'setNotify'], func_get_args()); + } +} diff --git a/inc/core/Main.php b/inc/core/Main.php new file mode 100644 index 0000000..512ee69 --- /dev/null +++ b/inc/core/Main.php @@ -0,0 +1,379 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Core; + +use Inc\Core\Lib\QueryBuilder; +use Inc\Core\Lib\Templates; +use Inc\Core\Lib\Router; +use Inc\Core\Lib\Settings; +use Inc\Core\Lib\License; + +/** + * Base for core classes + */ +abstract class Main +{ + /** + * Language array + * + * @var array + */ + public $lang = []; + + /** + * Templates instance + * + * @var Templates + */ + public $tpl; + + /** + * Router instance + * + * @var Router + */ + public $router; + + /** + * Settings instance + * + * @var Settings + */ + public $settings; + + /** + * List of additional header or footer content + * + * @var array + */ + public $appends = []; + + /** + * Reference to ModulesCollection + * + * @var \Inc\Core\Lib\ModulesCollection|null + */ + public $module = null; + + /** + * Settings cache + * + * @var array + */ + protected static $settingsCache = []; + + /** + * User cache + * + * @var array + */ + protected static $userCache = []; + + /** + * Main constructor + */ + public function __construct() + { + $this->setSession(); + + $dbFile = BASE_DIR.'/inc/data/database.sdb'; + + if (file_exists($dbFile)) { + QueryBuilder::connect("sqlite:{$dbFile}"); + } else { + $this->freshInstall($dbFile); + } + + $this->settings = new Settings($this); + date_default_timezone_set($this->settings->get('settings.timezone')); + + $this->tpl = new Templates($this); + $this->router = new Router; + + $this->append(base64_decode('PG1ldGEgbmFtZT0iZ2VuZXJhdG9yIiBjb250ZW50PSJCYXRmbGF0IiAvPg=='), 'header'); + } + + /** + * New instance of QueryBuilder + * + * @param string $table + * @return QueryBuilder + */ + public function db($table = null) + { + return new QueryBuilder($table); + } + + /** + * get module settings + * @param string $module + * @param string $field + * @param bool $refresh + * + * @deprecated + * + * @return string or array + */ + public function getSettings($module = 'settings', $field = null, $refresh = false) + { + if ($refresh) { + $this->settings->reload(); + } + + return $this->settings->get($module, $field); + } + + /** + * Set module settings value + * + * @param string $module + * @param string $field + * @param string $value + * + * @deprecated + * + * @return bool + */ + public function setSettings($module, $field, $value) + { + return $this->settings->set($module, $field, $value); + } + + /** + * safe session + * @return void + */ + private function setSession() + { + ini_set('session.use_only_cookies', 1); + session_name('bat'); + session_start(); + } + + /** + * create notification + * @param string $type ('success' or 'failure') + * @param string $text + * @param mixed $args [, mixed $... ]] + * @return void + */ + public function setNotify($type, $text, $args = null) + { + $variables = []; + $numargs = func_num_args(); + $arguments = func_get_args(); + + if ($numargs > 1) { + for ($i = 1; $i < $numargs; $i++) { + $variables[] = $arguments[$i]; + } + $text = call_user_func_array('sprintf', $variables); + $_SESSION[$arguments[0]] = $text; + } + } + + /** + * display notification + * @return array or false + */ + public function getNotify() + { + if (isset($_SESSION['failure'])) { + $result = ['text' => $_SESSION['failure'], 'type' => 'danger']; + unset($_SESSION['failure']); + return $result; + } elseif (isset($_SESSION['success'])) { + $result = ['text' => $_SESSION['success'], 'type' => 'success']; + unset($_SESSION['success']); + return $result; + } else { + return false; + } + } + + /** + * adds CSS URL to array + * @param string $path + * @return void + */ + public function addCSS($path) + { + $this->appends['header'][] = "\n"; + } + + /** + * adds JS URL to array + * @param string $path + * @param string $location (header / footer) + * @return void + */ + public function addJS($path, $location = 'header') + { + $this->appends[$location][] = "\n"; + } + + /** + * adds string to array + * @param string $string + * @param string $location (header / footer) + * @return void + */ + public function append($string, $location) + { + $this->appends[$location][] = $string."\n"; + } + + /** + * Batflat license verify + * By removing or modifing these procedures you break our license. + * + * @param string $buffer + * @return string + */ + public static function verifyLicense($buffer) + { + $core = isset_or($GLOBALS['core'], false); + if (!$core) { + return $buffer; + } + $checkBuffer = preg_replace('//', '', $buffer); + $isHTML = strpos(get_headers_list('Content-Type'), 'text/html') !== false; + $hasBacklink = strpos($checkBuffer, 'Powered by Batflat') !== false; + $hasHeader = get_headers_list('X-Created-By') === 'Batflat '; + $license = License::verify($core->settings->get('settings.license')); + if (($license == License::FREE) && $isHTML && (!$hasBacklink || !$hasHeader)) { + return 'Batflat license system
The return link has been deleted or modified.'; + } elseif ($license == License::TIME_OUT) { + return $buffer.''; + } elseif ($license == License::ERROR) { + return 'Batflat license system
The license is not valid. Please correct it or go to free version.'; + } + + return trim($buffer); + } + + /** + * chcec if user is login + * @return bool + */ + public function loginCheck() + { + if (isset($_SESSION['bat_user']) && isset($_SESSION['token']) && isset($_SESSION['userAgent']) && isset($_SESSION['IPaddress'])) { + if ($_SESSION['IPaddress'] != $_SERVER['REMOTE_ADDR']) { + return false; + } + if ($_SESSION['userAgent'] != $_SERVER['HTTP_USER_AGENT']) { + return false; + } + + if (empty(parseURL(1))) { + redirect(url([ADMIN, 'dashboard', 'main'])); + } elseif (!isset($_GET['t']) || ($_SESSION['token'] != @$_GET['t'])) { + return false; + } + + return true; + } elseif (isset($_COOKIE['batflat_remember'])) { + $token = explode(":", $_COOKIE['batflat_remember']); + if (count($token) == 2) { + $row = $this->db('users')->leftJoin('remember_me', 'remember_me.user_id = users.id')->where('users.id', $token[0])->where('remember_me.token', $token[1])->select(['users.*', 'remember_me.expiry', 'token_id' => 'remember_me.id'])->oneArray(); + + if ($row) { + if (time() - $row['expiry'] > 0) { + $this->db('remember_me')->delete(['id' => $row['token_id']]); + } else { + $_SESSION['bat_user'] = $row['id']; + $_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(6)); + $_SESSION['userAgent'] = $_SERVER['HTTP_USER_AGENT']; + $_SESSION['IPaddress'] = $_SERVER['REMOTE_ADDR']; + + $this->db('remember_me')->where('remember_me.user_id', $token[0])->where('remember_me.token', $token[1])->save(['expiry' => time()+60*60*24*30]); + + if (strpos($_SERVER['SCRIPT_NAME'], '/'.ADMIN.'/') !== false) { + redirect(url([ADMIN, 'dashboard', 'main'])); + } + + return true; + } + } + } + setcookie('batflat_remember', null, -1, '/'); + } + + return false; + } + + /** + * get user informations + * @param string $filed + * @param int $id (optional) + * @return string + */ + public function getUserInfo($field, $id = null, $refresh = false) + { + if (!$id) { + $id = isset_or($_SESSION['bat_user'], 0); + } + + if (empty(self::$userCache) || $refresh) { + self::$userCache = $this->db('users')->where('id', $id)->oneArray(); + } + + return self::$userCache[$field]; + } + + /** + * Load installed modules + * + * @return void + */ + public function loadModules() + { + if ($this->module == null) { + $this->module = new Lib\ModulesCollection($this); + } + } + + /** + * Generating database with Batflat data + * @param string $dbFile path to Batflat SQLite database + * @return void + */ + private function freshInstall($dbFile) + { + QueryBuilder::connect("sqlite:{$dbFile}"); + $pdo = QueryBuilder::pdo(); + + $core = $this; + + $modules = unserialize(BASIC_MODULES); + foreach ($modules as $module) { + $file = MODULES.'/'.$module.'/Info.php'; + + if (file_exists($file)) { + $this->lang[$module] = parse_ini_file(MODULES.'/'.$module.'/lang/admin/en_english.ini'); + + $info = include($file); + if (isset($info['install'])) { + $info['install'](); + } + } + } + + foreach ($modules as $order => $name) { + $core->db('modules')->save(['dir' => $name, 'sequence' => $order]); + } + + + redirect(url()); + } +} diff --git a/inc/core/Site.php b/inc/core/Site.php new file mode 100644 index 0000000..a811e84 --- /dev/null +++ b/inc/core/Site.php @@ -0,0 +1,125 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Core; + +/** + * Core Site class + */ +class Site extends Main +{ + /** + * Current site template file + * Does not use template if set to false + * + * @var mixed + */ + public $template = 'index.html'; + + /** + * Site constructor + */ + public function __construct() + { + parent::__construct(); + $this->loadLanguage(); + $this->loadModules(); + + $return = $this->router->execute(); + + if (is_string($this->template)) { + $this->drawTheme($this->template); + } elseif ($this->template === false) { + if (strpos(get_headers_list('Content-Type'), 'text/html') !== false) { + header("Content-type: text/plain"); + } + + echo $return; + } + + $this->module->finishLoop(); + } + + /** + * set variables to template core and display them + * @param string $file + * @return void + */ + private function drawTheme($file) + { + $assign = []; + $assign['notify'] = $this->getNotify(); + $assign['powered'] = 'Powered by Batflat'; + $assign['path'] = url(); + $assign['theme'] = url(THEMES.'/'.$this->settings->get('settings.theme')); + $assign['lang'] = $this->lang['name']; + + $assign['header'] = isset_or($this->appends['header'], ['']); + $assign['footer'] = isset_or($this->appends['footer'], ['']); + + $this->tpl->set('bat', $assign); + echo $this->tpl->draw(THEMES.'/'.$this->settings->get('settings.theme').'/'.$file, true); + } + + /** + * load files with language + * @param string $lang + * @return void + */ + public function loadLanguage($lang = null) + { + $this->lang = []; + + if ($lang != null && is_dir('inc/lang/'.$lang)) { + $_SESSION['lang'] = $lang; + } + + if (!isset($_SESSION['lang']) || !is_dir('inc/lang/'.$_SESSION['lang'])) { + $this->lang['name'] = $this->settings->get('settings.lang_site'); + $_SESSION['lang'] = $this->lang['name']; + } else { + $this->lang['name'] = $_SESSION['lang']; + } + + + foreach (glob(MODULES.'/*/lang/'.$this->lang['name'].'.ini') as $file) { + $base = str_replace($this->lang['name'], 'en_english', $file); + $module = str_replace([MODULES.'/', '/lang/'.$this->lang['name'].'.ini'], null, $file); + $this->lang[$module] = array_merge(parse_ini_file($base), parse_ini_file($file)); + } + foreach (glob('inc/lang/'.$this->lang['name'].'/*.ini') as $file) { + $base = str_replace($this->lang['name'], 'en_english', $file); + $pathInfo = pathinfo($file); + $this->lang[$pathInfo['filename']] = array_merge(parse_ini_file($base), parse_ini_file($file)); + } + + $this->tpl->set('lang', $this->lang); + } + + /** + * check if user is login + * @return bool + */ + public function loginCheck() + { + if (isset($_SESSION['bat_user']) && isset($_SESSION['token']) && isset($_SESSION['userAgent']) && isset($_SESSION['IPaddress'])) { + if ($_SESSION['IPaddress'] != $_SERVER['REMOTE_ADDR']) { + return false; + } + if ($_SESSION['userAgent'] != $_SERVER['HTTP_USER_AGENT']) { + return false; + } + return true; + } else { + return false; + } + } +} diff --git a/inc/core/SiteModule.php b/inc/core/SiteModule.php new file mode 100644 index 0000000..1d66f69 --- /dev/null +++ b/inc/core/SiteModule.php @@ -0,0 +1,57 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Core; + +/** + * Site class for website's module controller + */ +abstract class SiteModule extends BaseModule +{ + /** + * Routes declarations for Site + * Moved from __construct() + * + * @return void + */ + public function routes() + { + } + + /** + * Set route + * + * @param string $pattern + * @param mixed $callback callable function or name of module method + * @return void + */ + protected function route($pattern, $callback) + { + if (is_callable($callback)) { + $this->core->router->set($pattern, $callback); + } else { + $this->core->router->set($pattern, function () use ($callback) { + return call_user_func_array([$this, $callback], func_get_args()); + }); + } + } + + /** + * Set site template + * + * @param string $file + * @return void + */ + protected function setTemplate($file) + { + $this->core->template = $file; + } +} diff --git a/inc/core/defines.php b/inc/core/defines.php new file mode 100644 index 0000000..d14d57b --- /dev/null +++ b/inc/core/defines.php @@ -0,0 +1,51 @@ + + * @author Wojciech Król + * @copyright 2017 Paweł Klockiewicz, Wojciech Król + * @license https://batflat.org/license + * @link https://batflat.org + */ + + if (!version_compare(PHP_VERSION, '5.5.0', '>=')) { + exit("Batflat requires at least PHP 5.5"); + } + + // Admin cat name + define('ADMIN', 'admin'); + + // Themes path + define('THEMES', BASE_DIR . '/themes'); + + // Modules path + define('MODULES', BASE_DIR . '/inc/modules'); + + // Uploads path + define('UPLOADS', BASE_DIR . '/uploads'); + + // Lock files + define('FILE_LOCK', false); + + // Basic modules + define('BASIC_MODULES', serialize([ + 8 => 'settings', + 0 => 'dashboard', + 2 => 'pages', + 3 => 'navigation', + 7 => 'users', + 1 => 'blog', + 4 => 'galleries', + 5 => 'snippets', + 6 => 'modules', + 9 => 'contact', + 10 => 'langswitcher', + 11 => 'devbar', + ])); + + // HTML beautifier + define('HTML_BEAUTY', false); + + // Developer mode + define('DEV_MODE', false); \ No newline at end of file diff --git a/inc/core/lib/Autoloader.php b/inc/core/lib/Autoloader.php new file mode 100644 index 0000000..ddc5f98 --- /dev/null +++ b/inc/core/lib/Autoloader.php @@ -0,0 +1,47 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +require_once('functions.php'); + +/** + * Batflat autoloader + */ +class Autoloader +{ + /** + * Autoload initialization + * + * @param string $className + * @return void + */ + public static function init($className) + { + // Convert directories to lowercase and process uppercase for class files + $className = explode('\\', $className); + $file = array_pop($className); + $file = strtolower(implode('/', $className)).'/'.$file.'.php'; + + if (strpos($_SERVER['SCRIPT_NAME'], '/'.ADMIN.'/') !== false) { + $file = '../'.$file; + } + if (is_readable($file)) { + require_once($file); + } + } +} + +header(gz64_decode("eNqL0HUuSk0sSU3Rdaq0UnBKLEnLSSxRsEmCMPTyi9LtANXtDCw")); +spl_autoload_register('Autoloader::init'); + +// Autoload vendors if exist +if (file_exists(BASE_DIR.'/vendor/autoload.php')) { + require_once(BASE_DIR.'/vendor/autoload.php'); +} diff --git a/inc/core/lib/Event.php b/inc/core/lib/Event.php new file mode 100644 index 0000000..5cbd924 --- /dev/null +++ b/inc/core/lib/Event.php @@ -0,0 +1,49 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Core\Lib; + +/** + * Events class + */ +class Event +{ + /** @var array */ + protected static $events = []; + + /** + * Add new event handler + * + * @param string $name + * @param callable $callback + * @return void + */ + public static function add($name, callable $callback) + { + static::$events[$name][] = $callback; + } + + /** + * Execute registered event handlers + * + * @param string $name + * @param array $params + * @return bool + */ + public static function call($name, array $params = []) + { + $return = true; + foreach (isset_or(static::$events[$name], []) as $value) { + $return = $return && (call_user_func_array($value, $params) !== false); + } + return $return; + } +} diff --git a/inc/core/lib/HttpRequest.php b/inc/core/lib/HttpRequest.php new file mode 100644 index 0000000..3db2ad8 --- /dev/null +++ b/inc/core/lib/HttpRequest.php @@ -0,0 +1,95 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Core\Lib; + +/** + * HttpRequest Class + */ +class HttpRequest +{ + /** + * Status of latest request + * + * @var string + */ + protected static $lastStatus = null; + + /** + * GET method request + * + * @param string $url + * @param array $datafields + * @param array $headers + * @return string Output + */ + public static function get($url, $datafields = [], $headers = []) + { + return self::request('GET', $url, $datafields, $headers); + } + + /** + * POST method request + * + * @param string $url + * @param array $datafields + * @param array $headers + * @return string Output + */ + public static function post($url, $datafields = [], $headers = []) + { + return self::request('POST', $url, $datafields, $headers); + } + + /** + * Get last request status + * + * @return string + */ + public static function getStatus() + { + return self::$lastStatus; + } + + /** + * Universal request method + * + * @param string $type GET, POST, PUT, DELETE, UPDATE + * @param string $url + * @param array $datafields + * @param array $headers + * @return string + */ + protected static function request($type, $url, $datafields, $headers) + { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $type); + + if (!empty($datafields)) { + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($datafields)); + } + + if (!empty($headers)) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + } + + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0); + curl_setopt($ch, CURLOPT_TIMEOUT, 3); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $output = curl_exec($ch); + self::$lastStatus = curl_error($ch); + curl_close($ch); + + return $output; + } +} diff --git a/inc/core/lib/Image.php b/inc/core/lib/Image.php new file mode 100644 index 0000000..98e72e8 --- /dev/null +++ b/inc/core/lib/Image.php @@ -0,0 +1,449 @@ + + * @link https://github.com/ushis/PHP-Image-Class + * @license DBAD License (http://philsturgeon.co.uk/code/dbad-license) + */ + +namespace Inc\Core\Lib; + +/** + * Image processor + */ +class Image +{ + + /** + * Image resource + * + * @var resource + * @access private + */ + private $image; + + /** + * Type of image + * + * @var string + * @access private + */ + private $type = 'png'; + + /** + * Width of the image in pixel + * + * @var integer + * @access private + */ + private $width; + + /** + * Height of the image in pixel + * + * @var integer + * @access private + */ + private $height; + + /** + * Return infos about the image + * + * @return array image infos + * @access public + */ + public function getInfos($what = null) + { + if ($what !== null && in_array($what, ['width', 'height', 'type'])) { + return $this->{$what}; + } + + return array( + 'width' => $this->width, + 'height' => $this->height, + 'type' => $this->type, + 'resource' => $this->image, + ); + } + + /** + * Get color in rgb + * + * @param string $hex Color in hexadecimal code + * @return array Color in rgb + * @access public + */ + private function hex2rgb($hex) + { + $hex = str_replace('#', '', $hex); + $hex = (preg_match('/^([a-fA-F0-9]{3})|([a-fA-F0-9]{6})$/', $hex)) ? $hex : '000'; + + switch (strlen($hex)) { + case 3: + $rgb['r'] = hexdec(substr($hex, 0, 1).substr($hex, 0, 1)); + $rgb['g'] = hexdec(substr($hex, 1, 1).substr($hex, 1, 1)); + $rgb['b'] = hexdec(substr($hex, 2, 1).substr($hex, 2, 1)); + break; + + case 6: + $rgb['r'] = hexdec(substr($hex, 0, 2)); + $rgb['g'] = hexdec(substr($hex, 2, 2)); + $rgb['b'] = hexdec(substr($hex, 4, 2)); + break; + } + + return $rgb; + } + + /** + * Creates image resource from file + * + * @param string $path Path to an image + * @return boolean true if resource was created + * @access public + */ + public function load($path) + { + if (empty($path)) { + return false; + } + + $file = @fopen($path, 'r'); + + if (!$file) { + return false; + } + + fclose($file); + $info = getimagesize($path); + + switch ($info[2]) { + case 1: + $this->image = imagecreatefromgif($path); + $this->type = 'gif'; + break; + + case 2: + $this->image = imagecreatefromjpeg($path); + $this->type = 'jpg'; + break; + + case 3: + $this->image = imagecreatefrompng($path); + $this->type = 'png'; + imagealphablending($this->image, false); + imagesavealpha($this->image, true); + break; + + default: + return false; + } + + $this->width = $info[0]; + $this->height = $info[1]; + return true; + } + + /** + * Creates image resource with background + * + * @param integer $width Width of the image + * @param integer $height Height of the image + * @param string $background Background color in hexadecimal code + * @return boolean true if resource was created + * @access public + */ + public function create($width, $height, $background = null) + { + if ($width > 0 && $height > 0) { + $this->image = imagecreatetruecolor($width, $height); + $this->width = $width; + $this->height = $height; + if (preg_match('/^([a-fA-F0-9]{3})|([a-fA-F0-9]{6})$/', $background)) { + $rgb = $this->hex2rgb($background); + $background = imagecolorallocate($this->image, $rgb['r'], $rgb['g'], $rgb['b']); + imagefill($this->image, 0, 0, $background); + } else { + imagealphablending($this->image, false); + $black = imagecolorallocate($this->image, 0, 0, 0); + imagefilledrectangle($this->image, 0, 0, $this->width, $this->height, $black); + imagecolortransparent($this->image, $black); + imagealphablending($this->image, true); + } + return true; + } + + return false; + } + + /** + * Resizes the image + * + * @param integer $width New width + * @param integer $height New height + * @return boolean true if image was resized + * @access public + */ + public function resize($width, $height = 0) + { + if ($width <= 0 && $height <= 0) { + return false; + } elseif ($width > 0 && $height <= 0) { + $height = $this->height*$width/$this->width; + } elseif ($width <= 0 && $height > 0) { + $width = $this->width*$height/$this->height; + } + + $image = imagecreatetruecolor($width, $height); + imagealphablending($image, false); + imagesavealpha($image, true); + imagecopyresampled($image, $this->image, 0, 0, 0, 0, $width, $height, $this->width, $this->height); + $this->image = $image; + $this->width = $width; + $this->height = $height; + return true; + } + + /** + * Crops a part of the image + * + * @param integer $x X-coordinate + * @param integer $y Y-coordinate + * @param integer $width Width of cutout + * @param integer $height Height of cutout + * @access public + */ + public function crop($x, $y, $width, $height) + { + $image = imagecreatetruecolor($width, $height); + imagealphablending($image, false); + imagesavealpha($image, true); + imagecopyresampled($image, $this->image, 0, 0, $x, $y, $width, $height, $width, $height); + $this->image = $image; + $this->width = $width; + $this->height = $height; + } + + /** + * Resizes and crops image to specified size + * + * @param int $width New width + * @param int $height New height + * @access public + * @return void + */ + public function fit($width, $height) + { + if ($this->width > $this->height) { + $this->resize(0, $height); + $this->crop(($this->width - $width) / 2, 0, $width, $height); + } else { + $this->resize($width); + $this->crop(0, ($this->height - $height) / 2, $width, $height); + } + } + + /** + * Rotates image + * + * @param integer $angle in degree + * @access public + */ + public function rotate($angle) + { + $this->image = imagerotate($this->image, $angle, 0); + } + + /** + * Creates rectangle + * + * @param integer $x1 X1-coordinate + * @param integer $y1 Y1-coordinate + * @param integer $x2 X2-coordinate + * @param integer $y2 Y2-coordinate + * @param string $color Color in hexadecimal code + * @access public + */ + public function rectangle($x1, $y1, $x2, $y2, $color) + { + $rgb = $this->hex2rgb($color); + $color = imagecolorallocate($this->image, $rgb['r'], $rgb['g'], $rgb['b']); + imagefilledrectangle($this->image, $x1, $y1, $x2, $y2, $color); + } + /** + * Creates ellipse + * + * @param integer $x X-coordinate + * @param integer $y Y-coordinate + * @param integer $width Width of ellipse + * @param integer $height Height of ellipse + * @param string $color Color in hexadecimal code + * @access public + */ + public function ellipse($x, $y, $width, $height, $color) + { + $rgb = $this->hex2rgb($color); + $color = imagecolorallocate($this->image, $rgb['r'], $rgb['g'], $rgb['b']); + imagefilledellipse($this->image, $x, $y, $width, $height, $color); + } + /** + * Creates polygon + * + * @param array $points Coordinates of the vertices + * @param string $color Color in hexadecimal code + * @access public + */ + public function polygon($points, $color) + { + $rgb = $this->hex2rgb($color); + $color = imagecolorallocate($this->image, $rgb['r'], $rgb['g'], $rgb['b']); + $num = count($points)/2; + imagefilledpolygon($this->image, $points, $num, $color); + } + /** + * Draws a line + * + * @param array $points Coordinates of the vertices + * @param string $color Color in hexadecimal code + * @access public + */ + public function line($points, $color) + { + $rgb = $this->hex2rgb($color); + $color = imagecolorallocate($this->image, $rgb['r'], $rgb['g'], $rgb['b']); + imageline($this->image, $points[0], $points[1], $points[2], $points[3], $color); + } + + /** + * Writes on image + * + * @param integer $x X-coordinate + * @param integer $y Y-coordinate + * @param string $font Path to ttf + * @param integer $size Font size + * @param integer $angle in degree + * @param string $color Color in hexadecimal code + * @param string $text Text + * @access public + */ + public function write($x, $y, $font, $size, $angle, $color, $text) + { + $rgb = $this->hex2rgb($color); + $color = imagecolorallocate($this->image, $rgb['r'], $rgb['g'], $rgb['b']); + imagettftext($this->image, $size, $angle, $x, $y, $color, $font, $text); + } + + /** + * Merges image with another + * + * @param Image $img object + * @param mixed $x X-coordinate + * @param mixed $y Y-coordinate + * @access public + */ + public function merge($img, $x, $y) + { + $infos = $img->getInfos(); + + switch ($x) { + case 'left': + $x = 0; + break; + + case 'right': + $x = $this->width-$infos['width']; + break; + + default: + $x = $x; + } + + switch ($y) { + case 'top': + $y = 0; + break; + + case 'bottom': + $y = $this->height-$infos['height']; + break; + + default: + $y = $y; + } + + imagealphablending($this->image, true); + imagecopy($this->image, $infos['resource'], $x, $y, 0, 0, $infos['width'], $infos['height']); + } + + /** + * Shows image + * + * @param string $type Filetype + * @access public + */ + public function show($type = 'png') + { + $type = ($type != 'gif' && $type != 'jpeg' && $type != 'png') ? $this->type : $type; + + switch ($type) { + case 'gif': + header('Content-type: image/gif'); + imagegif($this->image); + break; + + case 'jpeg': + header('Content-type: image/jpeg'); + imagejpeg($this->image, '', 100); + break; + + default: + header('Content-type: image/png'); + imagepng($this->image); + } + } + + /** + * Saves image + * + * @param string $path Path to location + * @param string $type Filetype + * @return boolean true if image was saved + * @access public + */ + public function save($path, $quality = 90) + { + $dir = dirname($path); + $type = pathinfo($path, PATHINFO_EXTENSION); + + if (!file_exists($dir) || !is_dir($dir)) { + return false; + } + + if (!is_writable($dir)) { + return false; + } + + if ($type != 'gif' && $type != 'jpeg' && $type != 'jpg' && $type != 'png') { + $type = $this->type; + $path .= '.'.$type; + } + + switch ($type) { + case 'gif': + imagegif($this->image, $path); + break; + + case 'jpeg': case 'jpg': + imagejpeg($this->image, $path, $quality); + break; + + default: + imagepng($this->image, $path); + } + + return true; + } +} diff --git a/inc/core/lib/Indenter.php b/inc/core/lib/Indenter.php new file mode 100644 index 0000000..17e6faf --- /dev/null +++ b/inc/core/lib/Indenter.php @@ -0,0 +1,184 @@ + ' ' + ); + private $inline_elements = array('b', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'cite', 'code', 'dfn', 'em', 'kbd', 'strong', 'samp', 'var', 'a', 'bdo', 'br', 'img', 'span', 'sub', 'sup'); + private $temporary_replacements_script = array(); + private $temporary_replacements_inline = array(); + + const ELEMENT_TYPE_BLOCK = 0; + const ELEMENT_TYPE_INLINE = 1; + + const MATCH_INDENT_NO = 0; + const MATCH_INDENT_DECREASE = 1; + const MATCH_INDENT_INCREASE = 2; + const MATCH_DISCARD = 3; + + /** + * @param array $options + */ + public function __construct(array $options = array()) + { + foreach ($options as $name => $value) { + if (!array_key_exists($name, $this->options)) { + trigger_error('Indenter: Unrecognized option.', E_USER_NOTICE); + } + + $this->options[$name] = $value; + } + } + + /** + * @param string $element_name Element name, e.g. "b". + * @param ELEMENT_TYPE_BLOCK|ELEMENT_TYPE_INLINE $type + * @return null + */ + public function setElementType($element_name, $type) + { + if ($type === static::ELEMENT_TYPE_BLOCK) { + $this->inline_elements = array_diff($this->inline_elements, array($element_name)); + } elseif ($type === static::ELEMENT_TYPE_INLINE) { + $this->inline_elements[] = $element_name; + } else { + trigger_error('Indenter: Unrecognized element type.', E_USER_NOTICE); + } + + $this->inline_elements = array_unique($this->inline_elements); + } + + /** + * @param string $input HTML input. + * @return string Indented HTML. + */ + public function indent($input) + { + $this->log = array(); + + // Dindent does not indent ', $input); + } + } + + // Removing double whitespaces to make the source code easier to read. + // $input = str_replace("\t", '', $input); + // $input = preg_replace('/\s{2,}/', ' ', $input); + + // Remove inline elements and replace them with text entities. + if (preg_match_all('/<(' . implode('|', $this->inline_elements) . ')[^>]*>(?:[^<]*)<\/\1>/', $input, $matches)) { + $this->temporary_replacements_inline = $matches[0]; + foreach ($matches[0] as $i => $match) { + $input = str_replace($match, 'ᐃ' . ($i + 1) . 'ᐃ', $input); + } + } + + $subject = $input; + + $output = ''; + + $next_line_indentation_level = 0; + + do { + $indentation_level = $next_line_indentation_level; + + $patterns = array( + // block tag + '/^(<([a-z]+)(?:[^>]*)>(?:[^<]*)<\/(?:\2)>)/' => static::MATCH_INDENT_NO, + // DOCTYPE + '/^]*)>/' => static::MATCH_INDENT_NO, + // tag with implied closing + '/^<(input|link|meta|base|br|img|hr)([^>]*)>/' => static::MATCH_INDENT_NO, + // opening tag + '/^<[^\/]([^>]*)>/' => static::MATCH_INDENT_INCREASE, + // closing tag + '/^<\/([^>]*)>/' => static::MATCH_INDENT_DECREASE, + // self-closing tag + '/^<(.+)\/>/' => static::MATCH_INDENT_DECREASE, + // whitespace + '/^(\s+)/' => static::MATCH_DISCARD, + // text node + '/([^<]+)/' => static::MATCH_INDENT_NO + ); + $rules = array('NO', 'DECREASE', 'INCREASE', 'DISCARD'); + + foreach ($patterns as $pattern => $rule) { + if ($match = preg_match($pattern, $subject, $matches)) { + $this->log[] = array( + 'rule' => $rules[$rule], + 'pattern' => $pattern, + 'subject' => $subject, + 'match' => $matches[0] + ); + + $subject = mb_substr($subject, mb_strlen($matches[0])); + + if ($rule === static::MATCH_DISCARD) { + break; + } + + if ($rule === static::MATCH_INDENT_NO) { + } elseif ($rule === static::MATCH_INDENT_DECREASE) { + $next_line_indentation_level--; + $indentation_level--; + } else { + $next_line_indentation_level++; + } + + if ($indentation_level < 0) { + $indentation_level = 0; + } + + $output .= str_repeat($this->options['indentation_character'], $indentation_level) . $matches[0] . "\n"; + + break; + } + } + } while ($match); + + $interpreted_input = ''; + foreach ($this->log as $e) { + $interpreted_input .= $e['match']; + } + + if ($interpreted_input !== $input) { + trigger_error('Indenter: Did not reproduce the exact input.', E_USER_NOTICE); + } + + $output = preg_replace('/(<(\w+)[^>]*>)\s*(<\/\2>)/', '\\1\\3', $output); + + foreach ($this->temporary_replacements_script as $i => $original) { + $output = str_replace('', $original, $output); + } + + foreach ($this->temporary_replacements_inline as $i => $original) { + $output = str_replace('ᐃ' . ($i + 1) . 'ᐃ', $original, $output); + } + + return trim($output); + } + + /** + * Debugging utility. Get log for the last indent operation. + * + * @return array + */ + public function getLog() + { + return $this->log; + } +} diff --git a/inc/core/lib/License.php b/inc/core/lib/License.php new file mode 100644 index 0000000..ff8fec4 --- /dev/null +++ b/inc/core/lib/License.php @@ -0,0 +1,94 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Core\Lib; + +/** + * Batflat License class + * By removing or modifing these procedures you break license. + */ +class License +{ + const FREE = 1; + const COMMERCIAL = 2; + const ERROR = 3; + const INVALID_DOMAIN = 4; + const TIME_OUT = 5; + + private static $feedURL = 'http://feed.sruu.pl'; + + public static function verify($license) + { + $license = self::unpack($license); + + if ($license[0] === false && $license[1] === false && $license[2] === false && $license[3] === false && $license[4] === false) { + return License::FREE; + } + + if ($license[0] == md5($license[1].$license[2].$license[3].domain(false))) { + if (time() < $license[4] || strtotime("-48 hours") > $license[4]) { + if (self::remoteCheck($license)) { + self::update($license); + return License::COMMERCIAL; + } elseif (strpos(HttpRequest::getStatus(), 'timed out') !== false) { + return License::TIME_OUT; + } else { + return License::ERROR; + } + } else { + return License::COMMERCIAL; + } + } + + return License::ERROR; + } + + public static function getLicenseData($domainCode) + { + $response = json_decode(HttpRequest::post(self::$feedURL.'/batflat/commercial/license/data', ['code' => $domainCode, 'domain' => domain(false)]), true); + + if (isset_or($response['status'], false) == 'verified') { + return $response['data']; + } + + return false; + } + + private static function unpack($code) + { + $code = base64_decode($code); + $code = empty($code) ? [] : json_decode($code, true); + $code = array_replace(array_fill(0, 5, false), $code); + array_walk($code, function (&$value) { + $value = is_numeric($value) ? intval($value) : $value; + }); + return $code; + } + + private static function update($license) + { + $license[4] = time(); + $core = $GLOBALS['core']; + $core->db('settings')->where('module', 'settings')->where('field', 'license')->save(['value' => base64_encode(json_encode($license))]); + } + + private static function remoteCheck($license) + { + $output = HttpRequest::post(self::$feedURL.'/batflat/commercial/license/verify', ['pid' => $license[1], 'code' => $license[2], 'domain' => domain(false), 'domainCode' => $license[3]]); + $output = json_decode($output, true); + + if (isset_or($output['status'], false) == 'verified') { + return true; + } + + return false; + } +} diff --git a/inc/core/lib/ModulesCollection.php b/inc/core/lib/ModulesCollection.php new file mode 100644 index 0000000..275f3e1 --- /dev/null +++ b/inc/core/lib/ModulesCollection.php @@ -0,0 +1,128 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Core\Lib; + +/** + * Batflat modules collection + */ +class ModulesCollection +{ + /** + * List of loaded modules + * + * @var array + */ + protected $modules = []; + + /** + * ModulesCollection constructor + * + * @param \Inc\Core\Main $core + */ + public function __construct($core) + { + $modules = array_column($core->db('modules')->asc('sequence')->toArray(), 'dir'); + if ($core instanceof \Inc\Core\Admin) { + $clsName = 'Admin'; + } else { + $clsName = 'Site'; + } + + foreach ($modules as $dir) { + $file = MODULES.'/'.$dir.'/'.$clsName.'.php'; + if (file_exists($file)) { + $namespace = 'inc\modules\\'.$dir.'\\'.$clsName; + $this->modules[$dir] = new $namespace($core); + } + } + + // Init loop + $this->initLoop(); + + // Routes loop for Site + if ($clsName != 'Admin') { + $this->routesLoop(); + } + } + + /** + * Executes all init methods + * + * @return void + */ + protected function initLoop() + { + foreach ($this->modules as $module) { + $module->init(); + } + } + + /** + * Executes all routes methods + * + * @return void + */ + protected function routesLoop() + { + foreach ($this->modules as $module) { + $module->routes(); + } + } + + /** + * Executes all finish methods + * + * @return void + */ + public function finishLoop() + { + foreach ($this->modules as $module) { + $module->finish(); + } + } + + /** + * Get list of modules as array + * + * @return array + */ + public function getArray() + { + return $this->modules; + } + + /** + * Check if collection has loaded module + * + * @param string $name + * @return bool + */ + public function has($name) + { + return array_key_exists($name, $this->modules); + } + + /** + * Get specified module by magic method + * + * @param string $module + * @return \Inc\Core\BaseModule + */ + public function __get($module) + { + if (isset($this->modules[$module])) { + return $this->modules[$module]; + } else { + return null; + } + } +} diff --git a/inc/core/lib/Pagination.php b/inc/core/lib/Pagination.php new file mode 100644 index 0000000..b6e105e --- /dev/null +++ b/inc/core/lib/Pagination.php @@ -0,0 +1,179 @@ + + * @link http://leonard.shtika.info + * @copyright (C) Leonard Shtika + * @license MIT. + */ + +namespace Inc\Core\Lib; + +class Pagination +{ + private $_currentPage; + private $_totalRecords; + private $_recordsPerPage; + private $_url; + + public function __construct($currentPage = 1, $totalRecords = 0, $recordsPerPage = 10, $url = '?page=%d') + { + $this->_currentPage = (int) $currentPage; + $this->_totalRecords = (int) $totalRecords; + $this->_recordsPerPage = (int) $recordsPerPage; + $this->_url = $url; + } + + /** + * Calculate offset + * @return int + * Example: for 10 recores per page + * page 1 has offset 0 + * page 2 has offset 10 + */ + public function offset() + { + return ($this->_currentPage - 1) * $this->_recordsPerPage; + } + + /** + * Get the records per page + * @return int + */ + public function getRecordsPerPage() + { + return $this->_recordsPerPage; + } + + /** + * Calculate total pages + */ + private function _totalPages() + { + return ceil($this->_totalRecords / $this->_recordsPerPage); + } + + /** + * Calculate previous page + * @return int + */ + private function _previousPage() + { + return $this->_currentPage - 1; + } + + /** + * Calculate next page + * @return int + */ + private function _nextPage() + { + return $this->_currentPage + 1; + } + + /** + * Check if there is a previous page + * @return boolean + */ + private function _hasPreviousPage() + { + return ($this->_previousPage() >= 1) ? true : false; + } + + /** + * Check if there is a next page + * @return boolean + */ + private function _hasNextPage() + { + return ($this->_nextPage() <= $this->_totalPages()) ? true : false; + } + + /** + * Generate navigation + * @param string $type ('pagination' or 'pager') + * @param int $maxLinks + * @return mixed (string or false) + */ + public function nav($type = 'pagination', $maxLinks = 10) + { + if ($this->_totalPages() > 1) { + $filename = htmlspecialchars(pathinfo($_SERVER["SCRIPT_FILENAME"], PATHINFO_BASENAME), ENT_QUOTES, "utf-8"); + + $links = ''; + + // Return all links of Pagination + return $links; + } else { + return false; + } + } +} diff --git a/inc/core/lib/Parsedown.php b/inc/core/lib/Parsedown.php new file mode 100644 index 0000000..ec38cdc --- /dev/null +++ b/inc/core/lib/Parsedown.php @@ -0,0 +1,1301 @@ +`~\\'; + protected $specialCharacters = array( + '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', + ); + protected $DefinitionData; + protected $StrongRegex = array( + '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s', + '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us', + ); + protected $EmRegex = array( + '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', + '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us', + ); + protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?'; + protected $voidElements = array( + 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', + ); + protected $textLevelElements = array( + 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont', + 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', + 'i', 'rp', 'del', 'code', 'strike', 'marquee', + 'q', 'rt', 'ins', 'font', 'strong', + 's', 'tt', 'sub', 'mark', + 'u', 'xm', 'sup', 'nobr', + 'var', 'ruby', + 'wbr', 'span', + 'time', + ); + private static $instances = array(); + + public function text($text) + { + # make sure no definitions are set + $this->DefinitionData = array(); + + # standardize line breaks + $text = str_replace(array("\r\n", "\r"), "\n", $text); + + # remove surrounding line breaks + $text = trim($text, "\n"); + + # split text into lines + $lines = explode("\n", $text); + + # iterate through lines to identify blocks + $markup = $this->lines($lines); + + # trim line breaks + $markup = trim($markup, "\n"); + + return $markup; + } + + public function setBreaksEnabled($breaksEnabled) + { + $this->breaksEnabled = $breaksEnabled; + + return $this; + } + + public function setMarkupEscaped($markupEscaped) + { + $this->markupEscaped = $markupEscaped; + + return $this; + } + + public function setUrlsLinked($urlsLinked) + { + $this->urlsLinked = $urlsLinked; + + return $this; + } + + protected $urlsLinked = true; + + protected $BlockTypes = array( + '#' => array('Header'), + '*' => array('Rule', 'List'), + '+' => array('List'), + '-' => array('SetextHeader', 'Table', 'Rule', 'List'), + '0' => array('List'), + '1' => array('List'), + '2' => array('List'), + '3' => array('List'), + '4' => array('List'), + '5' => array('List'), + '6' => array('List'), + '7' => array('List'), + '8' => array('List'), + '9' => array('List'), + ':' => array('Table'), + '<' => array('Comment', 'Markup'), + '=' => array('SetextHeader'), + '>' => array('Quote'), + '[' => array('Reference'), + '_' => array('Rule'), + '`' => array('FencedCode'), + '|' => array('Table'), + '~' => array('FencedCode'), + ); + + protected $unmarkedBlockTypes = array( + 'Code', + ); + + protected function lines(array $lines) + { + $CurrentBlock = null; + + foreach ($lines as $line) { + if (chop($line) === '') { + if (isset($CurrentBlock)) { + $CurrentBlock['interrupted'] = true; + } + + continue; + } + + if (strpos($line, "\t") !== false) { + $parts = explode("\t", $line); + + $line = $parts[0]; + + unset($parts[0]); + + foreach ($parts as $part) { + $shortage = 4 - mb_strlen($line, 'utf-8') % 4; + + $line .= str_repeat(' ', $shortage); + $line .= $part; + } + } + + $indent = 0; + + while (isset($line[$indent]) and $line[$indent] === ' ') { + $indent ++; + } + + $text = $indent > 0 ? substr($line, $indent) : $line; + + # ~ + + $Line = array('body' => $line, 'indent' => $indent, 'text' => $text); + + # ~ + + if (isset($CurrentBlock['continuable'])) { + $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock); + + if (isset($Block)) { + $CurrentBlock = $Block; + + continue; + } else { + if ($this->isBlockCompletable($CurrentBlock['type'])) { + $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); + } + } + } + + # ~ + + $marker = $text[0]; + + # ~ + + $blockTypes = $this->unmarkedBlockTypes; + + if (isset($this->BlockTypes[$marker])) { + foreach ($this->BlockTypes[$marker] as $blockType) { + $blockTypes []= $blockType; + } + } + + # + # ~ + + foreach ($blockTypes as $blockType) { + $Block = $this->{'block'.$blockType}($Line, $CurrentBlock); + + if (isset($Block)) { + $Block['type'] = $blockType; + + if (! isset($Block['identified'])) { + $Blocks []= $CurrentBlock; + + $Block['identified'] = true; + } + + if ($this->isBlockContinuable($blockType)) { + $Block['continuable'] = true; + } + + $CurrentBlock = $Block; + + continue 2; + } + } + + # ~ + + if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted'])) { + $CurrentBlock['element']['text'] .= "\n".$text; + } else { + $Blocks []= $CurrentBlock; + + $CurrentBlock = $this->paragraph($Line); + + $CurrentBlock['identified'] = true; + } + } + + # ~ + + if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) { + $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); + } + + # ~ + + $Blocks []= $CurrentBlock; + + unset($Blocks[0]); + + # ~ + + $markup = ''; + + foreach ($Blocks as $Block) { + if (isset($Block['hidden'])) { + continue; + } + + $markup .= "\n"; + $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']); + } + + $markup .= "\n"; + + # ~ + + return $markup; + } + + protected function isBlockContinuable($Type) + { + return method_exists($this, 'block'.$Type.'Continue'); + } + + protected function isBlockCompletable($Type) + { + return method_exists($this, 'block'.$Type.'Complete'); + } + + protected function blockCode($Line, $Block = null) + { + if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted'])) { + return; + } + + if ($Line['indent'] >= 4) { + $text = substr($Line['body'], 4); + + $Block = array( + 'element' => array( + 'name' => 'pre', + 'handler' => 'element', + 'text' => array( + 'name' => 'code', + 'text' => $text, + ), + ), + ); + + return $Block; + } + } + + protected function blockCodeContinue($Line, $Block) + { + if ($Line['indent'] >= 4) { + if (isset($Block['interrupted'])) { + $Block['element']['text']['text'] .= "\n"; + + unset($Block['interrupted']); + } + + $Block['element']['text']['text'] .= "\n"; + + $text = substr($Line['body'], 4); + + $Block['element']['text']['text'] .= $text; + + return $Block; + } + } + + protected function blockCodeComplete($Block) + { + $text = $Block['element']['text']['text']; + + $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8'); + + $Block['element']['text']['text'] = $text; + + return $Block; + } + + protected function blockComment($Line) + { + if ($this->markupEscaped) { + return; + } + + if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!') { + $Block = array( + 'markup' => $Line['body'], + ); + + if (preg_match('/-->$/', $Line['text'])) { + $Block['closed'] = true; + } + + return $Block; + } + } + + protected function blockCommentContinue($Line, array $Block) + { + if (isset($Block['closed'])) { + return; + } + + $Block['markup'] .= "\n" . $Line['body']; + + if (preg_match('/-->$/', $Line['text'])) { + $Block['closed'] = true; + } + + return $Block; + } + + protected function blockFencedCode($Line) + { + if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches)) { + $Element = array( + 'name' => 'code', + 'text' => '', + ); + + if (isset($matches[1])) { + $class = 'language-'.$matches[1]; + + $Element['attributes'] = array( + 'class' => $class, + ); + } + + $Block = array( + 'char' => $Line['text'][0], + 'element' => array( + 'name' => 'pre', + 'handler' => 'element', + 'text' => $Element, + ), + ); + + return $Block; + } + } + + protected function blockFencedCodeContinue($Line, $Block) + { + if (isset($Block['complete'])) { + return; + } + + if (isset($Block['interrupted'])) { + $Block['element']['text']['text'] .= "\n"; + + unset($Block['interrupted']); + } + + if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text'])) { + $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1); + + $Block['complete'] = true; + + return $Block; + } + + $Block['element']['text']['text'] .= "\n".$Line['body']; + ; + + return $Block; + } + + protected function blockFencedCodeComplete($Block) + { + $text = $Block['element']['text']['text']; + + $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8'); + + $Block['element']['text']['text'] = $text; + + return $Block; + } + + protected function blockHeader($Line) + { + if (isset($Line['text'][1])) { + $level = 1; + + while (isset($Line['text'][$level]) and $Line['text'][$level] === '#') { + $level ++; + } + + if ($level > 6) { + return; + } + + $text = trim($Line['text'], '# '); + + $Block = array( + 'element' => array( + 'name' => 'h' . min(6, $level), + 'text' => $text, + 'handler' => 'line', + ), + ); + + return $Block; + } + } + + protected function blockList($Line) + { + list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]'); + + if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches)) { + $Block = array( + 'indent' => $Line['indent'], + 'pattern' => $pattern, + 'element' => array( + 'name' => $name, + 'handler' => 'elements', + ), + ); + + $Block['li'] = array( + 'name' => 'li', + 'handler' => 'li', + 'text' => array( + $matches[2], + ), + ); + + $Block['element']['text'] []= & $Block['li']; + + return $Block; + } + } + + protected function blockListContinue($Line, array $Block) + { + if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches)) { + if (isset($Block['interrupted'])) { + $Block['li']['text'] []= ''; + + unset($Block['interrupted']); + } + + unset($Block['li']); + + $text = isset($matches[1]) ? $matches[1] : ''; + + $Block['li'] = array( + 'name' => 'li', + 'handler' => 'li', + 'text' => array( + $text, + ), + ); + + $Block['element']['text'] []= & $Block['li']; + + return $Block; + } + + if ($Line['text'][0] === '[' and $this->blockReference($Line)) { + return $Block; + } + + if (! isset($Block['interrupted'])) { + $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); + + $Block['li']['text'] []= $text; + + return $Block; + } + + if ($Line['indent'] > 0) { + $Block['li']['text'] []= ''; + + $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); + + $Block['li']['text'] []= $text; + + unset($Block['interrupted']); + + return $Block; + } + } + + protected function blockQuote($Line) + { + if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) { + $Block = array( + 'element' => array( + 'name' => 'blockquote', + 'handler' => 'lines', + 'text' => (array) $matches[1], + ), + ); + + return $Block; + } + } + + protected function blockQuoteContinue($Line, array $Block) + { + if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) { + if (isset($Block['interrupted'])) { + $Block['element']['text'] []= ''; + + unset($Block['interrupted']); + } + + $Block['element']['text'] []= $matches[1]; + + return $Block; + } + + if (! isset($Block['interrupted'])) { + $Block['element']['text'] []= $Line['text']; + + return $Block; + } + } + + protected function blockRule($Line) + { + if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text'])) { + $Block = array( + 'element' => array( + 'name' => 'hr' + ), + ); + + return $Block; + } + } + + protected function blockSetextHeader($Line, array $Block = null) + { + if (! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) { + return; + } + + if (chop($Line['text'], $Line['text'][0]) === '') { + $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; + + return $Block; + } + } + + protected function blockMarkup($Line) + { + if ($this->markupEscaped) { + return; + } + + if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches)) { + $element = strtolower($matches[1]); + + if (in_array($element, $this->textLevelElements)) { + return; + } + + $Block = array( + 'name' => $matches[1], + 'depth' => 0, + 'markup' => $Line['text'], + ); + + $length = strlen($matches[0]); + + $remainder = substr($Line['text'], $length); + + if (trim($remainder) === '') { + if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) { + $Block['closed'] = true; + + $Block['void'] = true; + } + } else { + if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) { + return; + } + + if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder)) { + $Block['closed'] = true; + } + } + + return $Block; + } + } + + protected function blockMarkupContinue($Line, array $Block) + { + if (isset($Block['closed'])) { + return; + } + + if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) { # open + $Block['depth'] ++; + } + + if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) { # close + if ($Block['depth'] > 0) { + $Block['depth'] --; + } else { + $Block['closed'] = true; + } + } + + if (isset($Block['interrupted'])) { + $Block['markup'] .= "\n"; + + unset($Block['interrupted']); + } + + $Block['markup'] .= "\n".$Line['body']; + + return $Block; + } + + protected function blockReference($Line) + { + if (preg_match('/^\[(.+?)\]:[ ]*?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches)) { + $id = strtolower($matches[1]); + + $Data = array( + 'url' => $matches[2], + 'title' => null, + ); + + if (isset($matches[3])) { + $Data['title'] = $matches[3]; + } + + $this->DefinitionData['Reference'][$id] = $Data; + + $Block = array( + 'hidden' => true, + ); + + return $Block; + } + } + + protected function blockTable($Line, array $Block = null) + { + if (! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) { + return; + } + + if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '') { + $alignments = array(); + + $divider = $Line['text']; + + $divider = trim($divider); + $divider = trim($divider, '|'); + + $dividerCells = explode('|', $divider); + + foreach ($dividerCells as $dividerCell) { + $dividerCell = trim($dividerCell); + + if ($dividerCell === '') { + continue; + } + + $alignment = null; + + if ($dividerCell[0] === ':') { + $alignment = 'left'; + } + + if (substr($dividerCell, - 1) === ':') { + $alignment = $alignment === 'left' ? 'center' : 'right'; + } + + $alignments []= $alignment; + } + + # ~ + + $HeaderElements = array(); + + $header = $Block['element']['text']; + + $header = trim($header); + $header = trim($header, '|'); + + $headerCells = explode('|', $header); + + foreach ($headerCells as $index => $headerCell) { + $headerCell = trim($headerCell); + + $HeaderElement = array( + 'name' => 'th', + 'text' => $headerCell, + 'handler' => 'line', + ); + + if (isset($alignments[$index])) { + $alignment = $alignments[$index]; + + $HeaderElement['attributes'] = array( + 'style' => 'text-align: '.$alignment.';', + ); + } + + $HeaderElements []= $HeaderElement; + } + + # ~ + + $Block = array( + 'alignments' => $alignments, + 'identified' => true, + 'element' => array( + 'name' => 'table', + 'handler' => 'elements', + ), + ); + + $Block['element']['text'] []= array( + 'name' => 'thead', + 'handler' => 'elements', + ); + + $Block['element']['text'] []= array( + 'name' => 'tbody', + 'handler' => 'elements', + 'text' => array(), + ); + + $Block['element']['text'][0]['text'] []= array( + 'name' => 'tr', + 'handler' => 'elements', + 'text' => $HeaderElements, + ); + + return $Block; + } + } + + protected function blockTableContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) { + return; + } + + if ($Line['text'][0] === '|' or strpos($Line['text'], '|')) { + $Elements = array(); + + $row = $Line['text']; + + $row = trim($row); + $row = trim($row, '|'); + + preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches); + + foreach ($matches[0] as $index => $cell) { + $cell = trim($cell); + + $Element = array( + 'name' => 'td', + 'handler' => 'line', + 'text' => $cell, + ); + + if (isset($Block['alignments'][$index])) { + $Element['attributes'] = array( + 'style' => 'text-align: '.$Block['alignments'][$index].';', + ); + } + + $Elements []= $Element; + } + + $Element = array( + 'name' => 'tr', + 'handler' => 'elements', + 'text' => $Elements, + ); + + $Block['element']['text'][1]['text'] []= $Element; + + return $Block; + } + } + + protected function paragraph($Line) + { + $Block = array( + 'element' => array( + 'name' => 'p', + 'text' => $Line['text'], + 'handler' => 'line', + ), + ); + + return $Block; + } + + protected $InlineTypes = array( + '"' => array('SpecialCharacter'), + '!' => array('Image'), + '&' => array('SpecialCharacter'), + '*' => array('Emphasis'), + ':' => array('Url'), + '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'), + '>' => array('SpecialCharacter'), + '[' => array('Link'), + '_' => array('Emphasis'), + '`' => array('Code'), + '~' => array('Strikethrough'), + '\\' => array('EscapeSequence'), + ); + + public function line($text) + { + $markup = ''; + + # $excerpt is based on the first occurrence of a marker + + while ($excerpt = strpbrk($text, $this->inlineMarkerList)) { + $marker = $excerpt[0]; + + $markerPosition = strpos($text, $marker); + + $Excerpt = array('text' => $excerpt, 'context' => $text); + + foreach ($this->InlineTypes[$marker] as $inlineType) { + $Inline = $this->{'inline'.$inlineType}($Excerpt); + + if (! isset($Inline)) { + continue; + } + + # makes sure that the inline belongs to "our" marker + + if (isset($Inline['position']) and $Inline['position'] > $markerPosition) { + continue; + } + + # sets a default inline position + + if (! isset($Inline['position'])) { + $Inline['position'] = $markerPosition; + } + + # the text that comes before the inline + $unmarkedText = substr($text, 0, $Inline['position']); + + # compile the unmarked text + $markup .= $this->unmarkedText($unmarkedText); + + # compile the inline + $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']); + + # remove the examined text + $text = substr($text, $Inline['position'] + $Inline['extent']); + + continue 2; + } + + # the marker does not belong to an inline + + $unmarkedText = substr($text, 0, $markerPosition + 1); + + $markup .= $this->unmarkedText($unmarkedText); + + $text = substr($text, $markerPosition + 1); + } + + $markup .= $this->unmarkedText($text); + + return $markup; + } + + protected function inlineCode($Excerpt) + { + $marker = $Excerpt['text'][0]; + + if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(? strlen($matches[0]), + 'element' => array( + 'name' => 'code', + 'text' => $text, + ), + ); + } + } + + protected function inlineEmailTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches)) { + $url = $matches[1]; + + if (! isset($matches[2])) { + $url = 'mailto:' . $url; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $matches[1], + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + protected function inlineEmphasis($Excerpt) + { + if (! isset($Excerpt['text'][1])) { + return; + } + + $marker = $Excerpt['text'][0]; + + if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) { + $emphasis = 'strong'; + } elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) { + $emphasis = 'em'; + } else { + return; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => $emphasis, + 'handler' => 'line', + 'text' => $matches[1], + ), + ); + } + + protected function inlineEscapeSequence($Excerpt) + { + if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters)) { + return array( + 'markup' => $Excerpt['text'][1], + 'extent' => 2, + ); + } + } + + protected function inlineImage($Excerpt) + { + if (! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[') { + return; + } + + $Excerpt['text']= substr($Excerpt['text'], 1); + + $Link = $this->inlineLink($Excerpt); + + if ($Link === null) { + return; + } + + $Inline = array( + 'extent' => $Link['extent'] + 1, + 'element' => array( + 'name' => 'img', + 'attributes' => array( + 'src' => $Link['element']['attributes']['href'], + 'alt' => $Link['element']['text'], + ), + ), + ); + + $Inline['element']['attributes'] += $Link['element']['attributes']; + + unset($Inline['element']['attributes']['href']); + + return $Inline; + } + + protected function inlineLink($Excerpt) + { + $Element = array( + 'name' => 'a', + 'handler' => 'line', + 'text' => null, + 'attributes' => array( + 'href' => null, + 'title' => null, + ), + ); + + $extent = 0; + + $remainder = $Excerpt['text']; + + if (preg_match('/\[((?:[^][]|(?R))*)\]/', $remainder, $matches)) { + $Element['text'] = $matches[1]; + + $extent += strlen($matches[0]); + + $remainder = substr($remainder, $extent); + } else { + return; + } + + if (preg_match('/^[(]((?:[^ ()]|[(][^ )]+[)])+)(?:[ ]+("[^"]*"|\'[^\']*\'))?[)]/', $remainder, $matches)) { + $Element['attributes']['href'] = $matches[1]; + + if (isset($matches[2])) { + $Element['attributes']['title'] = substr($matches[2], 1, - 1); + } + + $extent += strlen($matches[0]); + } else { + if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) { + $definition = strlen($matches[1]) ? $matches[1] : $Element['text']; + $definition = strtolower($definition); + + $extent += strlen($matches[0]); + } else { + $definition = strtolower($Element['text']); + } + + if (! isset($this->DefinitionData['Reference'][$definition])) { + return; + } + + $Definition = $this->DefinitionData['Reference'][$definition]; + + $Element['attributes']['href'] = $Definition['url']; + $Element['attributes']['title'] = $Definition['title']; + } + + $Element['attributes']['href'] = str_replace(array('&', '<'), array('&', '<'), $Element['attributes']['href']); + + return array( + 'extent' => $extent, + 'element' => $Element, + ); + } + + protected function inlineMarkup($Excerpt) + { + if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false) { + return; + } + + if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches)) { + return array( + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ); + } + + if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches)) { + return array( + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ); + } + + if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches)) { + return array( + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ); + } + } + + protected function inlineSpecialCharacter($Excerpt) + { + if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text'])) { + return array( + 'markup' => '&', + 'extent' => 1, + ); + } + + $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot'); + + if (isset($SpecialCharacter[$Excerpt['text'][0]])) { + return array( + 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';', + 'extent' => 1, + ); + } + } + + protected function inlineStrikethrough($Excerpt) + { + if (! isset($Excerpt['text'][1])) { + return; + } + + if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) { + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'del', + 'text' => $matches[1], + 'handler' => 'line', + ), + ); + } + } + + protected function inlineUrl($Excerpt) + { + if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') { + return; + } + + if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) { + $Inline = array( + 'extent' => strlen($matches[0][0]), + 'position' => $matches[0][1], + 'element' => array( + 'name' => 'a', + 'text' => $matches[0][0], + 'attributes' => array( + 'href' => $matches[0][0], + ), + ), + ); + + return $Inline; + } + } + + protected function inlineUrlTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches)) { + $url = str_replace(array('&', '<'), array('&', '<'), $matches[1]); + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + protected function unmarkedText($text) + { + if ($this->breaksEnabled) { + $text = preg_replace('/[ ]*\n/', "
\n", $text); + } else { + $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "
\n", $text); + $text = str_replace(" \n", "\n", $text); + } + + return $text; + } + + protected function element(array $Element) + { + $markup = '<'.$Element['name']; + + if (isset($Element['attributes'])) { + foreach ($Element['attributes'] as $name => $value) { + if ($value === null) { + continue; + } + + $markup .= ' '.$name.'="'.$value.'"'; + } + } + + if (isset($Element['text'])) { + $markup .= '>'; + + if (isset($Element['handler'])) { + $markup .= $this->{$Element['handler']}($Element['text']); + } else { + $markup .= $Element['text']; + } + + $markup .= ''; + } else { + $markup .= ' />'; + } + + return $markup; + } + + protected function elements(array $Elements) + { + $markup = ''; + + foreach ($Elements as $Element) { + $markup .= "\n" . $this->element($Element); + } + + $markup .= "\n"; + + return $markup; + } + + protected function li($lines) + { + $markup = $this->lines($lines); + + $trimmedMarkup = trim($markup); + + if (! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '

') { + $markup = $trimmedMarkup; + $markup = substr($markup, 3); + + $position = strpos($markup, "

"); + + $markup = substr_replace($markup, '', $position, 4); + } + + return $markup; + } + + public function parse($text) + { + $markup = $this->text($text); + + return $markup; + } + + public static function instance($name = 'default') + { + if (isset(self::$instances[$name])) { + return self::$instances[$name]; + } + + $instance = new static(); + + self::$instances[$name] = $instance; + + return $instance; + } +} diff --git a/inc/core/lib/QueryBuilder.php b/inc/core/lib/QueryBuilder.php new file mode 100644 index 0000000..c8c70f8 --- /dev/null +++ b/inc/core/lib/QueryBuilder.php @@ -0,0 +1,897 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Core\Lib; + +/** + * Batflat QueryBuilder class + */ +class QueryBuilder +{ + protected static $db = null; + + protected static $last_sqls = []; + + protected static $options = []; + + protected $table = null; + + protected $columns = []; + + protected $joins = []; + + protected $conditions = []; + + protected $condition_binds = []; + + protected $sets = []; + + protected $set_binds = []; + + protected $orders = []; + + protected $group_by = []; + + protected $having = []; + + protected $limit = ''; + + protected $offset = ''; + + /** + * constructor + * + * @param string $table + */ + public function __construct($table = null) + { + if ($table) { + $this->table = $table; + } + } + + /** + * PDO instance + * + * @return PDO + */ + public static function pdo() + { + return static::$db; + } + + /** + * last SQL queries + * + * @return array SQLs array + */ + public static function lastSqls() + { + return static::$last_sqls; + } + + /** + * creates connection with database + * + * Qb::connect($dsn); // default user, password and options + * Qb::connect($dsn, $user); // default password and options + * Qb::connect($dsn, $user, $pass); // default options + * Qb::connect($dsn, $user, $pass, $options); + * Qb::connect($dsn, $options); + * Qb::connect($dsn, $user, $options); + * + * @param string $dsn + * @param string $user + * @param string $pass + * @param array $options + * primary_key: primary column name, default: 'id' + * error_mode: default: \PDO::ERRMODE_EXCEPTION + * json_options: default: JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT + */ + public static function connect($dsn, $user = '', $pass = '', $options = []) + { + if (is_array($user)) { + $options = $user; + $user = ''; + $pass = ''; + } elseif (is_array($pass)) { + $options = $pass; + $pass = ''; + } + static::$options = array_merge([ + 'primary_key' => 'id', + 'error_mode' => \PDO::ERRMODE_EXCEPTION, + 'json_options' => JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT, + ], $options); + static::$db = new \PDO($dsn, $user, $pass); + static::$db->setAttribute(\PDO::ATTR_ERRMODE, static::$options['error_mode']); + if (strpos($dsn, 'sqlite') !== false) { + static::$db->exec("pragma synchronous = off;"); + } + } + + /** + * close connection with database + */ + public static function close() + { + static::$db = null; + } + + /** + * get or set options + * + * @param string $name + * @param mixed $value + */ + public static function config($name, $value = null) + { + if ($value === null) { + return static::$options[$name]; + } else { + static::$options[$name] = $value; + } + } + + /** + * SELECT + * + * select('column1')->select('column2') // SELECT column1, column2 + * select(['column1', 'column2', ...]) // SELECT column1, column2, ... + * select(['alias1' => 'column1', 'column2', ...]) // SELECT column1 AS alias1, column2, ... + * + * @param string|array $columns + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function select($columns) + { + if (!is_array($columns)) { + $columns = array($columns); + } + foreach ($columns as $alias => $column) { + if (!is_numeric($alias)) { + $column .= " AS $alias"; + } + array_push($this->columns, $column); + } + return $this; + } + + /** + * INNER JOIN + * + * @param string $table + * @param string $condition + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function join($table, $condition) + { + array_push($this->joins, "INNER JOIN $table ON $condition"); + return $this; + } + + /** + * LEFT OUTER JOIN + * + * @param string $table + * @param string $condition + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function leftJoin($table, $condition) + { + array_push($this->joins, "LEFT JOIN $table ON $condition"); + return $this; + } + + /** + * HAVING + * + * having(aggregate_function, operator, value) // HAVING aggregate_function (=, <, >, <=, >=, <>) value + * having(aggregate_function, value) // HAVING aggregate_function = value + * + * @param string $aggregate_function + * @param mixed $value + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function having($aggregate_function, $operator, $value = null, $ao = 'AND') + { + if ($value === null) { + $value = $operator; + $operator = '='; + } + + if (is_array($value)) { + $qs = '(' . implode(',', array_fill(0, count($value), '?')) . ')'; + if (empty($this->having)) { + array_push($this->having, "$aggregate_function $operator $qs"); + } else { + array_push($this->having, "$ao $aggregate_function $operator $qs"); + } + foreach ($value as $v) { + array_push($this->condition_binds, $v); + } + } else { + if (empty($this->having)) { + array_push($this->having, "$aggregate_function $operator ?"); + } else { + array_push($this->having, "$ao $aggregate_function $operator ?"); + } + array_push($this->condition_binds, $value); + } + return $this; + } + + public function orHaving($aggregate_function, $operator, $value = null) + { + return $this->having($aggregate_function, $operator, $value, 'OR'); + } + + /** + * WHERE + * + * where(column, operator, value) // WHERE column (=, <, >, <=, >=, <>) value + * where(column, value) // WHERE column = value + * where(value) // WHERE id = value + * where(function($st) { + * $st->where()... + * }) + * + * @param mixed $column + * @param mixed $value + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function where($column, $operator = null, $value = null, $ao = 'AND') + { + // Where group + if (!is_string($column) && is_callable($column)) { + if (empty($this->conditions) || strpos(end($this->conditions), '(') !== false) { + array_push($this->conditions, '('); + } else { + array_push($this->conditions, $ao.' ('); + } + + call_user_func($column, $this); + array_push($this->conditions, ')'); + + return $this; + } + + if ($operator === null) { + $value = $column; + $column = static::$options['primary_key']; + $operator = '='; + } elseif ($value === null) { + $value = $operator; + $operator = '='; + } + + if (is_array($value)) { + foreach ($value as $v) { + array_push($this->condition_binds, $v); + } + $value = '(' . implode(',', array_fill(0, count($value), '?')) . ')'; + } else { + array_push($this->condition_binds, $value); + } + + if (empty($this->conditions) || strpos(end($this->conditions), '(') !== false) { + array_push($this->conditions, "$column $operator ?"); + } else { + array_push($this->conditions, "$ao $column $operator ?"); + } + + return $this; + } + + /** + * OR WHERE + * + * orWhere(column, operator, value) // WHERE column (=, <, >, <=, >=, <>) value + * orWhere(column, value) // WHERE column = value + * orWhere(value) // WHERE id = value + * orWhere(function($st) { + * $st->where()... + * }) + * + * @param mixed $column + * @param mixed $value + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function orWhere($column, $operator = null, $value = null) + { + return $this->where($column, $operator, $value, 'OR'); + } + + /** + * WHERE IS NULL + * + * @param string $column + * @param string $ao + * @return \Inc\Core\Lib\QueryBuilder + */ + public function isNull($column, $ao = 'AND') + { + if (is_array($column)) { + foreach ($column as $c) { + $this->isNull($c, $ao); + } + + return $this; + } + + if (empty($this->conditions) || strpos(end($this->conditions), '(') !== false) { + array_push($this->conditions, "$column IS NULL"); + } else { + array_push($this->conditions, "$ao $column IS NULL"); + } + + return $this; + } + + /** + * WHERE IS NOT NULL + * + * @param string $column + * @param string $ao + * @return \Inc\Core\Lib\QueryBuilder + */ + public function isNotNull($column, $ao = 'AND') + { + if (is_array($column)) { + foreach ($column as $c) { + $this->isNotNull($c, $ao); + } + + return $this; + } + + if (empty($this->conditions) || strpos(end($this->conditions), '(') !== false) { + array_push($this->conditions, "$column IS NOT NULL"); + } else { + array_push($this->conditions, "$ao $column IS NOT NULL"); + } + + return $this; + } + + /** + * OR WHERE IS NULL + * + * @param string $column + * @return \Inc\Core\Lib\QueryBuilder + */ + public function orIsNull($column) + { + return $this->isNull($column, 'OR'); + } + + /** + * OR WHERE IS NOT NULL + * + * @param string $column + * @return \Inc\Core\Lib\QueryBuilder + */ + public function orIsNotNull($column) + { + return $this->isNotNull($column, 'OR'); + } + + /** + * WHERE LIKE + * + * @param string $column + * @param mixed $value + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function like($column, $value) + { + $this->where($column, 'LIKE', $value); + return $this; + } + + /** + * WHERE OR LIKE + * + * @param string $column + * @param mixed $value + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function orLike($column, $value) + { + $this->where($column, 'LIKE', $value, 'OR'); + return $this; + } + + /** + * WHERE NOT LIKE + * + * @param string $column + * @param mixed $value + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function notLike($column, $value) + { + $this->where($column, 'NOT LIKE', $value); + return $this; + } + + /** + * WHERE OR NOT LIKE + * + * @param string $column + * @param mixed $value + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function orNotLike($column, $value) + { + $this->where($column, 'NOT LIKE', $value, 'OR'); + return $this; + } + + /** + * WHERE IN + * + * @param string $column + * @param array $values + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function in($column, $values) + { + $this->where($column, 'IN', $values); + return $this; + } + + /** + * WHERE OR IN + * + * @param string $column + * @param array $values + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function orIn($column, $values) + { + $this->where($column, 'IN', $values, 'OR'); + return $this; + } + + /** + * WHERE NOT IN + * + * @param string $column + * @param array $values + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function notIn($column, $values) + { + $this->where($column, 'NOT IN', $values); + return $this; + } + + /** + * WHERE OR NOT IN + * + * @param string $column + * @param array $values + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function orNotIn($column, $values) + { + $this->where($column, 'NOT IN', $values, 'OR'); + return $this; + } + + /** + * get or set column value + * + * @param string $column + * @param mixed $value + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function set($column, $value = null) + { + if (is_array($column)) { + $sets = $column; + } else { + $sets = [$column => $value]; + } + $this->sets += $sets; + return $this; + } + + /** + * UPDATE or INSERT + * + * @param string $column + * @param mixed $value + * + * @return integer / boolean + */ + public function save($column = null, $value = null) + { + if ($column) { + $this->set($column, $value); + } + $st = $this->_build(); + if ($lid = static::$db->lastInsertId()) { + return $lid; + } else { + return $st; + } + } + + /** + * UPDATE + * + * @param string $column + * @param mixed $value + * + * @return boolean + */ + public function update($column = null, $value = null) + { + if ($column) { + $this->set($column, $value); + } + return $this->_build(['only_update' => true]); + } + + /** + * ORDER BY ASC + * + * @param string $column + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function asc($column) + { + array_push($this->orders, "$column ASC"); + return $this; + } + + /** + * ORDER BY DESC + * + * @param string $column + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function desc($column) + { + array_push($this->orders, "$column DESC"); + return $this; + } + + /** + * GROUP BY + * + * @param mixed $column + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function group($columns) + { + if (is_array($columns)) { + foreach ($columns as $column) { + array_push($this->group_by, "$column"); + } + } else { + array_push($this->group_by, "$columns"); + } + return $this; + } + + /** + * LIMIT + * + * @param integer $num + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function limit($num) + { + $this->limit = " LIMIT $num"; + return $this; + } + + /** + * OFFSET + * + * @param integer $num + * + * @return \Inc\Core\Lib\QueryBuilder + */ + public function offset($num) + { + $this->offset = " OFFSET $num"; + return $this; + } + + /** + * create array with all rows + * + * @return array + */ + public function toArray() + { + $st = $this->_build(); + return $st->fetchAll(\PDO::FETCH_ASSOC); + } + + /** + * create object with all rows + * + * @return \stdObject[] + */ + public function toObject() + { + $st = $this->_build(); + return $st->fetchAll(\PDO::FETCH_OBJ); + } + + /** + * create JSON array with all rows + * + * @return string + */ + public function toJson() + { + $rows = $this->toArray(); + return json_encode($rows, static::$options['json_options']); + } + + /** + * create array with one row + * + * @param string $column + * @param mixed $value + * + * @return array + */ + public function oneArray($column = null, $value = null) + { + if ($column !== null) { + $this->where($column, $value); + } + $st = $this->_build(); + return $st->fetch(\PDO::FETCH_ASSOC); + } + + /** + * create object with one row + * + * @param string $column + * @param mixed $value + * + * @return \stdObject + */ + public function oneObject($column = null, $value = null) + { + if ($column !== null) { + $this->where($column, $value); + } + $st = $this->_build(); + return $st->fetch(\PDO::FETCH_OBJ); + } + + /** + * create JSON array with one row + * + * @param string $column + * @param mixed $value + * + * @return string + */ + public function oneJson($column = null, $value = null) + { + if ($column !== null) { + $this->where($column, $value); + } + $row = $this->oneArray(); + return json_encode($row, static::$options['json_options']); + } + + /** + * returns rows count + * + * @return integer + */ + public function count() + { + $st = $this->_build('count'); + return $st->fetchColumn(); + } + + /** + * Last inserted id + * + * @return integer + */ + public function lastInsertId() + { + return static::$db->lastInsertId(); + } + + /** + * DELETE + * + * @param string $column + * @param mixed $value + */ + public function delete($column = null, $value = null) + { + if ($column !== null) { + $this->where($column, $value); + } + $st = $this->_build('delete'); + return $st->rowCount(); + } + + /** + * Create SQL query + * + * @param $type `default`, `delete`, `count` + * + * @return string + */ + public function toSql($type = 'default') + { + $sql = ''; + $sql_where = ''; + $sql_having = ''; + + // build conditions + $conditions = implode(' ', $this->conditions); + $conditions = str_replace(['( ', ' )'], ['(', ')'], $conditions); + if ($conditions) { + $sql_where .= " WHERE $conditions"; + } + + // build having + $having = implode(' ', $this->having); + if ($having) { + $sql_having .= " HAVING $having"; + } + + // if some columns have set value then UPDATE or INSERT + if ($this->sets) { + // get table columns + $table_cols = $this->_getColumns(); + + // Update updated_at column if exists + if (in_array('updated_at', $table_cols) && !array_key_exists('updated_at', $this->sets)) { + $this->set('updated_at', time()); + } + + // if there are some conditions then UPDATE + if (!empty($this->conditions)) { + $insert = false; + $columns = implode('=?,', array_keys($this->sets)) . '=?'; + $this->set_binds = array_values($this->sets); + $sql = "UPDATE $this->table SET $columns"; + $sql .= $sql_where; + + return $sql; + } + // if there aren't conditions, then INSERT + else { + // Update created_at column if exists + if (in_array('created_at', $table_cols) && !array_key_exists('created_at', $this->sets)) { + $this->set('created_at', time()); + } + + $columns = implode(',', array_keys($this->sets)); + $this->set_binds = array_values($this->sets); + $qs = implode(',', array_fill(0, count($this->sets), '?')); + $sql = "INSERT INTO $this->table($columns) VALUES($qs)"; + $this->condition_binds = array(); + + return $sql; + } + } else { + if ($type == 'delete') { + // DELETE + $sql = "DELETE FROM $this->table"; + $sql .= $sql_where; + + return $sql; + } else { + // SELECT + $columns = implode(',', $this->columns); + if (!$columns) { + $columns = '*'; + } + if ($type == 'count') { + $columns = "COUNT($columns) AS count"; + } + $sql = "SELECT $columns FROM $this->table"; + $joins = implode(' ', $this->joins); + if ($joins) { + $sql .= " $joins"; + } + $order = ''; + if (count($this->orders) > 0) { + $order = ' ORDER BY ' . implode(',', $this->orders); + } + + $group_by = ''; + if (count($this->group_by) > 0) { + $group_by = ' GROUP BY ' . implode(',', $this->group_by); + } + + $sql .= $sql_where . $group_by . $order . $sql_having . $this->limit . $this->offset; + + return $sql; + } + } + + return null; + } + /** + * build SQL query + * + * @param array $type `default`, `delete`, `count` + * + * @return PDOStatement + */ + protected function _build($type = 'default') + { + return $this->_query($this->toSql($type)); + } + + /** + * execute SQL query + * + * @param string $sql + * + * @return PDOStatement + */ + protected function _query($sql) + { + $binds = array_merge($this->set_binds, $this->condition_binds); + $st = static::$db->prepare($sql); + foreach ($binds as $key => $bind) { + $pdo_param = \PDO::PARAM_STR; + if (is_numeric($bind)) { + $pdo_param = \PDO::PARAM_INT; + } + $st->bindValue($key+1, $bind, $pdo_param); + } + $st->execute(); + static::$last_sqls[] = $sql; + return $st; + } + + /** + * Get current table columns + * + * @return array + */ + protected function _getColumns() + { + $q = $this->pdo()->query("PRAGMA table_info(".$this->table.")")->fetchAll(); + return array_column($q, 'name'); + } +} diff --git a/inc/core/lib/Router.php b/inc/core/lib/Router.php new file mode 100644 index 0000000..16e83ad --- /dev/null +++ b/inc/core/lib/Router.php @@ -0,0 +1,110 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Core\Lib; + +/** + * Batflat Router class + */ +class Router +{ + /** + * Declared routes + * + * @var array + */ + private $routes = array(); + + /** + * Route patterns that converts to regexp style + * + * @var array + */ + private $patterns = array( + ':any' => '.*', + ':int' => '[0-9]+', + ':str' => '[a-zA-Z0-9_-]+', + ); + + /** + * Set route + * + * @param string $pattern + * @param callable $callback + * @return void + */ + public function set($pattern, $callback) + { + $pattern = str_replace('/', '\/', $pattern); + + $this->routes[$pattern] = $callback; + } + + /** + * Executes routing and parse matches + * + * @param boolean $returnPath + * @return mixed + */ + public function execute($returnPath = false) + { + if (empty($path) && empty($_SERVER['PATH_INFO'])) { + $_SERVER['PATH_INFO'] = explode("?", $_SERVER['REQUEST_URI'])[0]; + } + + $url = rtrim(dirname($_SERVER["SCRIPT_NAME"]), '/'); + $url = trim(str_replace($url, '', $_SERVER['PATH_INFO']), '/'); + + if ($returnPath) { + return $url; + } + + $patterns = '/('.implode('|', array_keys($this->patterns)).')/'; + uksort($this->routes, function ($a, $b) use ($patterns) { + $pointsA = preg_match_all('/(\/)/', $a); + $pointsB = preg_match_all('/(\/)/', $b); + + if ($pointsA == $pointsB) { + $pointsA = preg_match_all($patterns, $a); + $pointsB = preg_match_all($patterns, $b); + } + + return $pointsA > $pointsB; + }); + + foreach ($this->routes as $pattern => $callback) { + if (strpos($pattern, ':') !== false) { + $pattern = str_replace(array_keys($this->patterns), array_values($this->patterns), $pattern); + } + if (preg_match('#^'.$pattern.'$#', $url, $params) === 1) { + array_shift($params); + array_walk($params, function (&$val) { + $val = $val ?: null; + }); + + return call_user_func_array($callback, array_values($params)); + } + } + + Event::call('router.notfound'); + } + + /** + * Change current "path" to custom + * + * @param string $path + * @return void + */ + public function changeRoute($path) + { + $_SERVER['PATH_INFO'] = $path; + } +} diff --git a/inc/core/lib/Settings.php b/inc/core/lib/Settings.php new file mode 100644 index 0000000..6268708 --- /dev/null +++ b/inc/core/lib/Settings.php @@ -0,0 +1,113 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Core\Lib; + +/** + * Batflat modules settings + */ +class Settings +{ + /** + * Instance of core class + * + * @var \Inc\Core\Main + */ + protected $core; + + /** + * Cached settings variables + * + * @var array + */ + protected $cache = []; + + /** + * Settings constructor + * + * @param \Inc\Core\Main $core + */ + public function __construct(\Inc\Core\Main $core) + { + $this->core = $core; + $this->reload(); + } + + /** + * Get all settings + * + * @return array + */ + public function all() + { + return $this->cache; + } + + /** + * Fetch fresh data from database + * + * @return void + */ + public function reload() + { + $results = $this->core->db('settings')->toArray(); + foreach ($results as $result) { + $this->cache[$result['module']][$result['field']] = $result['value']; + } + } + + /** + * Get specified field + * + * @param string $module Example 'module' or shorter 'module.field' + * @param string $field OPTIONAL + * @return string + */ + public function get($module, $field = false) + { + if (substr_count($module, '.') == 1) { + list($module, $field) = explode('.', $module); + } + + if (empty($field)) { + return $this->cache[$module]; + } + + return $this->cache[$module][$field]; + } + + /** + * Save specified settings value + * + * @param string $module Example 'module' or shorter 'module.field' + * @param string $field If module has field it contains value + * @param string $value OPTIONAL + * @return bool + */ + public function set($module, $field, $value = false) + { + if (substr_count($module, '.') == 1) { + $value = $field; + list($module, $field) = explode('.', $module); + } + + if ($value === false) { + throw new \Exception('Value cannot be empty'); + } + + if ($this->core->db('settings')->where('module', $module)->where('field', $field)->save(['value' => $value])) { + $this->cache[$module][$field] = $value; + return true; + } + + return false; + } +} diff --git a/inc/core/lib/Templates.php b/inc/core/lib/Templates.php new file mode 100644 index 0000000..f3373b1 --- /dev/null +++ b/inc/core/lib/Templates.php @@ -0,0 +1,306 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Core\Lib; + +/** + * Templates class + */ +class Templates +{ + /** + * Variables for template usage + * + * @var array + */ + private $data = []; + + /** + * Temporary directory for Templates cache + * + * @var string + */ + private $tmp = 'tmp/'; + + /** + * Template tags list + * + * @var array + */ + private $tags = [ + '{\*(.*?)\*}' => 'self::comment', + '{noparse}(.*?){\/noparse}' => 'self::noParse', + '{if: ([^}]*)}' => '', + '{else}' => '', + '{elseif: ([^}]*)}' => '', + '{\/if}' => '', + '{loop: ([^}]*) as ([^}]*)=>([^}]*)}' => '$3): ?>', + '{loop: ([^}]*) as ([^}]*)}' => ' $2): ?>', + '{loop: ([^}]*)}' => ' $value): ?>', + '{\/loop}' => '', + '{\?(\=){0,1}([^}]*)\?}' => '', + '{(\$[a-zA-Z\-\._\[\]\'"0-9]+)}' => '', + '{(\$[a-zA-Z\-\._\[\]\'"0-9]+)\|e}' => '', + '{(\$[a-zA-Z\-\._\[\]\'"0-9]+)\|cut:([0-9]+)}' => '', + '{widget: ([\.\-a-zA-Z0-9]+)}' => '', + '{include: (.+?\.[a-z]{2,4})}' => '', + '{template: (.+?\.[a-z]{2,4})}' => '', + '{lang: ([a-z]{2}_[a-z]+)}' => '', + '{/lang}' => '', + ]; + + /** + * Instance of Batflat core class + * + * @var \Inc\Core\Main + */ + public $core; + + /** + * Templates constructor + * + * @param Inc\Core\Main $object + */ + public function __construct($object) + { + $this->core = $object; + if (!file_exists($this->tmp)) { + mkdir($this->tmp); + } + } + + /** + * set variable + * @param string $name + * @param mixed $value + * @return Templates $this + */ + public function set($name, $value) + { + $this->data[$name] = $value; + + return $this; + } + + /** + * append array variable + * @param string $name + * @param mixed $value + * @return void + */ + public function append($name, $value) + { + $this->data[$name][] = $value; + } + + /** + * content parsing + * @param string $content + * @return string + */ + private function parse($content) + { + // replace tags with PHP + foreach ($this->tags as $regexp => $replace) { + if (strpos($replace, 'self') !== false) { + $content = preg_replace_callback('#'.$regexp.'#s', $replace, $content); + } else { + $content = preg_replace('#'.$regexp.'#', $replace, $content); + } + } + + // replace variables + if (preg_match_all('/(\$(?:[a-zA-Z0-9_-]+)(?:\.(?:(?:[a-zA-Z0-9_-][^\s]+)))*)/', $content, $matches)) { + $matches = $this->organize_array($matches); + usort($matches, function ($a, $b) { + return strlen($a[0]) < strlen($b[0]); + }); + + foreach ($matches as $match) { + // $a.b to $a["b"] + $rep = $this->replaceVariable($match[1]); + $content = str_replace($match[0], $rep, $content); + } + } + + // remove spaces betweend %% and $ + $content = preg_replace('/\%\%\s+/', '%%', $content); + + // call cv() for signed variables + if (preg_match_all('/\%\%(.)([a-zA-Z0-9_-]+)/', $content, $matches)) { + $matches = $this->organize_array($matches); + usort($matches, function ($a, $b) { + return strlen($a[2]) < strlen($b[2]); + }); + + foreach ($matches as $match) { + if ($match[1] == '$') { + $content = str_replace($match[0], 'cv($'.$match[2].')', $content); + } else { + $content = str_replace($match[0], $match[1].$match[2], $content); + } + } + } + + return $content; + } + + /** + * Organize preg_match_all matches array + * + * @param array $input + * @return array + */ + protected function organize_array($input) + { + for ($z = 0; $z < count($input); $z++) { + for ($x = 0; $x < count($input[$z]); $x++) { + $rt[$x][$z] = $input[$z][$x]; + } + } + + return $rt; + } + + /** + * execute PHP code + * @param string $file + * @return string + */ + private function execute($file, $counter = 0) + { + $pathInfo = pathinfo($file); + $tmpFile = $this->tmp.$pathInfo['basename']; + + if (!is_file($file)) { + echo "Template '$file' not found."; + } else { + $content = file_get_contents($file); + + if ($this->searchTags($content) && ($counter < 3)) { + file_put_contents($tmpFile, $content); + $content = $this->execute($tmpFile, ++$counter); + } + file_put_contents($tmpFile, $this->parse($content)); + + extract($this->data, EXTR_SKIP); + + ob_start(); + include($tmpFile); + if (!DEV_MODE) { + unlink($tmpFile); + } + return ob_get_clean(); + } + } + + /** + * display compiled code + * @param string $file + * @param bool $last + * @return string + */ + public function draw($file, $last = false) + { + if (preg_match('#inc(\/modules\/[^"]*\/)view\/([^"]*.'.pathinfo($file, PATHINFO_EXTENSION).')#', $file, $m)) { + $themeFile = THEMES.'/'.$this->core->settings->get('settings.theme').$m[1].$m[2]; + if (is_file($themeFile)) { + $file = $themeFile; + } + } + + $result = $this->execute($file); + if (!$last) { + return $result; + } else { + $result = str_replace(['*bracket*','*/bracket*'], ['{', '}'], $result); + $result = str_replace('*dollar*', '$', $result); + + if (HTML_BEAUTY) { + $tidyHTML = new Indenter; + return $tidyHTML->indent($result); + } + return $result; + } + } + + /** + * replace signs {,},$ in string with *words* + * @param string $content + * @return string + */ + public function noParse($content) + { + if (is_array($content)) { + $content = $content[1]; + } + $content = str_replace(['{', '}'], ['*bracket*', '*/bracket*'], $content); + return str_replace('$', '*dollar*', $content); + } + + /** + * replace signs {,},$ in array with *words* + * @param arry $array + * @return array + */ + public function noParse_array($array) + { + foreach ($array as $key => $value) { + if (is_array($value)) { + $array[$key] = $this->noParse_array($value); + } else { + $array[$key] = $this->noParse($value); + } + } + return $array; + } + + /** + * remove selected content from source code + * @param string $content + * @return null + */ + public function comment($content) + { + return null; + } + + /** + * search tags in content + * @param string $content + * @return bool + */ + private function searchTags($content) + { + foreach ($this->tags as $regexp => $replace) { + if (preg_match('#'.$regexp.'#sU', $content, $matches)) { + return true; + } + } + return false; + } + + /** + * Replace dot based variable to PHP version + * $a.b => $a['b'] + * + * @param string $var + * @return string + */ + private function replaceVariable($var) + { + if (strpos($var, '.') === false) { + return $var; + } + + return preg_replace('/\.([a-zA-Z\-_0-9]*(?![a-zA-Z\-_0-9]*(\'|\")))/', "['$1']", $var); + } +} diff --git a/inc/core/lib/Widget.php b/inc/core/lib/Widget.php new file mode 100644 index 0000000..ba2d6fb --- /dev/null +++ b/inc/core/lib/Widget.php @@ -0,0 +1,53 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Core\Lib; + +/** + * Widgets class + */ +class Widget +{ + /** @var array Widgets collection */ + protected static $widgets = []; + + /** + * Add widget to collection + * + * @param string $name + * @param callable $callback + * @return void + */ + public static function add($name, callable $callback) + { + static::$widgets[$name][] = $callback; + } + + /** + * Execute all widgets and get content + * + * @param string $name + * @param array $params + * @return string + */ + public static function call($name, $params = []) + { + $result = []; + foreach (isset_or(static::$widgets[$name], []) as $widget) { + $content = call_user_func_array($widget, $params); + if (is_string($content)) { + $result[] = $content; + } + } + + return implode("\n", $result); + } +} diff --git a/inc/core/lib/functions.php b/inc/core/lib/functions.php new file mode 100644 index 0000000..12b2af3 --- /dev/null +++ b/inc/core/lib/functions.php @@ -0,0 +1,423 @@ + + * @author Wojciech Król + * @copyright 2017 Paweł Klockiewicz, Wojciech Król + * @license https://batflat.org/license + * @link https://batflat.org + */ + +/** + * check if array have an empty values + * + * @param array $keys + * @param array $array + * + * @return boolean + */ +function checkEmptyFields(array $keys, array $array) +{ + foreach ($keys as $field) { + if (empty($array[$field])) { + return true; + } + } + + return false; +} + +/** + * delte dir with files + * + * @param string $path + * + * @return boolean + */ +function deleteDir($path) +{ + return !empty($path) && is_file($path) + ? @unlink($path) + : (array_reduce(glob($path.'/*'), + function ($r, $i) { + return $r && deleteDir($i); + }, true)) + && @rmdir($path); +} + +/** + * remove special chars from string + * + * @param string $text + * + * @return string + */ +function createSlug($text) +{ + setlocale(LC_ALL, 'pl_PL'); + $text = str_replace(' ', '-', trim($text)); + $text = str_replace('.', '-', trim($text)); + $text = iconv('utf-8', 'ascii//translit', $text); + $text = preg_replace('#[^a-z0-9\-]#si', '', $text); + + return strtolower(str_replace('\'', '', $text)); +} + +/** + * convert special chars from array + * + * @param array $array + * + * @return array + */ +function htmlspecialchars_array(array $array) +{ + foreach ($array as $key => $value) { + if (is_array($value)) { + $array[$key] = htmlspecialchars_array($value); + } else { + $array[$key] = htmlspecialchars($value, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + } + } + + return $array; +} + +/** + * convert all characters to HTML entities from array + * + * @param array $array + * + * @return array + */ +function htmlentities_array(array $array) +{ + foreach ($array as $key => $value) { + if (is_array($value)) { + $array[$key] = htmlentities_array($value); + } else { + $array[$key] = htmlentities($value, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + } + } + + return $array; +} + +/** + * redirect to URL + * + * @param string $url + * @param array $data + * + * @return void + */ +function redirect($url, array $data = []) +{ + if ($data) { + $_SESSION['REDIRECT_DATA'] = $data; + } + + header("Location: $url"); + exit(); +} + +/** + * get data from session + * + * @return array or null + */ +function getRedirectData() +{ + if (isset($_SESSION['REDIRECT_DATA'])) { + $tmp = $_SESSION['REDIRECT_DATA']; + unset($_SESSION['REDIRECT_DATA']); + + return $tmp; + } + + return null; +} + +/** + * Returns current url + * + * @param boolean $query + * + * @return string + */ +function currentURL($query = false) +{ + if (isset_or($GLOBALS['core'], null) instanceof \Inc\Core\Admin) { + $url = url(ADMIN.'/'.implode('/', parseURL())); + } else { + $url = url(implode('/', parseURL())); + } + + if ($query) { + return $url.'?'.$_SERVER['QUERY_STRING']; + } else { + return $url; + } +} + +/** + * parse URL + * + * @param int $key + * + * @return mixed array, string or false + */ +function parseURL($key = null) +{ + $url = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/'); + $url = trim(str_replace($url, '', $_SERVER['REQUEST_URI']), '/'); + $url = explode('?', $url); + $array = explode('/', $url[0]); + + if ($key) { + return isset_or($array[$key - 1], false); + } else { + return $array; + } +} + +/** + * add token to URL + * + * @param string $url + * + * @return string + */ +function addToken($url) +{ + if (isset($_SESSION['token'])) { + if (parse_url($url, PHP_URL_QUERY)) { + return $url.'&t='.$_SESSION['token']; + } else { + return $url.'?t='.$_SESSION['token']; + } + } + + return $url; +} + +/** + * create URL + * + * @param string / array $data + * + * @return string + */ +function url($data = null) +{ + if (filter_var($data, FILTER_VALIDATE_URL) !== false) { + return $data; + } + + if (!is_array($data) && strpos($data, '#') === 0) { + return $data; + } + + if ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') + || isset_or($_SERVER['SERVER_PORT'], null) == 443 + || isset_or($_SERVER['HTTP_X_FORWARDED_PORT'], null) == 443 + ) { + $protocol = 'https://'; + } else { + $protocol = 'http://'; + } + + $url = trim($protocol.$_SERVER['HTTP_HOST'].dirname($_SERVER['SCRIPT_NAME']), '/\\'); + $url = str_replace('/'.ADMIN, '', $url); + + if (is_array($data)) { + $url = $url.'/'.implode('/', $data); + } elseif ($data) { + $data = str_replace(BASE_DIR.'/', null, $data); + $url = $url.'/'.trim($data, '/'); + } + + if (strpos($url, '/'.ADMIN.'/') !== false) { + $url = addToken($url); + } + + return $url; +} + +/** + * Current domain name + * + * @return string + */ +function domain($with_protocol = true) +{ + $url = parse_url(url()); + + if ($with_protocol) { + return $url['scheme'].'://'.$url['host']; + } + + return $url['host']; +} + +/** + * toggle empty variable + * + * @param mixed $var + * @param mixed $alternate + * + * @return mixed + */ +function isset_or(&$var, $alternate = null) +{ + return (isset($var)) ? $var : $alternate; +} + +/** + * compares two version number strings + * + * @param string $a + * @param string $b + * + * @return int + */ +function cmpver($a, $b) +{ + $a = explode(".", $a); + $b = explode(".", $b); + foreach ($a as $depth => $aVal) { + if (isset($b[$depth])) { + $bVal = $b[$depth]; + } else { + $bVal = "0"; + } + + list($aLen, $bLen) = [strlen($aVal), strlen($bVal)]; + + if ($aLen > $bLen) { + $bVal = str_pad($bVal, $aLen, "0"); + } elseif ($bLen > $aLen) { + $aVal = str_pad($aVal, $bLen, "0"); + } + + if ($aVal == $bVal) { + continue; + } + + if ($aVal > $bVal) { + return 1; + } + + if ($aVal < $bVal) { + return -1; + } + } + + return 0; +} + +/** + * Limits string to specified length and appends with $end value + * + * @param string $text Input text + * @param integer $limit String max length + * @param string $end Appending variable if text is longer than limit + * + * @return string Limited string + */ +function str_limit($text, $limit = 100, $end = '...') +{ + if (mb_strlen($text, 'UTF-8') > $limit) { + return mb_substr($text, 0, $limit, 'UTF-8').$end; + } + + return $text; +} + +/** + * Get response headers list + * + * @param string $key + * + * @return mixed Array of headers or specified header by $key + */ +function get_headers_list($key = null) +{ + $headers_list = headers_list(); + $headers = []; + foreach ($headers_list as $header) { + $e = explode(":", $header); + $headers[strtolower(array_shift($e))] = trim(implode(":", $e)); + } + + if ($key) { + return isset_or($headers[strtolower($key)], false); + } + + return $headers; +} + +/** + * Generating random hash from specified characters + * + * @param int $length Hash length + * @param string $characters Characters for hash + * + * @return string Generated random string + */ +function str_gen($length, $characters = "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM") +{ + $return = null; + + if (is_string($characters)) { + $characters = str_split($characters); + } + + for ($i = 0; $i < $length; $i++) { + $return .= $characters[rand(0, count($characters) - 1)]; + } + + return $return; +} + +/** + * Compressed base64_encode + * + * @param strin $string + * + * @return string + */ +function gz64_encode($string) +{ + return str_replace(['+', '/'], ['_', '-'], trim(base64_encode(gzcompress($string, 9)), "=")); +} + +/** + * Decompress base64_decode + * + * @param string $string + * + * @return string + */ +function gz64_decode($string) +{ + return gzuncompress(base64_decode(str_replace(['_', '-'], ['+', '/'], $string))); +} + +/** + * Call variable which can be callback or other type. + * If it is anonymous function it will be executed, otherwise $variable will be returned. + * + * @param mixed $variable + * + * @return mixed + */ +function cv($variable) +{ + if (!is_string($variable) && is_callable($variable)) { + return $variable(); + } + + return $variable; +} diff --git a/inc/css/bootstrap-theme.min.css b/inc/css/bootstrap-theme.min.css new file mode 100644 index 0000000..2e6f5c0 --- /dev/null +++ b/inc/css/bootstrap-theme.min.css @@ -0,0 +1 @@ +@import url("//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700&subset=latin,latin-ext");body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#656565;background-color:#f5f5f5}a{color:#656d78}a:hover,a:focus{color:#434a54;text-decoration:underline}.img-rounded{border-radius:0}.img-thumbnail{padding:4px;line-height:1.4;background-color:#f5f5f5;border:1px solid #e9e9e9;border-radius:0}hr{margin-top:21px;margin-bottom:21px;border-top:1px solid #e9e9e9}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{color:#434a54}h1,.h1,h2,.h2,h3,.h3{margin-top:21px;margin-bottom:10.5px}h4,.h4,h5,.h5,h6,.h6{margin-top:10.5px;margin-bottom:10.5px}h1,.h1{font-size:39px}h2,.h2{font-size:32px}h3,.h3{font-size:26px}h4,.h4{font-size:19px}h5,.h5{font-size:15px}h6,.h6{font-size:13px}p{margin:0 0 10.5px}.lead{margin-bottom:21px;font-size:17px}@media (min-width:768px){.lead{font-size:22.5px}}small,.small{font-size:80%}mark,.mark{background-color:#fcf8e3}.text-muted{color:#434a54}.text-primary{color:#434a54}a.text-primary:hover,a.text-primary:focus{color:#2c3138}.text-success{color:#8cc152}a.text-success:hover,a.text-success:focus{color:#72a53b}.text-info{color:#3bafda}a.text-info:hover,a.text-info:focus{color:#2494be}.text-warning{color:#f8be12}a.text-warning:hover,a.text-warning:focus{color:#d19e06}.text-danger{color:#e9573f}a.text-danger:hover,a.text-danger:focus{color:#dc3519}.bg-primary{background-color:#434a54}a.bg-primary:hover,a.bg-primary:focus{background-color:#2c3138}.bg-success{background-color:#dff0d8}a.bg-success:hover,a.bg-success:focus{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover,a.bg-info:focus{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover,a.bg-warning:focus{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover,a.bg-danger:focus{background-color:#e4b9b9}.page-header{padding-bottom:9.5px;margin:42px 0 21px;border-bottom:1px solid #e9e9e9}ul,ol{margin-bottom:10.5px}dl{margin-bottom:21px}dt,dd{line-height:1.4}@media (min-width:768px){.dl-horizontal dt{width:160px}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{border-bottom:1px dotted #434a54}blockquote{padding:10.5px 21px;margin:0 0 21px;font-size:18.75px;border-left:5px solid #e9e9e9}blockquote footer,blockquote small,blockquote .small{line-height:1.4;color:#434a54}.blockquote-reverse,blockquote.pull-right{border-right:5px solid #e9e9e9}address{margin-bottom:21px;line-height:1.4}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{color:#656565;background-color:#f6f8fa;border-radius:0}kbd{color:#fff;background-color:#333;border-radius:0}pre{padding:10px;margin:0 0 10.5px;font-size:14px;line-height:1.4;color:#656d78;background-color:#f6f8fa;border:1px solid #e9e9e9;border-radius:0}.pre-scrollable{max-height:340px}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#434a54}.table{margin-bottom:21px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.4;border-top:1px solid #e9e9e9}.table>thead>tr>th{border-bottom:2px solid #e9e9e9}.table>tbody+tbody{border-top:2px solid #e9e9e9}.table .table{background-color:#f5f5f5}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #e9e9e9}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #e9e9e9}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}@media screen and (max-width:767px){.table-responsive{margin-bottom:15.75px;border:1px solid #e9e9e9}}legend{margin-bottom:21px;font-size:22.5px;color:#656d78;border-bottom:1px solid #e5e5e5}output{padding-top:9px;font-size:15px;line-height:1.4;color:#434a54}.form-control{height:39px;padding:8px 12px;font-size:15px;line-height:1.4;color:#434a54;background-color:#fff;border:1px solid #ccc;border-radius:0}.form-control:focus{border-color:#ffce54;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(255, 206, 84, 0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(255, 206, 84, 0.6)}.form-control::-moz-placeholder{color:#ccd1d9;opacity:1}.form-control:-ms-input-placeholder{color:#ccd1d9}.form-control::-webkit-input-placeholder{color:#ccd1d9}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#ccd1d9}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}@media screen and (-webkit-min-device-pixel-ratio:0){input[type="date"].form-control,input[type="time"].form-control,input[type="datetime-local"].form-control,input[type="month"].form-control{line-height:39px}input[type="date"].input-sm,input[type="time"].input-sm,input[type="datetime-local"].input-sm,input[type="month"].input-sm,.input-group-sm input[type="date"],.input-group-sm input[type="time"],.input-group-sm input[type="datetime-local"],.input-group-sm input[type="month"]{line-height:36px}input[type="date"].input-lg,input[type="time"].input-lg,input[type="datetime-local"].input-lg,input[type="month"].input-lg,.input-group-lg input[type="date"],.input-group-lg input[type="time"],.input-group-lg input[type="datetime-local"],.input-group-lg input[type="month"]{line-height:60px}}.form-group{margin-bottom:15px}.radio label,.checkbox label{min-height:21px}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"].disabled,input[type="checkbox"].disabled,fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:9px;padding-bottom:9px;min-height:36px}.input-sm{height:36px;padding:8px 12px;font-size:12px;line-height:1.5;border-radius:0}select.input-sm{height:36px;line-height:36px}textarea.input-sm,select[multiple].input-sm{height:auto}.form-group-sm .form-control{height:36px;padding:8px 12px;font-size:12px;line-height:1.5;border-radius:0}.form-group-sm select.form-control{height:36px;line-height:36px}.form-group-sm .form-control-static{height:36px;min-height:33px;padding:9px 12px;font-size:12px;line-height:1.5}.input-lg{height:60px;padding:16px 20px;font-size:19px;line-height:1.3333333;border-radius:0}select.input-lg{height:60px;line-height:60px}textarea.input-lg,select[multiple].input-lg{height:auto}.form-group-lg .form-control{height:60px;padding:16px 20px;font-size:19px;line-height:1.3333333;border-radius:0}.form-group-lg select.form-control{height:60px;line-height:60px}.form-group-lg .form-control-static{height:60px;min-height:40px;padding:17px 20px;font-size:19px;line-height:1.3333333}.has-feedback .form-control{padding-right:48.75px}.form-control-feedback{width:39px;height:39px;line-height:39px}.input-lg+.form-control-feedback,.input-group-lg+.form-control-feedback,.form-group-lg .form-control+.form-control-feedback{width:60px;height:60px;line-height:60px}.input-sm+.form-control-feedback,.input-group-sm+.form-control-feedback,.form-group-sm .form-control+.form-control-feedback{width:36px;height:36px;line-height:36px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#8cc152}.has-success .form-control{border-color:#8cc152;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#72a53b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #bedc9d;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #bedc9d}.has-success .input-group-addon{color:#8cc152;border-color:#8cc152;background-color:#dff0d8}.has-success .form-control-feedback{color:#8cc152}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#f8be12}.has-warning .form-control{border-color:#f8be12;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#d19e06;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fbd975;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fbd975}.has-warning .input-group-addon{color:#f8be12;border-color:#f8be12;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#f8be12}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#e9573f}.has-error .form-control{border-color:#e9573f;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#dc3519;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #f3a79b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #f3a79b}.has-error .input-group-addon{color:#e9573f;border-color:#e9573f;background-color:#f2dede}.has-error .form-control-feedback{color:#e9573f}.has-feedback label~.form-control-feedback{top:26px}.help-block{color:#a5a5a5}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:9px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:30px}@media (min-width:768px){.form-horizontal .control-label{padding-top:9px}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:17px;font-size:19px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:9px;font-size:12px}}.btn{font-weight:normal;padding:8px 12px;font-size:15px;line-height:1.4;border-radius:0}.btn:hover,.btn:focus,.btn.focus{color:#434a54}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed}.btn-default{color:#434a54;background-color:#e6e9ed;border-color:#656d78}.btn-default:focus,.btn-default.focus{color:#434a54;background-color:#ccd1d9;border-color:#656d78}.btn-default:hover{color:#434a54;background-color:#ccd1d9;border-color:#717a86}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#434a54;background-color:#ccd1d9;border-color:#717a86}.btn-default:active:hover,.btn-default.active:hover,.open>.dropdown-toggle.btn-default:hover,.btn-default:active:focus,.btn-default.active:focus,.open>.dropdown-toggle.btn-default:focus,.btn-default:active.focus,.btn-default.active.focus,.open>.dropdown-toggle.btn-default.focus{color:#434a54;background-color:#ccd1d9;border-color:#656d78}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus{background-color:#e6e9ed;border-color:#656d78}.btn-default .badge{color:#e6e9ed;background-color:#434a54}.btn-primary{color:#fff;background-color:#434a54;border-color:#383e46}.btn-primary:focus,.btn-primary.focus{color:#fff;background-color:#656d78;border-color:#383e46}.btn-primary:hover{color:#fff;background-color:#656d78;border-color:#434a54}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#656d78;border-color:#434a54}.btn-primary:active:hover,.btn-primary.active:hover,.open>.dropdown-toggle.btn-primary:hover,.btn-primary:active:focus,.btn-primary.active:focus,.open>.dropdown-toggle.btn-primary:focus,.btn-primary:active.focus,.btn-primary.active.focus,.open>.dropdown-toggle.btn-primary.focus{color:#fff;background-color:#656d78;border-color:#383e46}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus{background-color:#434a54;border-color:#383e46}.btn-primary .badge{color:#434a54;background-color:#fff}.btn-success{color:#fff;background-color:#8cc152;border-color:#7fb842}.btn-success:focus,.btn-success.focus{color:#fff;background-color:#a0d468;border-color:#7fb842}.btn-success:hover{color:#fff;background-color:#a0d468;border-color:#8cc152}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#a0d468;border-color:#8cc152}.btn-success:active:hover,.btn-success.active:hover,.open>.dropdown-toggle.btn-success:hover,.btn-success:active:focus,.btn-success.active:focus,.open>.dropdown-toggle.btn-success:focus,.btn-success:active.focus,.btn-success.active.focus,.open>.dropdown-toggle.btn-success.focus{color:#fff;background-color:#a0d468;border-color:#7fb842}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus{background-color:#8cc152;border-color:#7fb842}.btn-success .badge{color:#8cc152;background-color:#fff}.btn-info{color:#fff;background-color:#3bafda;border-color:#28a5d4}.btn-info:focus,.btn-info.focus{color:#fff;background-color:#4fc1e9;border-color:#28a5d4}.btn-info:hover{color:#fff;background-color:#4fc1e9;border-color:#3bafda}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#4fc1e9;border-color:#3bafda}.btn-info:active:hover,.btn-info.active:hover,.open>.dropdown-toggle.btn-info:hover,.btn-info:active:focus,.btn-info.active:focus,.open>.dropdown-toggle.btn-info:focus,.btn-info:active.focus,.btn-info.active.focus,.open>.dropdown-toggle.btn-info.focus{color:#fff;background-color:#4fc1e9;border-color:#28a5d4}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus{background-color:#3bafda;border-color:#28a5d4}.btn-info .badge{color:#3bafda;background-color:#fff}.btn-warning{color:#fff;background-color:#f8be12;border-color:#eab007}.btn-warning:focus,.btn-warning.focus{color:#fff;background-color:#ffce54;border-color:#eab007}.btn-warning:hover{color:#fff;background-color:#ffce54;border-color:#f8be12}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ffce54;border-color:#f8be12}.btn-warning:active:hover,.btn-warning.active:hover,.open>.dropdown-toggle.btn-warning:hover,.btn-warning:active:focus,.btn-warning.active:focus,.open>.dropdown-toggle.btn-warning:focus,.btn-warning:active.focus,.btn-warning.active.focus,.open>.dropdown-toggle.btn-warning.focus{color:#fff;background-color:#ffce54;border-color:#eab007}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus{background-color:#f8be12;border-color:#eab007}.btn-warning .badge{color:#f8be12;background-color:#fff}.btn-danger{color:#fff;background-color:#e9573f;border-color:#e64328}.btn-danger:focus,.btn-danger.focus{color:#fff;background-color:#fc6e51;border-color:#e64328}.btn-danger:hover{color:#fff;background-color:#fc6e51;border-color:#e9573f}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#fc6e51;border-color:#e9573f}.btn-danger:active:hover,.btn-danger.active:hover,.open>.dropdown-toggle.btn-danger:hover,.btn-danger:active:focus,.btn-danger.active:focus,.open>.dropdown-toggle.btn-danger:focus,.btn-danger:active.focus,.btn-danger.active.focus,.open>.dropdown-toggle.btn-danger.focus{color:#fff;background-color:#fc6e51;border-color:#e64328}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus{background-color:#e9573f;border-color:#e64328}.btn-danger .badge{color:#e9573f;background-color:#fff}.btn-link{color:#656d78}.btn-link:hover,.btn-link:focus{color:#434a54;text-decoration:underline}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#ccd1d9}.btn-lg{padding:16px 20px;font-size:19px;line-height:1.3333333;border-radius:0}.btn-sm{padding:8px 12px;font-size:12px;line-height:1.5;border-radius:0}.btn-xs{padding:4px 6px;font-size:12px;line-height:1.5;border-radius:0}.caret{border-top:4px dashed;border-top:4px solid \9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown-menu{font-size:15px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:0}.dropdown-menu .divider{height:1px;margin:9.5px 0;overflow:hidden;background-color:rgba(0,0,0,0.2)}.dropdown-menu>li>a{line-height:1.4;color:#555}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#59606a;background-color:#eee}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;background-color:#434a54}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#ccd1d9}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{cursor:not-allowed}.dropdown-header{font-size:12px;line-height:1.4;color:#ccd1d9}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-bottom:4px dashed;border-bottom:4px solid \9}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:0;border-top-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-bottom-right-radius:0;border-bottom-left-radius:0}.input-group-addon{padding:8px 12px;font-size:15px;color:#434a54;background-color:#e6e9ed;border:1px solid #ccc;border-radius:0}.input-group-addon.input-sm{padding:8px 12px;font-size:12px;border-radius:0}.input-group-addon.input-lg{padding:16px 20px;font-size:19px;border-radius:0}.nav>li>a{padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{background-color:#e6e9ed}.nav>li.disabled>a{color:#ccd1d9}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#ccd1d9;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#e6e9ed;border-color:#656d78}.nav-tabs{border-bottom:1px solid #e9e9e9}.nav-tabs>li>a{line-height:1.4;border-radius:0 0 0 0;color:#aab2bd}.nav-tabs>li>a:hover{border-color:#e6e9ed #e6e9ed #e9e9e9}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#656565;background-color:#f5f5f5;border:1px solid #e9e9e9}.nav-pills>li>a{border-radius:0}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#434a54}.nav-tabs-justified>li>a{border-radius:0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #e9e9e9}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #e9e9e9;border-radius:0 0 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#f5f5f5}}.navbar{min-height:50px;margin-bottom:21px}@media (min-width:768px){.navbar{border-radius:0}}.navbar-collapse{padding-right:15px;padding-left:15px}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-brand{padding:14.5px 15px;font-size:19px;line-height:21px;height:50px}.navbar-toggle{margin-right:15px;border-radius:0}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.25px -15px}.navbar-nav>li>a{line-height:21px}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:14.5px;padding-bottom:14.5px}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;margin-top:5.5px;margin-bottom:5.5px}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-top-right-radius:0;border-top-left-radius:0}.navbar-btn{margin-top:5.5px;margin-bottom:5.5px}.navbar-btn.btn-sm{margin-top:7px;margin-bottom:7px}.navbar-text{margin-top:14.5px;margin-bottom:14.5px}@media (min-width:768px){.navbar-text{margin-left:15px;margin-right:15px}}@media (min-width:768px){.navbar-right{margin-right:-15px}}.navbar-default{background-color:#333;border-color:#222}.navbar-default .navbar-brand{color:#fff}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#fff;background-color:none}.navbar-default .navbar-text{color:#fff}.navbar-default .navbar-nav>li>a{color:#fff}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#fff;background-color:#1a1a1a}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#fff;background-color:#1a1a1a}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:transparent}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#1a1a1a}.navbar-default .navbar-toggle .icon-bar{background-color:#fff}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#222}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#1a1a1a;color:#fff}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#fff}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:#1a1a1a}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#1a1a1a}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#fff}.navbar-default .navbar-link:hover{color:#fff}.navbar-default .btn-link{color:#fff}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#fff}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#fff;border-color:#e6e9ed}.navbar-inverse .navbar-brand{color:#656565}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#656d78;background-color:transparent}.navbar-inverse .navbar-text{color:#434a54}.navbar-inverse .navbar-nav>li>a{color:#656565}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#656d78;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#656d78;background-color:transparent}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:transparent}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:transparent}.navbar-inverse .navbar-toggle .icon-bar{background-color:#ccc}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#ededed}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:transparent;color:#656d78}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#e6e9ed}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#e6e9ed}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#656565}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#656d78;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#656d78;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-inverse .navbar-link{color:#656565}.navbar-inverse .navbar-link:hover{color:#656d78}.navbar-inverse .btn-link{color:#656565}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#656d78}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#ccc}.breadcrumb{padding:8px 15px;margin-bottom:21px;background-color:#f5f5f5;border-radius:0}.breadcrumb>li+li:before{content:"/\00a0";color:#ccd1d9}.breadcrumb>.active{color:#656d78}.pagination{margin:21px 0;border-radius:0}.pagination>li>a,.pagination>li>span{padding:8px 12px;line-height:1.4;color:#656d78;background-color:transparent;border:1px solid transparent}.pagination>li:first-child>a,.pagination>li:first-child>span{border-bottom-left-radius:0;border-top-left-radius:0}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:0;border-top-right-radius:0}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#434a54;background-color:#e6e9ed;border-color:transparent}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{color:#fff;background-color:#434a54;border-color:transparent}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#ccd1d9;background-color:#fff;border-color:transparent;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:16px 20px;font-size:19px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:0;border-top-left-radius:0}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:0;border-top-right-radius:0}.pagination-sm>li>a,.pagination-sm>li>span{padding:8px 12px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:0;border-top-left-radius:0}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:0;border-top-right-radius:0}.pager{margin:21px 0}.pager li>a,.pager li>span{background-color:transparent;border:1px solid transparent;border-radius:3px}.pager li>a:hover,.pager li>a:focus{background-color:#e6e9ed}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#ccd1d9;background-color:transparent;cursor:not-allowed}.label{color:#fff}a.label:hover,a.label:focus{color:#fff}.label-default{background-color:#aab2bd}.label-default[href]:hover,.label-default[href]:focus{background-color:#8d98a7}.label-primary{background-color:#434a54}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#2c3138}.label-success{background-color:#8cc152}.label-success[href]:hover,.label-success[href]:focus{background-color:#72a53b}.label-info{background-color:#3bafda}.label-info[href]:hover,.label-info[href]:focus{background-color:#2494be}.label-warning{background-color:#f8be12}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#d19e06}.label-danger{background-color:#e9573f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#dc3519}.badge{font-size:12px;font-weight:bold;color:#fff;line-height:1;background-color:#434a54;border-radius:10px}a.badge:hover,a.badge:focus{color:#fff}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#656d78;background-color:#fff}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#fafafa}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:23px}.jumbotron>hr{border-top-color:#e1e1e1}.container .jumbotron,.container-fluid .jumbotron{border-radius:0;padding-left:15px;padding-right:15px}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:68px}}.thumbnail{padding:4px;margin-bottom:21px;line-height:1.4;background-color:#f5f5f5;border:1px solid #e9e9e9;border-radius:0}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#656d78}.thumbnail .caption{padding:9px;color:#656565}.alert{padding:15px;margin-bottom:21px;border-radius:0}.alert .alert-link{font-weight:bold}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-success{background-color:#8cc152;border-color:#7fb842;color:#fff}.alert-success hr{border-top-color:#72a53b}.alert-success .alert-link{color:#e6e6e6}.alert-info{background-color:#3bafda;border-color:#269ecb;color:#fff}.alert-info hr{border-top-color:#228eb6}.alert-info .alert-link{color:#e6e6e6}.alert-warning{background-color:#f8be12;border-color:#eab007;color:#fff}.alert-warning hr{border-top-color:#d19e06}.alert-warning .alert-link{color:#e6e6e6}.alert-danger{background-color:#e9573f;border-color:#e64328;color:#fff}.alert-danger hr{border-top-color:#dc3519}.alert-danger .alert-link{color:#e6e6e6}.progress{height:21px;margin-bottom:21px;background-color:#f5f5f5;border-radius:0}.progress-bar{font-size:12px;line-height:21px;color:#fff;background-color:#434a54}.progress-bar-success{background-color:#8cc152}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#3bafda}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#f8be12}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#e9573f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.list-group-item{background-color:#fff;border:1px solid #e9e9e9}.list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.list-group-item:last-child{border-bottom-right-radius:0;border-bottom-left-radius:0}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{color:#555;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{background-color:#e6e9ed;color:#ccd1d9;cursor:not-allowed}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#ccd1d9}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{color:#fff;background-color:#434a54;border-color:#434a54}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#a9b0ba}.list-group-item-success{color:#8cc152;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#8cc152}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,button.list-group-item-success:hover,a.list-group-item-success:focus,button.list-group-item-success:focus{color:#8cc152;background-color:#d0e9c6}a.list-group-item-success.active,button.list-group-item-success.active,a.list-group-item-success.active:hover,button.list-group-item-success.active:hover,a.list-group-item-success.active:focus,button.list-group-item-success.active:focus{color:#fff;background-color:#8cc152;border-color:#8cc152}.list-group-item-info{color:#3bafda;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#3bafda}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,button.list-group-item-info:hover,a.list-group-item-info:focus,button.list-group-item-info:focus{color:#3bafda;background-color:#c4e3f3}a.list-group-item-info.active,button.list-group-item-info.active,a.list-group-item-info.active:hover,button.list-group-item-info.active:hover,a.list-group-item-info.active:focus,button.list-group-item-info.active:focus{color:#fff;background-color:#3bafda;border-color:#3bafda}.list-group-item-warning{color:#f8be12;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#f8be12}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,button.list-group-item-warning:hover,a.list-group-item-warning:focus,button.list-group-item-warning:focus{color:#f8be12;background-color:#faf2cc}a.list-group-item-warning.active,button.list-group-item-warning.active,a.list-group-item-warning.active:hover,button.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus,button.list-group-item-warning.active:focus{color:#fff;background-color:#f8be12;border-color:#f8be12}.list-group-item-danger{color:#e9573f;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#e9573f}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,button.list-group-item-danger:hover,a.list-group-item-danger:focus,button.list-group-item-danger:focus{color:#e9573f;background-color:#ebcccc}a.list-group-item-danger.active,button.list-group-item-danger.active,a.list-group-item-danger.active:hover,button.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus,button.list-group-item-danger.active:focus{color:#fff;background-color:#e9573f;border-color:#e9573f}.panel{margin-bottom:21px;background-color:#fff;border-radius:0}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-top-right-radius:-1;border-top-left-radius:-1}.panel-title{font-size:17px}.panel-footer{padding:10px 15px;background-color:#fafafa;border-top:1px solid #e9e9e9;border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top-right-radius:-1;border-top-left-radius:-1}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-left:15px;padding-right:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:-1;border-top-left-radius:-1}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:-1;border-top-right-radius:-1}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:-1}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:-1}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:-1;border-bottom-right-radius:-1}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:-1}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:-1}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #e9e9e9}.panel-group{margin-bottom:21px}.panel-group .panel{border-radius:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid #e9e9e9}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #e9e9e9}.panel-default{border-color:#e9e9e9}.panel-default>.panel-heading{color:#616262;background-color:#fafafa;border-color:#e9e9e9}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#e9e9e9}.panel-default>.panel-heading .badge{color:#fafafa;background-color:#616262}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#e9e9e9}.panel-primary{border-color:#434a54}.panel-primary>.panel-heading{color:#fff;background-color:#434a54;border-color:#434a54}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#434a54}.panel-primary>.panel-heading .badge{color:#434a54;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#434a54}.panel-success{border-color:#e9e9e9}.panel-success>.panel-heading{color:#fff;background-color:#8cc152;border-color:#e9e9e9}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#e9e9e9}.panel-success>.panel-heading .badge{color:#8cc152;background-color:#fff}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#e9e9e9}.panel-info{border-color:#e9e9e9}.panel-info>.panel-heading{color:#fff;background-color:#3bafda;border-color:#e9e9e9}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#e9e9e9}.panel-info>.panel-heading .badge{color:#3bafda;background-color:#fff}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#e9e9e9}.panel-warning{border-color:#e9e9e9}.panel-warning>.panel-heading{color:#fff;background-color:#f8be12;border-color:#e9e9e9}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#e9e9e9}.panel-warning>.panel-heading .badge{color:#f8be12;background-color:#fff}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#e9e9e9}.panel-danger{border-color:#e9e9e9}.panel-danger>.panel-heading{color:#fff;background-color:#e9573f;border-color:#e9e9e9}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#e9e9e9}.panel-danger>.panel-heading .badge{color:#e9573f;background-color:#fff}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#e9e9e9}.well{background-color:#fafafa;border:1px solid #e8e8e8;border-radius:0}.well-lg{border-radius:0}.well-sm{border-radius:0}.close{font-size:22.5px;font-weight:bold;color:#fff;text-shadow:0 1px 0 #fff}.close:hover,.close:focus{color:#fff}.modal-content{background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius:0}.modal-backdrop{background-color:#000}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-title{line-height:1.4}.modal-body{padding:20px}.modal-footer{padding:20px;border-top:1px solid #e5e5e5}@media (min-width:768px){.modal-dialog{width:600px}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{font-size:12px}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;color:#fff;background-color:#434a54;border-radius:0}.tooltip.top .tooltip-arrow{margin-left:-5px;border-width:5px 5px 0;border-top-color:#434a54}.tooltip.top-left .tooltip-arrow{right:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#434a54}.tooltip.top-right .tooltip-arrow{left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#434a54}.tooltip.right .tooltip-arrow{margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#434a54}.tooltip.left .tooltip-arrow{margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#434a54}.tooltip.bottom .tooltip-arrow{margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#434a54}.tooltip.bottom-left .tooltip-arrow{right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#434a54}.tooltip.bottom-right .tooltip-arrow{left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#434a54}.popover{max-width:276px;font-size:15px;background-color:#656d78;border:1px solid #656d78;border:1px solid transparent;border-radius:0}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{font-size:15px;background-color:#656d78;border-bottom:1px solid #59606a;border-radius:-1 -1 0 0}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px}.popover.top>.arrow{margin-left:-11px;border-top-color:#363b41;border-top-color:rgba(0,0,0,0.05);bottom:-11px}.popover.top>.arrow:after{margin-left:-10px;border-top-color:#656d78}.popover.right>.arrow{left:-11px;margin-top:-11px;border-right-color:#363b41;border-right-color:rgba(0,0,0,0.05)}.popover.right>.arrow:after{bottom:-10px;border-right-color:#656d78}.popover.bottom>.arrow{margin-left:-11px;border-bottom-color:#363b41;border-bottom-color:rgba(0,0,0,0.05);top:-11px}.popover.bottom>.arrow:after{margin-left:-10px;border-bottom-color:#656d78}.popover.left>.arrow{right:-11px;margin-top:-11px;border-left-color:#363b41;border-left-color:rgba(0,0,0,0.05)}.popover.left>.arrow:after{border-left-color:#656d78;bottom:-10px}.carousel-control{width:15%;opacity:.5;filter:alpha(opacity=50);font-size:20px;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-control:hover,.carousel-control:focus{color:#fff}.carousel-indicators li{border:1px solid #fff}.carousel-indicators .active{background-color:#fff}.carousel-caption{color:#fff;text-shadow:0 1px 2px rgba(0,0,0,0.6)}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}}.navbar-inverse .badge{background-color:#fff;color:#434a54}body{-webkit-font-smoothing:antialiased}.text-primary,.text-primary:hover{color:#434a54}.text-success,.text-success:hover{color:#8cc152}.text-danger,.text-danger:hover{color:#e9573f}.text-warning,.text-warning:hover{color:#f8be12}.text-info,.text-info:hover{color:#3bafda}table a:not(.btn),.table a:not(.btn){text-decoration:underline}table .dropdown-menu a,.table .dropdown-menu a{text-decoration:none}table .success,.table .success,table .warning,.table .warning,table .danger,.table .danger,table .info,.table .info{color:#fff}table .success a,.table .success a,table .warning a,.table .warning a,table .danger a,.table .danger a,table .info a,.table .info a{color:#fff}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label,.has-warning .form-control-feedback{color:#f8be12}.has-warning .form-control,.has-warning .form-control:focus,.has-warning .input-group-addon{border:1px solid #f8be12}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label,.has-error .form-control-feedback{color:#e9573f}.has-error .form-control,.has-error .form-control:focus,.has-error .input-group-addon{border:1px solid #e9573f}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label,.has-success .form-control-feedback{color:#8cc152}.has-success .form-control,.has-success .form-control:focus,.has-success .input-group-addon{border:1px solid #8cc152}.nav-pills>li>a{border-radius:0}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-image:none}.close{text-decoration:none;text-shadow:none;opacity:.4}.close:hover,.close:focus{opacity:1}.alert{border:none}.alert .alert-link{text-decoration:underline;color:#fff}.label{border-radius:0}.progress{height:8px;-webkit-box-shadow:none;box-shadow:none}.progress .progress-bar{font-size:8px;line-height:8px}.panel-heading,.panel-footer{border-top-right-radius:0;border-top-left-radius:0}.panel-default .close{color:#656565}a.list-group-item-success.active{background-color:#dff0d8}a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{background-color:#d0e9c6}a.list-group-item-warning.active{background-color:#fcf8e3}a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{background-color:#faf2cc}a.list-group-item-danger.active{background-color:#f2dede}a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{background-color:#ebcccc}.modal .close{color:#656565}.popover{color:#656565} \ No newline at end of file diff --git a/inc/css/bootstrap.min.css b/inc/css/bootstrap.min.css new file mode 100644 index 0000000..a9558eb --- /dev/null +++ b/inc/css/bootstrap.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/inc/data/.gitkeep b/inc/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/inc/fonts/glyphicons-halflings-regular.eot b/inc/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000..b93a495 Binary files /dev/null and b/inc/fonts/glyphicons-halflings-regular.eot differ diff --git a/inc/fonts/glyphicons-halflings-regular.svg b/inc/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 0000000..94fb549 --- /dev/null +++ b/inc/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/inc/fonts/glyphicons-halflings-regular.ttf b/inc/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000..1413fc6 Binary files /dev/null and b/inc/fonts/glyphicons-halflings-regular.ttf differ diff --git a/inc/fonts/glyphicons-halflings-regular.woff b/inc/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000..9e61285 Binary files /dev/null and b/inc/fonts/glyphicons-halflings-regular.woff differ diff --git a/inc/fonts/glyphicons-halflings-regular.woff2 b/inc/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000..64539b5 Binary files /dev/null and b/inc/fonts/glyphicons-halflings-regular.woff2 differ diff --git a/inc/jscripts/are-you-sure.min.js b/inc/jscripts/are-you-sure.min.js new file mode 100644 index 0000000..71e4252 --- /dev/null +++ b/inc/jscripts/are-you-sure.min.js @@ -0,0 +1,14 @@ +/*! + * jQuery Plugin: Are-You-Sure (Dirty Form Detection) + * https://github.com/codedance/jquery.AreYouSure/ + * + * Copyright (c) 2012-2014, Chris Dance and PaperCut Software http://www.papercut.com/ + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Author: chris.dance@papercut.com + * Version: 1.9.0 + * Date: 13th August 2014 + */ + +!function(e){e.fn.areYouSure=function(i){var t=e.extend({message:"You have unsaved changes!",dirtyClass:"dirty",change:null,silent:!1,addRemoveFieldsMarksDirty:!1,fieldEvents:"change keyup propertychange input",fieldSelector:":input:not(input[type=submit]):not(input[type=button])"},i),r=function(i){if(i.hasClass("ays-ignore")||i.hasClass("aysIgnore")||i.attr("data-ays-ignore")||void 0===i.attr("name"))return null;if(i.is(":disabled"))return"ays-disabled";var t,r=i.attr("type");switch(i.is("select")&&(r="select"),r){case"checkbox":case"radio":t=i.is(":checked");break;case"select":t="",i.find("option").each(function(i){var r=e(this);r.is(":selected")&&(t+=r.val())});break;default:t=i.val()}return t},n=function(e){e.data("ays-orig",r(e))},a=function(i){var n=function(e){var i=e.data("ays-orig");return void 0===i?!1:r(e)!=i},a=e(this).is("form")?e(this):e(this).parents("form");if(n(e(i.target)))return void o(a,!0);if($fields=a.find(t.fieldSelector),t.addRemoveFieldsMarksDirty){var s=a.data("ays-orig-field-count");if(s!=$fields.length)return void o(a,!0)}var d=!1;$fields.each(function(){return $field=e(this),n($field)?(d=!0,!1):void 0}),o(a,d)},s=function(i){var r=i.find(t.fieldSelector);e(r).each(function(){n(e(this))}),e(r).unbind(t.fieldEvents,a),e(r).bind(t.fieldEvents,a),i.data("ays-orig-field-count",e(r).length),o(i,!1)},o=function(e,i){var r=i!=e.hasClass(t.dirtyClass);e.toggleClass(t.dirtyClass,i),r&&(t.change&&t.change.call(e,e),i&&e.trigger("dirty.areYouSure",[e]),i||e.trigger("clean.areYouSure",[e]),e.trigger("change.areYouSure",[e]))},d=function(){var i=e(this),r=i.find(t.fieldSelector);e(r).each(function(){var i=e(this);i.data("ays-orig")||(n(i),i.bind(t.fieldEvents,a))}),i.trigger("checkform.areYouSure")},u=function(){s(e(this))};return t.silent||window.aysUnloadSet||(window.aysUnloadSet=!0,e(window).bind("beforeunload",function(){if($dirtyForms=e("form").filter("."+t.dirtyClass),0!=$dirtyForms.length){if(navigator.userAgent.toLowerCase().match(/msie|chrome/)){if(window.aysHasPrompted)return;window.aysHasPrompted=!0,window.setTimeout(function(){window.aysHasPrompted=!1},900)}return t.message}})),this.each(function(i){if(e(this).is("form")){var r=e(this);r.submit(function(){r.removeClass(t.dirtyClass)}),r.bind("reset",function(){o(r,!1)}),r.bind("rescan.areYouSure",d),r.bind("reinitialize.areYouSure",u),r.bind("checkform.areYouSure",a),s(r)}})}}(jQuery); \ No newline at end of file diff --git a/inc/jscripts/bootbox.min.js b/inc/jscripts/bootbox.min.js new file mode 100644 index 0000000..0dc0cbd --- /dev/null +++ b/inc/jscripts/bootbox.min.js @@ -0,0 +1,6 @@ +/** + * bootbox.js v4.4.0 + * + * http://bootboxjs.com/license.txt + */ +!function(a,b){"use strict";"function"==typeof define&&define.amd?define(["jquery"],b):"object"==typeof exports?module.exports=b(require("jquery")):a.bootbox=b(a.jQuery)}(this,function a(b,c){"use strict";function d(a){var b=q[o.locale];return b?b[a]:q.en[a]}function e(a,c,d){a.stopPropagation(),a.preventDefault();var e=b.isFunction(d)&&d.call(c,a)===!1;e||c.modal("hide")}function f(a){var b,c=0;for(b in a)c++;return c}function g(a,c){var d=0;b.each(a,function(a,b){c(a,b,d++)})}function h(a){var c,d;if("object"!=typeof a)throw new Error("Please supply an object of options");if(!a.message)throw new Error("Please specify a message");return a=b.extend({},o,a),a.buttons||(a.buttons={}),c=a.buttons,d=f(c),g(c,function(a,e,f){if(b.isFunction(e)&&(e=c[a]={callback:e}),"object"!==b.type(e))throw new Error("button with key "+a+" must be an object");e.label||(e.label=a),e.className||(e.className=2>=d&&f===d-1?"btn-primary":"btn-default")}),a}function i(a,b){var c=a.length,d={};if(1>c||c>2)throw new Error("Invalid argument length");return 2===c||"string"==typeof a[0]?(d[b[0]]=a[0],d[b[1]]=a[1]):d=a[0],d}function j(a,c,d){return b.extend(!0,{},a,i(c,d))}function k(a,b,c,d){var e={className:"bootbox-"+a,buttons:l.apply(null,b)};return m(j(e,d,c),b)}function l(){for(var a={},b=0,c=arguments.length;c>b;b++){var e=arguments[b],f=e.toLowerCase(),g=e.toUpperCase();a[f]={label:d(g)}}return a}function m(a,b){var d={};return g(b,function(a,b){d[b]=!0}),g(a.buttons,function(a){if(d[a]===c)throw new Error("button key "+a+" is not allowed (options are "+b.join("\n")+")")}),a}var n={dialog:"",header:"",footer:"",closeButton:"",form:"
",inputs:{text:"",textarea:"",email:"",select:"",checkbox:"
",date:"",time:"",number:"",password:""}},o={locale:"en",backdrop:"static",animate:!0,className:null,closeButton:!0,show:!0,container:"body"},p={};p.alert=function(){var a;if(a=k("alert",["ok"],["message","callback"],arguments),a.callback&&!b.isFunction(a.callback))throw new Error("alert requires callback property to be a function when provided");return a.buttons.ok.callback=a.onEscape=function(){return b.isFunction(a.callback)?a.callback.call(this):!0},p.dialog(a)},p.confirm=function(){var a;if(a=k("confirm",["cancel","confirm"],["message","callback"],arguments),a.buttons.cancel.callback=a.onEscape=function(){return a.callback.call(this,!1)},a.buttons.confirm.callback=function(){return a.callback.call(this,!0)},!b.isFunction(a.callback))throw new Error("confirm requires a callback");return p.dialog(a)},p.prompt=function(){var a,d,e,f,h,i,k;if(f=b(n.form),d={className:"bootbox-prompt",buttons:l("cancel","confirm"),value:"",inputType:"text"},a=m(j(d,arguments,["title","callback"]),["cancel","confirm"]),i=a.show===c?!0:a.show,a.message=f,a.buttons.cancel.callback=a.onEscape=function(){return a.callback.call(this,null)},a.buttons.confirm.callback=function(){var c;switch(a.inputType){case"text":case"textarea":case"email":case"select":case"date":case"time":case"number":case"password":c=h.val();break;case"checkbox":var d=h.find("input:checked");c=[],g(d,function(a,d){c.push(b(d).val())})}return a.callback.call(this,c)},a.show=!1,!a.title)throw new Error("prompt requires a title");if(!b.isFunction(a.callback))throw new Error("prompt requires a callback");if(!n.inputs[a.inputType])throw new Error("invalid prompt type");switch(h=b(n.inputs[a.inputType]),a.inputType){case"text":case"textarea":case"email":case"date":case"time":case"number":case"password":h.val(a.value);break;case"select":var o={};if(k=a.inputOptions||[],!b.isArray(k))throw new Error("Please pass an array of input options");if(!k.length)throw new Error("prompt with select requires options");g(k,function(a,d){var e=h;if(d.value===c||d.text===c)throw new Error("given options in wrong format");d.group&&(o[d.group]||(o[d.group]=b("").attr("label",d.group)),e=o[d.group]),e.append("")}),g(o,function(a,b){h.append(b)}),h.val(a.value);break;case"checkbox":var q=b.isArray(a.value)?a.value:[a.value];if(k=a.inputOptions||[],!k.length)throw new Error("prompt with checkbox requires options");if(!k[0].value||!k[0].text)throw new Error("given options in wrong format");h=b("
"),g(k,function(c,d){var e=b(n.inputs[a.inputType]);e.find("input").attr("value",d.value),e.find("label").append(d.text),g(q,function(a,b){b===d.value&&e.find("input").prop("checked",!0)}),h.append(e)})}return a.placeholder&&h.attr("placeholder",a.placeholder),a.pattern&&h.attr("pattern",a.pattern),a.maxlength&&h.attr("maxlength",a.maxlength),f.append(h),f.on("submit",function(a){a.preventDefault(),a.stopPropagation(),e.find(".btn-primary").click()}),e=p.dialog(a),e.off("shown.bs.modal"),e.on("shown.bs.modal",function(){h.focus()}),i===!0&&e.modal("show"),e},p.dialog=function(a){a=h(a);var d=b(n.dialog),f=d.find(".modal-dialog"),i=d.find(".modal-body"),j=a.buttons,k="",l={onEscape:a.onEscape};if(b.fn.modal===c)throw new Error("$.fn.modal is not defined; please double check you have included the Bootstrap JavaScript library. See http://getbootstrap.com/javascript/ for more details.");if(g(j,function(a,b){k+="",l[a]=b.callback}),i.find(".bootbox-body").html(a.message),a.animate===!0&&d.addClass("fade"),a.className&&d.addClass(a.className),"large"===a.size?f.addClass("modal-lg"):"small"===a.size&&f.addClass("modal-sm"),a.title&&i.before(n.header),a.closeButton){var m=b(n.closeButton);a.title?d.find(".modal-header").prepend(m):m.css("margin-top","-10px").prependTo(i)}return a.title&&d.find(".modal-title").html(a.title),k.length&&(i.after(n.footer),d.find(".modal-footer").html(k)),d.on("hidden.bs.modal",function(a){a.target===this&&d.remove()}),d.on("shown.bs.modal",function(){d.find(".btn-primary:first").focus()}),"static"!==a.backdrop&&d.on("click.dismiss.bs.modal",function(a){d.children(".modal-backdrop").length&&(a.currentTarget=d.children(".modal-backdrop").get(0)),a.target===a.currentTarget&&d.trigger("escape.close.bb")}),d.on("escape.close.bb",function(a){l.onEscape&&e(a,d,l.onEscape)}),d.on("click",".modal-footer button",function(a){var c=b(this).data("bb-handler");e(a,d,l[c])}),d.on("click",".bootbox-close-button",function(a){e(a,d,l.onEscape)}),d.on("keyup",function(a){27===a.which&&d.trigger("escape.close.bb")}),b(a.container).append(d),d.modal({backdrop:a.backdrop?"static":!1,keyboard:!1,show:!1}),a.show&&d.modal("show"),d},p.setDefaults=function(){var a={};2===arguments.length?a[arguments[0]]=arguments[1]:a=arguments[0],b.extend(o,a)},p.hideAll=function(){return b(".bootbox").modal("hide"),p};var q={bg_BG:{OK:"Ок",CANCEL:"Отказ",CONFIRM:"Потвърждавам"},br:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Sim"},cs:{OK:"OK",CANCEL:"Zrušit",CONFIRM:"Potvrdit"},da:{OK:"OK",CANCEL:"Annuller",CONFIRM:"Accepter"},de:{OK:"OK",CANCEL:"Abbrechen",CONFIRM:"Akzeptieren"},el:{OK:"Εντάξει",CANCEL:"Ακύρωση",CONFIRM:"Επιβεβαίωση"},en:{OK:"OK",CANCEL:"Cancel",CONFIRM:"OK"},es:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Aceptar"},et:{OK:"OK",CANCEL:"Katkesta",CONFIRM:"OK"},fa:{OK:"قبول",CANCEL:"لغو",CONFIRM:"تایید"},fi:{OK:"OK",CANCEL:"Peruuta",CONFIRM:"OK"},fr:{OK:"OK",CANCEL:"Annuler",CONFIRM:"D'accord"},he:{OK:"אישור",CANCEL:"ביטול",CONFIRM:"אישור"},hu:{OK:"OK",CANCEL:"Mégsem",CONFIRM:"Megerősít"},hr:{OK:"OK",CANCEL:"Odustani",CONFIRM:"Potvrdi"},id:{OK:"OK",CANCEL:"Batal",CONFIRM:"OK"},it:{OK:"OK",CANCEL:"Annulla",CONFIRM:"Conferma"},ja:{OK:"OK",CANCEL:"キャンセル",CONFIRM:"確認"},lt:{OK:"Gerai",CANCEL:"Atšaukti",CONFIRM:"Patvirtinti"},lv:{OK:"Labi",CANCEL:"Atcelt",CONFIRM:"Apstiprināt"},nl:{OK:"OK",CANCEL:"Annuleren",CONFIRM:"Accepteren"},no:{OK:"OK",CANCEL:"Avbryt",CONFIRM:"OK"},pl:{OK:"OK",CANCEL:"Anuluj",CONFIRM:"Potwierdź"},pt:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Confirmar"},ru:{OK:"OK",CANCEL:"Отмена",CONFIRM:"Применить"},sq:{OK:"OK",CANCEL:"Anulo",CONFIRM:"Prano"},sv:{OK:"OK",CANCEL:"Avbryt",CONFIRM:"OK"},th:{OK:"ตกลง",CANCEL:"ยกเลิก",CONFIRM:"ยืนยัน"},tr:{OK:"Tamam",CANCEL:"İptal",CONFIRM:"Onayla"},zh_CN:{OK:"OK",CANCEL:"取消",CONFIRM:"确认"},zh_TW:{OK:"OK",CANCEL:"取消",CONFIRM:"確認"}};return p.addLocale=function(a,c){return b.each(["OK","CANCEL","CONFIRM"],function(a,b){if(!c[b])throw new Error("Please supply a translation for '"+b+"'")}),q[a]={OK:c.OK,CANCEL:c.CANCEL,CONFIRM:c.CONFIRM},p},p.removeLocale=function(a){return delete q[a],p},p.setLocale=function(a){return p.setDefaults("locale",a)},p.init=function(c){return a(c||b)},p}); \ No newline at end of file diff --git a/inc/jscripts/bootstrap.min.js b/inc/jscripts/bootstrap.min.js new file mode 100644 index 0000000..9bcd2fc --- /dev/null +++ b/inc/jscripts/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under the MIT license + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/inc/jscripts/editor/highlight.min.js b/inc/jscripts/editor/highlight.min.js new file mode 100644 index 0000000..f5ac8b8 --- /dev/null +++ b/inc/jscripts/editor/highlight.min.js @@ -0,0 +1,2 @@ +/*! highlight.js v9.11.0 | BSD3 License | git.io/hljslicense */ +!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){s+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var l=0,s="",f=[];e.length||r.length;){var g=i();if(s+=n(a.substring(l,g[0].offset)),l=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===l);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return s+n(a.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):x(a.k).forEach(function(e){u(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return l("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var c=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=c.length?t(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function l(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?"":I.classPrefix,i='',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=l(E,r),e?(B+=e[1],a+=p(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!y[E.sL])return n(k);var t=e?f(E.sL,k,!0,x[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(B+=t.r),e&&(x[E.sL]=t.top),p(t.language,t.value,!1,!0)}function b(){L+=null!=E.sL?d():h(),k=""}function v(e){L+=e.cN?p(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(k+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),b(),t.rB||t.eB||(k=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),b(),a.eE&&(k=n));do E.cN&&(L+=C),E.skip||(B+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return k+=n,n.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,E=i||N,x={},L="";for(R=E;R!==N;R=R.parent)R.cN&&(L=p(R.cN,"",!0)+L);var k="",B=0;try{for(var M,j,O=0;;){if(E.t.lastIndex=O,M=E.t.exec(t),!M)break;j=m(t.substring(O,M.index),M[0]),O=M.index+j}for(m(t.substr(O)),R=E;R.parent;R=R.parent)R.cN&&(L+=C);return{r:B,value:L,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function g(e,t){t=t||I.languages||x(y);var r={r:0,value:n(e)},a=r;return t.filter(w).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return I.tabReplace||I.useBR?e.replace(M,function(e,n){return I.useBR&&"\n"===e?"
":I.tabReplace?n.replace(/\t/g,I.tabReplace):""}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function d(e){var n,t,r,o,l,s=i(e);a(s)||(I.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,l=n.textContent,r=s?f(s,l,!0):g(l),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),l)),r.value=p(r.value),e.innerHTML=r.value,e.className=h(e.className,s,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){I=o(I,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=y[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function R(){return x(y)}function w(e){return e=(e||"").toLowerCase(),y[e]||y[L[e]]}var E=[],x=Object.keys,y={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",I={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=d,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("xml",function(s){var e="[A-Za-z0-9\\._:-]+",t={eW:!0,i:/`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist"],cI:!0,c:[{cN:"meta",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},s.C("",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0}]},{cN:"tag",b:"|$)",e:">",k:{name:"style"},c:[t],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"|$)",e:">",k:{name:"script"},c:[t],starts:{e:"",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"meta",v:[{b:/<\?xml/,e:/\?>/,r:10},{b:/<\?\w+/,e:/\?>/}]},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,r:0},t]}]}});hljs.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"section",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"quote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"^```w*s*$",e:"^```s*$"},{b:"`.+?`"},{b:"^( {4}| )",e:"$",r:0}]},{b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"string",b:"\\[",e:"\\]",eB:!0,rE:!0,r:0},{cN:"link",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"symbol",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],r:10},{b:/^\[[^\n]+\]:/,rB:!0,c:[{cN:"symbol",b:/\[/,e:/\]/,eB:!0,eE:!0},{cN:"link",b:/:\s*/,e:/$/,eB:!0}]}]}});hljs.registerLanguage("javascript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},a={cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},n={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},c={cN:"string",b:"`",e:"`",c:[e.BE,n]};n.c=[e.ASM,e.QSM,c,a,e.RM];var s=n.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx"],k:t,c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,c,e.CLCM,e.CBCM,a,{b:/[{,]\s*/,r:0,c:[{b:r+"\\s*:",rB:!0,r:0,c:[{cN:"attr",b:r,r:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+r+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:r},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:s}]}]},{b://,sL:"xml",c:[{b:/<\w+\s*\/>/,skip:!0},{b:/<\w+/,e:/(\/\w+|\w+\/)>/,skip:!0,c:[{b:/<\w+\s*\/>/,skip:!0},"self"]}]}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:r}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:s}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage("php",function(e){var c={b:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},i={cN:"meta",b:/<\?(php)?|\?>/},t={cN:"string",c:[e.BE,i],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},a={v:[e.BNM,e.CNM]};return{aliases:["php3","php4","php5","php6"],cI:!0,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[e.HCM,e.C("//","$",{c:[i]}),e.C("/\\*","\\*/",{c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.C("__halt_compiler.+?;",!1,{eW:!0,k:"__halt_compiler",l:e.UIR}),{cN:"string",b:/<<<['"]?\w+['"]?$/,e:/^\w+;?$/,c:[e.BE,{cN:"subst",v:[{b:/\$\w+/},{b:/\{\$/,e:/\}/}]}]},i,{cN:"keyword",b:/\$this\b/},c,{b:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{cN:"function",bK:"function",e:/[;{]/,eE:!0,i:"\\$|\\[|%",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",c,e.CBCM,t,a]}]},{cN:"class",bK:"class interface",e:"{",eE:!0,i:/[:\(\$"]/,c:[{bK:"extends implements"},e.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[e.UTM]},{bK:"use",e:";",c:[e.UTM]},{b:"=>"},t,a]}});hljs.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",t={b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/},{b:/\(/,e:/\)/,c:[e.ASM,e.QSM]}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{b:"@",e:"[{;]",i:/:/,c:[{cN:"keyword",b:/\w+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:c,r:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,t]}]}}); \ No newline at end of file diff --git a/inc/jscripts/editor/images/bold.png b/inc/jscripts/editor/images/bold.png new file mode 100644 index 0000000..889ae80 Binary files /dev/null and b/inc/jscripts/editor/images/bold.png differ diff --git a/inc/jscripts/editor/images/clean.png b/inc/jscripts/editor/images/clean.png new file mode 100644 index 0000000..7e7cefb Binary files /dev/null and b/inc/jscripts/editor/images/clean.png differ diff --git a/inc/jscripts/editor/images/code.png b/inc/jscripts/editor/images/code.png new file mode 100644 index 0000000..63fe6ce Binary files /dev/null and b/inc/jscripts/editor/images/code.png differ diff --git a/inc/jscripts/editor/images/h1.png b/inc/jscripts/editor/images/h1.png new file mode 100644 index 0000000..9c122e9 Binary files /dev/null and b/inc/jscripts/editor/images/h1.png differ diff --git a/inc/jscripts/editor/images/h2.png b/inc/jscripts/editor/images/h2.png new file mode 100644 index 0000000..fbd8765 Binary files /dev/null and b/inc/jscripts/editor/images/h2.png differ diff --git a/inc/jscripts/editor/images/h3.png b/inc/jscripts/editor/images/h3.png new file mode 100644 index 0000000..c7836cf Binary files /dev/null and b/inc/jscripts/editor/images/h3.png differ diff --git a/inc/jscripts/editor/images/handle.png b/inc/jscripts/editor/images/handle.png new file mode 100644 index 0000000..3993b20 Binary files /dev/null and b/inc/jscripts/editor/images/handle.png differ diff --git a/inc/jscripts/editor/images/image.png b/inc/jscripts/editor/images/image.png new file mode 100644 index 0000000..fc3c393 Binary files /dev/null and b/inc/jscripts/editor/images/image.png differ diff --git a/inc/jscripts/editor/images/italic.png b/inc/jscripts/editor/images/italic.png new file mode 100644 index 0000000..8482ac8 Binary files /dev/null and b/inc/jscripts/editor/images/italic.png differ diff --git a/inc/jscripts/editor/images/link.png b/inc/jscripts/editor/images/link.png new file mode 100644 index 0000000..25eacb7 Binary files /dev/null and b/inc/jscripts/editor/images/link.png differ diff --git a/inc/jscripts/editor/images/list-bullet.png b/inc/jscripts/editor/images/list-bullet.png new file mode 100644 index 0000000..4a8672b Binary files /dev/null and b/inc/jscripts/editor/images/list-bullet.png differ diff --git a/inc/jscripts/editor/images/list-item.png b/inc/jscripts/editor/images/list-item.png new file mode 100644 index 0000000..8cb4d69 Binary files /dev/null and b/inc/jscripts/editor/images/list-item.png differ diff --git a/inc/jscripts/editor/images/list-numeric.png b/inc/jscripts/editor/images/list-numeric.png new file mode 100644 index 0000000..33b0b8d Binary files /dev/null and b/inc/jscripts/editor/images/list-numeric.png differ diff --git a/inc/jscripts/editor/images/menu.png b/inc/jscripts/editor/images/menu.png new file mode 100644 index 0000000..44a07af Binary files /dev/null and b/inc/jscripts/editor/images/menu.png differ diff --git a/inc/jscripts/editor/images/paragraph.png b/inc/jscripts/editor/images/paragraph.png new file mode 100644 index 0000000..95704fb Binary files /dev/null and b/inc/jscripts/editor/images/paragraph.png differ diff --git a/inc/jscripts/editor/images/picture.png b/inc/jscripts/editor/images/picture.png new file mode 100644 index 0000000..4a158fe Binary files /dev/null and b/inc/jscripts/editor/images/picture.png differ diff --git a/inc/jscripts/editor/images/quotes.png b/inc/jscripts/editor/images/quotes.png new file mode 100644 index 0000000..e54ebeb Binary files /dev/null and b/inc/jscripts/editor/images/quotes.png differ diff --git a/inc/jscripts/editor/images/stroke.png b/inc/jscripts/editor/images/stroke.png new file mode 100644 index 0000000..612058a Binary files /dev/null and b/inc/jscripts/editor/images/stroke.png differ diff --git a/inc/jscripts/editor/images/submenu.png b/inc/jscripts/editor/images/submenu.png new file mode 100644 index 0000000..03d1977 Binary files /dev/null and b/inc/jscripts/editor/images/submenu.png differ diff --git a/inc/jscripts/editor/markitup.highlight.min.css b/inc/jscripts/editor/markitup.highlight.min.css new file mode 100644 index 0000000..0059efd --- /dev/null +++ b/inc/jscripts/editor/markitup.highlight.min.css @@ -0,0 +1 @@ +.markItUpHighlight{position:relative;overflow:hidden}.markItUpHighlight textarea{position:relative;z-index:2;display:block;background:transparent!important;color:rgba(255,255,255,.5);-webkit-overflow-scrolling:touch;-webkit-text-fill-color:transparent;caret-color:#000;resize:vertical}.markItUpHighlight pre{pointer-events:none!important;border:0!important;flex-direction:column!important;word-wrap:break-word!important;word-break:normal!important;line-height:22px!important;font-size:12px!important;font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace!important}.markItUpHighlight pre code{position:absolute;z-index:1;top:0;left:0;bottom:5px;right:0;white-space:inherit!important;padding:6px 6px 6px 6px !important;line-height:22px!important;font-size:inherit!important;font-family:inherit!important;background:transparent!important}.hljs{display:block;padding:.5em;background:#fff;color:#000}.hljs-comment,.hljs-quote{color:#800}.hljs-keyword,.hljs-selector-tag,.hljs-section,.hljs-title,.hljs-name{color:#008}.hljs-variable,.hljs-template-variable{color:#660}.hljs-string,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-regexp{color:#080}.hljs-literal,.hljs-symbol,.hljs-bullet,.hljs-meta,.hljs-number,.hljs-link{color:#066}.hljs-title,.hljs-doctag,.hljs-type,.hljs-attr,.hljs-built_in,.hljs-builtin-name,.hljs-params{color:#606}.hljs-attribute,.hljs-subst{color:#000}.hljs-formula{background-color:#eee;font-style:italic}.hljs-selector-id,.hljs-selector-class{color:#9b703f}.hljs-addition{background-color:#baeeba}.hljs-deletion{background-color:#ffc8bd}.hljs-doctag,.hljs-strong{font-weight:700}.hljs-emphasis{font-style:italic} \ No newline at end of file diff --git a/inc/jscripts/editor/markitup.highlight.min.js b/inc/jscripts/editor/markitup.highlight.min.js new file mode 100644 index 0000000..b12afcc --- /dev/null +++ b/inc/jscripts/editor/markitup.highlight.min.js @@ -0,0 +1 @@ +!function(a){a.fn.highlight=function(b){function i(b,c){var d=a(b).val(),e=j(d+"\n");a(c).html(e),a(c).each(function(a,b){hljs.highlightBlock(b)})}function j(a){return a.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function k(){var b=a("
").css({visibility:"hidden",width:100,overflow:"scroll"}).appendTo("body"),c=a("
").css({width:"100%"}).appendTo(b).outerWidth();return b.remove(),100-c}if(!document.documentMode&&!window.StyleMedia&&!/Mobi/i.test(navigator.userAgent)){var c=this,d=k();if(b=a.extend({whiteSpace:"pre-wrap",lang:c.data("lang")},b),b.lang)var e=b.lang;else var e=c.closest('div[class^="markItUpSet"]').attr("class").replace("markItUpSet-","");var f=document.createElement("div");a(f).addClass("markItUpHighlight"),a(c).wrap(f);var g=document.createElement("pre");a(g).addClass("markItUpSyntax").appendTo(c.parent());var h=document.createElement("code");a(h).addClass(e).appendTo(a(g)),a(g).css("white-space",b.whiteSpace),c.css("white-space",b.whiteSpace),c.attr("spellcheck",!1),c[0].scrollHeight>c[0].clientHeight?a(h).css("right",d):a(h).css("right",0),i(c,h),c.on("input mouseup",function(){this.scrollHeight>this.clientHeight?a(h).css("right",d):a(h).css("right",0),i(this,h)}),c.scroll(function(){a(h).css({top:-c.scrollTop(),left:-c.scrollLeft()})})}}}(jQuery); \ No newline at end of file diff --git a/inc/jscripts/editor/markitup.min.css b/inc/jscripts/editor/markitup.min.css new file mode 100644 index 0000000..effecb7 --- /dev/null +++ b/inc/jscripts/editor/markitup.min.css @@ -0,0 +1 @@ +.markItUp *{margin:0;padding:0;outline:0;box-sizing:border-box}.markItUpContainer{font:11px Verdana,Arial,Helvetica,sans-serif}.markItUpEditor{font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace;font-size:12px;padding:5px;width:100%;height:320px;clear:both;line-height:22px;overflow:auto;color:#000}.markItUpEditor.nowrap{white-space:nowrap}.markItUpFooter{width:100%}.markItUpResizeHandle{overflow:hidden;width:22px;height:5px;margin-left:auto;margin-right:auto;background-image:url(images/handle.png);cursor:n-resize}.markItUpHeader ul{margin-bottom:5px}.markItUpHeader ul li{list-style:none;display:inline-block;position:relative;margin:0 4px;vertical-align:middle}.markItUpHeader ul li:first-of-type{margin-left:0}.markItUpHeader ul li:hover>ul{display:block}.markItUpHeader ul .markItUpDropMenu{background:url(images/menu.png) 115% 50% no-repeat;margin-right:5px}.markItUpHeader ul .markItUpDropMenu li{margin-right:0}.markItUpHeader ul ul{display:none;position:absolute;top:18px;left:0;background:#FFF;border:1px solid #000}.markItUpHeader ul ul li{float:none;border-bottom:1px solid #000}.markItUpHeader ul ul .markItUpDropMenu{background:url(images/submenu.png) 100% 50% no-repeat #FFF}.markItUpHeader ul .markItUpSeparator{margin:0 10px;width:1px;height:16px;overflow:hidden;background-color:#CCC}.markItUpHeader ul ul .markItUpSeparator{width:auto;height:1px;margin:0}.markItUpHeader ul ul ul{position:absolute;top:-1px;left:150px}.markItUpHeader ul ul ul li{float:none}.markItUpHeader ul a{display:block;width:16px;height:16px;text-indent:-10000px;background-repeat:no-repeat;padding:3px;margin:0}.markItUpHeader ul ul a{display:block;text-indent:0;width:120px;padding:5px 5px 5px 25px;background-position:2px 50%}.markItUpHeader ul ul a:hover{color:#FFF;background-color:#000} \ No newline at end of file diff --git a/inc/jscripts/editor/markitup.min.js b/inc/jscripts/editor/markitup.min.js new file mode 100644 index 0000000..ecdc88f --- /dev/null +++ b/inc/jscripts/editor/markitup.min.js @@ -0,0 +1,27 @@ +// ---------------------------------------------------------------------------- +// markItUp! Universal MarkUp Engine, JQuery plugin +// v 1.1.x +// Dual licensed under the MIT and GPL licenses. +// ---------------------------------------------------------------------------- +// Copyright (C) 2007-2012 Jay Salvat +// http://markitup.jaysalvat.com/ +// ---------------------------------------------------------------------------- +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// ---------------------------------------------------------------------------- +!function($){$.fn.markItUp=function(settings,extraSettings){var method,params,options,ctrlKey,shiftKey,altKey;ctrlKey=shiftKey=altKey=!1,"string"==typeof settings&&(method=settings,params=extraSettings),options={id:"",nameSpace:"",root:"",previewHandler:!1,previewInWindow:"",previewInElement:"",previewAutoRefresh:!0,previewPosition:"after",previewTemplatePath:"~/templates/preview.html",previewParser:!1,previewParserPath:"",previewParserVar:"data",resizeHandle:!0,beforeInsert:"",afterInsert:"",onEnter:{},onShiftEnter:{},onCtrlEnter:{},onTab:{},markupSet:[{}]},$.extend(options,settings,extraSettings),options.root||$("script").each(function(a,b){miuScript=$(b).get(0).src.match(/(.*)jquery\.markitup(\.pack)?\.js$/),null!==miuScript&&(options.root=miuScript[1])});var uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},matched=uaMatch(navigator.userAgent),browser={};return matched.browser&&(browser[matched.browser]=!0,browser.version=matched.version),browser.chrome?browser.webkit=!0:browser.webkit&&(browser.safari=!0),this.each(function(){function localize(a,b){return b?a.replace(/("|')~\//g,"$1"+options.root):a.replace(/^~\//,options.root)}function init(){id="",nameSpace="",options.id?id='id="'+options.id+'"':$$.attr("id")&&(id='id="markItUp'+$$.attr("id").substr(0,1).toUpperCase()+$$.attr("id").substr(1)+'"'),options.nameSpace&&(nameSpace='class="'+options.nameSpace+'"'),$$.wrap("
"),$$.wrap("
'),$$.wrap('
'),$$.addClass("markItUpEditor"),header=$('
').insertBefore($$),$(dropMenus(options.markupSet)).appendTo(header),footer=$('
').insertAfter($$),!0===options.resizeHandle&&!0!==browser.safari&&(resizeHandle=$('
').insertAfter($$).bind("mousedown.markItUp",function(a){var d,e,b=$$.height(),c=a.clientY;d=function(a){return $$.css("height",Math.max(20,a.clientY+b-c)+"px"),!1},e=function(a){return $("html").unbind("mousemove.markItUp",d).unbind("mouseup.markItUp",e),!1},$("html").bind("mousemove.markItUp",d).bind("mouseup.markItUp",e)}),footer.append(resizeHandle)),$$.bind("keydown.markItUp",keyPressed).bind("keyup",keyPressed),$$.bind("insertion.markItUp",function(a,b){!1!==b.target&&get(),textarea===$.markItUp.focused&&markup(b)}),$$.bind("focus.markItUp",function(){$.markItUp.focused=this}),options.previewInElement&&refreshPreview()}function dropMenus(markupSet){var ul=$("
    "),i=0;return $("li:hover > ul",ul).css("display","block"),$.each(markupSet,function(){var button=this,t="",title,li,j;if(title=button.key?(button.name||"")+" [Ctrl+"+button.key+"]":button.name||"",key=button.key?'accesskey="'+button.key+'"':"",button.separator)li=$('
  • '+(button.separator||"")+"
  • ").appendTo(ul);else{for(i++,j=levels.length-1;j>=0;j--)t+=levels[j]+"-";li=$('
  • '+(button.name||"")+"
  • ").bind("contextmenu.markItUp",function(){return!1}).bind("click.markItUp",function(a){a.preventDefault()}).bind("focusin.markItUp",function(){$$.focus()}).bind("mouseup",function(){return button.call&&eval(button.call)(),setTimeout(function(){markup(button)},1),!1}).bind("mouseenter.markItUp",function(){$("> ul",this).show(),$(document).one("click",function(){$("ul ul",header).hide()})}).bind("mouseleave.markItUp",function(){$("> ul",this).hide()}).appendTo(ul),button.dropMenu&&(levels.push(i),$(li).addClass("markItUpDropMenu").append(dropMenus(button.dropMenu)))}}),levels.pop(),ul}function magicMarkups(a){return a?(a=a.toString(),a=a.replace(/\(\!\(([\s\S]*?)\)\!\)/g,function(a,b){var c=b.split("|!|");return!0===altKey?void 0!==c[1]?c[1]:c[0]:void 0===c[1]?"":c[0]}),a=a.replace(/\[\!\[([\s\S]*?)\]\!\]/g,function(a,b){var c=b.split(":!:");return!0!==abort&&(value=prompt(c[0],c[1]?c[1]:""),null===value&&(abort=!0),value)})):""}function prepare(a){return $.isFunction(a)&&(a=a(hash)),magicMarkups(a)}function build(a){var b=prepare(clicked.openWith),c=prepare(clicked.placeHolder),d=prepare(clicked.replaceWith),e=prepare(clicked.closeWith),f=prepare(clicked.openBlockWith),g=prepare(clicked.closeBlockWith),h=clicked.multiline;if(""!==d)block=b+d+e;else if(""===selection&&""!==c)block=b+c+e;else{a=a||selection;var i=[a],j=[];!0===h&&(i=a.split(/\r?\n/));for(var k=0;k=9.5&&0==b)return!1;range=textarea.createTextRange(),range.collapse(!0),range.moveStart("character",a),range.moveEnd("character",b),range.select()}else textarea.setSelectionRange&&textarea.setSelectionRange(a,a+b);textarea.scrollTop=scrollPosition,textarea.focus()}function get(){if(textarea.focus(),scrollPosition=textarea.scrollTop,document.selection)if(selection=document.selection.createRange().text,browser.msie){var a=document.selection.createRange(),b=a.duplicate();for(b.moveToElementText(textarea),caretPosition=-1;b.inRange(a);)b.moveStart("character"),caretPosition++}else caretPosition=textarea.selectionStart;else caretPosition=textarea.selectionStart,selection=textarea.value.substring(caretPosition,textarea.selectionEnd);return selection}function preview(){"function"==typeof options.previewHandler?previewWindow=!0:options.previewInElement?previewWindow=$(options.previewInElement):!previewWindow||previewWindow.closed?options.previewInWindow?(previewWindow=window.open("","preview",options.previewInWindow),$(window).unload(function(){previewWindow.close()})):(iFrame=$(''),"after"==options.previewPosition?iFrame.insertAfter(footer):iFrame.insertBefore(header),previewWindow=iFrame[iFrame.length-1].contentWindow||frame[iFrame.length-1]):!0===altKey&&(iFrame?iFrame.remove():previewWindow.close(),previewWindow=iFrame=!1),options.previewAutoRefresh||refreshPreview(),options.previewInWindow&&previewWindow.focus()}function refreshPreview(){renderPreview()}function renderPreview(){if(options.previewHandler&&"function"==typeof options.previewHandler)options.previewHandler($$.val());else if(options.previewParser&&"function"==typeof options.previewParser){var b=options.previewParser($$.val());writeInPreview(localize(b,1))}else""!==options.previewParserPath?$.ajax({type:"POST",dataType:"text",global:!1,url:options.previewParserPath,data:options.previewParserVar+"="+encodeURIComponent($$.val()),success:function(a){writeInPreview(localize(a,1))}}):template||$.ajax({url:options.previewTemplatePath,dataType:"text",global:!1,success:function(a){writeInPreview(localize(a,1).replace(//g,$$.val()))}});return!1}function writeInPreview(a){if(options.previewInElement)$(options.previewInElement).html(a);else if(previewWindow&&previewWindow.document){try{sp=previewWindow.document.documentElement.scrollTop}catch(a){sp=0}previewWindow.document.open(),previewWindow.document.write(a),previewWindow.document.close(),previewWindow.document.documentElement.scrollTop=sp}}function keyPressed(a){if(shiftKey=a.shiftKey,altKey=a.altKey,ctrlKey=(!a.altKey||!a.ctrlKey)&&(a.ctrlKey||a.metaKey),"keydown"===a.type){if(!0===ctrlKey&&(li=$('a[accesskey="'+(13==a.keyCode?"\\n":String.fromCharCode(a.keyCode))+'"]',header).parent("li"),0!==li.length))return ctrlKey=!1,setTimeout(function(){li.triggerHandler("mouseup")},1),!1;if(13===a.keyCode||10===a.keyCode)return!0===ctrlKey?(ctrlKey=!1,markup(options.onCtrlEnter),options.onCtrlEnter.keepDefault):!0===shiftKey?(shiftKey=!1,markup(options.onShiftEnter),options.onShiftEnter.keepDefault):(markup(options.onEnter),options.onEnter.keepDefault);if(9===a.keyCode)return 1!=shiftKey&&1!=ctrlKey&&1!=altKey&&(-1!==caretOffset?(get(),caretOffset=$$.val().length-caretOffset,set(caretOffset,0),caretOffset=-1,!1):(markup(options.onTab),options.onTab.keepDefault))}}function remove(){$$.unbind(".markItUp").removeClass("markItUpEditor"),$$.parent("div").closest("div.markItUp").parent("div").replaceWith($$),$$.data("markItUp",null)}var $$,textarea,levels,scrollPosition,caretPosition,caretOffset,clicked,hash,header,footer,previewWindow,template,iFrame,abort;if($$=$(this),textarea=this,levels=[],abort=!1,scrollPosition=caretPosition=0,caretOffset=-1,options.previewParserPath=localize(options.previewParserPath),options.previewTemplatePath=localize(options.previewTemplatePath),method)switch(method){case"remove":remove();break;case"insert":markup(params);break;default:$.error("Method "+method+" does not exist on jQuery.markItUp")}else init()})},$.fn.markItUpRemove=function(){return this.each(function(){$(this).markItUp("remove")})},$.markItUp=function(a){var b={target:!1};if($.extend(b,a),b.target)return $(b.target).each(function(){$(this).focus(),$(this).trigger("insertion",[b])});$("textarea").trigger("insertion",[b])}}(jQuery); diff --git a/inc/jscripts/editor/sets/html/set.min.css b/inc/jscripts/editor/sets/html/set.min.css new file mode 100644 index 0000000..213d13a --- /dev/null +++ b/inc/jscripts/editor/sets/html/set.min.css @@ -0,0 +1 @@ +.markItUpSet-html .markItUpButton1 a{background-image:url(../../images/h1.png)}.markItUpSet-html .markItUpButton2 a{background-image:url(../../images/h2.png)}.markItUpSet-html .markItUpButton3 a{background-image:url(../../images/h3.png)}.markItUpSet-html .markItUpButton4 a{background-image:url(../../images/paragraph.png)}.markItUpSet-html .markItUpButton5 a{background-image:url(../../images/bold.png)}.markItUpSet-html .markItUpButton6 a{background-image:url(../../images/italic.png)}.markItUpSet-html .markItUpButton7 a{background-image:url(../../images/stroke.png)}.markItUpSet-html .markItUpButton8 a{background-image:url(../../images/list-bullet.png)}.markItUpSet-html .markItUpButton9 a{background-image:url(../../images/list-numeric.png)}.markItUpSet-html .markItUpButton10 a{background-image:url(../../images/list-item.png)}.markItUpSet-html .markItUpButton11 a{background-image:url(../../images/picture.png)}.markItUpSet-html .markItUpButton12 a{background-image:url(../../images/link.png)}.markItUpSet-html .markItUpButton13 a{background-image:url(../../images/clean.png)} \ No newline at end of file diff --git a/inc/jscripts/editor/sets/html/set.min.js b/inc/jscripts/editor/sets/html/set.min.js new file mode 100644 index 0000000..a724158 --- /dev/null +++ b/inc/jscripts/editor/sets/html/set.min.js @@ -0,0 +1 @@ +var markItUp_html={nameSpace:"markItUpSet-html",onShiftEnter:{keepDefault:!1,replaceWith:"
    \n"},onCtrlEnter:{keepDefault:!1,openWith:"\n

    ",closeWith:"

    "},onTab:{keepDefault:!1,replaceWith:" "},markupSet:[{key:"1",openWith:'',closeWith:""},{key:"2",openWith:'',closeWith:""},{key:"3",openWith:'',closeWith:""},{openWith:'',closeWith:"

    "},{separator:"---------------"},{key:"B",openWith:"(!(|!|)!)",closeWith:"(!(|!|)!)"},{key:"I",openWith:"(!(|!|)!)",closeWith:"(!(|!|)!)"},{key:"S",openWith:"",closeWith:""},{separator:"---------------"},{openWith:"
  • ",closeWith:"
  • ",multiline:!0,openBlockWith:"
      \n",closeBlockWith:"\n
    "},{openWith:"
  • ",closeWith:"
  • ",multiline:!0,openBlockWith:"
      \n",closeBlockWith:"\n
    "},{openWith:"
  • ",closeWith:"
  • "},{separator:"---------------"},{key:"P",replaceWith:'[![Alternative text]!]'},{key:"L",openWith:'[![Name]!]',closeWith:""},{separator:"---------------"},{className:"clean",replaceWith:function(a){return a.selection.replace(/<(.*?)>/g,"")}}],afterInsert:function(a){$(a.textarea).trigger("input")}}; \ No newline at end of file diff --git a/inc/jscripts/editor/sets/markdown/set.min.css b/inc/jscripts/editor/sets/markdown/set.min.css new file mode 100644 index 0000000..d327539 --- /dev/null +++ b/inc/jscripts/editor/sets/markdown/set.min.css @@ -0,0 +1 @@ +.markItUpSet-markdown .markItUpButton1 a{background-image:url(../../images/h1.png)}.markItUpSet-markdown .markItUpButton2 a{background-image:url(../../images/h2.png)}.markItUpSet-markdown .markItUpButton3 a{background-image:url(../../images/h3.png)}.markItUpSet-markdown .markItUpButton4 a{background-image:url(../../images/bold.png)}.markItUpSet-markdown .markItUpButton5 a{background-image:url(../../images/italic.png)}.markItUpSet-markdown .markItUpButton6 a{background-image:url(../../images/list-bullet.png)}.markItUpSet-markdown .markItUpButton7 a{background-image:url(../../images/list-numeric.png)}.markItUpSet-markdown .markItUpButton8 a{background-image:url(../../images/picture.png)}.markItUpSet-markdown .markItUpButton9 a{background-image:url(../../images/link.png)}.markItUpSet-markdown .markItUpButton10 a{background-image:url(../../images/quotes.png)}.markItUpSet-markdown .markItUpButton11 a{background-image:url(../../images/code.png)} \ No newline at end of file diff --git a/inc/jscripts/editor/sets/markdown/set.min.js b/inc/jscripts/editor/sets/markdown/set.min.js new file mode 100644 index 0000000..8a057b5 --- /dev/null +++ b/inc/jscripts/editor/sets/markdown/set.min.js @@ -0,0 +1 @@ +var markItUp_markdown={nameSpace:"markItUpSet-markdown",onShiftEnter:{keepDefault:!1,openWith:"\n\n"},onTab:{keepDefault:!1,replaceWith:" "},markupSet:[{key:"1",placeHolder:"H1",closeWith:function(a){return miu.markdownTitle(a,"=")}},{key:"2",placeHolder:"H2",closeWith:function(a){return miu.markdownTitle(a,"-")}},{openWith:"### ",placeHolder:"H3"},{separator:"---------------"},{key:"B",openWith:"**",closeWith:"**"},{key:"I",openWith:"_",closeWith:"_"},{separator:"---------------"},{openWith:"- "},{openWith:function(a){return a.line+". "}},{separator:"---------------"},{key:"P",replaceWith:"![[![Alternative text]!]]([![Url:!:http://]!])"},{key:"L",openWith:"[[![Name]!]",closeWith:"]([![Url:!:http://]!])"},{separator:"---------------"},{openWith:"> "},{openWith:"(!(\t|!|`)!)",closeWith:"(!(`)!)"}],afterInsert:function(a){$(a.textarea).trigger("input")}},miu={markdownTitle:function(a,b){for(heading="",n=$.trim(a.selection||a.placeHolder).length,i=0;it){var c=i-t,f=p(this).top;if(ld&&a.pageY>f+i-c)return}void 0===e.oldDisplay&&(e.oldDisplay=e.style.display),e.style.display="none",l
    ')}b(h).each(function(i){b(this).on("click",function(j){j.preventDefault();g.updatePictureInLightbox(b(this),h,i);b("#bootstrap-media-lightbox").modal("show")})})};a.prototype.updatePictureInLightbox=function(f,i,d){var e=this;if(this.options.gallery===false||i.size()<2){b("#bootstrap-media-lightbox-backward").hide();b("#bootstrap-media-lightbox-forward").hide()}else{if(d==0){b("#bootstrap-media-lightbox-backward").hide();b("#bootstrap-media-lightbox-forward").show()}else{if(d==i.size()-1){b("#bootstrap-media-lightbox-backward").show();b("#bootstrap-media-lightbox-forward").hide()}else{b("#bootstrap-media-lightbox-backward").show();b("#bootstrap-media-lightbox-forward").show()}}}var g=f.attr("href");var c=["png","jpg","jpeg","bmp"];var h=g.split(".").pop().toLowerCase();if(f.data("target")!==undefined){}if(b.inArray(h,c)>-1){this.addImage(g)}else{if(g.substr(0,22)=="http://www.youtube.com"){this.addYoutubeVideo(g)}else{if(g.substr(0,16)=="http://vimeo.com"){this.addVimeoVideo(g)}else{if(g.substr(0,4)=="http"){this.addIframe(g)}}}}this.addCaption(f);b("#bootstrap-media-lightbox-forward").unbind("click");b("#bootstrap-media-lightbox-forward").click(function(){e.updatePictureInLightbox(i.eq(d+1),i,d+1)});b("#bootstrap-media-lightbox-backward").unbind("click");b("#bootstrap-media-lightbox-backward").click(function(){e.updatePictureInLightbox(i.eq(d-1),i,d-1)});b("#bootstrap-media-lightbox-close").click(function(){b("#bootstrap-media-lightbox-iframe").attr("src","")})};a.prototype.addImage=function(e){var d=this;var c=new Image();c.onload=function(){if(d.options.width===undefined&&d.options.height===undefined){d.contentWidth=c.width;d.contentHeight=c.height}else{if(d.options.width===undefined){d.contentWidth=d.options.height/c.height*c.width;d.contentHeight=d.options.height}else{if(d.options.height===undefined){d.contentHeight=d.options.width/c.width*c.height;d.contentWidth=d.options.width}}}d.validateSize();var f=b("#bootstrap-media-lightbox-content-container");f.html('');d.setMargins(f)};c.src=e};a.prototype.addIframe=function(d){if(this.options.width===undefined&&this.options.height===undefined){this.contentWidth=420;this.contentHeight=315}else{if(this.options.width===undefined){this.contentWidth=420/(315/this.options.height);this.contentHeight=this.options.height}else{if(this.options.height===undefined){this.contentHeight=315/(420/this.options.width);this.contentWidth=this.options.width}}}var e=b("#bootstrap-media-lightbox-content-container");var c='';this.setMargins(e);e.html(c)};a.prototype.addYoutubeVideo=function(e){if(this.options.width===undefined&&this.options.height===undefined){this.contentWidth=420;this.contentHeight=315}else{if(this.options.width===undefined){this.contentWidth=420/(315/this.options.height);this.contentHeight=this.options.height}else{if(this.options.height===undefined){this.contentHeight=315/(420/this.options.width);this.contentWidth=this.options.width}}}this.validateSize();var f=b("#bootstrap-media-lightbox-content-container");var d=e.substr(31);var c='';f.html(c);this.setMargins(f)};a.prototype.addVimeoVideo=function(e){if(this.options.width===undefined&&this.options.height===undefined){this.contentWidth=420;this.contentHeight=315}else{if(this.options.width===undefined){this.contentWidth=420/(315/this.options.height);this.contentHeight=this.options.height}else{if(this.options.height===undefined){this.contentHeight=315/(420/this.options.width);this.contentWidth=this.options.width}}}this.validateSize();var f=b("#bootstrap-media-lightbox-content-container");var d=e.substr(17);var c='';f.html(c);this.setMargins(f)};a.prototype.addCaption=function(d){var c=d.attr("title");if(c!==""&&c!==undefined&&this.options.caption===true){b("#bootstrap-media-lightbox-caption-container").show();b("#bootstrap-media-lightbox-caption").text(c)}else{b("#bootstrap-media-lightbox-caption-container").hide()}};a.prototype.getFontSet=function(){var c=b('');if(c.css("font-family")==="FontAwesome"){return"fa"}return"glyphicon"};a.prototype.validateSize=function(e){var g=b(window).height();var d=b(window).width();if(this.contentWidth+50>d){var c=this.contentWidth;this.contentWidth=d-50;this.contentHeight=this.contentHeight*this.contentWidth/c}if(this.contentHeight+80>g){var f=this.contentHeight;this.contentHeight=g-80;this.contentWidth=this.contentWidth*this.contentHeight/f}};a.prototype.setMargins=function(c){var e=b(window).height();var d=b(window).width();c.css({"margin-top":(e-50-this.contentHeight)/2});c.css({"margin-left":(d-this.contentWidth)/2})};b.fn.lightbox=function(c){new a(b(this),c)};b(".lightbox").lightbox()})(jQuery); \ No newline at end of file diff --git a/inc/jscripts/tinynav.min.js b/inc/jscripts/tinynav.min.js new file mode 100644 index 0000000..74a0405 --- /dev/null +++ b/inc/jscripts/tinynav.min.js @@ -0,0 +1,3 @@ +/*! http://tinynav.viljamis.com v1.2 by @viljamis */ +(function(a,k,g){a.fn.tinyNav=function(l){var c=a.extend({active:"selected",header:"",indent:"- ",label:""},l);return this.each(function(){g++;var h=a(this),b="tinynav"+g,f=".l_"+b,e=a(" +
    +
    + +
    + + + + + + + +
    +
    +
    +

    {$lang.general.settings}

    +
    +
    +
    + + {if: !empty($blog.form.cover_photo)} + + {else} + + {/if} +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + +
    + +
    + +
    + + + {$lang.general.cancel} +
    +
    +
    + + \ No newline at end of file diff --git a/inc/modules/blog/view/admin/manage.html b/inc/modules/blog/view/admin/manage.html new file mode 100644 index 0000000..597f31d --- /dev/null +++ b/inc/modules/blog/view/admin/manage.html @@ -0,0 +1,86 @@ +
    +
    +
    +
    +

    {$lang.general.manage}

    + +
    + +
    +
    +
    +

    {$lang.blog.post_count} {$blog.postCount}

    + +
    +
    + + + + + + + + + + + + + + {if: !empty($blog.posts)} + {loop: $blog.posts} + + + + + + + + + + {/loop} + {else} + + {/if} + +
    {$lang.blog.manage_title}{$lang.blog.manage_status}{$lang.blog.manage_author}{$lang.blog.manage_comments}{$lang.blog.manage_date}{$lang.general.actions}
    {$value.title} + {if: $value.status >= 2} + + {elseif: $value.status == 1} + + {else} + + {/if} + {$value.user}{$value.comments}{$value.published_at} + + + +
    {$lang.general.empty_array}
    +
    +
    + {$blog.pagination} +
    +
    +
    +
    + + diff --git a/inc/modules/blog/view/admin/settings.html b/inc/modules/blog/view/admin/settings.html new file mode 100644 index 0000000..55f7032 --- /dev/null +++ b/inc/modules/blog/view/admin/settings.html @@ -0,0 +1,42 @@ +
    +
    +
    +
    +
    +

    {$lang.settings.general}

    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    + +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/inc/modules/blog/view/disqus.html b/inc/modules/blog/view/disqus.html new file mode 100644 index 0000000..16608ec --- /dev/null +++ b/inc/modules/blog/view/disqus.html @@ -0,0 +1,23 @@ +{if: !empty($settings.blog.disqus)} + {if: isset($isPost) && $post.comments == 1} + + {/if} + {if: isset($isBlog) || isset($isPost)} + + {/if} +{/if} \ No newline at end of file diff --git a/inc/modules/blog/view/feed.xml b/inc/modules/blog/view/feed.xml new file mode 100644 index 0000000..d97b436 --- /dev/null +++ b/inc/modules/blog/view/feed.xml @@ -0,0 +1,23 @@ + + + + {$settings.title} + {?= url() ?} + {$settings.description} +{loop: $posts} + + {$value.title} + {$value.url} + {$value.content|cut:250} + {$value.published_at} + {if: $value.cover_photo} + + {$value.cover_url} + {$value.title} + {?= url() ?} + + {/if} + +{/loop} + + \ No newline at end of file diff --git a/inc/modules/carousel/Info.php b/inc/modules/carousel/Info.php new file mode 100644 index 0000000..4ae29f5 --- /dev/null +++ b/inc/modules/carousel/Info.php @@ -0,0 +1,19 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +return [ + 'name' => $core->lang['carousel']['module_name'], + 'description' => $core->lang['carousel']['module_desc'], + 'author' => 'Sruu.pl', + 'version' => '1.0', + 'compatibility' => '1.3.*', + 'icon' => 'retweet', +]; diff --git a/inc/modules/carousel/ReadMe.md b/inc/modules/carousel/ReadMe.md new file mode 100644 index 0000000..5ff6067 --- /dev/null +++ b/inc/modules/carousel/ReadMe.md @@ -0,0 +1,5 @@ +This module displays images from the selected photo gallery. Paste code below in the template or in the page content: + +`{$carousel.gallery-name}` + +After that, change `gallery-name` to chosen gallery slug. \ No newline at end of file diff --git a/inc/modules/carousel/Site.php b/inc/modules/carousel/Site.php new file mode 100644 index 0000000..7513a04 --- /dev/null +++ b/inc/modules/carousel/Site.php @@ -0,0 +1,48 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Carousel; + +use Inc\Core\SiteModule; + +class Site extends SiteModule +{ + public function init() + { + $this->tpl->set('carousel', $this->_insertCarousels()); + } + + private function _insertCarousels() + { + $assign = []; + $tempAssign = []; + $galleries = $this->db('galleries')->toArray(); + + if (!empty($galleries)) { + foreach ($galleries as $gallery) { + $items = $this->db('galleries_items')->where('gallery', $gallery['id'])->toArray(); + $tempAssign = $gallery; + + if (count($items)) { + foreach ($items as &$item) { + $item['src'] = unserialize($item['src']); + } + + $tempAssign['items'] = $items; + + $assign[$gallery['slug']] = $this->draw('carousel.html', ['carousel' => $tempAssign]); + } + } + } + + return $assign; + } +} diff --git a/inc/modules/carousel/lang/admin/en_english.ini b/inc/modules/carousel/lang/admin/en_english.ini new file mode 100644 index 0000000..2b9436c --- /dev/null +++ b/inc/modules/carousel/lang/admin/en_english.ini @@ -0,0 +1,2 @@ +module_name = "Carousel" +module_desc = "Cyclic slideshow. It requires the gallery module." \ No newline at end of file diff --git a/inc/modules/carousel/lang/admin/pl_polski.ini b/inc/modules/carousel/lang/admin/pl_polski.ini new file mode 100644 index 0000000..d373fd5 --- /dev/null +++ b/inc/modules/carousel/lang/admin/pl_polski.ini @@ -0,0 +1,2 @@ +module_name = "Karuzela" +module_desc = "Cykliczny pokaz slajdów. Do działania wymaga modułu galerii." \ No newline at end of file diff --git a/inc/modules/carousel/view/carousel.html b/inc/modules/carousel/view/carousel.html new file mode 100644 index 0000000..e5b4e2d --- /dev/null +++ b/inc/modules/carousel/view/carousel.html @@ -0,0 +1,29 @@ + \ No newline at end of file diff --git a/inc/modules/contact/Admin.php b/inc/modules/contact/Admin.php new file mode 100644 index 0000000..8642d46 --- /dev/null +++ b/inc/modules/contact/Admin.php @@ -0,0 +1,92 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Contact; + +use Inc\Core\AdminModule; + +class Admin extends AdminModule +{ + public function navigation() + { + return [ + $this->lang('settings', 'general') => 'settings', + ]; + } + + public function getSettings() + { + $value = $this->settings('contact'); + + if (is_numeric($value['email'])) { + $assign['users'] = $this->_getUsers($value['email']); + $assign['email'] = null; + } else { + $assign['users'] = $this->_getUsers(); + $assign['email'] = $value['email']; + } + + $assign['driver'] = $value['driver']; + $assign['phpmailer'] = [ + 'server' => $value['phpmailer.server'], + 'port' => $value['phpmailer.port'], + 'username' => $value['phpmailer.username'], + 'password' => $value['phpmailer.password'], + 'name' => $value['phpmailer.name'], + ]; + + return $this->draw('settings.html', ['contact' => $assign]); + } + + public function postSave() + { + $update = [ + 'email' => ($_POST['user'] > 0 ? $_POST['user'] : $_POST['email']), + 'driver' => $_POST['driver'], + 'phpmailer.server' => $_POST['phpmailer']['server'], + 'phpmailer.port' => $_POST['phpmailer']['port'], + 'phpmailer.username' => $_POST['phpmailer']['username'], + 'phpmailer.password' => $_POST['phpmailer']['password'], + 'phpmailer.name' => $_POST['phpmailer']['name'], + ]; + + $errors = 0; + foreach ($update as $field => $value) { + if (!$this->db('settings')->where('module', 'contact')->where('field', $field)->save(['value' => $value])) { + $errors++; + } + } + + if (!$errors) { + $this->notify('success', $this->lang('save_success')); + } else { + $this->notify('failure', $this->lang('save_failure')); + } + + redirect(url([ADMIN, 'contact', 'settings'])); + } + + private function _getUsers($id = null) + { + $rows = $this->db('users')->where('role', 'admin')->toArray(); + if (count($rows)) { + foreach ($rows as $row) { + if ($id == $row['id']) { + $attr = 'selected'; + } else { + $attr = null; + } + $result[] = $row + ['attr' => $attr]; + } + } + return $result; + } +} diff --git a/inc/modules/contact/Info.php b/inc/modules/contact/Info.php new file mode 100644 index 0000000..70b3d1d --- /dev/null +++ b/inc/modules/contact/Info.php @@ -0,0 +1,35 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +return [ + 'name' => $core->lang['contact']['module_name'], + 'description' => $core->lang['contact']['module_desc'], + 'author' => 'Sruu.pl', + 'version' => '1.1', + 'compatibility' => '1.3.*', + 'icon' => 'envelope', + + 'install' => function () use ($core) { + $core->db()->pdo()->exec("INSERT INTO `settings` + (`module`, `field`, `value`) + VALUES + ('contact', 'email', 1), + ('contact', 'driver', 'mail'), + ('contact', 'phpmailer.server', 'smtp.example.com'), + ('contact', 'phpmailer.port', '587'), + ('contact', 'phpmailer.username', 'login@example.com'), + ('contact', 'phpmailer.name', 'Batflat contact'), + ('contact', 'phpmailer.password', 'yourpassword')"); + }, + 'uninstall' => function () use ($core) { + $core->db()->pdo()->exec("DELETE FROM `settings` WHERE `module` = 'contact'"); + } +]; diff --git a/inc/modules/contact/Site.php b/inc/modules/contact/Site.php new file mode 100644 index 0000000..1b296b3 --- /dev/null +++ b/inc/modules/contact/Site.php @@ -0,0 +1,177 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Contact; + +use Inc\Core\SiteModule; + +class Site extends SiteModule +{ + private $_headers; + private $_params; + private $_error = null; + + private $mail = []; + + public function init() + { + $this->tpl->set('contact', function () { + if (isset($_POST['send-email'])) { + if ($this->_initDriver()) { + if ($this->_sendEmail()) { + $this->notify('success', $this->lang('send_success')); + } else { + $this->notify('failure', $this->_error); + } + } else { + $this->notify('failure', $this->_error); + } + + redirect(currentURL()); + } + return ['form' => $this->_insertForm()]; + }); + } + + private function _insertForm() + { + return $this->draw('form.html'); + } + + private function _initDriver() + { + $settings = $this->settings('contact'); + + $this->email['driver'] = $settings['driver']; + + $data = $_POST; + htmlspecialchars_array($data); + + if ($this->_checkErrors($data)) { + return false; + } + + $this->email['subject'] = $data['subject']; + $this->email['from'] = $data['from']; + + if ($settings['driver'] == 'mail') { + $this->email['sender'] = $this->settings('settings', 'title')." "; + } elseif ($settings['driver'] == 'phpmailer' && class_exists('PHPMailer')) { + $this->email['sender'] = [ + $this->settings('contact', 'phpmailer.username'), + $this->settings('contact', 'phpmailer.name'), + ]; + } + + if (!is_numeric($settings['email'])) { + $this->email['to'] = $settings['email']; + } else { + $user = $this->db('users')->where($settings['email'])->oneArray(); + $this->email['to'] = $user['email']; + } + + $this->email['message'] = $this->draw('mail.html', ['mail' => $data]); + + return true; + } + + private function _checkErrors($array) + { + if (!filter_var($array['from'], FILTER_VALIDATE_EMAIL)) { + $this->_error = $this->lang('wrong_email'); + } + + if (checkEmptyFields(['name', 'subject', 'from', 'message'], $array)) { + $this->_error = $this->lang('empty_inputs'); + } + + // antibot field + if (!empty($array['title'])) { + exit(); + } + + if (isset($_COOKIE['MailWasSend'])) { + $this->_error = $this->lang('antiflood'); + } + + if ($this->_error) { + return true; + } + + return false; + } + + private function _sendEmail() + { + if ($this->email['driver'] == 'mail') { + $headers = "From: {$this->email['sender']}\n"; + $headers .= "Reply-To: {$this->email['from']}\n"; + $headers .= "MIME-Version: 1.0\n"; + $headers .= "Content-type: text/html; charset=utf-8\n"; + + if (@mail($this->email['to'], '=?UTF-8?B?'.base64_encode($this->email['subject']).'?=', $this->email['message'], $headers)) { + // cookies antiflood + $cookieParams = session_get_cookie_params(); + setcookie("MailWasSend", 'BATFLAT', time()+360, $cookieParams["path"], $cookieParams["domain"], null, true); + return true; + } else { + $this->_error = $this->lang('send_failure'); + return false; + } + } elseif ($this->email['driver'] == 'phpmailer') { + $settings = $this->settings('contact'); + + try { + $mail = new \PHPMailer(true); + $mail->isSMTP(); // Set mailer to use SMTP + $mail->Host = $settings['phpmailer.server']; // Specify main and backup SMTP servers + $mail->SMTPAuth = true; // Enable SMTP authentication + $mail->Username = $settings['phpmailer.username']; // SMTP username + $mail->Password = $settings['phpmailer.password']; // SMTP password + $mail->SMTPSecure = 'TLS'; // Enable TLS encryption, `ssl` also accepted + $mail->Port = $settings['phpmailer.port']; // TCP port to connect to + $mail->CharSet = 'UTF-8'; + + $mail->Subject = $this->email['subject']; + $mail->Body = $this->email['message']; + + $mail->addReplyTo($this->email['from']); + $mail->setFrom($this->email['sender'][0], $this->email['sender'][1]); + $mail->addAddress($this->email['to']); + + $mail->SMTPOptions = array( + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true + ) + ); + + $mail->isHTML(true); + + if ($mail->send()) { + $cookieParams = session_get_cookie_params(); + setcookie("MailWasSend", 'BATFLAT', time()+360, $cookieParams["path"], $cookieParams["domain"], null, true); + } + } catch (\phpmailerException $e) { + $this->_error = $e->errorMessage(); + } catch (\Exception $e) { + $this->_error = $e->getMessage(); + } + + if ($this->_error) { + return false; + } + + return true; + } + } +} diff --git a/inc/modules/contact/lang/admin/en_english.ini b/inc/modules/contact/lang/admin/en_english.ini new file mode 100644 index 0000000..3a1a01b --- /dev/null +++ b/inc/modules/contact/lang/admin/en_english.ini @@ -0,0 +1,21 @@ +module_name = "Contact" +module_desc = "Display contact form on the page." + +recipient = "Recipient" +custom = "-- Custom email address --" +or_mail = "or custom e-mail address" +example = "Example: contact@johndoe.com" +save_success = "Contact data saved." +save_failure = "Failed to save contact data." +info = "Place this tag in website template" + +phpmailer_error = "Sorry, I can't find PHPMailer class. Please, execute composer require phpmailer/phpmailer in your project terminal." + +driver = "Mail driver" +mail = "mail() (default)" +phpmailer = "PHPMailer (SMTP)" +server = "Server" +port = "Port" +username = "Username" +password = "Password" +name = "Sender name" \ No newline at end of file diff --git a/inc/modules/contact/lang/admin/pl_polski.ini b/inc/modules/contact/lang/admin/pl_polski.ini new file mode 100644 index 0000000..4944ab2 --- /dev/null +++ b/inc/modules/contact/lang/admin/pl_polski.ini @@ -0,0 +1,21 @@ +module_name = "Kontakt" +module_desc = "Wyświetla formularz kontaktowy na stronie." + +recipient = "Odbiorca" +custom = "-- Własny adres mailowy --" +or_mail = "lub adres e-mail" +example = "Przykład: adam@nowak.com" +save_success = "Pomyślnie zaktualizowano dane kontaktowe." +save_failure = "Nie udało się zaktualizować danych kontaktowych." +info = "Aby wyświetlić formularz na stronie, wklej tag" + +phpmailer_error = "Klasa PHPMailer nie jest zainstalowana. Użyj composer require phpmailer/phpmailer aby zainstalować dodatkowy pakiet." + +driver = "Wybierz sterownik do wysyłania wiadomości" +mail = "mail() (domyślne)" +phpmailer = "PHPMailer (SMTP)" +server = "Serwer poczty wychodzącej" +port = "Port" +username = "Nazwa użytkownika" +password = "Hasło użytkownika" +name = "Nazwa wyświetlana" \ No newline at end of file diff --git a/inc/modules/contact/lang/en_english.ini b/inc/modules/contact/lang/en_english.ini new file mode 100644 index 0000000..0d5a526 --- /dev/null +++ b/inc/modules/contact/lang/en_english.ini @@ -0,0 +1,10 @@ +full_name = "Firstname and surname" +email = "E-mail" +subject = "Subject" +message = "Message" +send = "Send" +send_success = "Mail successfully sent. I will contact you soon." +send_failure = "Unable to send a message. Probably mail() function is disabled on the server." +wrong_email = "Submited e-mail address is incorrect." +empty_inputs = "Fill all required fields to send a message." +antiflood = "You have to wait a while before you will send another message." \ No newline at end of file diff --git a/inc/modules/contact/lang/pl_polski.ini b/inc/modules/contact/lang/pl_polski.ini new file mode 100644 index 0000000..b83d4db --- /dev/null +++ b/inc/modules/contact/lang/pl_polski.ini @@ -0,0 +1,10 @@ +full_name = "Imię i nazwisko" +email = "Adres e-mail" +subject = "Temat" +message = "Wiadomość" +send = "Wyślij" +send_success = "Wiadomość została pomyślnie wysłana! Wkrótce się z Tobą skontaktujemy." +send_failure = "Nie udało się wysłać wiadomości. Możliwe, że funkcja mail() jest wyłączona na serwerze." +wrong_email = "Wprowadzony adres e-mail jest niepoprawny. Popraw go." +empty_inputs = "Nie uzupełniono wszystkich wymaganych pól formularza. Popraw je." +antiflood = "Przed chwilą wysłałeś już jednego maila. Musisz trochę poczekać." \ No newline at end of file diff --git a/inc/modules/contact/view/admin/settings.html b/inc/modules/contact/view/admin/settings.html new file mode 100644 index 0000000..31522af --- /dev/null +++ b/inc/modules/contact/view/admin/settings.html @@ -0,0 +1,89 @@ +
    +
    +
    +
    {$lang.general.settings}
    +
    +
    +
    + + +
    +
    + + +
    +
    + +

    + +
    + +

    +
    + {if: !class_exists("PHPMailer")} +
    {$lang.contact.phpmailer_error}
    + {/if} +
    +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + +
    +
    + {$lang.contact.info} {noparse}{$contact.form}{/noparse}. + info +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/inc/modules/contact/view/form.html b/inc/modules/contact/view/form.html new file mode 100644 index 0000000..75a5952 --- /dev/null +++ b/inc/modules/contact/view/form.html @@ -0,0 +1,32 @@ +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    + + +
    + +
    + + + + +
    \ No newline at end of file diff --git a/inc/modules/contact/view/mail.html b/inc/modules/contact/view/mail.html new file mode 100644 index 0000000..8f9f70e --- /dev/null +++ b/inc/modules/contact/view/mail.html @@ -0,0 +1,318 @@ + + + + + + {$mail.subject} + + + + + + + + + + +
      +
    + + + + +
    + + {$mail.name|e} ({$mail.from}) +

    {$mail.subject|e}

    + + + +
    + {?=nl2br($mail.message)?} +
    +
    + + + + + +
    +
     
    + + \ No newline at end of file diff --git a/inc/modules/dashboard/Admin.php b/inc/modules/dashboard/Admin.php new file mode 100644 index 0000000..c07c7d1 --- /dev/null +++ b/inc/modules/dashboard/Admin.php @@ -0,0 +1,100 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Dashboard; + +use Inc\Core\AdminModule; + +class Admin extends AdminModule +{ + public function navigation() + { + return [ + 'Main' => 'main' + ]; + } + + public function getMain() + { + $this->core->addCSS(url(MODULES.'/dashboard/css/style.css?v={$bat.version}')); + return $this->draw('dashboard.html', ['modules' => $this->_modulesList(), 'news' => $this->_fetchNews()]); + } + + private function _modulesList() + { + $modules = array_column($this->db('modules')->toArray(), 'dir'); + $result = []; + + if ($this->core->getUserInfo('access') != 'all') { + $modules = array_intersect($modules, explode(',', $this->core->getUserInfo('access'))); + } + + foreach ($modules as $name) { + $files = [ + 'info' => MODULES.'/'.$name.'/Info.php', + 'admin' => MODULES.'/'.$name.'/Admin.php', + ]; + + if (file_exists($files['info']) && file_exists($files['admin'])) { + $details = $this->core->getModuleInfo($name); + $features = $this->core->getModuleNav($name); + + if (empty($features)) { + continue; + } + + $details['url'] = url([ADMIN, $name, array_shift($features)]); + + $result[] = $details; + } + } + return $result; + } + + private function _fetchNews() + { + \libxml_use_internal_errors(true); + $assign = []; + $lang = $this->settings('settings.lang_admin'); + if (!in_array($lang, ['en_english', 'pl_polski'])) { + $lang = 'en_english'; + } + + $xml = \Inc\Core\Lib\HttpRequest::get('https://batflat.org/blog/feed/'.$lang); + if (!empty($xml) && ($rss = simplexml_load_string($xml))) { + $counter = 0; + foreach ($rss->channel->item as $item) { + if ($counter >= 3) { + break; + } + + $assign[$counter]['title'] = (string) $item->title; + $assign[$counter]['link'] = (string) $item->link; + $assign[$counter]['desc'] = (string) $item->description; + $assign[$counter]['date'] = date('Y-m-d', strtotime($item->pubDate)); + if (isset($item->image)) { + $assign[$counter]['image'] = (string) $item->image->url; + } + + $counter++; + } + } else { + $assign[] = [ + 'title' => $this->lang('rss_fail_title'), + 'link' => '#', + 'desc' => $this->lang('rss_fail_desc'), + 'date' => null + ]; + } + + return $assign; + } +} diff --git a/inc/modules/dashboard/Info.php b/inc/modules/dashboard/Info.php new file mode 100644 index 0000000..c04999e --- /dev/null +++ b/inc/modules/dashboard/Info.php @@ -0,0 +1,19 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +return [ + 'name' => $core->lang['dashboard']['module_name'], + 'description' => $core->lang['dashboard']['module_desc'], + 'author' => 'Sruu.pl', + 'version' => '1.1', + 'compatibility' => '1.3.*', + 'icon' => 'home' +]; diff --git a/inc/modules/dashboard/css/style.css b/inc/modules/dashboard/css/style.css new file mode 100644 index 0000000..8151b8d --- /dev/null +++ b/inc/modules/dashboard/css/style.css @@ -0,0 +1,82 @@ +.module { + display: inline-block; + width: 25%; + padding-left: 15px; + padding-right: 15px; + margin-right: -4px; +} + .module .panel-body { + display: block; + padding: 0; + } + .module .panel-body .panel-thumb { + color: #222222; + font-size: 48px; + background: #F8BE12; + height: 108px; + position: relative; + overflow: hidden; + } + .module .panel-body .panel-thumb .fa { + font-size: 48px; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%) scale(1, 1); + -webkit-transform: translate(-50%, -50%) scale(1, 1); + transition: transform 0.2s; + } + .module .panel-body .panel-thumb:hover .fa { + transform: translate(-50%, -50%) scale(1.5, 1.5); + -webkit-transform: translate(-50%, -50%) scale(1.5, 1.5); + } + .module .panel-body h4 { + color: #656565; + } + + .module:nth-child(-3n+5) .panel-thumb, + .module:nth-child(3n+4):nth-child(-3n+13) .panel-thumb, + .module:nth-child(3n+12):nth-child(-3n+18) .panel-thumb, + .module:nth-child(n+20):nth-child(-n+21) .panel-thumb, + .module:nth-child(2n+21) .panel-thumb { + background: #222222; + color: #F8BE12; + } + +@media (min-width: 992px) and (max-width: 1200px) { + .module { + width: 33.33%; + } +} +@media (min-width: 768px) and (max-width: 992px) { + .module { + width: 50%; + } +} +@media (max-width: 768px) { + .module { + width: 100%; + } +} + +.news { + max-height: 425px; + overflow: auto; +} + .news-item:not(:last-of-type) { + padding-bottom: 15px; + margin-bottom: 10px; + border-bottom: 1px solid #e9e9e9; + } + .news-item .news-header { + margin-bottom: 8px; + } + .news-item .news-header .title { + font-weight: 700; + } + .news-item .news-header time { + font-size: 12px; + } + .news-item .news-content { + font-size: 14px; + } \ No newline at end of file diff --git a/inc/modules/dashboard/lang/admin/en_english.ini b/inc/modules/dashboard/lang/admin/en_english.ini new file mode 100644 index 0000000..0a6fb5c --- /dev/null +++ b/inc/modules/dashboard/lang/admin/en_english.ini @@ -0,0 +1,6 @@ +module_name = "Dashboard" +module_desc = "Fast access to modules and news." + +news = "News" +rss_fail_title = "Connection timeout" +rss_fail_desc = "Unable to download latest news from Batflat.org." \ No newline at end of file diff --git a/inc/modules/dashboard/lang/admin/pl_polski.ini b/inc/modules/dashboard/lang/admin/pl_polski.ini new file mode 100644 index 0000000..9371a2a --- /dev/null +++ b/inc/modules/dashboard/lang/admin/pl_polski.ini @@ -0,0 +1,6 @@ +module_name = "Ekran startowy" +module_desc = "Szybki dostęp do modułów i aktualności." + +news = "Aktualności" +rss_fail_title = "Upłnął czas żądania" +rss_fail_desc = "Nie można pobrać aktualności ze strony Batflat.org." \ No newline at end of file diff --git a/inc/modules/dashboard/view/admin/dashboard.html b/inc/modules/dashboard/view/admin/dashboard.html new file mode 100644 index 0000000..adb1e69 --- /dev/null +++ b/inc/modules/dashboard/view/admin/dashboard.html @@ -0,0 +1,50 @@ +
    +
    +
    + + {loop: $modules} + + {/loop} + +
    +
    + +
    + +
    +
    \ No newline at end of file diff --git a/inc/modules/devbar/Admin.php b/inc/modules/devbar/Admin.php new file mode 100644 index 0000000..5a61cbe --- /dev/null +++ b/inc/modules/devbar/Admin.php @@ -0,0 +1,62 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Devbar; + +use Inc\Core\AdminModule; + +class Admin extends AdminModule +{ + private $timer = 0; + + public function __construct(\Inc\Core\Main $core) + { + parent::__construct($core); + + $this->timer = -microtime(true); + } + public function init() + { + if (DEV_MODE && strpos(get_headers_list('Content-Type'), 'text/html') !== false) { + $this->core->addCSS(url(MODULES.'/devbar/css/style.css?ver={?= time() ?}')); + } + } + + public function finish() + { + if (DEV_MODE && strpos(get_headers_list('Content-Type'), 'text/html') !== false) { + $a = \debug_backtrace(); + foreach (Dump::$data as &$d) { + $d['value'] = \htmlspecialchars($this->tpl->noParse($d['value'])); + } + + echo $this->draw(MODULES.'/devbar/view/bar.html', [ + 'devbar' => [ + 'version' => $this->settings('settings', 'version'), + 'timer' => round(($this->timer + microtime(true))*1000, 2), + 'memory' => round(memory_get_usage()/1024/1024, 2), + 'database' => \Inc\Core\Lib\QueryBuilder::lastSqls(), + 'requests' => [ + '$_GET' => ['print' => print_r($_GET, true), 'count' => count($_GET)], + '$_POST' => ['print' => print_r($_POST, true), 'count' => count($_POST)], + '$_COOKIE' => ['print' => print_r($_COOKIE, true), 'count' => 0], + '$_SERVER' => ['print' => print_r($_SERVER, true), 'count' => 0], + ], + 'dump' => Dump::$data, + 'sqlite' => $this->db()->pdo()->query('select sqlite_version()')->fetch()[0], + 'modules' => array_keys($this->core->module->getArray()), + ], + ]); + } + } +} + +require_once('functions.php'); diff --git a/inc/modules/devbar/Dump.php b/inc/modules/devbar/Dump.php new file mode 100644 index 0000000..e652c23 --- /dev/null +++ b/inc/modules/devbar/Dump.php @@ -0,0 +1,7 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +return [ + 'name' => 'Debug bar', + 'description' => 'Shows helpful information for developers', + 'author' => 'Sruu.pl', + 'version' => '1.0', + 'compatibility' => '1.3.*', + 'icon' => 'bug', + + 'install' => function () use ($core) { + }, + 'uninstall' => function () use ($core) { + } +]; diff --git a/inc/modules/devbar/ReadMe.md b/inc/modules/devbar/ReadMe.md new file mode 100644 index 0000000..0e0e1f5 --- /dev/null +++ b/inc/modules/devbar/ReadMe.md @@ -0,0 +1,3 @@ +After installing this module, you must enable `DEV_MODE` in the `inc/core/defines.php` file. Otherwise, the debugger will not appear. + +To debug variable values put in the code `dump([your_variable])` function. \ No newline at end of file diff --git a/inc/modules/devbar/Site.php b/inc/modules/devbar/Site.php new file mode 100644 index 0000000..70af545 --- /dev/null +++ b/inc/modules/devbar/Site.php @@ -0,0 +1,62 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Devbar; + +use Inc\Core\SiteModule; + +class Site extends SiteModule +{ + private $timer = 0; + + public function __construct(\Inc\Core\Main $core) + { + parent::__construct($core); + + $this->timer = -microtime(true); + } + public function init() + { + if (DEV_MODE && strpos(get_headers_list('Content-Type'), 'text/html') !== false) { + $this->core->addCSS(url(MODULES.'/devbar/css/style.css?ver={?= time() ?}')); + } + } + + public function finish() + { + if (DEV_MODE && strpos(get_headers_list('Content-Type'), 'text/html') !== false) { + $a = \debug_backtrace(); + foreach (Dump::$data as &$d) { + $d['value'] = \htmlspecialchars($this->tpl->noParse($d['value'])); + } + + echo $this->draw('bar.html', [ + 'devbar' => [ + 'version' => $this->settings('settings', 'version'), + 'timer' => round(($this->timer + microtime(true))*1000, 2), + 'memory' => round(memory_get_usage()/1024/1024, 2), + 'database' => \Inc\Core\Lib\QueryBuilder::lastSqls(), + 'requests' => [ + '$_GET' => ['print' => print_r($_GET, true), 'count' => count($_GET)], + '$_POST' => ['print' => print_r($_POST, true), 'count' => count($_POST)], + '$_COOKIE' => ['print' => print_r($_COOKIE, true), 'count' => 0], + '$_SERVER' => ['print' => print_r($_SERVER, true), 'count' => 0], + ], + 'dump' => Dump::$data, + 'sqlite' => $this->db()->pdo()->query('select sqlite_version()')->fetch()[0], + 'modules' => array_keys($this->core->module->getArray()), + ], + ]); + } + } +} + +require_once('functions.php'); diff --git a/inc/modules/devbar/css/style.css b/inc/modules/devbar/css/style.css new file mode 100644 index 0000000..083d190 --- /dev/null +++ b/inc/modules/devbar/css/style.css @@ -0,0 +1,113 @@ +body { + margin-bottom: 30px; +} +#devbar { + z-index: 1000; + position: fixed; + bottom: 0; + left: -2px; + right: -2px; + padding: 0px; + background: #222; + border-top: 1px solid #444; + color: #FFF; + font-size: 14px; + font-family: "Lucida Console", Monaco, monospace !important; + font-weight: 400; + line-height: 1.4; +} +#devbar > ul { + position: relative; + min-height: auto; + border: 0; + border-radius: 0; + list-style: none; + padding: 0; + margin: 0; +} +#devbar > ul > li { + position: static; + padding: 0; + margin: 0; + float: left; +} +#devbar > ul > li > a { + color: #FFF; + text-decoration: none; +} +#devbar > ul > li:not(:last-of-type) { + border-right: 1px solid #444; +} +#devbar > ul > li.title { + font-weight: bold; +} +#devbar > ul > li.title span > span { + color: #f8be12; +} +#devbar > ul > li.show-hide { + position: absolute; + top: 0; + right: 14px; + bottom: 0; + padding: 3px 6px 0 6px; + cursor: pointer; +} +#devbar > ul > li.show-hide i.fa:after { + content: '\f0d8'; +} +#devbar > ul > li.show-hide[aria-expanded="true"] i.fa:after { + content: '\f0d7'; +} +#devbar > ul > li > span, +#devbar > ul > li > a { + display: inline-block; + padding: 3px 10px 2px 10px; +} +#devbar > ul > li.logo { + padding: 2px 20px 2px 20px; +} +#devbar > ul > li.logo img { + height: 18px; + width: auto; +} +#devbar .tab-pane { + background: #FFF; + height: 300px; + color: #000; + width: 100%; + overflow: auto; +} +#devbar .tab-pane ul { + list-style: none; + padding: 0; + margin: 0; +} +#devbar .tab-pane ul li { + padding: 5px 10px; + border-bottom: 1px solid #e5e5e5; + font-family: monospace; +} +#devbar .tab-pane ul li dl { + margin-bottom: 0; +} +#devbar .tab-pane ul li dl dd, +#devbar .tab-pane ul li dl dt { + line-height: 1.4; +} +#devbar .tab-pane ul li .truncate { + cursor: pointer; +} +#devbar .tab-pane ul li .truncate.active { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} +#devbar .tab-pane ul li pre { + background: none; + border: none; + margin: 0; + padding: 0; + white-space: pre-wrap; + color: #656d78; + font-size: 13px; +} diff --git a/inc/modules/devbar/css/style.less b/inc/modules/devbar/css/style.less new file mode 100644 index 0000000..519d3fc --- /dev/null +++ b/inc/modules/devbar/css/style.less @@ -0,0 +1,121 @@ +body { + margin-bottom: 30px; +} +#devbar { + z-index: 1000; + position: fixed; + bottom: 0; + left: -2px; + right: -2px; + padding: 0px; + background: #222; + border-top: 1px solid #444; + color: #FFF; + font-size: 14px; + font-family: "Lucida Console", Monaco, monospace !important; + font-weight: 400; + line-height: 1.4; + > ul { + position: relative; + min-height: auto; + border: 0; + border-radius: 0; + list-style: none; + padding: 0; + margin: 0; + > li { + position: static; + padding: 0; + margin: 0; + float: left; + > a { + color:#FFF; + text-decoration: none; + } + &:not(:last-of-type) { + border-right: 1px solid #444; + } + &.title { + font-weight: bold; + + span > span { + color:#f8be12; + } + } + &.show-hide { + position: absolute; + top: 0; + right: 14px; + bottom: 0; + padding: 3px 6px 0 6px; + cursor: pointer; + + i.fa:after { + content: '\f0d8'; + } + &[aria-expanded="true"] i.fa:after { + content: '\f0d7'; + } + } + & > span, & > a { + display: inline-block; + padding: 3px 10px 2px 10px; + } + + &.logo { + padding: 2px 20px 2px 20px; + img { + height: 18px; + width: auto; + } + } + } + } + .tab-pane { + background: #FFF; + height: 300px; + color: #000; + width: 100%; + overflow: auto; + + ul { + list-style: none; + padding: 0; + margin: 0; + + li { + padding: 5px 10px; + border-bottom: 1px solid #e5e5e5; + font-family: monospace; + + dl { + margin-bottom: 0; + + dd, dt { + line-height: 1.4; + } + } + + .truncate { + cursor: pointer; + + &.active { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + } + + pre { + background: none; + border: none; + margin: 0; + padding: 0; + white-space: pre-wrap; + color: #656d78; + font-size: 13px; + } + } + } + } +} \ No newline at end of file diff --git a/inc/modules/devbar/functions.php b/inc/modules/devbar/functions.php new file mode 100644 index 0000000..591cec6 --- /dev/null +++ b/inc/modules/devbar/functions.php @@ -0,0 +1,68 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +function dump($variable) +{ + $backtrace = debug_backtrace()[0]; + Inc\Modules\Devbar\Dump::$data[] = [ + 'trace' => 'file '.str_replace(BASE_DIR, null, $backtrace['file']).' in line '.$backtrace['line'].'', + 'value' => print_r($variable, true), + ]; +} + +function http_response_status() +{ + $text = null; + switch (http_response_code()) { + case 100: $text = 'Continue'; break; + case 101: $text = 'Switching Protocols'; break; + case 200: $text = 'OK'; break; + case 201: $text = 'Created'; break; + case 202: $text = 'Accepted'; break; + case 203: $text = 'Non-Authoritative Information'; break; + case 204: $text = 'No Content'; break; + case 205: $text = 'Reset Content'; break; + case 206: $text = 'Partial Content'; break; + case 300: $text = 'Multiple Choices'; break; + case 301: $text = 'Moved Permanently'; break; + case 302: $text = 'Moved Temporarily'; break; + case 303: $text = 'See Other'; break; + case 304: $text = 'Not Modified'; break; + case 305: $text = 'Use Proxy'; break; + case 400: $text = 'Bad Request'; break; + case 401: $text = 'Unauthorized'; break; + case 402: $text = 'Payment Required'; break; + case 403: $text = 'Forbidden'; break; + case 404: $text = 'Not Found'; break; + case 405: $text = 'Method Not Allowed'; break; + case 406: $text = 'Not Acceptable'; break; + case 407: $text = 'Proxy Authentication Required'; break; + case 408: $text = 'Request Time-out'; break; + case 409: $text = 'Conflict'; break; + case 410: $text = 'Gone'; break; + case 411: $text = 'Length Required'; break; + case 412: $text = 'Precondition Failed'; break; + case 413: $text = 'Request Entity Too Large'; break; + case 414: $text = 'Request-URI Too Large'; break; + case 415: $text = 'Unsupported Media Type'; break; + case 500: $text = 'Internal Server Error'; break; + case 501: $text = 'Not Implemented'; break; + case 502: $text = 'Bad Gateway'; break; + case 503: $text = 'Service Unavailable'; break; + case 504: $text = 'Gateway Time-out'; break; + case 505: $text = 'HTTP Version not supported'; break; + default: + $text = 'Unknown http status code "' . htmlentities($code) . '"'; + break; + } + + return $text; +} diff --git a/inc/modules/devbar/view/bar.html b/inc/modules/devbar/view/bar.html new file mode 100644 index 0000000..6f1a91c --- /dev/null +++ b/inc/modules/devbar/view/bar.html @@ -0,0 +1,97 @@ +
    + +
    +
    +
      +
    • HTTP status: {?= http_response_code() ?} {?= http_response_status() ?}
    • +
    • Route: /{?= implode('/', parseURL()) ?}
    • +
    • Batflat version: {$devbar.version}
    • +
    • PHP version: {?= phpversion() ?}
    • +
    • SQLite version: {?= $devbar.sqlite ?}
    • +
    • Batflat modules: {?= implode(', ', $devbar.modules) ?}
    • +
    • PHP extensions: {?= implode(', ', get_loaded_extensions()) ?}
    • +
    +
    +
    +
      + {loop: $devbar.database} +
    • {$value}
    • + {/loop} +
    +
    +
    +
      + {loop: $devbar.requests} +
    • +
      +
      {$key}
      +
      {$value.print}
      +
      +
    • + {/loop} +
    +
    +
    +
      + {loop: $devbar.dump} +
    • + + {$value.trace} +
      {$value.value}
      +
    • + {/loop} +
    +
    +
    +
    + \ No newline at end of file diff --git a/inc/modules/galleries/Admin.php b/inc/modules/galleries/Admin.php new file mode 100644 index 0000000..1233413 --- /dev/null +++ b/inc/modules/galleries/Admin.php @@ -0,0 +1,254 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Galleries; + +use Inc\Core\AdminModule; + +class Admin extends AdminModule +{ + private $_thumbs = ['md' => 600, 'sm' => 300, 'xs' => 150]; + private $_uploads = UPLOADS.'/galleries'; + + public function navigation() + { + return [ + $this->lang('manage', 'general') => 'manage', + ]; + } + + /** + * galleries manage + */ + public function getManage() + { + $assign = []; + + // list + $rows = $this->db('galleries')->toArray(); + if (count($rows)) { + foreach ($rows as $row) { + $row['tag'] = $this->tpl->noParse('{$gallery.'.$row['slug'].'}'); + $row['editURL'] = url([ADMIN, 'galleries', 'edit', $row['id']]); + $row['delURL'] = url([ADMIN, 'galleries', 'delete', $row['id']]); + + $assign[] = $row; + } + } + + return $this->draw('manage.html', ['galleries' => $assign]); + } + + /** + * add new gallery + */ + public function anyAdd() + { + $location = [ADMIN, 'galleries', 'manage']; + + if (!empty($_POST['name'])) { + $name = trim($_POST['name']); + if (!$this->db('galleries')->where('slug', createSlug($name))->count()) { + $query = $this->db('galleries')->save(['name' => $name, 'slug' => createSlug($name)]); + + if ($query) { + $id = $this->db()->lastInsertId(); + $dir = $this->_uploads.'/'.$id; + + if (mkdir($dir, 0755, true)) { + $this->notify('success', $this->lang('add_gallery_success')); + $location = [ADMIN, 'galleries', 'edit', $this->db()->lastInsertId()]; + } + } else { + $this->notify('failure', $this->lang('add_gallery_failure')); + } + } else { + $this->notify('failure', $this->lang('gallery_already_exists')); + } + } else { + $this->notify('failure', $this->lang('empty_inputs', 'general')); + } + + redirect(url($location)); + } + + /** + * remove gallery + */ + public function getDelete($id) + { + $query = $this->db('galleries')->delete($id); + + deleteDir($this->_uploads.'/'.$id); + + if ($query) { + $this->notify('success', $this->lang('delete_gallery_success')); + } else { + $this->notify('failure', $this->lang('delete_gallery_failure')); + } + + redirect(url([ADMIN, 'galleries', 'manage'])); + } + + /** + * edit gallery + */ + public function getEdit($id, $page = 1) + { + $assign = []; + $assign['settings'] = $this->db('galleries')->oneArray($id); + + // pagination + $totalRecords = $this->db('galleries_items')->where('gallery', $id)->toArray(); + $pagination = new \Inc\Core\Lib\Pagination($page, count($totalRecords), 10, url([ADMIN, 'galleries', 'edit', $id, '%d'])); + $assign['pagination'] = $pagination->nav(); + $assign['page'] = $page; + + // items + if ($assign['settings']['sort'] == 'ASC') { + $rows = $this->db('galleries_items')->where('gallery', $id) + ->limit($pagination->offset().', '.$pagination->getRecordsPerPage()) + ->asc('id')->toArray(); + } else { + $rows = $this->db('galleries_items')->where('gallery', $id) + ->limit($pagination->offset().', '.$pagination->getRecordsPerPage()) + ->desc('id')->toArray(); + } + + if (count($rows)) { + foreach ($rows as $row) { + $row['title'] = $this->tpl->noParse(htmlspecialchars($row['title'])); + $row['desc'] = $this->tpl->noParse(htmlspecialchars($row['desc'])); + $row['src'] = unserialize($row['src']); + + if (!isset($row['src']['sm'])) { + $row['src']['sm'] = $row['src']['xs']; + } + + $assign['images'][] = $row; + } + } + + $assign['id'] = $id; + + $this->core->addCSS(url('inc/jscripts/lightbox/lightbox.min.css')); + $this->core->addJS(url('inc/jscripts/lightbox/lightbox.min.js')); + $this->core->addJS(url('inc/jscripts/are-you-sure.min.js')); + + return $this->draw('edit.html', ['gallery' => $assign]); + } + + /** + * save gallery data + */ + public function postSaveSettings($id) + { + if (checkEmptyFields(['name', 'sort'], $_POST)) { + $this->notify('failure', $this->lang('empty_inputs', 'general')); + redirect(url([ADMIN, 'galleries', 'edit', $id])); + } + + $_POST['slug'] = createSlug($_POST['name']); + if ($this->db('galleries')->where($id)->save($_POST)) { + $this->notify('success', $this->lang('save_settings_success')); + } + + redirect(url([ADMIN, 'galleries', 'edit', $id])); + } + + /** + * save images data + */ + public function postSaveImages($id, $page) + { + foreach ($_POST['img'] as $key => $val) { + $query = $this->db('galleries_items')->where($key)->save(['title' => $val['title'], 'desc' => $val['desc']]); + } + + if ($query) { + $this->notify('success', $this->lang('save_settings_success')); + } + + redirect(url([ADMIN, 'galleries', 'edit', $id, $page])); + } + + /** + * image uploading + */ + public function postUpload($id) + { + $dir = $this->_uploads.'/'.$id; + $cntr = 0; + + if (!is_uploaded_file($_FILES['files']['tmp_name'][0])) { + $this->notify('failure', $this->lang('no_files')); + } else { + foreach ($_FILES['files']['tmp_name'] as $image) { + $img = new \Inc\Core\Lib\Image(); + + if ($img->load($image)) { + $imgName = time().$cntr++; + $imgPath = $dir.'/'.$imgName.'.'.$img->getInfos('type'); + $src = []; + + // oryginal size + $img->save($imgPath); + $src['lg'] = str_replace(BASE_DIR.'/', null, $imgPath); + + // generate thumbs + foreach ($this->_thumbs as $key => $width) { + if ($img->getInfos('width') > $width) { + $img->resize($width); + $img->save($thumbPath = "{$dir}/{$imgName}-{$key}.{$img->getInfos('type')}"); + $src[$key] = str_replace(BASE_DIR.'/', null, $thumbPath); + } + } + + $query = $this->db('galleries_items')->save(['src' => serialize($src), 'gallery' => $id]); + } else { + $this->notify('failure', $this->lang('wrong_extension'), 'jpg, png, gif'); + } + } + + if ($query) { + $this->notify('success', $this->lang('add_images_success')); + }; + } + + redirect(url([ADMIN, 'galleries', 'edit', $id])); + } + + /** + * remove image + */ + public function getDeleteImage($id) + { + $image = $this->db('galleries_items')->where($id)->oneArray(); + if (!empty($image)) { + if ($this->db('galleries_items')->delete($id)) { + $images = unserialize($image['src']); + foreach ($images as $src) { + if (file_exists(BASE_DIR.'/'.$src)) { + if (!unlink(BASE_DIR.'/'.$src)) { + $this->notify('failure', $this->lang('delete_image_failure')); + } else { + $this->notify('success', $this->lang('delete_image_success')); + } + } + } + } + } else { + $this->notify('failure', $this->lang('image_doesnt_exists')); + } + + redirect(url([ADMIN, 'galleries', 'edit', $image['gallery']])); + } +} diff --git a/inc/modules/galleries/Info.php b/inc/modules/galleries/Info.php new file mode 100644 index 0000000..8b68c4f --- /dev/null +++ b/inc/modules/galleries/Info.php @@ -0,0 +1,46 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +return [ + 'name' => $core->lang['galleries']['module_name'], + 'description' => $core->lang['galleries']['module_desc'], + 'author' => 'Sruu.pl', + 'version' => '1.0', + 'compatibility' => '1.3.*', + 'icon' => 'camera', + + 'install' => function () use ($core) { + $core->db()->pdo()->exec("CREATE TABLE IF NOT EXISTS `galleries` ( + `id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, + `name` text NOT NULL, + `slug` text NOT NULL, + `img_per_page` integer NOT NULL DEFAULT 0, + `sort` text NOT NULL DEFAULT 'DESC' + )"); + + $core->db()->pdo()->exec("CREATE TABLE IF NOT EXISTS `galleries_items` ( + `id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, + `gallery` integer NOT NULL, + `src` text NOT NULL, + `title` text NULL, + `desc` text NULL + )"); + + if (!file_exists(UPLOADS.'/galleries')) { + mkdir(UPLOADS.'/galleries', 0755, true); + } + }, + 'uninstall' => function () use ($core) { + $core->db()->pdo()->exec("DROP TABLE `galleries`"); + $core->db()->pdo()->exec("DROP TABLE `galleries_items`"); + deleteDir(UPLOADS.'/galleries'); + } +]; diff --git a/inc/modules/galleries/Site.php b/inc/modules/galleries/Site.php new file mode 100644 index 0000000..dce6197 --- /dev/null +++ b/inc/modules/galleries/Site.php @@ -0,0 +1,58 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Galleries; + +use Inc\Core\SiteModule; + +class Site extends SiteModule +{ + public function init() + { + $this->_importGalleries(); + } + + private function _importGalleries() + { + $assign = []; + $tempAssign = []; + $galleries = $this->db('galleries')->toArray(); + + if (count($galleries)) { + foreach ($galleries as $gallery) { + if ($gallery['sort'] == 'ASC') { + $items = $this->db('galleries_items')->where('gallery', $gallery['id'])->asc('id')->toArray(); + } else { + $items = $this->db('galleries_items')->where('gallery', $gallery['id'])->desc('id')->toArray(); + } + + $tempAssign = $gallery; + + if (count($items)) { + foreach ($items as &$item) { + $item['src'] = unserialize($item['src']); + if (!isset($item['src']['sm'])) { + $item['src']['sm'] = $item['src']['xs']; + } + } + + $tempAssign['items'] = $items; + + $assign[$gallery['slug']] = $this->draw('gallery.html', ['gallery' => $tempAssign]); + } + } + } + $this->tpl->set('gallery', $assign); + + $this->core->addCSS(url('inc/jscripts/lightbox/lightbox.min.css')); + $this->core->addJS(url('inc/jscripts/lightbox/lightbox.min.js')); + } +} diff --git a/inc/modules/galleries/lang/admin/en_english.ini b/inc/modules/galleries/lang/admin/en_english.ini new file mode 100644 index 0000000..20210d9 --- /dev/null +++ b/inc/modules/galleries/lang/admin/en_english.ini @@ -0,0 +1,28 @@ +module_name = "Galleries" +module_desc = "Allows you to create galleries, and then display them on the page." + +add_gallery = "Add gallery" +edit = "Edit gallery" +settings = "Settings" +delete_confirm = "Are you sure you want do delete?" +delete_gallery_success = "Gallery successfully deleted." +delete_gallery_failure = "Failed to delete gallery." +add_gallery_success = "Gallery created." +add_gallery_failure = "Unable to create new gallery." +gallery_already_exists = "Gallery with this name already exists." +img_per_page = "Pictures per page" +sort = "Sort" +desc = "Descending" +asc = "Ascending" +add_images = "Add photo" +thumbnail = "Thumbnail" +data = "Data" +title = "Title" +description = "Description" +no_files = "You have to select at least one picture to upload." +wrong_extension = "One ore more has incorrect extension. Allowed extensions: %s." +add_images_success = "New photos successfully added." +save_settings_success = "Gallery data successfully saved." +delete_image_success = "Picture successfully deleted." +delete_image_failure = "Unable to delete photo from server. Delete it manually and check directory CHMODs." +image_doesnt_exists = "Image does not exist!" \ No newline at end of file diff --git a/inc/modules/galleries/lang/admin/pl_polski.ini b/inc/modules/galleries/lang/admin/pl_polski.ini new file mode 100644 index 0000000..09c60f6 --- /dev/null +++ b/inc/modules/galleries/lang/admin/pl_polski.ini @@ -0,0 +1,28 @@ +module_name = "Galerie" +module_desc = "Pozwala utworzyć galerie, a następnie wyświetlić je na stronie." + +add_gallery = "Dodaj galerię" +edit = "Edycja galerii" +settings = "Ustawienia" +delete_confirm = "Czy na pewno chcesz usunąć wybrany obiekt?" +delete_gallery_success = "Pomyślnie usunięto wybraną galerię." +delete_gallery_failure = "Nie udało się usunąć wybranej galerii." +add_gallery_success = "Pomyślnie dodano nową galerię." +add_gallery_failure = "Nie udało się dodać nowej galerii." +gallery_already_exists = "Galeria o takiej nazwie już istnieje." +img_per_page = "Liczba obrazów na stronę" +sort = "Sortowanie" +desc = "Malejąco" +asc = "Rosnąco" +add_images = "Dodaj zdjęcia" +thumbnail = "Miniaturka" +data = "Dane" +title = "Tytuł" +description = "Opis" +no_files = "Brak plików do wgrania! Musisz wybrać co najmniej jeden." +wrong_extension = "Jeden lub więcej plików posiada niedozwolone rozszerzenie. Akceptowane pliki: %s." +add_images_success = "Pomyślnie dodano nowe pliki do galerii." +save_settings_success = "Pomyślnie zapisano dane galerii." +delete_image_success = "Pomyślnie usunięto zdjęcie." +delete_image_failure = "Nie udało się usunąć zdjęcia z serwera. Zrób to ręcznie i sprawdź CHMOD-y." +image_doesnt_exists = "Taki obraz nie istnieje!" \ No newline at end of file diff --git a/inc/modules/galleries/view/admin/edit.html b/inc/modules/galleries/view/admin/edit.html new file mode 100644 index 0000000..124c55f --- /dev/null +++ b/inc/modules/galleries/view/admin/edit.html @@ -0,0 +1,109 @@ +
    +
    +
    +
    +

    {$lang.galleries.edit}

    +
    + +
    + {if: !empty($gallery.images)} +
    +
    + + + + + + + + {loop: $gallery.images} + + + + + + {/loop} + +
    {$lang.galleries.thumbnail}{$lang.galleries.data}{$lang.general.actions}
    + + + + +
    + +
    +
    + +
    +
    + + + +
    +
    + +
    + {$gallery.pagination} + {else} +
    + +

    {$lang.general.empty_array}

    +
    + {/if} +
    +
    +
    + +
    +
    +
    +

    {$lang.galleries.settings}

    +
    +
    +
    +
    + + +
    + +
    + +
    + + +
    +
    + +
    +
    +
    + +
    +
    +

    {$lang.galleries.add_images}

    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/inc/modules/galleries/view/admin/manage.html b/inc/modules/galleries/view/admin/manage.html new file mode 100644 index 0000000..4663ef3 --- /dev/null +++ b/inc/modules/galleries/view/admin/manage.html @@ -0,0 +1,59 @@ +
    +
    +
    +
    +

    {$lang.general.manage}

    +
    +
    + {if: !empty($galleries)} +
    + + + + + + + + + + {loop: $galleries} + + + + + + {/loop} + +
    {$lang.general.name}Tag{$lang.general.actions}
    {$value.name}{$value.tag} + + + + + + +
    +
    + {else} +

    {$lang.general.empty_array}

    + {/if} +
    +
    +
    + +
    +
    +
    +

    {$lang.galleries.add_gallery}

    +
    +
    +
    +
    + + +
    + +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/inc/modules/galleries/view/gallery.html b/inc/modules/galleries/view/gallery.html new file mode 100644 index 0000000..7f0a524 --- /dev/null +++ b/inc/modules/galleries/view/gallery.html @@ -0,0 +1,26 @@ +
    + +
    + + \ No newline at end of file diff --git a/inc/modules/langswitcher/Info.php b/inc/modules/langswitcher/Info.php new file mode 100644 index 0000000..9ecfe8c --- /dev/null +++ b/inc/modules/langswitcher/Info.php @@ -0,0 +1,25 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +return [ + 'name' => $core->lang['langswitcher']['module_name'], + 'description' => $core->lang['langswitcher']['module_desc'], + 'author' => 'Sruu.pl', + 'version' => '1.1', + 'compatibility' => '1.3.*', + 'icon' => 'flag', + 'install' => function () use ($core) { + $core->db()->pdo()->exec("INSERT INTO `settings` (`module`, `field`, `value`) VALUES ('settings', 'autodetectlang', 0)"); + }, + 'uninstall' => function () use ($core) { + $core->db()->pdo()->exec("DELETE FROM `settings` WHERE `field` = 'autodetectlang'"); + } +]; diff --git a/inc/modules/langswitcher/ReadMe.md b/inc/modules/langswitcher/ReadMe.md new file mode 100644 index 0000000..8456cb9 --- /dev/null +++ b/inc/modules/langswitcher/ReadMe.md @@ -0,0 +1 @@ +Paste `{$langSwitcher}` in a template file to display select box with available languages. \ No newline at end of file diff --git a/inc/modules/langswitcher/Site.php b/inc/modules/langswitcher/Site.php new file mode 100644 index 0000000..f33baf6 --- /dev/null +++ b/inc/modules/langswitcher/Site.php @@ -0,0 +1,103 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\LangSwitcher; + +use Inc\Core\SiteModule; + +class Site extends SiteModule +{ + public function init() + { + if ($this->settings('settings', 'autodetectlang') == '1' && empty(parseURL(1)) && !isset($_SESSION['langswitcher']['detected'])) { + $detedcted = false; + $languages = $this->_detectBrowserLanguage(); + foreach ($languages as $value => $priority) { + $value = substr($value, 0, 2); + if ($detect = glob('inc/lang/'.$value.'_*')) { + $this->core->loadLanguage(basename($detect[0])); + break; + } + } + } + + $_SESSION['langswitcher']['detected'] = true; + if (isset($_GET['lang'])) { + $lang = explode('_', $_GET['lang'])[0]; + $this->_setLanguage($_GET['lang']); + + $dir = trim(dirname($_SERVER['SCRIPT_NAME']), '/'); + + $e = parseURL(); + foreach ($this->_getLanguages() as $lng) { + if ($lng['symbol'] == $e[0]) { + array_shift($e); + break; + } + } + $slug = implode('/', $e); + + if ($this->db('pages')->where('slug', $slug)->where('lang', $_GET['lang'])->oneArray()) { + redirect(url($lang.'/'.$slug)); + } else { + redirect(url($slug)); + } + } + + $this->tpl->set('langSwitcher', $this->_insertSwitcher()); + } + + private function _insertSwitcher() + { + return $this->draw('switcher.html', ['languages' => $this->_getLanguages()]); + } + + protected function _getLanguages($selected = null, $attr = 'selected') + { + $langs = glob('inc/lang/*', GLOB_ONLYDIR); + + $result = []; + foreach ($langs as $lang) { + $lang = basename($lang); + + $result[] = [ + 'dir' => $lang, + 'name' => mb_strtoupper(preg_replace('/_[a-z]+/', null, $lang)), + 'symbol'=> preg_replace('/_[a-z]+/', null, $lang), + 'attr' => (($selected ? $selected : $this->core->lang['name']) == $lang) ? $attr : null + ]; + } + return $result; + } + + private function _setLanguage($value) + { + if (in_array($value, array_column($this->_getLanguages(), 'dir'))) { + $_SESSION['lang'] = $value; + return true; + } + return false; + } + + private function _detectBrowserLanguage() + { + $prefLocales = array_reduce( + explode(',', isset_or($_SERVER['HTTP_ACCEPT_LANGUAGE'], null)), + function ($res, $el) { + list($l, $q) = array_merge(explode(';q=', $el), [1]); + $res[$l] = (float) $q; + return $res; + }, []); + arsort($prefLocales); + + return $prefLocales; + } +} diff --git a/inc/modules/langswitcher/lang/admin/en_english.ini b/inc/modules/langswitcher/lang/admin/en_english.ini new file mode 100644 index 0000000..53b5a5f --- /dev/null +++ b/inc/modules/langswitcher/lang/admin/en_english.ini @@ -0,0 +1,3 @@ +module_name = "langSwitcher" +module_desc = "Allows to change the language by guests." +autodetect = "Auto detect browser language" \ No newline at end of file diff --git a/inc/modules/langswitcher/lang/admin/pl_polski.ini b/inc/modules/langswitcher/lang/admin/pl_polski.ini new file mode 100644 index 0000000..652859b --- /dev/null +++ b/inc/modules/langswitcher/lang/admin/pl_polski.ini @@ -0,0 +1,3 @@ +module_name = "langSwitcher" +module_desc = "Pozwala na zmianę języka przez gości." +autodetect = "Automatycznie wykryj język użytkownika" \ No newline at end of file diff --git a/inc/modules/langswitcher/view/switcher.html b/inc/modules/langswitcher/view/switcher.html new file mode 100644 index 0000000..3b995d3 --- /dev/null +++ b/inc/modules/langswitcher/view/switcher.html @@ -0,0 +1,7 @@ +
    + +
    \ No newline at end of file diff --git a/inc/modules/modules/Admin.php b/inc/modules/modules/Admin.php new file mode 100644 index 0000000..4aa38d7 --- /dev/null +++ b/inc/modules/modules/Admin.php @@ -0,0 +1,264 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Modules; + +use Inc\Core\AdminModule; + +class Admin extends AdminModule +{ + public function navigation() + { + return [ + $this->lang('manage', 'general') => 'manage', + $this->lang('upload_new') => 'upload' + ]; + } + + /** + * list of active/inactive modules + */ + public function getManage($type = 'active') + { + $modules = $this->_modulesList($type); + return $this->draw('manage.html', ['modules' => array_chunk($modules, 2), 'tab' => $type]); + } + + /** + * module upload + */ + public function getUpload() + { + return $this->draw('upload.html'); + } + + /** + * module extract + */ + public function postExtract() + { + if (isset($_FILES['zip_module']['tmp_name']) && !FILE_LOCK) { + $backURL = url([ADMIN, 'modules', 'upload']); + $file = $_FILES['zip_module']['tmp_name']; + + // Verify ZIP + $zip = zip_open($file); + $modules = array(); + while ($entry = zip_read($zip)) { + $entry = zip_entry_name($entry); + if (preg_match('/^(.*?)\/Info.php$/', $entry, $matches)) { + $modules[] = ['path' => $matches[0], 'name' => $matches[1]]; + } + + if (strpos($entry, '/') === false) { + $this->notify('failure', $this->lang('upload_bad_file')); + redirect($backURL); + } + } + + // Extract to modules + $zip = new \ZipArchive; + if ($zip->open($file) === true) { + foreach ($modules as $module) { + if (file_exists(MODULES.'/'.$module['name'])) { + $tmpName = md5(time().rand(1, 9999)); + file_put_contents('tmp/'.$tmpName, $zip->getFromName($module['path'])); + $info_new = include('tmp/'.$tmpName); + $info_old = include(MODULES.'/'.$module['name'].'/Info.php'); + unlink('tmp/'.$tmpName); + + if (cmpver($info_new['version'], $info_old['version']) <= 0) { + $this->notify('failure', $this->lang('upload_bad_version')); + continue; + } + } + $this->unzip($file, MODULES.'/'.$module['name'], $module['name']); + } + + $this->notify('success', $this->lang('upload_success')); + } else { + $this->notify('failure', $this->lang('upload_bad_file')); + } + } + + redirect($backURL); + } + + public function getInstall($dir) + { + $files = [ + 'info' => MODULES.'/'.$dir.'/Info.php', + 'admin' => MODULES.'/'.$dir.'/Admin.php', + 'site' => MODULES.'/'.$dir.'/Site.php' + ]; + + if ((file_exists($files['info']) && file_exists($files['admin'])) || (file_exists($files['info']) && file_exists($files['site']))) { + $core = $this->core; + $info = include($files['info']); + if (!$this->checkCompatibility(isset_or($info['compatibility']))) { + $this->notify('failure', $this->lang('module_outdated'), $dir); + } elseif ($this->db('modules')->save(['dir' => $dir, 'sequence' => $this->db('modules')->count()])) { + if (isset($info['install'])) { + $info['install'](); + } + + $this->notify('success', $this->lang('activate_success'), $dir); + } else { + $this->notify('failure', $this->lang('activate_failure'), $dir); + } + } else { + $this->notify('failure', $this->lang('activate_failure_files'), $dir); + } + + redirect(url([ADMIN, 'modules', 'manage', 'inactive'])); + } + + public function getUninstall($dir) + { + if (in_array($dir, unserialize(BASIC_MODULES))) { + $this->notify('failure', $this->lang('deactivate_failure'), $dir); + redirect(url([ADMIN, 'modules', 'manage', 'active'])); + } + + if ($this->db('modules')->delete('dir', $dir)) { + $core = $this->core; + $info = include(MODULES.'/'.$dir.'/Info.php'); + + if (isset($info['uninstall'])) { + $info['uninstall'](); + } + + $this->notify('success', $this->lang('deactivate_success'), $dir); + } else { + $this->notify('failure', $this->lang('deactivate_failure'), $dir); + } + + redirect(url([ADMIN, 'modules', 'manage', 'active'])); + } + + public function getRemove($dir) + { + if (in_array($dir, unserialize(BASIC_MODULES))) { + $this->notify('failure', $this->lang('remove_failure'), $dir); + redirect(url([ADMIN, 'modules', 'manage', 'inactive'])); + } + + $path = MODULES.'/'.$dir; + if (is_dir($path)) { + if (deleteDir($path)) { + $this->notify('success', $this->lang('remove_success'), $dir); + } else { + $this->notify('failure', $this->lang('remove_failure'), $dir); + } + } + redirect(url([ADMIN, 'modules', 'manage', 'inactive'])); + } + + public function getDetails($dir) + { + $files = [ + 'info' => MODULES.'/'.$dir.'/Info.php', + 'readme' => MODULES.'/'.$dir.'/ReadMe.md' + ]; + + $module = $this->core->getModuleInfo($dir); + $module['description'] = $this->tpl->noParse($module['description']); + $module['last_modified'] = date("Y-m-d", filemtime($files['info'])); + + // ReadMe.md + if (file_exists($files['readme'])) { + $parsedown = new \Inc\Core\Lib\Parsedown(); + $module['readme'] = $parsedown->text($this->tpl->noParse(file_get_contents($files['readme']))); + } + + $this->tpl->set('module', $module); + echo $this->tpl->draw(MODULES.'/modules/view/admin/details.html', true); + exit(); + } + + private function _modulesList($type) + { + $dbModules = array_column($this->db('modules')->toArray(), 'dir'); + $result = []; + + foreach (glob(MODULES.'/*', GLOB_ONLYDIR) as $dir) { + $dir = basename($dir); + $files = [ + 'info' => MODULES.'/'.$dir.'/Info.php', + 'admin' => MODULES.'/'.$dir.'/Admin.php', + 'site' => MODULES.'/'.$dir.'/Site.php' + ]; + + if ($type == 'active') { + $inArray = in_array($dir, $dbModules); + } else { + $inArray = !in_array($dir, $dbModules); + } + + if (((file_exists($files['info']) && file_exists($files['admin'])) || (file_exists($files['info']) && file_exists($files['site']))) && $inArray) { + $details = $this->core->getModuleInfo($dir); + $details['description'] = $this->tpl->noParse($details['description']); + $features = $this->core->getModuleNav($dir); + $other = []; + $urls = [ + 'url' => (is_array($features) ? url([ADMIN, $dir, array_shift($features)]) : '#'), + 'uninstallUrl' => url([ADMIN, 'modules', 'uninstall', $dir]), + 'removeUrl' => url([ADMIN, 'modules', 'remove', $dir]), + 'installUrl' => url([ADMIN, 'modules', 'install', $dir]), + 'detailsUrl' => url([ADMIN, 'modules', 'details', $dir]) + ]; + + $other['installed'] = $type == 'active' ? true : false; + + if (in_array($dir, unserialize(BASIC_MODULES))) { + $other['basic'] = true; + } else { + $other['basic'] = false; + } + + $other['compatible'] = $this->checkCompatibility(isset_or($details['compatibility'], '1.0.0')); + $result[] = $details + $urls + $other; + } + } + return $result; + } + + private function unzip($zipFile, $to, $path = '/') + { + $path = trim($path, '/'); + $zip = new \ZipArchive; + $zip->open($zipFile); + + for ($i = 0; $i < $zip->numFiles; $i++) { + $filename = $zip->getNameIndex($i); + + if (empty($path) || strpos($filename, $path) == 0) { + $file = $to.'/'.str_replace($path, null, $filename); + if (!file_exists(dirname($file))) { + mkdir(dirname($file), 0777, true); + } + + if (substr($file, -1) != '/') { + file_put_contents($to.'/'.str_replace($path, null, $filename), $zip->getFromIndex($i)); + } + } + } + + $zip->close(); + } + + private function checkCompatibility($version) + { + $systemVersion = $this->settings('settings', 'version'); + $version = str_replace(['.', '*'], ['\\.', '[0-9]+'], $version); + return preg_match('/^'.$version.'[a-z]*$/', $systemVersion); + } +} diff --git a/inc/modules/modules/Info.php b/inc/modules/modules/Info.php new file mode 100644 index 0000000..968cfb1 --- /dev/null +++ b/inc/modules/modules/Info.php @@ -0,0 +1,30 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +return [ + 'name' => $core->lang['modules']['module_name'], + 'description' => $core->lang['modules']['module_desc'], + 'author' => 'Sruu.pl', + 'version' => '1.1', + 'compatibility' => '1.3.*', + 'icon' => 'plug', + + 'install' => function () use ($core) { + $core->db()->pdo()->exec("CREATE TABLE IF NOT EXISTS `modules` ( + `id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, + `dir` text NOT NULL, + `sequence` integer DEFAULT 0 + )"); + }, + 'uninstall' => function () use ($core) { + $core->db()->pdo()->exec("DROP TABLE `modules`"); + } +]; diff --git a/inc/modules/modules/lang/admin/en_english.ini b/inc/modules/modules/lang/admin/en_english.ini new file mode 100644 index 0000000..0a5f185 --- /dev/null +++ b/inc/modules/modules/lang/admin/en_english.ini @@ -0,0 +1,40 @@ +module_name = "Modules" +module_desc = "Management modules." + +module = "Module" +version = "Version" +status = "Status" +author = "Author" +active = "Active" +activate = "Activate" +inactive = "Inactive" +deactivate = "Deactivate" +details = "Details" +upload_new = "Upload" +outdated = "outdated" +compatible = "compatible" +short_description = "Short description" +full_description = "Full description" +created_by = "Created by" +compatible_with = "Compatible with" +last_modified = "Last modified" +module_outdated = "Can't install module because is outdated. Please update module and try again." +deactivate_success = "'%s' module successfully deactivated." +deactivate_failure = "Unable to deactivate '%s' module." +deactivate_confirm = "Are you sure you want to deactivate this module?" +activate_success = "'%s' module successfully activated." +activate_failure = "Unable to activate '%s' module." +activate_failure_files = "Unable to activate '%s' module, because does not contain required files." +remove_confirm = "Are you sure you want to remove selected module?" +remove_success = "Module files '%s' has been successfully deleted." +remove_failure = "Unable to delete '%s' module files." +select_zip = "Select module with .zip extension." +upload_zip_subinfo = "Keep your modules up-to-date and upload compressed modules with .zip extension." +upload_zip_warning = "Module will be replaced if already exists." +upload_zip = "Upload" +upload_bad_file = "Module is incorrect or is interrupted." +upload_no_info = "File with informations about module does not exist." +upload_success = "Module successfully added. Go to Inactive page and activate it." +upload_bad_version = "Uploaded module is older or has the same version as installed." + +file_lock = "File uploading is disabled. You have to upload packages through FTP." \ No newline at end of file diff --git a/inc/modules/modules/lang/admin/pl_polski.ini b/inc/modules/modules/lang/admin/pl_polski.ini new file mode 100644 index 0000000..70a7ddb --- /dev/null +++ b/inc/modules/modules/lang/admin/pl_polski.ini @@ -0,0 +1,40 @@ +module_name = "Moduły" +module_desc = "Zarządzanie modułami." + +module = "Moduł" +version = "Wersja" +status = "Status" +author = "Autor" +active = "Aktywne" +activate = "Aktywuj" +inactive = "Nieaktywne" +deactivate = "Dezaktywuj" +details = "Szczegóły" +upload_new = "Wgraj nowy" +outdated = "przestarzały" +compatible = "kompatybilny" +short_description = "Krótki opis" +full_description = "Pełen opis" +created_by = "Stworzony przez" +compatible_with = "Kompatybilny z" +last_modified = "Ostatnio zmodyfikowany" +module_outdated = "Moduł jest przestarzały i nie może zostać zainstalowany. Prosimy zaktualizować go i spróbować ponownie." +deactivate_success = "Moduł '%s' został pomyślnie dezaktywowany." +deactivate_failure = "Nie udało się dezaktywować modułu '%s'." +deactivate_confirm = "Czy na pewno chcesz dezaktywować wybrany moduł?" +activate_success = "Moduł '%s' został pomyślnie aktywowany." +activate_failure = "Nie udało się aktywować modułu '%s'." +activate_failure_files = "Nie udało się aktywować modułu '%s', ponieważ nie posiada on wymaganych plików." +remove_confirm = "Na pewno chcesz usunąć wybrany moduł?" +remove_success = "Pliki modułu '%s' zostały pomyślnie usunięte." +remove_failure = "Nie udało się usunąć plików modułu '%s'." +select_zip = "Wybierz moduł w formacie *.zip" +upload_zip_subinfo = "Skompresowane moduły z rozszerzeniem .zip pozwolą ci szybko i sprawnie wzbogacić twój system o kolejne moduły." +upload_zip_warning = "Jeżeli moduł w systemie już istnieje, to zostanie nadpisany." +upload_zip = "Prześlij" +upload_bad_file = "Archiwum modułu jest niepoprawne lub zostało uszkodzone." +upload_no_info = "Brak pliku z informacjami o module" +upload_success = "Moduł został pomyślnie wgrany. Przejdź do zakładki Nieaktywne w celu jego aktywacji." +upload_bad_version = "Wgrywany moduł jest starszy lub posiadą tą samą wersję co obecnie zainstalowany." + +file_lock = "Przesyłanie paczek jest zablokowane. Aktualizację modułu musisz przeprowadzić ręcznie." \ No newline at end of file diff --git a/inc/modules/modules/view/admin/details.html b/inc/modules/modules/view/admin/details.html new file mode 100644 index 0000000..38254c8 --- /dev/null +++ b/inc/modules/modules/view/admin/details.html @@ -0,0 +1,64 @@ + + + \ No newline at end of file diff --git a/inc/modules/modules/view/admin/manage.html b/inc/modules/modules/view/admin/manage.html new file mode 100644 index 0000000..6b6c59f --- /dev/null +++ b/inc/modules/modules/view/admin/manage.html @@ -0,0 +1,84 @@ +
    +
    +
    +
    +

    {$lang.general.manage}

    + +
    +
    + {if: !empty($modules)} +
    + + + + + + + + + + + {loop: $modules} + {loop: $value} + + + + + + + + {/loop} + {/loop} + +
    {$lang.modules.module}{$lang.modules.version}{$lang.modules.status}{$lang.general.actions}
    + + {$value.name} + +

    {$value.description|cut:54}

    +
    {$value.version} + {if: !$value.compatible} + {$lang.modules.outdated} + {else} + {$lang.modules.compatible} + {/if} + + + + + {if: $value.installed} + + + + {else} + + + + + + + {/if} +
    +
    + {else} +

    {$lang.general.empty_array}

    + {/if} +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/inc/modules/modules/view/admin/upload.html b/inc/modules/modules/view/admin/upload.html new file mode 100644 index 0000000..49e571c --- /dev/null +++ b/inc/modules/modules/view/admin/upload.html @@ -0,0 +1,30 @@ +
    +
    +
    +
    {$lang.modules.upload_new}
    +
    + {if: FILE_LOCK} +
    {$lang.modules.file_lock}FILE_LOCK
    + {/if} +
    +
    + {$lang.modules.upload_zip_subinfo} + info +
    + +
    + {$lang.modules.upload_zip_warning} + info +
    + +
    + + +
    + + +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/inc/modules/navigation/Admin.php b/inc/modules/navigation/Admin.php new file mode 100644 index 0000000..2eff3bb --- /dev/null +++ b/inc/modules/navigation/Admin.php @@ -0,0 +1,447 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Navigation; + +use Inc\Core\AdminModule; + +class Admin extends AdminModule +{ + private $assign = []; + public function navigation() + { + return [ + $this->lang('manage', 'general') => 'manage', + $this->lang('add_link') => 'newLink', + $this->lang('add_nav') => 'newNav' + ]; + } + + /** + * list of navs and their children + */ + public function getManage() + { + // lang + if (!empty($_GET['lang'])) { + $lang = $_GET['lang']; + $_SESSION['navigation']['last_lang'] = $lang; + } elseif (!empty($_SESSION['navigation']['last_lang'])) { + $lang = $_SESSION['navigation']['last_lang']; + } else { + $lang = $this->settings('settings', 'lang_site'); + } + + $this->assign['langs'] = $this->_getLanguages($lang, 'active'); + + // list + $rows = $this->db('navs')->toArray(); + if (count($rows)) { + foreach ($rows as $row) { + $row['name'] = $this->tpl->noParse('{$navigation.'.$row['name'].'}'); + $row['editURL'] = url([ADMIN, 'navigation', 'editNav', $row['id']]); + $row['delURL'] = url([ADMIN, 'navigation', 'deleteNav', $row['id']]); + $row['items'] = $this->_getNavItems($row['id'], $lang); + + $this->assign['navs'][] = $row; + } + } + + return $this->draw('manage.html', ['navigation' => $this->assign]); + } + + /** + * add new link + */ + public function getNewLink() + { + // lang + if (isset($_GET['lang'])) { + $lang = $_GET['lang']; + } else { + $lang = $this->settings('settings', 'lang_site'); + } + $this->assign['langs'] = $this->_getLanguages($lang, 'selected'); + + $this->assign['link'] = ['name' => '', 'lang' => '', 'page' => '', 'url' => '', 'parent' => '', 'class' => '']; + + // list of pages + $this->assign['pages'] = $this->_getPages($lang); + foreach ($this->core->getRegisteredPages() as $page) { + $this->assign['pages'][] = array_merge($page, ['id' => $page['slug'], 'attr' => null]); + } + + // list of parents + $this->assign['navs'] = $this->_getParents($lang); + + $this->assign['title'] = $this->lang('add_link'); + return $this->draw('form.link.html', ['navigation' => $this->assign]); + } + + /** + * edit link + */ + public function getEditLink($id) + { + $row = $this->db('navs_items')->oneArray($id); + + if (!empty($row)) { + // lang + if (isset($_GET['lang'])) { + $lang = $_GET['lang']; + } else { + $lang = $row['lang']; + } + $this->assign['langs'] = $this->_getLanguages($lang, 'selected'); + + $this->assign['link'] = filter_var_array($row, FILTER_SANITIZE_SPECIAL_CHARS); + + // list of pages + $this->assign['pages'] = $this->_getPages($lang, $row['page']); + foreach ($this->core->getRegisteredPages() as $page) { + $this->assign['pages'][] = array_merge($page, ['id' => $page['slug'], 'attr' => (($row['page'] == 0 && $row['url'] == $page['slug']) ? 'selected' : null)]); + } + + // list of parents + $this->assign['navs'] = $this->_getParents($lang, $row['nav'], $row['parent'], $row['id']); + + $this->assign['title'] = $this->lang('edit_link'); + return $this->draw('form.link.html', ['navigation' => $this->assign]); + } else { + redirect(url([ADMIN, 'navigation', 'manage'])); + } + } + + /** + * save link data + */ + public function postSaveLink($id = null) + { + unset($_POST['save']); + + // check if it's an external link + if ($_POST['page']) { + $fields = ['name', 'page', 'lang', 'parent']; + } else { + $fields = ['name', 'url', 'lang', 'parent']; + } + + if (!$id) { + $location = url([ADMIN, 'navigation', 'newLink']); + } else { + $location = url([ADMIN, 'navigation', 'editLink', $id]); + } + + if (checkEmptyFields($fields, $_POST)) { + $this->notify('failure', $this->lang('empty_inputs', 'general')); + $this->assign['form'] = filter_var_array($_POST, FILTER_SANITIZE_SPECIAL_CHARS); + redirect($location); + } + + if ($_POST['page']) { + $_POST['url'] = null; + } + + // get parent + $parent = explode('_', $_POST['parent']); + $_POST['nav'] = $parent[0]; + $_POST['parent'] = (isset($parent[1]) ? $parent[1] : 0); + + if (!is_numeric($_POST['page'])) { + $_POST['url'] = $_POST['page']; + $_POST['page'] = 0; + } + + if (!$id) { + $_POST['"order"'] = $this->_getHighestOrder($_POST['nav'], $_POST['parent'], $_POST['lang']) + 1; + $query = $this->db('navs_items')->save($_POST); + } else { + $query = $this->db('navs_items')->where($id)->save($_POST); + if ($query) { + $query = $this->db('navs_items')->where('parent', $id)->update(['nav' => $_POST['nav']]); + } + } + + if ($query) { + $this->notify('success', $this->lang('save_link_success')); + } else { + $this->notify('failure', $this->lang('save_link_failure')); + } + + redirect($location); + } + + /** + * delete link + */ + public function getDeleteLink($id) + { + if ($this->db('navs_items')->where('id', $id)->orWhere('parent', $id)->delete()) { + $this->notify('success', $this->lang('delete_link_success')); + } else { + $this->notify('failure', $this->lang('delete_link_failure')); + } + + redirect(url([ADMIN, 'navigation', 'manage'])); + } + + /** + * add new nav + */ + public function getNewNav() + { + $this->assign['title'] = $this->lang('add_nav'); + + $this->assign['name'] = ''; + return $this->draw('form.nav.html', ['navigation' => $this->assign]); + } + + /** + * edit nav + */ + public function getEditNav($id) + { + $this->assign['title'] = $this->lang('edit_nav'); + $row = $this->db('navs')->where($id)->oneArray(); + + if (!empty($row)) { + $this->assign['name'] = $row['name']; + $this->assign['id'] = $row['id']; + } else { + redirect(url([ADMIN, 'navigation', 'manage'])); + } + + return $this->draw('form.nav.html', ['navigation', $this->assign]); + } + + /** + * save nav + */ + public function postSaveNav($id = null) + { + if (empty($_POST['name'])) { + if (!$id) { + redirect(url([ADMIN, 'navigation', 'newNav'])); + } else { + redirect(url([ADMIN, 'navigation', 'editNav', $id])); + } + + $this->notify('failure', $this->lang('empty_inputs', 'general')); + } + + $name = createSlug($_POST['name']); + + // check if nav already exists + if (!$this->db('navs')->where('name', $name)->count()) { + if (!$id) { + $query = $this->db('navs')->save(['name' => $name]); + } else { + $query = $this->db('navs')->where($id)->save(['name' => $name]); + } + + if ($query) { + $this->notify('success', $this->lang('save_nav_success')); + } else { + $this->notify('success', $this->lang('save_nav_failure')); + } + } else { + $this->notify('failure', $this->lang('nav_already_exists')); + } + + redirect(url([ADMIN, 'navigation', 'manage'])); + } + + /** + * remove nav + */ + public function getDeleteNav($id) + { + if ($this->db('navs')->delete($id)) { + $this->db('navs_items')->delete('nav', $id); + $this->notify('success', $this->lang('delete_nav_success')); + } else { + $this->notify('failure', $this->lang('delete_nav_failure')); + } + + redirect(url([ADMIN, 'navigation', 'manage'])); + } + + /** + * list of pages + * @param string $lang + * @param integer $selected + * @return array + */ + private function _getPages($lang, $selected = null) + { + $rows = $this->db('pages')->where('lang', $lang)->toArray(); + if (count($rows)) { + foreach ($rows as $row) { + if ($selected == $row['id']) { + $attr = 'selected'; + } else { + $attr = null; + } + $result[] = ['id' => $row['id'], 'title' => $row['title'], 'slug' => $row['slug'], 'attr' => $attr]; + } + } + return $result; + } + + /** + * list of parents + * @param string $lang + * @param integer $selected + * @return array + */ + private function _getParents($lang, $nav = null, $page = null, $except = null) + { + $rows = $this->db('navs')->toArray(); + if (count($rows)) { + foreach ($rows as &$row) { + $row['name'] = $this->tpl->noParse('{$navigation.'.$row['name'].'}'); + $row['items'] = $this->_getNavItems($row['id'], $lang); + + if ($nav && !$page && ($nav == $row['id'])) { + $row['attr'] = 'selected'; + } else { + $row['attr'] = null; + } + + if (is_array($row['items'])) { + foreach ($row['items'] as $key => &$value) { + if ($except && ($except == $value['id'])) { + unset($row['items'][$key]); + } else { + if ($nav && $page && ($page == $value['id'])) { + $value['attr'] = 'selected'; + } else { + $value['attr'] = null; + } + } + } + } + } + } + return $rows; + } + + /** + * list of nav items + * @param integer $nav + * @param string $lang + * @return array + */ + private function _getNavItems($nav, $lang) + { + $items = $this->db('navs_items')->where('nav', $nav)->where('lang', $lang)->asc('"order"')->toArray(); + + if (count($items)) { + foreach ($items as &$item) { + $item['editURL'] = url([ADMIN, 'navigation', 'editLink', $item['id']]); + $item['delURL'] = url([ADMIN, 'navigation', 'deleteLink', $item['id']]); + $item['upURL'] = url([ADMIN, 'navigation', 'changeOrder', 'up', $item['id']]); + $item['downURL'] = url([ADMIN, 'navigation', 'changeOrder', 'down', $item['id']]); + + if ($item['page'] > 0) { + $page = $this->db('pages')->where('id', $item['page'])->oneArray(); + $item['fullURL'] = '/'.$page['slug']; + } else { + $item['fullURL'] = (parse_url($item['url'], PHP_URL_SCHEME) || strpos($item['url'], '#') === 0 ? '' : '/').trim($item['url'], '/'); + } + } + return $this->buildTree($items); + } + } + + /** + * generate tree from array + * @param array $items + * @return array + */ + public function buildTree(array $items) + { + $children = [0 => []]; + + foreach ($items as &$item) { + $children[$item['parent']][] = &$item; + } + unset($item); + + foreach ($items as &$item) { + if (isset($children[$item['id']])) { + $item['children'] = $children[$item['id']]; + } + } + + return $children[0]; + } + + /** + * change order of nav item + * @param string $direction + * @param integer $id + * @return void + */ + public function getChangeOrder($direction, $id) + { + $item = $this->db('navs_items')->oneArray($id); + + if (!empty($item)) { + if ($direction == 'up') { + $nextItem = $this->db('navs_items') + ->where('"order"', '<', $item['order']) + ->where('nav', $item['nav']) + ->where('parent', $item['parent']) + ->where('lang', $item['lang']) + ->desc('"order"') + ->oneArray(); + } else { + $nextItem = $this->db('navs_items') + ->where('"order"', '>', $item['order']) + ->where('nav', $item['nav']) + ->where('parent', $item['parent']) + ->where('lang', $item['lang']) + ->asc('"order"') + ->oneArray(); + } + + if (!empty($nextItem)) { + $this->db('navs_items')->where('id', $item['id'])->save(['"order"' => $nextItem['order']]); + $this->db('navs_items')->where('id', $nextItem['id'])->save(['"order"' => $item['order']]); + } + } + redirect(url(ADMIN.'/navigation/manage?lang='.$item['lang'])); + } + + /** + * get item with highest order + * @param integer $nav + * @param integer $parent + * @param string $lang + * @return integer + */ + private function _getHighestOrder($nav, $parent, $lang) + { + $item = $this->db('navs_items') + ->where('nav', $nav) + ->where('parent', $parent) + ->where('lang', $lang) + ->desc('"order"') + ->oneArray(); + + if (!empty($item)) { + return $item['order']; + } else { + return 0; + } + } +} diff --git a/inc/modules/navigation/Info.php b/inc/modules/navigation/Info.php new file mode 100644 index 0000000..d05a777 --- /dev/null +++ b/inc/modules/navigation/Info.php @@ -0,0 +1,54 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +return [ + 'name' => $core->lang['navigation']['module_name'], + 'description' => $core->lang['navigation']['module_desc'], + 'author' => 'Sruu.pl', + 'version' => '1.1', + 'compatibility' => '1.3.*', + 'icon' => 'list-ul', + + 'install' => function () use ($core) { + $core->db()->pdo()->exec("CREATE TABLE IF NOT EXISTS `navs` ( + `id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, + `name` text NOT NULL + )"); + $core->db()->pdo()->exec("CREATE TABLE IF NOT EXISTS `navs_items` ( + `id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, + `name` text NOT NULL, + `url` text NULL, + `page` integer NULL, + `lang` text NOT NULL, + `parent` integer NOT NULL DEFAULT 0, + `nav` integer NOT NULL, + `order` integer NOT NULL, + `class` text NULL + )"); + $core->db()->pdo()->exec("INSERT INTO `navs` (`name`) VALUES ('main')"); + $core->db()->pdo()->exec("INSERT INTO `navs_items` (`name`, `url`, `page`, `lang`, `nav`, `order`) + VALUES ('Home', 'blog', 0, 'en_english', 1, 1)"); + $core->db()->pdo()->exec("INSERT INTO `navs_items` (`name`, `url`, `page`, `lang`, `nav`, `order`) + VALUES ('Strona główna', 'blog', 0, 'pl_polski', 1, 1)"); + $core->db()->pdo()->exec("INSERT INTO `navs_items` (`name`, `page`, `lang`, `nav`, `order`) + VALUES ('About me', 1, 'en_english', 1, 2)"); + $core->db()->pdo()->exec("INSERT INTO `navs_items` (`name`, `page`, `lang`, `nav`, `order`) + VALUES ('O mnie', 2, 'pl_polski', 1, 2)"); + $core->db()->pdo()->exec("INSERT INTO `navs_items` (`name`, `page`, `lang`, `nav`, `order`) + VALUES ('Contact', 3, 'en_english', 1, 3)"); + $core->db()->pdo()->exec("INSERT INTO `navs_items` (`name`, `page`, `lang`, `nav`, `order`) + VALUES ('Kontakt', 4, 'pl_polski', 1, 3)"); + }, + 'uninstall' => function () use ($core) { + $core->db()->pdo()->exec("DROP TABLE `navs`"); + $core->db()->pdo()->exec("DROP TABLE `navs_items`"); + } +]; diff --git a/inc/modules/navigation/Site.php b/inc/modules/navigation/Site.php new file mode 100644 index 0000000..e94c2e9 --- /dev/null +++ b/inc/modules/navigation/Site.php @@ -0,0 +1,103 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Navigation; + +use Inc\Core\SiteModule; + +class Site extends SiteModule +{ + public function routes() + { + $this->_insertMenu(); + } + + /** + * get nav data + */ + private function _insertMenu() + { + $assign = []; + $homepage = $this->settings('settings', 'homepage'); + + $lang_prefix = $this->core->lang['name']; + if ($lang_prefix != $this->settings('settings', 'lang_site')) { + $lang_prefix = explode('_', $lang_prefix)[0]; + } else { + $lang_prefix = null; + } + + // get nav + $navs = $this->db('navs')->toArray(); + foreach ($navs as $nav) { + // get nav children + $items = $this->db('navs_items')->leftJoin('pages', 'pages.id = navs_items.page')->where('navs_items.nav', $nav['id'])->where('navs_items.lang', $this->core->lang['name'])->asc('`order`')->select(['navs_items.*', 'pages.slug'])->toArray(); + + if (count($items)) { + // generate URL + foreach ($items as &$item) { + // if external URL field is empty, it means that it's a batflat page + $item['active'] = null; + if (!$item['url']) { + if ($item['slug'] == $homepage) { + $item['url'] = $lang_prefix ? url([$lang_prefix]) : url(''); + } else { + $item['url'] = $lang_prefix ? url([$lang_prefix, $item['slug']]) : url([$item['slug']]); + } + + $url = parseURL(); + if ($url[0] == $item['slug'] || (preg_match('/^[a-z]{2}$/', $url[0]) && isset_or($url[1], $homepage) == $item['slug']) || $this->_isChildActive($item['id'], $url[0]) || ($url[0] == null && $homepage == $item['slug'])) { + $item['active'] = 'active'; + } + } else { + $item['url'] = url($item['url']); + $page = ['slug' => null]; + + if (url(parseURL(1)) == $item['url'] || $this->_isChildActive($item['id'], parseURL(1)) || (parseURL(1) == null && url($homepage) == $item['url'])) { + $item['active'] = 'active'; + } + + if ($item['url'] == url($homepage)) { + $item['url'] = url(''); + } + } + } + + $navigation_admin = new Admin($this->core); + $assign[$nav['name']] = $this->draw('nav.html', ['navigation' => ['list' => $navigation_admin->buildTree($items)]]); + } else { + $assign[$nav['name']] = null; + } + } + + $this->tpl->set('navigation', $assign); + } + + /** + * check if parent's child is active + */ + private function _isChildActive($itemID, $slug) + { + $rows = $this->db('pages') + ->leftJoin('navs_items', 'pages.id = navs_items.page') + ->where('navs_items.parent', $itemID) + ->toArray(); + + if (count($rows)) { + foreach ($rows as $row) { + if ($slug == $row['slug']) { + return true; + } + } + } + return false; + } +} diff --git a/inc/modules/navigation/lang/admin/en_english.ini b/inc/modules/navigation/lang/admin/en_english.ini new file mode 100644 index 0000000..3f1253c --- /dev/null +++ b/inc/modules/navigation/lang/admin/en_english.ini @@ -0,0 +1,22 @@ +module_name = "Navigation" +module_desc = "Management elements in navigation." + +add_link = "Add link" +edit_link = "Edit link" +save_link_success = "Link successfully saved." +save_link_failure = "Failed to save link." +delete_link_success = "Link successfully deleted." +delete_link_failure = "Unable to delete link." +delete_link_confirm = "Are you sure you want to delete link?" +add_nav = "Add navigation" +edit_nav = "Edit navigation" +save_nav_success = "Navigation successfully saved." +save_nav_failure = "Failed to save navigation." +delete_nav_success = "Navigation successfully deleted." +delete_nav_failure = "Unable to delete navigation." +delete_nav_confirm = "Are you sure you want to delete navigation?" +nav_already_exists = "Navigation already exists." +page = "Page" +url = "URL" +class = "Additional CSS class" +parent = "Parent" \ No newline at end of file diff --git a/inc/modules/navigation/lang/admin/pl_polski.ini b/inc/modules/navigation/lang/admin/pl_polski.ini new file mode 100644 index 0000000..f85c368 --- /dev/null +++ b/inc/modules/navigation/lang/admin/pl_polski.ini @@ -0,0 +1,22 @@ +module_name = "Nawigacja" +module_desc = "Zarządzanie elementami w nawigacji." + +add_link = "Dodaj odnośnik" +edit_link = "Edycja odnośnika" +save_link_success = "Pomyślnie zapisano odnośnik." +save_link_failure = "Nie udało się zapisać odnośnika." +delete_link_success = "Pomyślnie usunięto odnośnik." +delete_link_failure = "Nie udało się usunąć odnośnika." +delete_link_confirm = "Czy na pewno chcesz usunąć wybrany odnośnik?" +add_nav = "Dodaj nawigację" +edit_nav = "Edycja nawigacji" +save_nav_success = "Pomyślnie zapisano nawigację." +save_nav_failure = "Nie udało się zapisać nawigacji." +delete_nav_success = "Pomyślnie usunięto nawigację." +delete_nav_failure = "Nie udało się usunąć nawigacji." +delete_nav_confirm = "Czy na pewno chcesz usunąć wybraną nawigację?" +nav_already_exists = "Nawigacja o takiej nazwie już istnieje." +page = "Strona" +url = "URL" +class = "Dodatkowa klasa CSS" +parent = "Rodzic" \ No newline at end of file diff --git a/inc/modules/navigation/view/admin/form.link.html b/inc/modules/navigation/view/admin/form.link.html new file mode 100644 index 0000000..f8b96d5 --- /dev/null +++ b/inc/modules/navigation/view/admin/form.link.html @@ -0,0 +1,77 @@ +
    +
    +
    +
    +

    {$navigation.title}

    +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    + +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/inc/modules/navigation/view/admin/form.nav.html b/inc/modules/navigation/view/admin/form.nav.html new file mode 100644 index 0000000..ddf5477 --- /dev/null +++ b/inc/modules/navigation/view/admin/form.nav.html @@ -0,0 +1,18 @@ +
    +
    +
    +
    +

    {$navigation.title}

    +
    +
    +
    +
    + + +
    + +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/inc/modules/navigation/view/admin/manage.html b/inc/modules/navigation/view/admin/manage.html new file mode 100644 index 0000000..f48cf4a --- /dev/null +++ b/inc/modules/navigation/view/admin/manage.html @@ -0,0 +1,68 @@ +
    +
    +
    +
    +

    {$lang.general.manage}

    + +
    +
    + {if: isset($navigation.navs)} + {loop: $navigation.navs} +
    + + + + + + + + + + {if: is_array($value.items)} + {loop: $value.items} + + + + + + {if: isset($value.children)} + {loop: $value.children} + + + + + + {/loop} + {/if} + {/loop} + {/if} + +
    {$value.name} + + + + + + +
    {$value.name}{$value.fullURL} + + + + +
    {$value.name}{$value.fullURL} + + + + +
    +
    + {/loop} + {/if} +
    +
    +
    +
    \ No newline at end of file diff --git a/inc/modules/navigation/view/nav.html b/inc/modules/navigation/view/nav.html new file mode 100644 index 0000000..2fe8cc3 --- /dev/null +++ b/inc/modules/navigation/view/nav.html @@ -0,0 +1,19 @@ +{loop: $navigation.list} + {if: !isset($value.children)} +
  • + {$value.name} +
  • + {else} + + {/if} +{/loop} \ No newline at end of file diff --git a/inc/modules/pages/Admin.php b/inc/modules/pages/Admin.php new file mode 100644 index 0000000..20fe236 --- /dev/null +++ b/inc/modules/pages/Admin.php @@ -0,0 +1,281 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Pages; + +use Inc\Core\AdminModule; + +class Admin extends AdminModule +{ + private $assign = []; + + public function navigation() + { + return [ + $this->lang('manage', 'general') => 'manage', + $this->lang('add_new') => 'add' + ]; + } + + /** + * list of pages + */ + public function getManage($page = 1) + { + // lang + if (!empty($_GET['lang'])) { + $lang = $_GET['lang']; + $_SESSION['pages']['last_lang'] = $lang; + } elseif (!empty($_SESSION['pages']['last_lang'])) { + $lang = $_SESSION['pages']['last_lang']; + } else { + $lang = $this->settings('settings', 'lang_site'); + } + + // pagination + $totalRecords = $this->db('pages')->where('lang', $lang)->toArray(); + $pagination = new \Inc\Core\Lib\Pagination($page, count($totalRecords), 10, url([ADMIN, 'pages', 'manage', '%d'])); + $this->assign['pagination'] = $pagination->nav(); + // list + $rows = $this->db('pages')->where('lang', $lang) + ->limit($pagination->offset().', '.$pagination->getRecordsPerPage()) + ->toArray(); + + $this->assign['list'] = []; + if (count($rows)) { + foreach ($rows as $row) { + $row = htmlspecialchars_array($row); + $row['editURL'] = url([ADMIN, 'pages', 'edit', $row['id']]); + $row['delURL'] = url([ADMIN, 'pages', 'delete', $row['id']]); + $row['viewURL'] = url(explode('_', $lang)[0].'/'.$row['slug']); + $row['desc'] = str_limit($row['desc'], 48); + + $this->assign['list'][] = $row; + } + } + + $this->assign['langs'] = $this->_getLanguages($lang); + return $this->draw('manage.html', ['pages' => $this->assign]); + } + + /** + * add new page + */ + public function getAdd() + { + $this->assign['editor'] = $this->settings('settings', 'editor'); + $this->_addHeaderFiles(); + + // Unsaved data with failure + if (!empty($e = getRedirectData())) { + $this->assign['form'] = ['title' => isset_or($e['title'], ''), 'desc' => isset_or($e['desc'], ''), 'content' => isset_or($e['content'], ''), 'slug' => isset_or($e['slug'], '')]; + } else { + $this->assign['form'] = ['title' => '', 'desc' => '', 'content' => '', 'slug' => '', 'markdown' => 0]; + } + + $this->assign['title'] = $this->lang('new_page'); + $this->assign['langs'] = $this->_getLanguages($this->settings('settings.lang_site'), 'selected'); + $this->assign['templates'] = $this->_getTemplates(isset_or($e['template'], 'index.html')); + $this->assign['manageURL'] = url([ADMIN, 'pages', 'manage']); + + return $this->draw('form.html', ['pages' => $this->assign]); + } + + + /** + * edit page + */ + public function getEdit($id) + { + $this->assign['editor'] = $this->settings('settings', 'editor'); + $this->_addHeaderFiles(); + + $page = $this->db('pages')->where('id', $id)->oneArray(); + + if (!empty($page)) { + // Unsaved data with failure + if (!empty($e = getRedirectData())) { + $page = array_merge($page, ['title' => isset_or($e['title'], ''), 'desc' => isset_or($e['desc'], ''), 'content' => isset_or($e['content'], ''), 'slug' => isset_or($e['slug'], '')]); + } + + $this->assign['form'] = htmlspecialchars_array($page); + $this->assign['form']['content'] = $this->tpl->noParse($this->assign['form']['content']); + + $this->assign['title'] = $this->lang('edit_page'); + $this->assign['langs'] = $this->_getLanguages($page['lang'], 'selected'); + $this->assign['templates'] = $this->_getTemplates($page['template']); + $this->assign['manageURL'] = url([ADMIN, 'pages', 'manage']); + + return $this->draw('form.html', ['pages' => $this->assign]); + } else { + redirect(url([ADMIN, 'pages', 'manage'])); + } + } + + /** + * save data + */ + public function postSave($id = null) + { + unset($_POST['save'], $_POST['files']); + + if (!$id) { + $location = url([ADMIN, 'pages', 'add']); + } else { + $location = url([ADMIN, 'pages', 'edit', $id]); + } + + if (checkEmptyFields(['title', 'lang', 'template'], $_POST)) { + $this->notify('failure', $this->lang('empty_inputs', 'general')); + redirect($location, $_POST); + } + + $_POST['title'] = trim($_POST['title']); + if (!isset($_POST['markdown'])) { + $_POST['markdown'] = 0; + } + + if (empty($_POST['slug'])) { + $_POST['slug'] = createSlug($_POST['title']); + } else { + $_POST['slug'] = createSlug($_POST['slug']); + } + + if ($id != null && $this->db('pages')->where('slug', $_POST['slug'])->where('lang', $_POST['lang'])->where('id', '!=', $id)->oneArray()) { + $this->notify('failure', $this->lang('page_exists')); + redirect(url([ADMIN, 'pages', 'edit', $id]), $_POST); + } elseif ($id == null && $this->db('pages')->where('slug', $_POST['slug'])->where('lang', $_POST['lang'])->oneArray()) { + $this->notify('failure', $this->lang('page_exists')); + redirect(url([ADMIN, 'pages', 'add']), $_POST); + } + + if (!$id) { + $_POST['date'] = date('Y-m-d H:i:s'); + $query = $this->db('pages')->save($_POST); + $location = url([ADMIN, 'pages', 'edit', $this->db()->pdo()->lastInsertId()]); + } else { + $query = $this->db('pages')->where('id', $id)->save($_POST); + } + + if ($query) { + $this->notify('success', $this->lang('save_success')); + } else { + $this->notify('failure', $this->lang('save_failure')); + } + + redirect($location); + } + + /** + * remove page + */ + public function getDelete($id) + { + if ($this->db('pages')->delete($id)) { + $this->notify('success', $this->lang('delete_success')); + } else { + $this->notify('failure', $this->lang('delete_failure')); + } + + redirect(url([ADMIN, 'pages', 'manage'])); + } + + + /** + * image upload from WYSIWYG + */ + public function postEditorUpload() + { + header('Content-type: application/json'); + $dir = UPLOADS.'/pages'; + $error = null; + + if (!file_exists($dir)) { + mkdir($dir, 0777, true); + } + + if (isset($_FILES['file']['tmp_name'])) { + $img = new \Inc\Core\Lib\Image; + + if ($img->load($_FILES['file']['tmp_name'])) { + $imgPath = $dir.'/'.time().'.'.$img->getInfos('type'); + $img->save($imgPath); + echo json_encode(['status' => 'success', 'result' => url($imgPath)]); + } else { + $error = $this->lang('editor_upload_fail'); + } + + if ($error) { + echo json_encode(['status' => 'failure', 'result' => $error]); + } + } + exit(); + } + + /** + * module JavaScript + */ + public function getJavascript() + { + header('Content-type: text/javascript'); + echo $this->draw(MODULES.'/pages/js/admin/pages.js'); + exit(); + } + + /** + * list of theme's templates + * @param string $selected + * @return array + */ + private function _getTemplates($selected = null) + { + $theme = $this->settings('settings', 'theme'); + $tpls = glob(THEMES.'/'.$theme.'/*.html'); + + $result = []; + foreach ($tpls as $tpl) { + if ($selected == basename($tpl)) { + $attr = 'selected'; + } else { + $attr = null; + } + $result[] = ['name' => basename($tpl), 'attr' => $attr]; + } + return $result; + } + + private function _addHeaderFiles() + { + // WYSIWYG + $this->core->addCSS(url('inc/jscripts/wysiwyg/summernote.min.css')); + $this->core->addJS(url('inc/jscripts/wysiwyg/summernote.min.js')); + if ($this->settings('settings', 'lang_admin') != 'en_english') { + $this->core->addJS(url('inc/jscripts/wysiwyg/lang/'.$this->settings('settings', 'lang_admin').'.js')); + } + + // HTML & MARKDOWN EDITOR + $this->core->addCSS(url('/inc/jscripts/editor/markitup.min.css')); + $this->core->addCSS(url('/inc/jscripts/editor/markitup.highlight.min.css')); + $this->core->addCSS(url('/inc/jscripts/editor/sets/html/set.min.css')); + $this->core->addCSS(url('/inc/jscripts/editor/sets/markdown/set.min.css')); + $this->core->addJS(url('/inc/jscripts/editor/highlight.min.js')); + $this->core->addJS(url('/inc/jscripts/editor/markitup.min.js')); + $this->core->addJS(url('/inc/jscripts/editor/markitup.highlight.min.js')); + $this->core->addJS(url('/inc/jscripts/editor/sets/html/set.min.js')); + $this->core->addJS(url('/inc/jscripts/editor/sets/markdown/set.min.js')); + + // ARE YOU SURE? + $this->core->addJS(url('inc/jscripts/are-you-sure.min.js')); + + // MODULE SCRIPTS + $this->core->addJS(url([ADMIN, 'pages', 'javascript'])); + } +} diff --git a/inc/modules/pages/Info.php b/inc/modules/pages/Info.php new file mode 100644 index 0000000..218a301 --- /dev/null +++ b/inc/modules/pages/Info.php @@ -0,0 +1,81 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +return [ + 'name' => $core->lang['pages']['module_name'], + 'description' => $core->lang['pages']['module_desc'], + 'author' => 'Sruu.pl', + 'version' => '1.1', + 'compatibility' => '1.3.*', + 'icon' => 'file', + + 'install' => function () use ($core) { + $core->db()->pdo()->exec("CREATE TABLE IF NOT EXISTS `pages` ( + `id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, + `title` text NOT NULL, + `slug` text NOT NULL, + `desc` text NULL, + `lang` text NOT NULL, + `template` text NOT NULL, + `date` text NOT NULL, + `content` text NOT NULL, + `markdown` INTEGER DEFAULT 0 + )"); + + // About - EN + $core->db()->pdo()->exec("INSERT INTO `pages` (`title`, `slug`, `desc`, `lang`, `template`, `date`, `content`) + VALUES ('About me', 'about-me', 'Maecenas cursus accumsan est, sed interdum est pharetra quis.', 'en_english', 'index.html', datetime('now'), + '

    My name is Merely Ducard but I speak for Ra’s al Ghul… a man greatly feared by the criminal underworld. A mon who can offer you a path. Someone like you is only here by choice. You have been exploring the criminal fraternity but whatever your original intentions you have to become truly lost. The path of a man who shares his hatred of evil and wishes to serve true justice. The path of the League of Shadows.

    +

    Every year, I took a holiday. I went to Florence, this cafe on the banks of the Arno. Every fine evening, I would sit there and order a Fernet Branca. I had this fantasy, that I would look across the tables and I would see you there with a wife maybe a couple of kids. You wouldn’t say anything to me, nor me to you. But we would both know that you’ve made it, that you were happy. I never wanted you to come back to Gotham. I always knew there was nothing here for you except pain and tragedy and I wanted something more for you than that. I still do.

    ') + "); + + // About - PL + $core->db()->pdo()->exec("INSERT INTO `pages` (`title`, `slug`, `desc`, `lang`, `template`, `date`, `content`) + VALUES ('O mnie', 'about-me', 'Maecenas cursus accumsan est, sed interdum est pharetra quis.', 'pl_polski', 'index.html', datetime('now'), + '

    O, jak drudzy i świadki. I też same szczypiąc trawę ciągnęły powoli pod Twoją opiek ofiarowany, martwą podniosłem powiek i na kształt ogrodowych grządek: Że architekt był legijonistą przynosił kości stare na nim widzi sprzęty, też nie rozwity, lecz podmurowany. Świeciły się nagłe, jej wzrost i goście proszeni. Sień wielka jak znawcy, ci znowu w okolicy. i narody giną. Więc zbliżył się kołem. W mym domu przyszłą urządza zabawę. Dał rozkaz ekonomom, wójtom i w tkackim pudermanie). Wdział więc, jak wytnie dwa smycze chartów przedziwnie udawał psy tuż na polu szukała kogoś okiem, daleko, na Ojczyzny.

    +

    Bonapartą. tu pan Hrabia z rzadka ciche szmery a brano z boru i Waszeć z Podkomorzym przy zachodzie wszystko porzucane niedbale i w pogody lilia jeziór skroń ucałowawszy, uprzejmie pozdrowił. A zatem. tu mieszkał? Stary żołnierz, stał w bitwie, gdzie panieńskim rumieńcem dzięcielina pała a brano z nieba spadała w pomroku. Wprawdzie zdała się pan Sędzia w lisa, tak nie rzuca w porządku. naprzód dzieci mało wdawał się ukłoni i czytając, z których nie śmieli. I bór czernił się pan rejent Bolesta, zwano go powitać.

    ') + "); + + // Contact - EN + $core->db()->pdo()->exec("INSERT INTO `pages` (`title`, `slug`, `desc`, `lang`, `template`, `date`, `content`) + VALUES ('Contact', 'contact', '', 'en_english', 'index.html', datetime('now'), + '

    Want to get in touch with me? Fill out the form below to send me a message and I will try to get back to you within 24 hours!

    + {\$contact.form}') + "); + + // Contact - PL + $core->db()->pdo()->exec("INSERT INTO `pages` (`title`, `slug`, `desc`, `lang`, `template`, `date`, `content`) + VALUES ('Kontakt', 'contact', '', 'pl_polski', 'index.html', datetime('now'), + '

    Chcesz się ze mną skontaktować? Wypełnij poniższy formularz, aby wysłać mi wiadomość, a ja postaram się odpisać w ciągu 24 godzin!

    + {\$contact.form}') + "); + + // 404 - EN + $core->db()->pdo()->exec("INSERT INTO `pages` (`title`, `slug`, `desc`, `lang`, `template`, `date`, `content`) + VALUES ('404', '404', 'Not found', 'en_english', 'index.html', datetime('now'), + '

    Sorry, page does not exist.

    ') + "); + + // 404 -PL + $core->db()->pdo()->exec("INSERT INTO `pages` (`title`, `slug`, `desc`, `lang`, `template`, `date`, `content`) + VALUES ('404', '404', 'Not found', 'pl_polski', 'index.html', datetime('now'), + '

    Niestety taka strona nie istnieje.

    ') + "); + + if (!is_dir(UPLOADS."/pages")) { + mkdir(UPLOADS."/pages", 0777); + } + }, + 'uninstall' => function () use ($core) { + $core->db()->pdo()->exec("DROP TABLE `pages`"); + deleteDir(UPLOADS."/pages"); + } +]; diff --git a/inc/modules/pages/ReadMe.md b/inc/modules/pages/ReadMe.md new file mode 100644 index 0000000..5908828 --- /dev/null +++ b/inc/modules/pages/ReadMe.md @@ -0,0 +1,18 @@ +`{$page.title}` — displays the title of the page + +`{$page.desc}` — displays the page description + +`{$page.content}` — displays the contents of the page + +`{$pages}` — array with the data of all pages + +`{$pages.ID}` — array with the data of specific page + +If you want to make a "one page" website, you can use the loop: + +``` +{loop: $pages} +

    {$value.title}

    +

    {$value.content}

    +{/loop} +``` \ No newline at end of file diff --git a/inc/modules/pages/Site.php b/inc/modules/pages/Site.php new file mode 100644 index 0000000..9d38f49 --- /dev/null +++ b/inc/modules/pages/Site.php @@ -0,0 +1,144 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Pages; + +use Inc\Core\SiteModule; + +class Site extends SiteModule +{ + public function init() + { + $slug = parseURL(); + $lang = $this->_getLanguageBySlug($slug[0]); + if ($lang !== false) { + $this->core->loadLanguage($lang); + } + + if (empty($slug[0]) || ($lang !== false && empty($slug[1]))) { + $this->core->router->changeRoute($this->settings('settings', 'homepage')); + } + + \Inc\Core\Lib\Event::add('router.notfound', function () { + $this->get404(); + }); + } + + public function routes() + { + // Load pages from default language + $this->route('(:str)', function ($slug) { + $this->_importPage($slug); + }); + + // Load pages from specified language prefix + $this->route('(:str)/(:str)', function ($lang, $slug) { + // get current language by slug + $lang = $this->_getLanguageBySlug($lang); + + // Set current language to specified or if not exists to default + if ($lang) { + $this->core->loadLanguage($lang); + } else { + $slug = null; + } + + $this->_importPage($slug); + }); + + $this->_importAllPages(); + } + + /** + * get a specific page + */ + private function _importPage($slug = null) + { + if (!empty($slug)) { + $row = $this->db('pages')->where('slug', $slug)->where('lang', $this->_getCurrentLang())->oneArray(); + + if (empty($row)) { + return $this->get404(); + } + } else { + return $this->get404(); + } + + if (intval($row['markdown'])) { + $parsedown = new \Inc\Core\Lib\Parsedown(); + $row['content'] = $parsedown->text($row['content']); + } + + $this->filterRecord($row); + $this->setTemplate($row['template']); + $this->tpl->set('page', $row); + } + + /** + * get array with all pages + */ + private function _importAllPages() + { + $this->tpl->set('pages', function () { + $rows = $this->db('pages')->where('lang', $this->_getCurrentLang())->toArray(); + + $assign = []; + foreach ($rows as $row) { + $this->filterRecord($row); + $assign[$row['id']] = $row; + } + + return $assign; + }); + } + + public function get404() + { + http_response_code(404); + if (!($row = $this->db('pages')->like('slug', '404%')->where('lang', $this->_getCurrentLang())->oneArray())) { + echo '

    404 Not Found

    '; + echo $this->lang('not_found'); + exit; + } + + $this->setTemplate($row['template']); + $this->tpl->set('page', $row); + } + + private function _getCurrentLang() + { + if (!isset($_SESSION['lang'])) { + return $this->settings('settings', 'lang_site'); + } else { + return $_SESSION['lang']; + } + } + + protected function _getLanguageBySlug($slug) + { + $langs = parent::_getLanguages(); + foreach ($langs as $lang) { + preg_match_all('/([a-z]{2})_([a-z]+)/', $lang['name'], $matches); + if ($slug == $matches[1][0]) { + return $matches[0][0]; + } + } + + return false; + } + + protected function filterRecord(array &$page) + { + if (isset($page['title'])) { + $page['title'] = htmlspecialchars($page['title']); + } + } +} diff --git a/inc/modules/pages/js/admin/pages.js b/inc/modules/pages/js/admin/pages.js new file mode 100644 index 0000000..601baa8 --- /dev/null +++ b/inc/modules/pages/js/admin/pages.js @@ -0,0 +1,156 @@ +function insertEditor(type) +{ + var editor = $('.editor'); + + if(type == 'wysiwyg') + { + if($('.markItUp').length) + { + editor.markItUpRemove(); + } + + editor.summernote( + { + lang: '{$lang.name}', + height: 335, + callbacks: + { + onInit: function() + { + $('.note-codable').keyup(function() + { + editor.val($(this).val()); + }); + }, + onImageUpload: function(files) + { + sendFile(files[0], this); + }, + onChange: function() + { + editor.parents('form').trigger('checkform.areYouSure'); + } + } + }); + } + else + { + if($('.note-editor').length) + { + editor.each(function() + { + var isEmpty = $(this).summernote('isEmpty'); + $(this).summernote('destroy'); + if(isEmpty) + $(this).html(''); + }); + } + + var checkbox = $('input[name="markdown"]'); + editor.each(function() + { + var currentEditor = $(this); + currentEditor.markItUp(checkbox.is(':checked') ? markItUp_markdown : markItUp_html).highlight(); + + if($('.editor').data('editor') == 'html') + { + checkbox.change(function() + { + currentEditor.markItUpRemove(); + if (checkbox.is(':checked')) + currentEditor.markItUp(markItUp_markdown).highlight(); + else + currentEditor.markItUp(markItUp_html).highlight(); + }); + } + }); + } +} + +function sendFile(file, editor) +{ + var formData = new FormData(); + formData.append('file', file); + + var fileData = URL.createObjectURL(file); + $(editor).summernote('insertImage', fileData, function ($image) + { + $.ajax({ + xhr: function() + { + var xhr = new window.XMLHttpRequest(); + + $('input[type="submit"]').prop('disabled', true); + var progress = $('.progress:first').clone(); + progress = (progress.fadeIn()).appendTo($('.progress-wrapper')); + + xhr.upload.addEventListener("progress", function(evt) + { + if(evt.lengthComputable) + { + var percentComplete = evt.loaded / evt.total; + percentComplete = parseInt(percentComplete * 100); + progress.children().css('width', percentComplete + '%'); + + if(percentComplete === 100) + { + progress.fadeOut(); + progress.remove(); + $('input[type="submit"]').prop('disabled', false); + } + } + }, false); + + return xhr; + }, + url: '{?=url([ADMIN, "pages", "editorUpload"])?}', + data: formData, + type: 'POST', + cache: false, + contentType: false, + processData: false, + dataType: 'json', + success: function(data) + { + if(data.status == 'success') + { + $image.remove(); + $(editor).summernote('insertImage', data.result); + } + else if(data.status == 'failure') + { + $image.remove(); + bootbox.alert(data.result); + } + } + }); + }); +} + +function markdown() +{ + var checkbox = $('input[name="markdown"]'); + if($('.editor').data('editor') == 'wysiwyg') + { + checkbox.change(function() + { + if($(this).is(':checked')) + insertEditor('html'); + else + insertEditor('wysiwyg'); + }); + + if(checkbox.is(':checked')) + insertEditor('html'); + else + insertEditor('wysiwyg'); + } + else + insertEditor('html'); +} + +$(document).ready(function() +{ + markdown(); + $('form').areYouSure( {'message':'{$lang.general.unsaved_warning}'} ); +}); \ No newline at end of file diff --git a/inc/modules/pages/lang/admin/en_english.ini b/inc/modules/pages/lang/admin/en_english.ini new file mode 100644 index 0000000..86ff62f --- /dev/null +++ b/inc/modules/pages/lang/admin/en_english.ini @@ -0,0 +1,21 @@ +module_name = "Pages" +module_desc = "Management of sub-pages." + +pages_count = "Pages:" +add_new = "Add new" +new_page = "New page" +edit_page = "Edit page" +description = "Description" +keywords = "Tags" +content = "Content" +view = "Preview" +slug = "Slug" +markdown = "Enable Markdown" +save_success = "Page successfully saved." +save_failure = "Failed to save page." +delete_success = "Page successfully deleted." +delete_failure = "Unable to delete page." +delete_confirm = "Are you sure you want to delete this page?" +page_exists = "Page slug already exists. Changes on the page have not been saved." + +editor_upload_fail = "Can't load image. Probably unsupported type." \ No newline at end of file diff --git a/inc/modules/pages/lang/admin/pl_polski.ini b/inc/modules/pages/lang/admin/pl_polski.ini new file mode 100644 index 0000000..3216738 --- /dev/null +++ b/inc/modules/pages/lang/admin/pl_polski.ini @@ -0,0 +1,21 @@ +module_name = "Strony" +module_desc = "Zarządzanie podstronami." + +pages_count = "Podstron:" +add_new = "Dodaj nową" +new_page = "Nowa strona" +edit_page = "Edycja strony" +description = "Opis" +keywords = "Tagi" +content = "Zawartość" +view = "Zobacz" +slug = "Nazwa odnośnika" +markdown = "Włącz Markdown" +save_success = "Pomyślnie zapisano stronę." +save_failure = "Nie udało się zapisać strony." +delete_success = "Pomyślnie usunięto stronę." +delete_failure = "Nie udało się usunąć strony." +delete_confirm = "Na pewno chcesz usunąć wybraną stronę?" +page_exists = "Strona o takiej nazwie już istnieje. Strona nie została zapisana." + +editor_upload_fail = "Nie udało się załadować obrazu. Prawdopodobnie nieobsługiwany typ." \ No newline at end of file diff --git a/inc/modules/pages/lang/en_english.ini b/inc/modules/pages/lang/en_english.ini new file mode 100644 index 0000000..2380578 --- /dev/null +++ b/inc/modules/pages/lang/en_english.ini @@ -0,0 +1 @@ +not_found = "Sorry, page does not exist." \ No newline at end of file diff --git a/inc/modules/pages/lang/pl_polski.ini b/inc/modules/pages/lang/pl_polski.ini new file mode 100644 index 0000000..04056cb --- /dev/null +++ b/inc/modules/pages/lang/pl_polski.ini @@ -0,0 +1 @@ +not_found = "Żądana strona nie została odnaleziona." \ No newline at end of file diff --git a/inc/modules/pages/view/admin/form.html b/inc/modules/pages/view/admin/form.html new file mode 100644 index 0000000..539528a --- /dev/null +++ b/inc/modules/pages/view/admin/form.html @@ -0,0 +1,75 @@ +
    +
    +
    +
    +
    +

    {$pages.title}

    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +

    SEO

    +
    +
    +
    + + +
    +
    + + +
    +
    +
    + +
    +
    +

    {$lang.general.settings}

    +
    +
    +
    + + +
    +
    + + +
    + +
    + +
    + + {$lang.general.cancel} +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/inc/modules/pages/view/admin/manage.html b/inc/modules/pages/view/admin/manage.html new file mode 100644 index 0000000..017467b --- /dev/null +++ b/inc/modules/pages/view/admin/manage.html @@ -0,0 +1,54 @@ +
    +
    +
    +
    +

    {$lang.general.manage}

    + +
    +
    +

    {$lang.pages.pages_count} {?=count($pages.list)?}

    +
    + + + + + + + + + + + {if: !empty($pages.list)} + {loop: $pages.list} + + + + + + + {/loop} + {else} + + {/if} + +
    {$lang.general.title}{$lang.pages.slug}{$lang.general.template}{$lang.general.actions}
    {$value.title} {$value.desc}/{$value.slug}{$value.template} + + + + + + + + + +
    {$lang.general.empty_array}
    +
    + {$pages.pagination} +
    +
    +
    +
    \ No newline at end of file diff --git a/inc/modules/sample/Admin.php b/inc/modules/sample/Admin.php new file mode 100644 index 0000000..0b2ae65 --- /dev/null +++ b/inc/modules/sample/Admin.php @@ -0,0 +1,45 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Sample; + +use Inc\Core\AdminModule; + +/** + * Sample admin class + */ +class Admin extends AdminModule +{ + /** + * Module navigation + * Items of the returned array will be displayed in the administration sidebar + * + * @return array + */ + public function navigation() + { + return [ + $this->lang('index') => 'index', + ]; + } + + /** + * GET: /admin/sample/index + * Subpage method of the module + * + * @return string + */ + public function getIndex() + { + $text = 'Hello World'; + return $this->draw('index.html', ['text' => $text]); + } +} diff --git a/inc/modules/sample/Info.php b/inc/modules/sample/Info.php new file mode 100644 index 0000000..54a4140 --- /dev/null +++ b/inc/modules/sample/Info.php @@ -0,0 +1,27 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +return [ + 'name' => $core->lang['sample']['module_name'], + 'description' => $core->lang['sample']['module_desc'], + 'author' => 'Sruu.pl', + 'version' => '1.0', + 'compatibility' => '1.3.*', // Compatibility with Batflat version + 'icon' => 'code', // Icon from http://fontawesome.io/icons/ + + // Registering page for possible use as a homepage + 'pages' => ['Sample Page' => 'sample'], + + 'install' => function () use ($core) { + }, + 'uninstall' => function () use ($core) { + } +]; diff --git a/inc/modules/sample/Site.php b/inc/modules/sample/Site.php new file mode 100644 index 0000000..a8c4ea6 --- /dev/null +++ b/inc/modules/sample/Site.php @@ -0,0 +1,81 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Sample; + +use Inc\Core\SiteModule; + +/** + * Sample site class + */ +class Site extends SiteModule +{ + /** + * Example module variable + * + * @var string + */ + protected $foo; + + /** + * Module initialization + * Here everything is done while the module starts + * + * @return void + */ + public function init() + { + $this->foo = 'Hello'; + } + /** + * Register module routes + * Call the appropriate method/function based on URL + * + * @return void + */ + public function routes() + { + // Simple: + $this->route('sample', 'getIndex'); + /* + * Or: + * $this->route('sample', function() { + * $this->getIndex(); + * }); + * + * or: + * $this->router->set('sample', $this->getIndex()); + * + * or: + * $this->router->set('sample', function() { + * $this->getIndex(); + * }); + */ + } + + /** + * GET: /sample + * Called method by router + * + * @return string + */ + public function getIndex() + { + $page = [ + 'title' => $this->lang('title'), + 'desc' => 'Your page description here', + 'content' => $this->draw('hello.html') + ]; + + $this->setTemplate('index.html'); + $this->tpl->set('page', $page); + } +} diff --git a/inc/modules/sample/lang/admin/en_english.ini b/inc/modules/sample/lang/admin/en_english.ini new file mode 100644 index 0000000..392a68a --- /dev/null +++ b/inc/modules/sample/lang/admin/en_english.ini @@ -0,0 +1,6 @@ +module_name = "Sample module" +module_desc = "This is an example of module structure and usage in Batflat." + +index = "Look here!" +example = "Hello World!" +button = "Go to sample module front page" \ No newline at end of file diff --git a/inc/modules/sample/lang/admin/pl_polski.ini b/inc/modules/sample/lang/admin/pl_polski.ini new file mode 100644 index 0000000..07d4b5b --- /dev/null +++ b/inc/modules/sample/lang/admin/pl_polski.ini @@ -0,0 +1,6 @@ +module_name = "Przykładowy moduł" +module_desc = "To jest przykładowy moduł, dzięki któremu poznasz podstawę tworzenia modułów dla Batflata." + +index = "Zobacz tutaj!" +example = "Witaj świecie!" +button = "Przejdź do przykładowej podstrony modułu" \ No newline at end of file diff --git a/inc/modules/sample/lang/en_english.ini b/inc/modules/sample/lang/en_english.ini new file mode 100644 index 0000000..a8f0376 --- /dev/null +++ b/inc/modules/sample/lang/en_english.ini @@ -0,0 +1 @@ +title = "Sample module title" \ No newline at end of file diff --git a/inc/modules/sample/lang/pl_polski.ini b/inc/modules/sample/lang/pl_polski.ini new file mode 100644 index 0000000..703415a --- /dev/null +++ b/inc/modules/sample/lang/pl_polski.ini @@ -0,0 +1 @@ +title = "Testowa strona modułu" \ No newline at end of file diff --git a/inc/modules/sample/view/admin/index.html b/inc/modules/sample/view/admin/index.html new file mode 100644 index 0000000..1342e65 --- /dev/null +++ b/inc/modules/sample/view/admin/index.html @@ -0,0 +1,16 @@ +
    +
    +
    +
    +

    {$text}

    +
    +
    +
    +

    {$lang.sample.example}

    +
    + {$lang.sample.button} +
    +
    + +
    +
    diff --git a/inc/modules/sample/view/hello.html b/inc/modules/sample/view/hello.html new file mode 100644 index 0000000..351d311 --- /dev/null +++ b/inc/modules/sample/view/hello.html @@ -0,0 +1,9 @@ +

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce lacinia porta fermentum. Pellentesque lacus nulla, sagittis id mollis ut, euismod sit amet sapien. Nulla at est tellus. Suspendisse sed ligula nec sem bibendum volutpat at non neque. Nam fringilla pellentesque lacus. Morbi sit amet sem vitae nibh dignissim accumsan viverra ut lectus. Sed semper at elit id convallis. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec consequat nulla a risus placerat, ac convallis ligula eleifend. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Quisque rutrum mattis tortor, vitae efficitur lacus porta egestas. In gravida massa et iaculis congue.

    + +

    Morbi elementum suscipit neque, nec ultrices tellus dignissim ac. Vestibulum fermentum nisi ac tempus pulvinar. Donec at luctus tortor, eu mattis orci. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Proin faucibus dapibus consequat. Vestibulum vulputate varius metus, non consequat neque commodo vel. Morbi et velit nibh. In magna mi, auctor non metus sed, venenatis dignissim lorem. Duis quis sollicitudin ipsum. Curabitur euismod vitae est sit amet tristique. Cras tincidunt lobortis elit a fermentum. Vivamus luctus rhoncus turpis, dapibus tempor dui tristique quis. Nam sit amet purus ac tellus finibus dignissim eu in urna. Nulla fringilla justo sed efficitur suscipit.

    + +

    In hac habitasse platea dictumst. Donec lorem eros, vehicula ut condimentum non, consequat a nisl. Aliquam nunc turpis, tristique nec eros eget, sagittis aliquam turpis. Etiam rhoncus pharetra odio id euismod. Aliquam aliquet, quam et venenatis scelerisque, elit massa auctor massa, at iaculis lorem arcu at purus. Proin justo mi, lobortis ac vestibulum in, vestibulum quis mi. Sed dui lectus, pretium vitae tortor sed, luctus sagittis eros. Quisque tellus nisl, posuere at sem id, placerat porta risus. Quisque convallis ante tempor massa luctus pellentesque. Nunc mattis feugiat dolor, sed bibendum ante mattis eu. Quisque euismod pharetra dolor. Mauris aliquet imperdiet sollicitudin. Etiam maximus rhoncus sapien, nec euismod velit aliquet non. Vivamus non neque molestie, faucibus neque ac, eleifend libero. Cras aliquet bibendum enim, ac eleifend elit egestas a.

    + +

    Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla rutrum sapien nec arcu tempus, consectetur imperdiet ex commodo. Nulla pretium, tortor non suscipit vulputate, augue neque consectetur tortor, in rutrum lectus nisi nec eros. Praesent cursus, urna ac dictum fringilla, est dui efficitur odio, vitae porttitor velit ante ut tellus. Sed pharetra dolor eget gravida porttitor. Nullam tempor sapien leo, a ornare dui malesuada at. Donec at ante quis velit sollicitudin scelerisque.

    + +

    Nam a turpis vitae justo ullamcorper accumsan sit amet ac sapien. Donec sem dui, congue eu egestas et, consequat nec massa. Phasellus tristique fringilla orci vitae facilisis. Aenean condimentum, massa sed pretium lobortis, sem nulla finibus ipsum, vestibulum imperdiet diam magna vitae elit. Nullam id nulla lacinia, interdum metus id, bibendum diam. Nunc ullamcorper, elit eu scelerisque mollis, nisl sem auctor massa, at interdum nunc eros eget urna. Etiam augue arcu, laoreet quis tincidunt in, pulvinar ac odio. In placerat, mi ut feugiat tempor, velit odio efficitur neque, id cursus ligula leo nec libero. Quisque tincidunt ut lacus vitae mollis. Nam lectus libero, dapibus vitae turpis ut, sodales vulputate enim. Etiam luctus molestie enim, ut auctor mauris ultrices id. Cras eget aliquet elit, at lobortis eros. Nulla sit amet dictum metus, et dignissim metus. Donec a pellentesque risus, eget consequat purus. Curabitur purus quam, egestas sed felis eu, vehicula rhoncus eros.

    \ No newline at end of file diff --git a/inc/modules/searchbox/Info.php b/inc/modules/searchbox/Info.php new file mode 100644 index 0000000..c5ea9f4 --- /dev/null +++ b/inc/modules/searchbox/Info.php @@ -0,0 +1,28 @@ + + * @author Wojciech Król + * @copyright 2017 Paweł Klockiewicz, Wojciech Król + * @license https://batflat.org/license + * @link https://batflat.org + */ + + return [ + 'name' => 'SearchBox', + 'description' => $core->lang['searchbox']['module_desc'], + 'author' => 'Sruu.pl', + 'version' => '1.0', + 'compatibility' => '1.3.*', + 'icon' => 'search', + + 'install' => function() use($core) + { + + }, + 'uninstall' => function() use($core) + { + + } + ]; \ No newline at end of file diff --git a/inc/modules/searchbox/ReadMe.md b/inc/modules/searchbox/ReadMe.md new file mode 100644 index 0000000..deef9d7 --- /dev/null +++ b/inc/modules/searchbox/ReadMe.md @@ -0,0 +1,2 @@ +Use the `{$searchBox}` tag to display search form. +By default, module uses the `index.html` template to display search results. If you want to change this, create a `search.html` template inside your theme. \ No newline at end of file diff --git a/inc/modules/searchbox/Site.php b/inc/modules/searchbox/Site.php new file mode 100644 index 0000000..c619238 --- /dev/null +++ b/inc/modules/searchbox/Site.php @@ -0,0 +1,118 @@ + + * @author Wojciech Król + * @copyright 2017 Paweł Klockiewicz, Wojciech Król + * @license https://batflat.org/license + * @link https://batflat.org + */ + + namespace Inc\Modules\SearchBox; + + use Inc\Core\SiteModule; + + class Site extends SiteModule + { + + public function init() + { + if(isset($_GET['search'])) + redirect(url('search/'.urlencode(strip_tags($_GET['search'])))); + + $this->tpl->set('searchBox', $this->_insertSearchBox()); + } + + + public function routes() + { + $this->route('search/(:any)', 'getSearch'); + $this->route('search/(:any)/(:int)', 'getSearch'); + } + + public function getSearch($phrase, $index = 1) + { + $phrase = urldecode($phrase); + $searchTemplate = 'search.html'; + $phraseMinLength = 3; + + $page = [ + 'title' => $this->tpl->noParse(sprintf($this->lang('results_for'), $phrase)), + 'desc' => $this->settings('settings.description') + ]; + + // if $searchTemplate exists, use it instead of "index.html" + if(file_exists(THEMES.'/'.$this->settings('settings.theme').'/'.$searchTemplate)) + $this->setTemplate($searchTemplate); + else + $this->setTemplate('index.html'); + + // check if $phrase is long as value of $phraseMinLength + if(strlen($phrase) < $phraseMinLength) + $page['content'] = sprintf($this->lang('too_short_phrase'), $phraseMinLength); + else + { + // select pages + $pages = $this->db()->pdo()->prepare("SELECT * FROM pages WHERE lang = ? AND (title LIKE ? OR content LIKE ?)"); + $pages->execute([$this->_currentLanguage(), '%'.$phrase.'%', '%'.$phrase.'%']); + $pagesArray = $pages->fetchAll(); + + // add URL key to pages array + foreach($pagesArray as &$item) + { + $item['url'] = url($item['slug']); + } + + // select blog entries + $blog = $this->db()->pdo()->prepare("SELECT * FROM blog WHERE lang = ? AND status = ? AND (title LIKE ? OR content LIKE ?)"); + $blog->execute([$this->_currentLanguage(), 2, '%'.$phrase.'%', '%'.$phrase.'%']); + $blogArray = $blog->fetchAll(); + + // add URL key to blog array + foreach($blogArray as &$item) + { + $item['url'] = url('blog/post/'.$item['slug']); + } + + // merge of pages and blog entries + $rows = array_merge($pagesArray, $blogArray); + + // display results + if(!empty($rows) && (count($rows) >= $index)) + { + $pagination = new \Inc\Core\Lib\Pagination($index, count($rows), 10, url('search/'.$phrase.'/%d')); + $rows = array_chunk($rows, $pagination->getRecordsPerPage()); + $page['content'] = $this->_insertResults($rows[$pagination->offset()]) . $pagination->nav(); + } + else + $page['content'] = sprintf($this->lang('no_results'), $phrase); + } + + $this->tpl->set('page', $page); + } + + private function _insertSearchBox() + { + return $this->draw('input.html'); + } + + private function _insertResults(array $results) + { + foreach($results as &$result) + { + // remove HTML and Template tags + $result['content'] = preg_replace('/{(.*?)}/', '', strip_tags($result['content'])); + } + return $this->draw('results.html', ['results' => $results]); + } + + private function _currentLanguage() + { + if(!isset($_SESSION['lang'])) + return $this->settings('settings', 'lang_site'); + else + return $_SESSION['lang']; + } + + } \ No newline at end of file diff --git a/inc/modules/searchbox/lang/admin/en_english.ini b/inc/modules/searchbox/lang/admin/en_english.ini new file mode 100644 index 0000000..95ac792 --- /dev/null +++ b/inc/modules/searchbox/lang/admin/en_english.ini @@ -0,0 +1 @@ +module_desc = "Allows you to search for pages and blog entries that contain the phrase you entered." \ No newline at end of file diff --git a/inc/modules/searchbox/lang/admin/pl_polski.ini b/inc/modules/searchbox/lang/admin/pl_polski.ini new file mode 100644 index 0000000..fd7ae48 --- /dev/null +++ b/inc/modules/searchbox/lang/admin/pl_polski.ini @@ -0,0 +1 @@ +module_desc = "Umożliwia wyszukiwanie stron oraz wpisów na blogu zawierających wpisaną frazę." \ No newline at end of file diff --git a/inc/modules/searchbox/lang/en_english.ini b/inc/modules/searchbox/lang/en_english.ini new file mode 100644 index 0000000..648650c --- /dev/null +++ b/inc/modules/searchbox/lang/en_english.ini @@ -0,0 +1,4 @@ +placeholder = "Search for..." +results_for = "Search results for '%s'" +too_short_phrase = "The phrase you entered is too short! Please enter at least %d characters." +no_results = "No results found for '%s'." \ No newline at end of file diff --git a/inc/modules/searchbox/lang/pl_polski.ini b/inc/modules/searchbox/lang/pl_polski.ini new file mode 100644 index 0000000..63f65dd --- /dev/null +++ b/inc/modules/searchbox/lang/pl_polski.ini @@ -0,0 +1,4 @@ +placeholder = "Szukaj..." +results_for = "Wyniki wyszukiwania dla '%s'" +too_short_phrase = "Wprowadzona fraza jest zbyt krótka! Podaj minimum %d znaki." +no_results = "Nie znaleziono wyników dotyczących zapytania '%s'." \ No newline at end of file diff --git a/inc/modules/searchbox/view/input.html b/inc/modules/searchbox/view/input.html new file mode 100644 index 0000000..ce6d949 --- /dev/null +++ b/inc/modules/searchbox/view/input.html @@ -0,0 +1,8 @@ +
    +
    + + + + +
    +
    \ No newline at end of file diff --git a/inc/modules/searchbox/view/results.html b/inc/modules/searchbox/view/results.html new file mode 100644 index 0000000..af00704 --- /dev/null +++ b/inc/modules/searchbox/view/results.html @@ -0,0 +1,8 @@ +
    + {loop: $results} +
    +
    {$value.title}
    +
    {$value.content|cut:160}
    +
    + {/loop} +
    \ No newline at end of file diff --git a/inc/modules/settings/Admin.php b/inc/modules/settings/Admin.php new file mode 100644 index 0000000..c05bae8 --- /dev/null +++ b/inc/modules/settings/Admin.php @@ -0,0 +1,839 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Settings; + +use Inc\Core\AdminModule; +use Inc\Core\Lib\License; +use Inc\Core\Lib\HttpRequest; + +use ZipArchive; +use RecursiveIteratorIterator; +use RecursiveDirectoryIterator; +use FilesystemIterator; +use Inc\Modules\Settings\Inc\RecursiveDotFilterIterator; + +class Admin extends AdminModule +{ + private $assign = []; + private $feed_url = "http://feed.sruu.pl"; + + public function init() + { + if (file_exists(BASE_DIR.'/inc/engine')) { + deleteDir(BASE_DIR.'/inc/engine'); + } + } + + public function navigation() + { + return [ + $this->lang('general') => 'general', + $this->lang('theme', 'general') => 'theme', + $this->lang('translation') => 'translation', + $this->lang('updates') => 'updates', + ]; + } + + public function getGeneral() + { + $settings = $this->settings('settings'); + + // lang + if (isset($_GET['lang']) && !empty($_GET['lang'])) { + $lang = $_GET['lang']; + } else { + $lang = $settings['lang_site']; + } + + $settings['langs'] = [ + 'site' => $this->_getLanguages($settings['lang_site'], 'selected'), + 'admin' => $this->_getLanguages($settings['lang_admin'], 'selected') + ]; + $settings['themes'] = $this->_getThemes(); + $settings['pages'] = $this->_getPages($lang); + $settings['timezones'] = $this->_getTimezones(); + $settings['system'] = [ + 'php' => PHP_VERSION, + 'sqlite' => $this->db()->pdo()->query('select sqlite_version()')->fetch()[0], + 'sqlite_size' => $this->roundSize(filesize(BASE_DIR.'/inc/data/database.sdb')), + 'system_size' => $this->roundSize($this->_directorySize(BASE_DIR)), + ]; + + $settings['license'] = []; + $settings['license']['type'] = $this->_verifyLicense(); + switch ($settings['license']['type']) { + case License::FREE: + $settings['license']['name'] = $this->lang('free'); + break; + case License::COMMERCIAL: + $settings['license']['name'] = $this->lang('commercial'); + break; + default: + $settings['license']['name'] = $this->lang('invalid_license'); + } + + foreach ($this->core->getRegisteredPages() as $page) { + $settings['pages'][] = $page; + } + + if (!empty($redirectData = getRedirectData())) { + $settings = array_merge($settings, $redirectData); + } + + $this->tpl->set('settings', $this->tpl->noParse_array(htmlspecialchars_array($settings))); + $this->tpl->set('updateurl', url([ADMIN, 'settings', 'updates'])); + + return $this->draw('general.html'); + } + + public function postSaveGeneral() + { + unset($_POST['save']); + if (checkEmptyFields(array_keys($_POST), $_POST)) { + $this->notify('failure', $this->lang('empty_inputs', 'general')); + redirect(url([ADMIN, 'settings', 'general']), $_POST); + } else { + $errors = 0; + + if ($this->settings('settings', 'autodetectlang')) { + $_POST['autodetectlang'] = isset_or($_POST['autodetectlang'], 0); + } + + foreach ($_POST as $field => $value) { + if (!$this->db('settings')->where('module', 'settings')->where('field', $field)->save(['value' => $value])) { + $errors++; + } + } + + if (!$errors) { + $this->notify('success', $this->lang('save_settings_success')); + } else { + $this->notify('failure', $this->lang('save_settings_failure')); + } + + unset($_SESSION['lang']); + redirect(url([ADMIN, 'settings', 'general'])); + } + } + + public function anyLicense() + { + if (isset($_POST['license-key'])) { + $licenseKey = str_replace('-', null, $_POST['license-key']); + + if (!($licenseKey = License::getLicenseData($licenseKey))) { + $this->notify('failure', $this->lang('license_invalid_key')); + } + + $verify = License::verify($licenseKey); + if ($verify != License::COMMERCIAL) { + $this->notify('failure', $this->lang('license_invalid_key')); + } else { + $this->notify('success', $this->lang('license_good_key')); + } + } elseif (isset($_GET['downgrade'])) { + $this->db('settings')->where('module', 'settings')->where('field', 'license')->save(['value' => '']); + } + + redirect(url([ADMIN,'settings','general'])); + } + + public function anyTheme($theme = null, $file = null) + { + $this->core->addCSS(url(MODULES.'/settings/css/admin/settings.css')); + + if (empty($theme) && empty($file)) { + $this->tpl->set('settings', $this->settings('settings')); + $this->tpl->set('themes', $this->_getThemes()); + return $this->draw('themes.html'); + } else { + if ($file == 'activate') { + $this->db('settings')->where('module', 'settings')->where('field', 'theme')->save(['value' => $theme]); + $this->notify('success', $this->lang('theme_changed')); + redirect(url([ADMIN, 'settings', 'theme'])); + } + + // Source code editor + $this->core->addCSS(url('/inc/jscripts/editor/markitup.min.css')); + $this->core->addCSS(url('/inc/jscripts/editor/markitup.highlight.min.css')); + $this->core->addCSS(url('/inc/jscripts/editor/sets/html/set.min.css')); + $this->core->addJS(url('/inc/jscripts/editor/highlight.min.js')); + $this->core->addJS(url('/inc/jscripts/editor/markitup.min.js')); + $this->core->addJS(url('/inc/jscripts/editor/markitup.highlight.min.js')); + $this->core->addJS(url('/inc/jscripts/editor/sets/html/set.min.js')); + + $this->assign['files'] = $this->_getThemeFiles($file, $theme); + + if ($file) { + $file = $this->assign['files'][$file]['path']; + } else { + $file = reset($this->assign['files'])['path']; + } + + $this->assign['content'] = $this->tpl->noParse(htmlspecialchars(file_get_contents($file))); + $this->assign['lang'] = pathinfo($file, PATHINFO_EXTENSION); + + if (isset($_POST['save']) && !FILE_LOCK) { + if (file_put_contents($file, htmlspecialchars_decode($_POST['content']))) { + $this->notify('success', $this->lang('save_file_success')); + } else { + $this->notify('failure', $this->lang('save_file_failure')); + } + + redirect(url([ADMIN, 'settings', 'theme', $theme, md5($file)])); + } + + $this->tpl->set('settings', $this->settings('settings')); + $this->tpl->set('theme', array_merge($this->_getThemes($theme), $this->assign)); + return $this->draw('theme.html'); + } + } + + public function getTranslation() + { + if (isset($_GET['export'])) { + $export = $_GET['export']; + if (file_exists(BASE_DIR.'/inc/lang/'.$export)) { + $file = tempnam("tmp", "zip"); + $zip = new ZipArchive(); + $zip->open($file, ZipArchive::OVERWRITE); + + foreach (glob(BASE_DIR.'/inc/lang/'.$export.'/admin/*.ini') as $f) { + $zip->addFile($f, str_replace(BASE_DIR, null, $f)); + } + + foreach (glob(MODULES.'/*/lang/'.$export.'.ini') as $f) { + $zip->addFile($f, str_replace(BASE_DIR, null, $f)); + } + + foreach (glob(MODULES.'/*/lang/admin/'.$export.'.ini') as $f) { + $zip->addFile($f, str_replace(BASE_DIR, null, $f)); + } + + // Close and send to users + $zip->close(); + header('Content-Type: application/zip'); + header('Content-Length: ' . filesize($file)); + header('Content-Disposition: attachment; filename="Batflat_'.str_replace('.', '-', $this->settings('settings', 'version')).'_'.$export.'.zip"'); + readfile($file); + unlink($file); + exit(); + } + } + + if (!isset($_GET['lang'])) { + $_GET['lang'] = $this->settings('settings', 'lang_site'); + } + + if (!isset($_GET['source'])) { + $_GET['source'] = 0; + } + + $settings['langs'] = $this->_getLanguages($_GET['lang']); + $settings['selected'] = $_GET['lang']; + + $translations = $this->_getAllTranslations($_GET['lang']); + $translation = $translations[$_GET['source']]; + $translations = array_keys($translations); + + $this->tpl->set('translation', $translation); + $this->tpl->set('translations', $translations); + $this->tpl->set('module', $_GET['source']); + $this->tpl->set('settings', $settings); + + //unset($translations, $settings); + + return $this->draw('translation.html'); + } + + public function postTranslation() + { + if (!isset($_GET['lang'])) { + $_GET['lang'] = $this->settings('settings', 'lang_site'); + } + + if (!isset($_GET['source'])) { + $_GET['source'] = 0; + } + + if (isset($_POST['upload']) && FILE_LOCK === false) { + $zip = new ZipArchive(); + $error = false; + $file = !empty($_FILES['lang_package']['tmp_name']) ? $_FILES['lang_package']['tmp_name'] : '/'; + $open = $zip->open($file); + if ($open === true) { + for ($i = 0; $i < $zip->numFiles; $i++) { + $filename = pathinfo($zip->getNameIndex($i)); + + if (strpos($filename['dirname'].'/', '/lang/') === false) { + $error = true; + break; + } + + if ($filename['extension'] != 'ini') { + $error = true; + break; + } + } + + if (!$error) { + $zip->extractTo(BASE_DIR); + $zip->close(); + $this->notify('success', $this->lang('lang_import_success')); + } else { + $this->notify('failure', $this->lang('lang_import_error')); + } + } + } + + if (isset($_POST['new_language']) && FILE_LOCK === false) { + $lang = $_POST['language_name']; + if (preg_match("/^[a-z]{2}_[a-z]+$/", $lang)) { + if (file_exists(BASE_DIR.'/inc/lang/'.$lang)) { + $this->notify('failure', $this->lang('new_lang_exists')); + } else { + if (mkdir(BASE_DIR.'/inc/lang/'.$lang.'/admin', 0755, true)) { + $this->notify('success', $this->lang('new_lang_success')); + redirect(url([ADMIN, 'settings', 'translation?lang='.$lang])); + } else { + $this->notify('success', $this->lang('new_lang_create_fail')); + } + } + } else { + $this->notify('failure', $this->lang('new_lang_failure')); + } + } + if (isset($_POST['save'], $_POST[$_GET['source']]) && FILE_LOCK === false) { + $toSave = $_POST[$_GET['source']]; + if (is_numeric($_GET['source'])) { + $pad = 0; + array_walk($toSave['admin'], function ($value, $key) use (&$pad) { + $length = strlen($key); + if ($pad < $length) { + $pad = $length; + } + }); + + $pad = $pad + 4 - $pad%4; + + $output = []; + foreach ($toSave['admin'] as $key => $value) { + $value = preg_replace("/(?notify('success', $this->lang('save_file_success')); + } else { + $this->notify('failure', $this->lang('save_file_failure')); + } + } else { + if (isset($toSave['front'])) { + $pad = 0; + array_walk($toSave['front'], function ($value, $key) use (&$pad) { + $length = strlen($key); + if ($pad < $length) { + $pad = $length; + } + }); + + $pad = $pad + 4 - $pad%4; + + $output = []; + foreach ($toSave['front'] as $key => $value) { + $value = preg_replace("/(?notify('success', $this->lang('save_file_success')); + } else { + $this->notify('failure', $this->lang('save_file_failure')); + } + } + + if (isset($toSave['admin'])) { + $pad = 0; + array_walk($toSave['admin'], function ($value, $key) use (&$pad) { + $length = strlen($key); + if ($pad < $length) { + $pad = $length; + } + }); + + $pad = $pad + 4 - $pad%4; + + $output = []; + foreach ($toSave['admin'] as $key => $value) { + $value = preg_replace("/(?notify('success', $this->lang('save_file_success')); + } else { + $this->notify('failure', $this->lang('save_file_failure')); + } + } + } + } + + redirect(url([ADMIN, 'settings', 'translation?lang='.$_GET['lang']])); + } + + public function anyUpdates() + { + $this->tpl->set('allow_curl', intval(function_exists('curl_init'))); + $settings = $this->settings('settings'); + + if (isset($_POST['check'])) { + $request = $this->updateRequest('/batflat/update', [ + 'ip' => isset_or($_SERVER['SERVER_ADDR'], $_SERVER['SERVER_NAME']), + 'version' => $settings['version'], + 'domain' => url(), + ]); + + $this->_updateSettings('update_check', time()); + + if (!is_array($request)) { + $this->tpl->set('error', $request); + } elseif ($request['status'] == 'error') { + $this->tpl->set('error', $request['message']); + } else { + $this->_updateSettings('update_version', $request['data']['version']); + $this->_updateSettings('update_changelog', $request['data']['changelog']); + $this->tpl->set('update_version', $request['data']['version']); + + // if(DEV_MODE) + // $this->tpl->set('request', $request); + } + } elseif (isset($_POST['update'])) { + if (!class_exists("ZipArchive")) { + $this->tpl->set('error', "ZipArchive is required to update Batflat."); + } + + if (!isset($_GET['manual'])) { + $request = $this->updateRequest('/batflat/update', [ + 'ip' => isset_or($_SERVER['SERVER_ADDR'], $_SERVER['SERVER_NAME']), + 'version' => $settings['version'], + 'domain' => url(), + ]); + + $this->download($request['data']['download'], BASE_DIR.'/tmp/latest.zip'); + } else { + $package = glob(BASE_DIR.'/batflat-*.zip'); + if (!empty($package)) { + $package = array_shift($package); + $this->rcopy($package, BASE_DIR.'/tmp/latest.zip'); + } + } + + define("UPGRADABLE", true); + // Making backup + $backup_date = date('YmdHis'); + $this->rcopy(BASE_DIR, BASE_DIR.'/backup/'.$backup_date.'/', 0755, [BASE_DIR.'/backup', BASE_DIR.'/tmp/latest.zip', (isset($package) ? BASE_DIR.'/'.basename($package) : '')]); + + // Unzip latest update + $zip = new ZipArchive; + $zip->open(BASE_DIR.'/tmp/latest.zip'); + $zip->extractTo(BASE_DIR.'/tmp/update'); + + // Copy files + $this->rcopy(BASE_DIR.'/tmp/update/inc/css', BASE_DIR.'/inc/css'); + $this->rcopy(BASE_DIR.'/tmp/update/inc/core', BASE_DIR.'/inc/core'); + $this->rcopy(BASE_DIR.'/tmp/update/inc/jscripts', BASE_DIR.'/inc/jscripts'); + $this->rcopy(BASE_DIR.'/tmp/update/inc/lang', BASE_DIR.'/inc/lang'); + $this->rcopy(BASE_DIR.'/tmp/update/inc/modules', BASE_DIR.'/inc/modules'); + + // Restore defines + $this->rcopy(BASE_DIR.'/backup/'.$backup_date.'/inc/core/defines.php', BASE_DIR.'/inc/core/defines.php'); + + // Run upgrade script + $version = $settings['version']; + $new_version = include(BASE_DIR.'/tmp/update/upgrade.php'); + + // Close archive and delete all unnecessary files + $zip->close(); + unlink(BASE_DIR.'/tmp/latest.zip'); + deleteDir(BASE_DIR.'/tmp/update'); + + $this->_updateSettings('version', $new_version); + $this->_updateSettings('update_version', 0); + $this->_updateSettings('update_changelog', ''); + $this->_updateSettings('update_check', time()); + + sleep(2); + redirect(url([ADMIN, 'settings', 'updates'])); + } elseif (isset($_GET['reset'])) { + $this->_updateSettings('update_version', 0); + $this->_updateSettings('update_changelog', ''); + $this->_updateSettings('update_check', 0); + } elseif (isset($_GET['manual'])) { + $package = glob(BASE_DIR.'/batflat-*.zip'); + $version = false; + if (!empty($package)) { + $package_path = array_shift($package); + preg_match('/batflat\-([0-9\.a-z]+)\.zip$/', $package_path, $matches); + $version = $matches[1]; + } + + $manual_mode = ['version' => $version]; + } + + $this->settings->reload(); + $settings = $this->settings('settings'); + $this->tpl->set('settings', $settings); + $this->tpl->set('manual_mode', isset_or($manual_mode, false)); + return $this->draw('update.html'); + } + + public function postChangeOrderOfNavItem() + { + foreach ($_POST as $module => $order) { + $this->db('modules')->where('dir', $module)->save(['sequence' => $order]); + } + exit(); + } + + public function _checkUpdate() + { + $settings = $this->settings('settings'); + if (time() - $settings['update_check'] > 3600*6) { + $request = $this->updateRequest('/batflat/update', [ + 'ip' => isset_or($_SERVER['SERVER_ADDR'], $_SERVER['SERVER_NAME']), + 'version' => $settings['version'], + 'domain' => url(), + ]); + + if (is_array($request) && $request['status'] != 'error') { + $settings['update_version'] = $request['data']['version']; + $this->_updateSettings('update_version', $request['data']['version']); + $this->_updateSettings('update_changelog', $request['data']['changelog']); + } + + $this->_updateSettings('update_check', time()); + } + + if (cmpver($settings['update_version'], $settings['version']) === 1) { + return true; + } + + return false; + } + + private function updateRequest($resource, $params = []) + { + $output = HttpRequest::post($this->feed_url.$resource, $params); + if ($output === false) { + $output = HttpRequest::getStatus(); + } else { + $output = json_decode($output, true); + } + + return $output; + } + + private function download($source, $dest) + { + set_time_limit(0); + $fp = fopen($dest, 'w+'); + $ch = curl_init($source); + curl_setopt($ch, CURLOPT_TIMEOUT, 50); + curl_setopt($ch, CURLOPT_FILE, $fp); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_exec($ch); + curl_close($ch); + fclose($fp); + } + + /** + * list of themes + * @return array + */ + private function _getThemes($theme = null) + { + $themes = glob(THEMES.'/*', GLOB_ONLYDIR); + $return = []; + foreach ($themes as $e) { + if ($e != THEMES.'/admin') { + $manifest = array_fill_keys(['name', 'version', 'author', 'email', 'thumb'], 'Unknown'); + $manifest['name'] = basename($e); + $manifest['thumb'] = '../admin/img/unknown_theme.png'; + + if (file_exists($e.'/manifest.json')) { + $manifest = array_merge($manifest, json_decode(file_get_contents($e.'/manifest.json'), true)); + } + + if ($theme == basename($e)) { + return array_merge($manifest, ['dir' => basename($e)]); + } + + $return[] = array_merge($manifest, ['dir' => basename($e)]); + } + } + + return $return; + } + + /** + * list of pages + * @param string $lang + * @param integer $selected + * @return array + */ + private function _getPages($lang) + { + $rows = $this->db('pages')->where('lang', $lang)->toArray(); + if (count($rows)) { + foreach ($rows as $row) { + $result[] = ['id' => $row['id'], 'title' => $row['title'], 'slug' => $row['slug']]; + } + } + return $result; + } + + /** + * list of theme files (html, css & js) + * @param string $selected + * @return array + */ + private function _getThemeFiles($selected = null, $theme = null) + { + $theme = ($theme ? $theme : $this->settings('settings', 'theme')); + $files = $this->rglob(THEMES.'/'.$theme.'/*.html'); + $files = array_merge($files, $this->rglob(THEMES.'/'.$theme.'/*.css')); + $files = array_merge($files, $this->rglob(THEMES.'/'.$theme.'/*.js')); + + $result = []; + foreach ($files as $file) { + if ($selected && ($selected == md5($file))) { + $attr = 'selected'; + } else { + $attr = null; + } + + $result[md5($file)] = ['name' => basename($file), 'path' => $file, 'short' => str_replace(BASE_DIR, null, $file), 'attr' => $attr]; + } + + return $result; + } + + private function _updateSettings($field, $value) + { + return $this->settings('settings', $field, $value); + } + + private function rcopy($source, $dest, $permissions = 0755, $expect = []) + { + foreach ($expect as $e) { + if ($e == $source) { + return; + } + } + + if (is_link($source)) { + return symlink(readlink($source), $dest); + } + + if (is_file($source)) { + if (!is_dir(dirname($dest))) { + mkdir(dirname($dest), 0777, true); + } + + return copy($source, $dest); + } + + if (!is_dir($dest)) { + mkdir($dest, $permissions, true); + } + + $dir = dir($source); + while (false !== $entry = $dir->read()) { + if ($entry == '.' || $entry == '..') { + continue; + } + + $this->rcopy("$source/$entry", "$dest/$entry", $permissions, $expect); + } + + $dir->close(); + return true; + } + + private function _verifyLicense() + { + $licenseArray = (array) json_decode(base64_decode($this->settings('settings', 'license')), true); + $license = array_replace(array_fill(0, 5, null), $licenseArray); + list($md5hash, $pid, $lcode, $dcode, $tstamp) = $license; + + if (empty($md5hash)) { + return License::FREE; + } + + if ($md5hash == md5($pid.$lcode.$dcode.domain(false))) { + return License::COMMERCIAL; + } + + return License::ERROR; + } + + private function _getTimezones() + { + $regions = array( + \DateTimeZone::AFRICA, + \DateTimeZone::AMERICA, + \DateTimeZone::ANTARCTICA, + \DateTimeZone::ASIA, + \DateTimeZone::ATLANTIC, + \DateTimeZone::AUSTRALIA, + \DateTimeZone::EUROPE, + \DateTimeZone::INDIAN, + \DateTimeZone::PACIFIC, + \DateTimeZone::UTC, + ); + + $timezones = array(); + foreach ($regions as $region) { + $timezones = array_merge($timezones, \DateTimeZone::listIdentifiers($region)); + } + + $timezone_offsets = array(); + foreach ($timezones as $timezone) { + $tz = new \DateTimeZone($timezone); + $timezone_offsets[$timezone] = $tz->getOffset(new \DateTime); + } + + // sort timezone by offset + asort($timezone_offsets); + + $timezone_list = array(); + foreach ($timezone_offsets as $timezone => $offset) { + $offset_prefix = $offset < 0 ? '-' : '+'; + $offset_formatted = gmdate('H:i', abs($offset)); + + $pretty_offset = "UTC${offset_prefix}${offset_formatted}"; + + $timezone_list[$timezone] = "(${pretty_offset}) $timezone"; + } + + return $timezone_list; + } + + private function _getAllTranslations($lang) + { + $modules = []; + + $general = parse_ini_file('../inc/lang/en_english/admin/general.ini'); + + if (file_exists('../inc/lang/'.$lang.'/admin/general.ini')) { + $current = parse_ini_file('../inc/lang/'.$lang.'/admin/general.ini'); + } else { + $current = []; + } + + foreach ($general as $key => $value) { + $modules[0]['admin'][] = [ + 'key' => $key, + 'value' => isset_or($current[$key], null), + 'english' => $value + ]; + } + + $dirs = glob(MODULES.'/*'); + foreach ($dirs as $dir) { + $modules[basename($dir)] = []; + if (file_exists($dir.'/lang/en_english.ini')) { + $tmp = parse_ini_file($dir.'/lang/en_english.ini'); + + if (file_exists($dir.'/lang/'.$lang.'.ini')) { + $current = parse_ini_file($dir.'/lang/'.$lang.'.ini'); + } else { + $current = []; + } + + foreach ($tmp as $key => $value) { + $modules[basename($dir)]['front'][] = [ + 'key' => $key, + 'value' => isset_or($current[$key], null), + 'english' => $value + ]; + } + } + + if (file_exists($dir.'/lang/admin/en_english.ini')) { + $tmp = parse_ini_file($dir.'/lang/admin/en_english.ini'); + + if (file_exists($dir.'/lang/admin/'.$lang.'.ini')) { + $current = parse_ini_file($dir.'/lang/admin/'.$lang.'.ini'); + } else { + $current = []; + } + + foreach ($tmp as $key => $value) { + $modules[basename($dir)]['admin'][] = [ + 'key' => $key, + 'value' => isset_or($current[$key], null), + 'english' => $value + ]; + } + } + } + + return $modules; + } + + private function rglob($pattern, $flags = 0) + { + $files = glob($pattern, $flags); + foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) { + $files = array_merge($files, $this->rglob($dir.'/'.basename($pattern), $flags)); + } + return $files; + } + + private function _directorySize($path) + { + $bytestotal = 0; + $path = realpath($path); + if ($path!==false) { + foreach (new RecursiveIteratorIterator(new RecursiveDotFilterIterator(new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS))) as $object) { + try { + $bytestotal += $object->getSize(); + } catch (\Exception $e) { + } + } + } + + return $bytestotal; + } + + private function roundSize($bytes) + { + if ($bytes/1024 < 1) { + return $bytes.' B'; + } + if ($bytes/1024/1024 < 1) { + return round($bytes/1024).' KB'; + } + if ($bytes/1024/1024/1024 < 1) { + return round($bytes/1024/1024, 2).' MB'; + } else { + return round($bytes/1024/1024/1024, 2).' GB'; + } + } +} diff --git a/inc/modules/settings/Info.php b/inc/modules/settings/Info.php new file mode 100644 index 0000000..0aa90d7 --- /dev/null +++ b/inc/modules/settings/Info.php @@ -0,0 +1,47 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +return [ + 'name' => $core->lang['settings']['module_name'], + 'description' => $core->lang['settings']['module_desc'], + 'author' => 'Sruu.pl', + 'version' => '1.2', + 'compatibility' => '1.3.*', + 'icon' => 'wrench', + + 'install' => function () use ($core) { + $core->db()->pdo()->exec("CREATE TABLE IF NOT EXISTS `settings` ( + `id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, + `module` text NOT NULL, + `field` text NOT NULL, + `value` text + )"); + + $core->db()->pdo()->exec("INSERT INTO `settings` (`module`, `field`, `value`) VALUES ('settings', 'title', 'Batflat')"); + $core->db()->pdo()->exec("INSERT INTO `settings` (`module`, `field`, `value`) VALUES ('settings', 'description', 'Gotham’s time has come.')"); + $core->db()->pdo()->exec("INSERT INTO `settings` (`module`, `field`, `value`) VALUES ('settings', 'keywords', 'key, words')"); + $core->db()->pdo()->exec("INSERT INTO `settings` (`module`, `field`, `value`) VALUES ('settings', 'footer', 'Copyright {?=date(\"Y\")?} © by Company Name. All rights reserved.')"); + $core->db()->pdo()->exec("INSERT INTO `settings` (`module`, `field`, `value`) VALUES ('settings', 'homepage', 'blog')"); + $core->db()->pdo()->exec("INSERT INTO `settings` (`module`, `field`, `value`) VALUES ('settings', 'timezone', '".date_default_timezone_get()."')"); + $core->db()->pdo()->exec("INSERT INTO `settings` (`module`, `field`, `value`) VALUES ('settings', 'theme', 'batblog')"); + $core->db()->pdo()->exec("INSERT INTO `settings` (`module`, `field`, `value`) VALUES ('settings', 'editor', 'wysiwyg')"); + $core->db()->pdo()->exec("INSERT INTO `settings` (`module`, `field`, `value`) VALUES ('settings', 'lang_site', 'en_english')"); + $core->db()->pdo()->exec("INSERT INTO `settings` (`module`, `field`, `value`) VALUES ('settings', 'lang_admin', 'en_english')"); + $core->db()->pdo()->exec("INSERT INTO `settings` (`module`, `field`, `value`) VALUES ('settings', 'version', '1.3.4')"); + $core->db()->pdo()->exec("INSERT INTO `settings` (`module`, `field`, `value`) VALUES ('settings', 'update_check', '0')"); + $core->db()->pdo()->exec("INSERT INTO `settings` (`module`, `field`, `value`) VALUES ('settings', 'update_changelog', '')"); + $core->db()->pdo()->exec("INSERT INTO `settings` (`module`, `field`, `value`) VALUES ('settings', 'update_version', '0')"); + $core->db()->pdo()->exec("INSERT INTO `settings` (`module`, `field`, `value`) VALUES ('settings', 'license', '')"); + }, + 'uninstall' => function () use ($core) { + $core->db()->pdo()->exec("DROP TABLE `settings`"); + } +]; diff --git a/inc/modules/settings/ReadMe.md b/inc/modules/settings/ReadMe.md new file mode 100644 index 0000000..6a5f99f --- /dev/null +++ b/inc/modules/settings/ReadMe.md @@ -0,0 +1,5 @@ +You can display settings data by using construction below: + +``` +{$settings.field-name} +``` \ No newline at end of file diff --git a/inc/modules/settings/Site.php b/inc/modules/settings/Site.php new file mode 100644 index 0000000..32f5761 --- /dev/null +++ b/inc/modules/settings/Site.php @@ -0,0 +1,30 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Settings; + +use Inc\Core\SiteModule; + +class Site extends SiteModule +{ + public function init() + { + $this->_importSettings(); + } + + private function _importSettings() + { + $tmp = $this->core->settings->all(); + $tmp = array_merge($tmp, $tmp['settings']); + unset($tmp['settings']); + $this->tpl->set('settings', $tmp); + } +} diff --git a/inc/modules/settings/css/admin/settings.css b/inc/modules/settings/css/admin/settings.css new file mode 100644 index 0000000..a75513e --- /dev/null +++ b/inc/modules/settings/css/admin/settings.css @@ -0,0 +1,14 @@ +.theme-preview { + position: relative; + display: block; + overflow: hidden; + padding-bottom: 75%; +} + .theme-preview img { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto; + } \ No newline at end of file diff --git a/inc/modules/settings/inc/RecursiveDotFilterIterator.php b/inc/modules/settings/inc/RecursiveDotFilterIterator.php new file mode 100644 index 0000000..d5f4986 --- /dev/null +++ b/inc/modules/settings/inc/RecursiveDotFilterIterator.php @@ -0,0 +1,10 @@ +current()->getFilename(), 0, 1); + } +} diff --git a/inc/modules/settings/lang/admin/en_english.ini b/inc/modules/settings/lang/admin/en_english.ini new file mode 100644 index 0000000..2a0c5eb --- /dev/null +++ b/inc/modules/settings/lang/admin/en_english.ini @@ -0,0 +1,71 @@ +module_name = "Settings" +module_desc = "Basic settings of Batflat." + +general = "General" +translation = "Translation" +system_info = "System informations" +php_version = "PHP version" +sqlite_version = "SQLite version" +system_size = "Batflat size" +sqlite_size = "Database size" +other = "Other" +updates = "Updates" +title = "Website title" +description = "Description" +keywords = "Keywords" +homepage = "Homepage" +site_lang = "Website language" +admin_lang = "Admin language" +footer = "Footer" +editor = "Editor" +timezone = "Timezone" + +version = "Version" +license = "License" +check_updates = "Check updates" +update_fopen_error = "Unable to update system with current server configuration." +update_curl_error = "Unable to update system with current server configuration." + +author = "Author" +active = "Active" +activate = "Activate" +theme_changed = "Default template was changed." + +update_info = "Thanks to Batflat updates system is always safe and up-to-date!" +update_available = "New version is available" +update_button = "Update" +update_check = "Check update" +up_to_date = "Great! Batflat is up-to-date :)" +update_newer_version = "It's weird but... you have newer version than we do :D" + +save_settings_success = "Settings successfully saved." +save_settings_failure = "Failed to save settings." +save_file_success = "File successfully saved." +save_file_failure = "Unable to save file." + +file_lock = "File editing is disabled. You cannot edit files through admin panel." + +notranslation = "No translation" +new_lang = "New language" +new_lang_failure = "Language name is incorrect. It should match pattern: en_english." +new_lang_exists = "Language already exists." +new_lang_success = "Language successfully created." +new_lang_create_fail = "Unable to create language." +lang_export = "Export language" +lang_import = "Import language" +lang_import_success = "Successfully uploaded language." +lang_import_error = "Package contains one or more illegal files." +lang_upload = "Choose ZIP file containing language pack." + +license_key = "License key" +license_invalid_key = "Invalid license key." +license_good_key = "License key has been successfully accepted." +confirm_downgrade = "Are you sure you want downgrade Batflat to Free license?" +downgrade = "Downgrade to Free" +upgrade = "Upgrade to Commercial" +free = "Free" +commercial = "Commercial" +invalid_license = "Invalid license" + +get_commercial = "If you want to create a website for your client, choose a commercial license." +activate_info = "To activate the commercial version of the system, enter the license code. You will find it in the email confirming the purchase of the Batflat license." \ No newline at end of file diff --git a/inc/modules/settings/lang/admin/pl_polski.ini b/inc/modules/settings/lang/admin/pl_polski.ini new file mode 100644 index 0000000..6a5035d --- /dev/null +++ b/inc/modules/settings/lang/admin/pl_polski.ini @@ -0,0 +1,71 @@ +module_name = "Ustawienia" +module_desc = "Podstawowe ustawienia Batflata." + +general = "Ogólne" +translation = "Tłumaczenie" +system_info = "Informacje o systemie" +php_version = "Wersja PHP" +sqlite_version = "Wersja SQLite" +system_size = "Rozmiar strony" +sqlite_size = "Rozmiar bazy danych" +other = "Pozostałe" +updates = "Aktualizacje" +title = "Tytuł strony" +description = "Opis" +keywords = "Słowa kluczowe" +homepage = "Strona startowa" +site_lang = "Język strony" +admin_lang = "Język administracji" +footer = "Stopka" +editor = "Edytor" +timezone = "Strefa czasowa" + +version = "Wersja" +license = "Licencja" +check_updates = "Sprawdź aktualizacje" +update_fopen_error = "Nie można zaktualizować systemu, ponieważ konfiguracja serwera na to nie pozwala." +update_curl_error = "Nie można zaktualizować systemu, ponieważ konfiguracja serwera na to nie pozwala." + +author = "Autor" +active = "Aktywny" +activate = "Aktywuj" +theme_changed = "Domyślny szablon został zmieniony." + +update_info = "Dzięki aktualizacjom Batflat jest bezpieczny i aktualny. Pamiętaj aby regularnie je sprawdzać!" +update_available = "Dostępna jest nowa wersja systemu" +update_button = "Aktualizuj" +update_check = "Sprawdź aktualizacje" +up_to_date = "Super! Batflat jest aktualny :)" +update_newer_version = "To bardzo dziwne... bo masz nowszą wersję niż my :D" + +save_settings_success = "Pomyślnie zapisano ustawienia." +save_settings_failure = "Nie udało się zapisać wszystkich ustawień." +save_file_success = "Pomyślnie zaktualizowano plik." +save_file_failure = "Nie udało się zaktualizować pliku." + +file_lock = "Edytowanie zostało wyłączone. Nie można wprowadzać zmian w plikach poprzez panel administracyjny." + +notranslation = "Brak tłumaczenia" +new_lang = "Nowy język" +new_lang_failure = "Nazwa języka jest niepoprawna. Nazewnictwo powinno pasować do wzoru: en_english." +new_lang_exists = "Wybrany język już isteniej." +new_lang_success = "Język został pomyślnie utworzony." +new_lang_create_fail = "Nie można utworzyć nowego języka." +lang_export = "Exportuj język" +lang_import = "Importuj język" +lang_import_success = "Pomyślnie wgrano język." +lang_import_error = "Paczka zawiera jeden lub więcej niedozwolonych plików." +lang_upload = "Wybierz z dysku plik ZIP zawierający pliki językowe." + +license_key = "Klucz licencyjny" +license_invalid_key = "Niepoprawny klucz licencyjny" +license_good_key = "Wersja komercyjna została pomyślnie aktywowana." +confirm_downgrade = "Czy na pewno chcesz wrócić do wersji darmowej?" +downgrade = "Przejdź do wersji darmowej" +upgrade = "Zaktualizuj do wersji komercyjnej" +free = "Darmowa" +commercial = "Komercyjna" +invalid_license = "Niepoprawna licencja" + +get_commercial = "Jeżeli chcesz stworzyć stronę internetową dla swojego klienta, wybierz licencję do użytku komercyjnego." +activate_info = "Aby aktywować wersję komercyjną należy podać klucz licencyjny. Znajdziesz go we wiadomości e-mail potwierdzającej zakup licencji." \ No newline at end of file diff --git a/inc/modules/settings/view/admin/general.html b/inc/modules/settings/view/admin/general.html new file mode 100644 index 0000000..126d224 --- /dev/null +++ b/inc/modules/settings/view/admin/general.html @@ -0,0 +1,170 @@ +
    +
    +
    +
    +
    +

    {$lang.settings.general}

    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    +
    +
    +

    {$lang.settings.other}

    +
    +
    +
    + + + {if: isset($settings.autodetectlang)} + + {/if} +
    +
    + + +
    +
    + + +
    +
    + +
    + + +
    +
    + +
    +
    +
    + + + +
    +
    +
    +

    {$lang.settings.license}

    +
    +
    +
    + {if: $settings.license.type == \Inc\Core\Lib\License::ERROR} + + + + + {else} + + + + + {/if} +

    +

    + {$settings.license.name} +

    + {?=domain()?} +

    + {if: $settings.license.type == \Inc\Core\Lib\License::FREE} + {$lang.settings.upgrade} + {else} + {$lang.settings.downgrade} + {/if} +
    +
    +
    +
    +
    +

    {$lang.settings.system_info}

    +
    +
    +
    +
    {$lang.settings.version}
    +
    {$settings.version} ({$lang.settings.check_updates})
    +
    {$lang.settings.license}
    +
    + {$settings.license.name} + ({$lang.general.edit}) +
    +
    +
    +
    +
    {$lang.settings.php_version}
    +
    {$settings.system.php}
    +
    {$lang.settings.sqlite_version}
    +
    {$settings.system.sqlite}
    +
    +
    +
    +
    {$lang.settings.system_size}
    +
    {$settings.system.system_size}
    +
    {$lang.settings.sqlite_size}
    +
    {$settings.system.sqlite_size}
    +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/inc/modules/settings/view/admin/theme.html b/inc/modules/settings/view/admin/theme.html new file mode 100644 index 0000000..9482584 --- /dev/null +++ b/inc/modules/settings/view/admin/theme.html @@ -0,0 +1,61 @@ +
    +
    +
    +
    +

    {$theme.name}

    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +

    {$lang.general.edit}

    +
    +
    + {if: FILE_LOCK} +
    {$lang.settings.file_lock}FILE_LOCK
    + {/if} +
    +
    + + +
    +
    + +
    +
    + + +
    + +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/inc/modules/settings/view/admin/themes.html b/inc/modules/settings/view/admin/themes.html new file mode 100644 index 0000000..1db40ef --- /dev/null +++ b/inc/modules/settings/view/admin/themes.html @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/inc/modules/settings/view/admin/translation.html b/inc/modules/settings/view/admin/translation.html new file mode 100644 index 0000000..e9dbd21 --- /dev/null +++ b/inc/modules/settings/view/admin/translation.html @@ -0,0 +1,107 @@ +{if: FILE_LOCK} +
    {$lang.settings.file_lock}FILE_LOCK
    +{/if} +
    +
    +
    +
    +

    {$lang.settings.translation}

    + +
    +
    +
    + + + +
    +
    +
    + + + {loop: $translation as $type => $row} + + + + {loop: $row} + + + + + {/loop} + {/loop} + +
    +

    {?= (is_numeric($module) ? 'General' : ucfirst($module)) ?} {?=($type == 'admin') ? 'Admin' : 'Front' ?}

    +
    {$value.key} +
    {$value.english|e}
    + +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +

    {$lang.settings.new_lang}

    +
    +
    +
    +
    + + +
    + +
    +
    +
    + +
    +
    +

    {$lang.settings.lang_import}

    +
    +
    +
    +
    + {$lang.settings.lang_upload} + info +
    +
    + + +
    + +
    +
    +
    + +
    +
    +

    {$lang.settings.lang_export}

    +
    +
    + + {loop: $settings.langs} + + + + + {/loop} +
    {$value.name} + +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/inc/modules/settings/view/admin/update.html b/inc/modules/settings/view/admin/update.html new file mode 100644 index 0000000..2d416ff --- /dev/null +++ b/inc/modules/settings/view/admin/update.html @@ -0,0 +1,65 @@ +
    +
    +
    +
    {$lang.settings.updates}
    +
    + {if: $allow_curl==0} +
    + {$lang.settings.update_curl_error} (curl does not exist) + Check Error +
    + {/if} + {if: isset($error)} +
    + {$error} + System Error +
    + {/if} + {if: isset($request)} +
    + {?=print_r($request,true)?} + Debug +
    + {/if} +

    {$lang.settings.update_info}

    +

    {$lang.settings.version}: {$settings.version}

    + {if: $manual_mode} +
    + Manual mode enabled. Pleasy copy latest version of batflat to base directory. System will detect it automatically. + Info +
    + {if: $manual_mode['version']} +

    Detected package with version: {$manual_mode['version']}

    +
    + +
    + {else} +

    /batflat-*.zip does not detected.

    + + {/if} + {elseif: cmpver($settings['update_version'], $settings['version']) === 1} +

    {$lang.settings.update_available} ({$settings['update_version']})

    +
    + +
    +

    Changelog:

    +
    {$settings['update_changelog']}
    + {elseif: isset($update_version) && cmpver($update_version, $settings['version']) === 0} +

    {$lang.settings.up_to_date} ({$settings['update_version']})

    +
    + +
    + {elseif: isset($update_version) && cmpver($update_version, $settings['version']) === -1} +

    {$lang.settings.update_newer_version} ({$settings['update_version']})

    +
    + +
    + {else} +
    + +
    + {/if} +
    +
    +
    +
    \ No newline at end of file diff --git a/inc/modules/sitemap/Info.php b/inc/modules/sitemap/Info.php new file mode 100644 index 0000000..1ed6a2f --- /dev/null +++ b/inc/modules/sitemap/Info.php @@ -0,0 +1,28 @@ + + * @author Wojciech Król + * @copyright 2017 Paweł Klockiewicz, Wojciech Król + * @license https://batflat.org/license + * @link https://batflat.org + */ + + return [ + 'name' => $core->lang['sitemap']['module_name'], + 'description' => $core->lang['sitemap']['module_desc'], + 'author' => 'Sruu.pl', + 'version' => '1.0', + 'compatibility' => '1.3.*', + 'icon' => 'sitemap', + + 'install' => function() use($core) + { + + }, + 'uninstall' => function() use($core) + { + + } + ]; \ No newline at end of file diff --git a/inc/modules/sitemap/ReadMe.md b/inc/modules/sitemap/ReadMe.md new file mode 100644 index 0000000..7141561 --- /dev/null +++ b/inc/modules/sitemap/ReadMe.md @@ -0,0 +1,4 @@ +Sitemap is available at `example.com/sitemap.xml`. There are two ways to make your sitemap available to search engines: + ++ Submit it to Google using the [Search Console Sitemaps tool](https://www.google.com/webmasters/tools/sitemap-list) ++ Insert special line anywhere in your `robots.txt` file, e.g. `Sitemap: http://example.com/sitemap.xml` \ No newline at end of file diff --git a/inc/modules/sitemap/Site.php b/inc/modules/sitemap/Site.php new file mode 100644 index 0000000..44eb113 --- /dev/null +++ b/inc/modules/sitemap/Site.php @@ -0,0 +1,87 @@ + + * @author Wojciech Król + * @copyright 2017 Paweł Klockiewicz, Wojciech Król + * @license https://batflat.org/license + * @link https://batflat.org + */ + + namespace Inc\Modules\Sitemap; + + use Inc\Core\SiteModule; + + class Site extends SiteModule + { + public function routes() + { + $this->route('sitemap.xml', function() { + $this->setTemplate(false); + header('Content-type: application/xml'); + + $urls = []; + + // Home Page + $urls[] = [ + 'url' => url(), + 'lastmod' => null, + ]; + + // Pages + $pages = $this->db('pages')->asc('lang')->asc('id')->toArray(); + $lang = $this->settings('settings.lang_site'); + $homepage = $this->settings('settings.homepage'); + foreach($pages as $page) + { + $page['date'] = strtotime($page['date']); + + $shortLang = strstr($page['lang'], '_', true); + if(strpos($page['slug'], '404') !== FALSE) + continue; + + if($lang == $page['lang'] && $homepage == $page['slug']) + $urls[0]['lastmod'] = date('c', $page['date']); + else if($homepage == $page['slug']) + $urls[] = ['url' => url($shortLang), 'lastmod' => date('c', $page['date'])]; + else if($lang == $page['lang']) + $urls[] = ['url' => url($page['slug']), 'lastmod' => date('c', $page['date'])]; + else + $urls[] = ['url' => url([$shortLang, $page['slug']]), 'lastmod' => date('c', $page['date'])]; + } + + // Blog + $posts = $this->db('blog')->where('status', 2)->desc('published_at')->toArray(); + $tags = $this->db('blog_tags_relationship')->leftJoin('blog_tags', 'blog_tags.id = blog_tags_relationship.tag_id')->leftJoin('blog', 'blog.id', 'blog_tags_relationship.blog_id')->where('blog.status', 2)->group('blog_tags.slug')->select(['slug' => 'blog_tags.slug'])->toArray(); + if($homepage != 'blog') + { + $urls[] = [ + 'url' => url('blog'), + 'lastmod' => date('c', $posts[0]['published_at']) + ]; + } + else + { + $urls[0]['lastmod'] = date('c', $posts[0]['published_at']); + } + foreach($posts as $post) + { + $post['published_at'] = $post['published_at']; + $urls[] = [ + 'url' => url(['blog','post',$post['slug']]), + 'lastmod' => date('c', $post['published_at']), + ]; + } + foreach($tags as $tag) + { + $urls[] = [ + 'url' => url(['blog', 'tag', $tag['slug']]), + 'lastmod' => null, + ]; + } + + echo $this->draw('sitemap.xml', compact('urls')); + }); + } + } \ No newline at end of file diff --git a/inc/modules/sitemap/lang/admin/en_english.ini b/inc/modules/sitemap/lang/admin/en_english.ini new file mode 100644 index 0000000..842b66b --- /dev/null +++ b/inc/modules/sitemap/lang/admin/en_english.ini @@ -0,0 +1,2 @@ +module_name = "Sitemap generator" +module_desc = "Sitemap.xml generator for Batflat." \ No newline at end of file diff --git a/inc/modules/sitemap/lang/admin/pl_polski.ini b/inc/modules/sitemap/lang/admin/pl_polski.ini new file mode 100644 index 0000000..4f083f4 --- /dev/null +++ b/inc/modules/sitemap/lang/admin/pl_polski.ini @@ -0,0 +1,2 @@ +module_name = "Generator mapy strony" +module_desc = "Generuje plik stiemap.xml dla strony opartej o Batflat." \ No newline at end of file diff --git a/inc/modules/sitemap/view/sitemap.xml b/inc/modules/sitemap/view/sitemap.xml new file mode 100644 index 0000000..57858de --- /dev/null +++ b/inc/modules/sitemap/view/sitemap.xml @@ -0,0 +1,14 @@ + + +{loop: $urls} + + {$value.url} + {if: !empty($value.lastmod)}{$value.lastmod}{/if} + + +{/loop} + \ No newline at end of file diff --git a/inc/modules/snippets/Admin.php b/inc/modules/snippets/Admin.php new file mode 100644 index 0000000..db3666f --- /dev/null +++ b/inc/modules/snippets/Admin.php @@ -0,0 +1,190 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Snippets; + +use Inc\Core\AdminModule; + +class Admin extends AdminModule +{ + public function navigation() + { + return [ + $this->lang('manage', 'general') => 'manage', + $this->lang('add') => 'add', + ]; + } + + /** + * list of snippets + */ + public function getManage() + { + $rows = $this->db('snippets')->toArray(); + if (count($rows)) { + foreach ($rows as &$row) { + $row['tag'] = $this->tpl->noParse('{$snippet.'.$row['slug'].'}'); + $row['editURL'] = url([ADMIN, 'snippets', 'edit', $row['id']]); + $row['delURL'] = url([ADMIN, 'snippets', 'delete', $row['id']]); + } + } + + return $this->draw('manage.html', ['snippets' => $rows]); + } + + /** + * add new snippet + */ + public function getAdd() + { + return $this->getEdit(); + } + + /** + * edit snippet + */ + public function getEdit($id = null) + { + $this->_add2header(); + + if (!empty($redirectData = getRedirectData())) { + $assign = $redirectData; + } + + if ($id === null) { + $row = ['name' => isset_or($assign['name'], null), 'content' => isset_or($assign['content'], null)]; + + $assign['title'] = $this->lang('add'); + } elseif (!empty($row = $this->db('snippets')->oneArray($id))) { + $assign['title'] = $this->lang('edit'); + } else { + redirect(url([ADMIN, 'snippets', 'manage'])); + } + + $assign = array_merge($assign, htmlspecialchars_array($row)); + $assign['languages'] = $this->_getLanguages($this->settings('settings', 'lang_site')); + + $assign['content'] = []; + preg_match_all("/{lang: ([a-z]{2}_[a-z]+)}(.*?){\/lang}/ms", $row['content'], $matches); + foreach ($matches[1] as $key => $value) { + $assign['content'][trim($value)] = $this->tpl->noParse(trim($matches[2][$key])); + } + + $assign['editor'] = $this->settings('settings', 'editor'); + + return $this->draw('form.html', ['snippets' => $assign]); + } + + /** + * remove snippet + */ + public function getDelete($id) + { + if ($this->db('snippets')->delete($id)) { + $this->notify('success', $this->lang('delete_success')); + } else { + $this->notify('failure', $this->lang('delete_failure')); + } + + redirect(url([ADMIN, 'snippets', 'manage'])); + } + + /** + * save snippet + */ + public function postSave($id = null) + { + unset($_POST['save']); + + if (checkEmptyFields(['name'], $_POST)) { + $this->notify('failure', $this->lang('empty_inputs', 'general')); + + if (!$id) { + redirect(url([ADMIN, 'snippets', 'add'])); + } else { + redirect(url([ADMIN, 'snippets', 'edit', $id])); + } + } + + $_POST['name'] = trim($_POST['name']); + $_POST['slug'] = createSlug($_POST['name']); + + $tmp = null; + foreach ($_POST['content'] as $lang => $content) { + $tmp .= "{lang: $lang}".$content."{/lang}"; + } + + $_POST['content'] = $tmp; + + if ($id === null) { // new + $location = url([ADMIN, 'snippets', 'add']); + if (!$this->db('snippets')->where('slug', $_POST['slug'])->count()) { + if ($this->db('snippets')->save($_POST)) { + $location = url([ADMIN, 'snippets', 'edit', $this->db()->lastInsertId()]); + $this->notify('success', $this->lang('save_success')); + } else { + $this->notify('failure', $this->lang('save_failure')); + } + } else { + $this->notify('failure', $this->lang('already_exists')); + } + } else { // edit + if (!$this->db('snippets')->where('slug', $_POST['slug'])->where('id', '<>', $id)->count()) { + if ($this->db('snippets')->where($id)->save($_POST)) { + $this->notify('success', $this->lang('save_success')); + } else { + $this->notify('failure', $this->lang('save_failure')); + } + } else { + $this->notify('failure', $this->lang('already_exists')); + } + + $location = url([ADMIN, 'snippets', 'edit', $id]); + } + + redirect($location, $_POST); + } + + /** + * module JavaScript + */ + public function getJavascript() + { + header('Content-type: text/javascript'); + echo $this->draw(MODULES.'/snippets/js/admin/snippets.js'); + exit(); + } + + private function _add2header() + { + // WYSIWYG + $this->core->addCSS(url('inc/jscripts/wysiwyg/summernote.min.css')); + $this->core->addJS(url('inc/jscripts/wysiwyg/summernote.min.js')); + if ($this->settings('settings', 'lang_admin') != 'en_english') { + $this->core->addJS(url('inc/jscripts/wysiwyg/lang/'.$this->settings('settings', 'lang_admin').'.js')); + } + + // HTML EDITOR + $this->core->addCSS(url('/inc/jscripts/editor/markitup.min.css')); + $this->core->addCSS(url('/inc/jscripts/editor/markitup.highlight.min.css')); + $this->core->addCSS(url('/inc/jscripts/editor/sets/html/set.min.css')); + $this->core->addJS(url('/inc/jscripts/editor/highlight.min.js')); + $this->core->addJS(url('/inc/jscripts/editor/markitup.min.js')); + $this->core->addJS(url('/inc/jscripts/editor/markitup.highlight.min.js')); + $this->core->addJS(url('/inc/jscripts/editor/sets/html/set.min.js')); + + // ARE YOU SURE? + $this->core->addJS(url('inc/jscripts/are-you-sure.min.js')); + + // MODULE SCRIPTS + $this->core->addJS(url([ADMIN, 'snippets', 'javascript'])); + } +} diff --git a/inc/modules/snippets/Info.php b/inc/modules/snippets/Info.php new file mode 100644 index 0000000..430a4c0 --- /dev/null +++ b/inc/modules/snippets/Info.php @@ -0,0 +1,31 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +return [ + 'name' => $core->lang['snippets']['module_name'], + 'description' => $core->lang['snippets']['module_desc'], + 'author' => 'Sruu.pl', + 'version' => '1.1', + 'compatibility' => '1.3.*', + 'icon' => 'puzzle-piece', + + 'install' => function () use ($core) { + $core->db()->pdo()->exec("CREATE TABLE IF NOT EXISTS `snippets` ( + `id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, + `name` text NOT NULL, + `slug` text NOT NULL, + `content` text NOT NULL + )"); + }, + 'uninstall' => function () use ($core) { + $core->db()->pdo()->exec("DROP TABLE `snippets`"); + } +]; diff --git a/inc/modules/snippets/Site.php b/inc/modules/snippets/Site.php new file mode 100644 index 0000000..324bd10 --- /dev/null +++ b/inc/modules/snippets/Site.php @@ -0,0 +1,34 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Snippets; + +use Inc\Core\SiteModule; + +class Site extends SiteModule +{ + public function init() + { + $this->_importSnippets(); + } + + private function _importSnippets() + { + $rows = $this->db('snippets')->toArray(); + + $snippets = []; + foreach ($rows as $row) { + $snippets[$row['slug']] = $row['content']; + } + + return $this->tpl->set('snippet', $snippets); + } +} diff --git a/inc/modules/snippets/js/admin/snippets.js b/inc/modules/snippets/js/admin/snippets.js new file mode 100644 index 0000000..f73867b --- /dev/null +++ b/inc/modules/snippets/js/admin/snippets.js @@ -0,0 +1,109 @@ +function insertEditor(type) { + var editor = $('.editor'); + + if (type == 'wysiwyg') { + if ($('.markItUp').length) { + editor.markItUpRemove(); + } + + editor.summernote({ + lang: '{$lang.name}', + height: 270, + callbacks: { + onInit: function() { + $('.note-codable').keyup(function() { + editor.val($(this).val()); + }); + }, + onImageUpload: function(files) { + sendFile(files[0], this); + }, + onChange: function() { + editor.parents('form').trigger('checkform.areYouSure'); + } + } + }); + + $('.note-group-select-from-files').remove(); + } else { + if ($('.note-editor').length) { + editor.each(function() { + var isEmpty = $(this).summernote('isEmpty'); + $(this).summernote('destroy'); + if (isEmpty) + $(this).html(''); + }); + } + + editor.each(function() { + $(this).markItUp(markItUp_html).highlight(); + }); + } +} + +function sendFile(file, editor) { + var formData = new FormData(); + formData.append('file', file); + + var fileData = URL.createObjectURL(file); + $(editor).summernote('insertImage', fileData, function($image) { + $.ajax({ + xhr: function() { + var xhr = new window.XMLHttpRequest(); + + $('input[type="submit"]').prop('disabled', true); + var progress = $('.progress:first').clone(); + progress = (progress.fadeIn()).appendTo($('.progress-wrapper')); + + xhr.upload.addEventListener("progress", function(evt) { + if (evt.lengthComputable) { + var percentComplete = evt.loaded / evt.total; + percentComplete = parseInt(percentComplete * 100); + progress.children().css('width', percentComplete + '%'); + + if (percentComplete === 100) { + progress.fadeOut(); + progress.remove(); + $('input[type="submit"]').prop('disabled', false); + } + } + }, false); + + return xhr; + }, + url: '{?=url([ADMIN, "blog", "editorUpload"])?}', + data: formData, + type: 'POST', + cache: false, + contentType: false, + processData: false, + dataType: 'json', + success: function(data) { + if (data.status == 'success') { + $image.remove(); + $(editor).summernote('insertImage', data.result); + } else if (data.status == 'failure') { + $image.remove(); + bootbox.alert(data.result); + } + } + }); + }); +} + +function select_editor() { + if ($('.editor').data('editor') == 'wysiwyg') { + insertEditor('wysiwyg'); + } else + insertEditor('html'); +} + +$(document).ready(function() { + select_editor(); + + $("#toggle-form label").click(function() { + $("#toggle-form .textarea").slideToggle("slow"); + }); + + $('form').areYouSure({ 'message': '{$lang.general.unsaved_warning}' }); +}); \ No newline at end of file diff --git a/inc/modules/snippets/lang/admin/en_english.ini b/inc/modules/snippets/lang/admin/en_english.ini new file mode 100644 index 0000000..8988b64 --- /dev/null +++ b/inc/modules/snippets/lang/admin/en_english.ini @@ -0,0 +1,12 @@ +module_name = "Snippets" +module_desc = "Displays your own pieces of code on the page." + +add = "Add" +edit = "Edit snippet" +save_success = "Snippet successfully saved." +save_failure = "Failed to save snippet." +content = "Content" +delete_success = "Snippet successfully deleted." +delete_failure = "Unable to delete snippet." +delete_confirm = "Are you sure you want to delete selected snippet?" +already_exists = "Snippet with that name already exists." \ No newline at end of file diff --git a/inc/modules/snippets/lang/admin/pl_polski.ini b/inc/modules/snippets/lang/admin/pl_polski.ini new file mode 100644 index 0000000..fc7cc06 --- /dev/null +++ b/inc/modules/snippets/lang/admin/pl_polski.ini @@ -0,0 +1,12 @@ +module_name = "Skrawki" +module_desc = "Wyświetla własne skrawki kodu na stronie." + +add = "Dodaj nowy" +edit = "Edycja skrawka" +save_success = "Pomyślnie zapisano skrawek." +save_failure = "Nie udało się zapisać skrawka." +content = "Zawartość" +delete_success = "Pomyślnie usunięto skrawek." +delete_failure = "Nie udało się usunąć skrawka." +delete_confirm = "Czy na pewno chcesz usunąć wybrany skrawek?" +already_exists = "Skrawek o takiej nazwie już istnieje." \ No newline at end of file diff --git a/inc/modules/snippets/view/admin/form.html b/inc/modules/snippets/view/admin/form.html new file mode 100644 index 0000000..2cd3f7b --- /dev/null +++ b/inc/modules/snippets/view/admin/form.html @@ -0,0 +1,33 @@ +
    +
    +
    +
    +

    {$snippets.title}

    + +
    +
    +
    +
    + + +
    +
    + +
    + {loop: $snippets.languages} +
    + +
    + {/loop} +
    +
    + +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/inc/modules/snippets/view/admin/manage.html b/inc/modules/snippets/view/admin/manage.html new file mode 100644 index 0000000..34933c2 --- /dev/null +++ b/inc/modules/snippets/view/admin/manage.html @@ -0,0 +1,42 @@ + \ No newline at end of file diff --git a/inc/modules/statistics/Admin.php b/inc/modules/statistics/Admin.php new file mode 100644 index 0000000..4beadcf --- /dev/null +++ b/inc/modules/statistics/Admin.php @@ -0,0 +1,159 @@ + + * @author Wojciech Król + * @copyright 2017 Paweł Klockiewicz, Wojciech Król + * @license https://batflat.org/license + * @link https://batflat.org + */ + +namespace Inc\Modules\Statistics; + +use Inc\Core\AdminModule; +use Inc\Modules\Statistics\Src\Chart; +use Inc\Modules\Statistics\Src\Statistics; + +class Admin extends AdminModule +{ + /** + * @var string + */ + protected $moduleDirectory = null; + + /** + * @var Chart + */ + protected $chart = null; + + /** + * @var Statistics + */ + protected $statistics = null; + + public function init() + { + $this->statistics = new Statistics(); + $this->chart = new Chart(); + + $this->moduleDirectory = MODULES.'/statistics'; + $this->core->addCSS(url($this->moduleDirectory.'/assets/css/style.css?v={$bat.version}')); + $this->core->addJS(url($this->moduleDirectory.'/assets/js/Chart.bundle.min.js')); + $this->core->addJS(url($this->moduleDirectory.'/assets/js/app.js?v={$bat.version}')); + } + + public function navigation() + { + return [ + 'Main' => 'main', + ]; + } + + public function getMain() + { + $statistics = $this->statistics; + $chart = $this->chart; + + // Numbers + $visitors['unique'] = $statistics->countUniqueVisits(); + $visitors['online'] = $statistics->countCurrentOnline(); + $visitors['visits']['today'] = $statistics->countAllVisits("TODAY"); + $visitors['visits']['yesterday'] = $statistics->countUniqueVisits("YESTERDAY"); + $visitors['visits']['7days'] = $statistics->countUniqueVisits("-7 days", 7); + $visitors['visits']['30days'] = $statistics->countUniqueVisits("-30 days", 30); + $visitors['visits']['all'] = $statistics->countUniqueVisits("ALL"); + + // Charts + $visitors['chart'] = $chart->getVisitors(14); + $visitors['platform'] = $chart->getOperatingSystems(); + $visitors['countries'] = $chart->getCountries(); + $visitors['browsers'] = $chart->getBrowsers(); + + // Lists + $visitors['pages'] = $statistics->getPages(); + $visitors['referrers'] = $statistics->getReferrers(); + + return $this->draw('dashboard.html', [ + 'visitors' => $visitors + ]); + } + + public function getUrl($urlBase64) + { + $statistics = $this->statistics; + $chart = $this->chart; + + $url = base64_decode($urlBase64); + + // Numbers + $visitors['unique'] = $statistics->countUniqueVisits('ALL', 1, $url); + $visitors['all'] = $statistics->countAllVisits('ALL', 1, $url); + + // Charts + $visitors['chart'] = $chart->getVisitors(14, 0, $url); + $visitors['platform'] = $chart->getOperatingSystems($url); + $visitors['countries'] = $chart->getCountries($url); + $visitors['browsers'] = $chart->getBrowsers($url); + + // Lists + $visitors['referrers'] = $statistics->getReferrers($url, false); + + return $this->draw('url.html', [ + 'url' => $url, + 'visitors' => $visitors + ]); + } + + public function getreferrer($urlBase64) + { + $statistics = $this->statistics; + $chart = $this->chart; + + $url = base64_decode($urlBase64); + + // Numbers + $visitors['unique'] = $statistics->countUniqueVisits('ALL', 1, null, $url); + $visitors['all'] = $statistics->countAllVisits('ALL', 1, null, $url); + + // Charts + $visitors['chart'] = $chart->getVisitors(14, 0, null, $url); + $visitors['platform'] = $chart->getOperatingSystems(null, $url); + $visitors['countries'] = $chart->getCountries(null, $url); + $visitors['browsers'] = $chart->getBrowsers(null, $url); + + // Lists + $visitors['referrers'] = $chart->getReferrers($url, false); + + return $this->draw('referrer.html', [ + 'url' => $url, + 'visitors' => $visitors + ]); + } + + public function getPages() + { + $statistics = $this->statistics; + $chart = $this->chart; + + $pages['chart'] = $chart->getPages(); + $pages['list'] = $statistics->getPages(null, false); + + return $this->draw('pages.html', [ + 'pages' => $pages + ]); + } + + public function getReferrers() + { + $statistics = $this->statistics; + $chart = $this->chart; + + $referrer['chart'] = $chart->getReferrers(); + $referrer['list'] = $statistics->getReferrers(null, false); + + return $this->draw('referrers.html', [ + 'referrers' => $referrer + ]); + } +} diff --git a/inc/modules/statistics/DB.php b/inc/modules/statistics/DB.php new file mode 100644 index 0000000..3c61f46 --- /dev/null +++ b/inc/modules/statistics/DB.php @@ -0,0 +1,16 @@ + + * @author Wojciech Król + * @copyright 2017 Paweł Klockiewicz, Wojciech Król + * @license https://batflat.org/license + * @link https://batflat.org + */ + +use Inc\Modules\Statistics\DB; + +return [ + 'name' => $core->lang['statistics']['module_name'], + 'description' => $core->lang['statistics']['module_desc'], + 'author' => 'Sruu.pl', + 'version' => '1.0', + 'compatibility' => '1.3.*', + 'icon' => 'pie-chart', + + 'install' => function () use ($core) { + if (file_exists(BASE_DIR.'/inc/data/statistics.sdb')) { + return; + } + + DB::pdo()->exec("CREATE TABLE IF NOT EXISTS `statistics` ( + `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + `ip` TEXT NOT NULL, + `useragent` TEXT, + `uniqhash` TEXT, + `browser` TEXT, + `country` TEXT, + `platform` TEXT, + `url` TEXT, + `referrer` TEXT, + `status_code` INTEGER NOT NULL, + `bot` INTEGER NOT NULL, + `created_at` INTEGER NOT NULL + );"); + + DB::pdo()->exec("CREATE INDEX statistics_idx1 ON statistics(ip,useragent,country,platform,url,referrer,status_code,bot)"); + }, + 'uninstall' => function () use ($core) { + unlink(BASE_DIR.'/inc/data/statistics.sdb'); + }, +]; diff --git a/inc/modules/statistics/Site.php b/inc/modules/statistics/Site.php new file mode 100644 index 0000000..5933d82 --- /dev/null +++ b/inc/modules/statistics/Site.php @@ -0,0 +1,80 @@ + + * @author Wojciech Król + * @copyright 2017 Paweł Klockiewicz, Wojciech Król + * @license https://batflat.org/license + * @link https://batflat.org + */ + +namespace Inc\Modules\Statistics; + +use Inc\Core\SiteModule; + +use Inc\Modules\Statistics\DB; +use Inc\Modules\Statistics\PHPBrowserDetector\Browser; +use Inc\Modules\Statistics\PHPBrowserDetector\Os; +use Inc\Core\Lib\HttpRequest; + +class Site extends SiteModule +{ + public function init() + { + // Browser + $browser = new Browser; + + // OS + $os = new Os; + + // IP and GEO + $ip = $_SERVER['REMOTE_ADDR']; + + // Get latest country or fetch new + $country = 'Unknown'; + $latest = $this->db('statistics')->where('ip', $ip)->desc('created_at')->limit(1)->oneArray(); + if (!$latest) { + $details = json_decode(HttpRequest::get('http://freegeoip.net/json/'.$ip), true); + if (!empty($details['country_code'])) { + $country = $details['country_code']; + } + } else { + $country = $latest['country']; + } + + // referrer + $referrer = isset_or($_SERVER['HTTP_referrer'], false); + if ($referrer && $url = parse_url($referrer)) { + if (strpos($url['host'], domain(false)) !== false) { + $referrer = null; + } + } else { + $referrer = null; + } + + // Add visitor record + $this->db('statistics')->save([ + 'ip' => $ip, + 'browser' => $browser->getName(), + 'useragent' => $browser->getUserAgent()->getUserAgentString(), + 'uniqhash' => md5($ip.$browser->getUserAgent()->getUserAgentString()), + 'country' => $country, + 'platform' => $os->getName(), + 'url' => '/'.implode('/', parseURL()), + 'referrer' => $referrer, + 'status_code' => http_response_code(), + 'bot' => ($browser->isRobot() ? 1 : 0), + ]); + } + + public function routes() + { + // + } + + protected function db($table = null) + { + return new DB($table); + } +} diff --git a/inc/modules/statistics/assets/css/style.css b/inc/modules/statistics/assets/css/style.css new file mode 100644 index 0000000..e165406 --- /dev/null +++ b/inc/modules/statistics/assets/css/style.css @@ -0,0 +1,14 @@ +.panel-body h1 { + font-weight:bold; + margin:10px 0; + font-size:48px; +} +.panel-body-nopadding { + padding:0; +} +.panel-body-nopadding table tr th { + border-top:0; +} +.panel-body-nopadding table tr td, .panel-body-nopadding table tr th { + padding-left:15px; +} \ No newline at end of file diff --git a/inc/modules/statistics/assets/js/Chart.bundle.min.js b/inc/modules/statistics/assets/js/Chart.bundle.min.js new file mode 100644 index 0000000..4621b00 --- /dev/null +++ b/inc/modules/statistics/assets/js/Chart.bundle.min.js @@ -0,0 +1,16 @@ +/*! + * Chart.js + * http://chartjs.org/ + * Version: 2.5.0 + * + * Copyright 2017 Nick Downie + * Released under the MIT license + * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md + */ +!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.Chart=t()}}(function(){var t;return function t(e,n,i){function a(o,s){if(!n[o]){if(!e[o]){var l="function"==typeof require&&require;if(!s&&l)return l(o,!0);if(r)return r(o,!0);var u=new Error("Cannot find module '"+o+"'");throw u.code="MODULE_NOT_FOUND",u}var d=n[o]={exports:{}};e[o][0].call(d.exports,function(t){var n=e[o][1][t];return a(n?n:t)},d,d.exports,t,e,n,i)}return n[o].exports}for(var r="function"==typeof require&&require,o=0;on?(e+.05)/(n+.05):(n+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb,e=(299*t[0]+587*t[1]+114*t[2])/1e3;return e<128},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;e<3;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){var e=this.values.hsl;return e[2]+=e[2]*t,this.setValues("hsl",e),this},darken:function(t){var e=this.values.hsl;return e[2]-=e[2]*t,this.setValues("hsl",e),this},saturate:function(t){var e=this.values.hsl;return e[1]+=e[1]*t,this.setValues("hsl",e),this},desaturate:function(t){var e=this.values.hsl;return e[1]-=e[1]*t,this.setValues("hsl",e),this},whiten:function(t){var e=this.values.hwb;return e[1]+=e[1]*t,this.setValues("hwb",e),this},blacken:function(t){var e=this.values.hwb;return e[2]+=e[2]*t,this.setValues("hwb",e),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){var e=this.values.alpha;return this.setValues("alpha",e-e*t),this},opaquer:function(t){var e=this.values.alpha;return this.setValues("alpha",e+e*t),this},rotate:function(t){var e=this.values.hsl,n=(e[0]+t)%360;return e[0]=n<0?360+n:n,this.setValues("hsl",e),this},mix:function(t,e){var n=this,i=t,a=void 0===e?.5:e,r=2*a-1,o=n.alpha()-i.alpha(),s=((r*o===-1?r:(r+o)/(1+r*o))+1)/2,l=1-s;return this.rgb(s*n.red()+l*i.red(),s*n.green()+l*i.green(),s*n.blue()+l*i.blue()).alpha(n.alpha()*a+i.alpha()*(1-a))},toJSON:function(){return this.rgb()},clone:function(){var t,e,n=new r,i=this.values,a=n.values;for(var o in i)i.hasOwnProperty(o)&&(t=i[o],e={}.toString.call(t),"[object Array]"===e?a[o]=t.slice(0):"[object Number]"===e?a[o]=t:console.error("unexpected color value:",t));return n}},r.prototype.spaces={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],hwb:["hue","whiteness","blackness"],cmyk:["cyan","magenta","yellow","black"]},r.prototype.maxes={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],hwb:[360,100,100],cmyk:[100,100,100,100]},r.prototype.getValues=function(t){for(var e=this.values,n={},i=0;i.04045?Math.pow((e+.055)/1.055,2.4):e/12.92,n=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92,i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92;var a=.4124*e+.3576*n+.1805*i,r=.2126*e+.7152*n+.0722*i,o=.0193*e+.1192*n+.9505*i;return[100*a,100*r,100*o]}function d(t){var e,n,i,a=u(t),r=a[0],o=a[1],s=a[2];return r/=95.047,o/=100,s/=108.883,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,s=s>.008856?Math.pow(s,1/3):7.787*s+16/116,e=116*o-16,n=500*(r-o),i=200*(o-s),[e,n,i]}function c(t){return Y(d(t))}function h(t){var e,n,i,a,r,o=t[0]/360,s=t[1]/100,l=t[2]/100;if(0==s)return r=255*l,[r,r,r];n=l<.5?l*(1+s):l+s-l*s,e=2*l-n,a=[0,0,0];for(var u=0;u<3;u++)i=o+1/3*-(u-1),i<0&&i++,i>1&&i--,r=6*i<1?e+6*(n-e)*i:2*i<1?n:3*i<2?e+(n-e)*(2/3-i)*6:e,a[u]=255*r;return a}function f(t){var e,n,i=t[0],a=t[1]/100,r=t[2]/100;return 0===r?[0,0,0]:(r*=2,a*=r<=1?r:2-r,n=(r+a)/2,e=2*a/(r+a),[i,100*e,100*n])}function m(t){return o(h(t))}function p(t){return s(h(t))}function v(t){return l(h(t))}function y(t){var e=t[0]/60,n=t[1]/100,i=t[2]/100,a=Math.floor(e)%6,r=e-Math.floor(e),o=255*i*(1-n),s=255*i*(1-n*r),l=255*i*(1-n*(1-r)),i=255*i;switch(a){case 0:return[i,l,o];case 1:return[s,i,o];case 2:return[o,i,l];case 3:return[o,s,i];case 4:return[l,o,i];case 5:return[i,o,s]}}function x(t){var e,n,i=t[0],a=t[1]/100,r=t[2]/100;return n=(2-a)*r,e=a*r,e/=n<=1?n:2-n,e=e||0,n/=2,[i,100*e,100*n]}function k(t){return o(y(t))}function _(t){return s(y(t))}function w(t){return l(y(t))}function S(t){var e,n,i,a,o=t[0]/360,s=t[1]/100,l=t[2]/100,u=s+l;switch(u>1&&(s/=u,l/=u),e=Math.floor(6*o),n=1-l,i=6*o-e,0!=(1&e)&&(i=1-i),a=s+i*(n-s),e){default:case 6:case 0:r=n,g=a,b=s;break;case 1:r=a,g=n,b=s;break;case 2:r=s,g=n,b=a;break;case 3:r=s,g=a,b=n;break;case 4:r=a,g=s,b=n;break;case 5:r=n,g=s,b=a}return[255*r,255*g,255*b]}function M(t){return i(S(t))}function D(t){return a(S(t))}function C(t){return s(S(t))}function T(t){return l(S(t))}function P(t){var e,n,i,a=t[0]/100,r=t[1]/100,o=t[2]/100,s=t[3]/100;return e=1-Math.min(1,a*(1-s)+s),n=1-Math.min(1,r*(1-s)+s),i=1-Math.min(1,o*(1-s)+s),[255*e,255*n,255*i]}function I(t){return i(P(t))}function A(t){return a(P(t))}function F(t){return o(P(t))}function O(t){return l(P(t))}function R(t){var e,n,i,a=t[0]/100,r=t[1]/100,o=t[2]/100;return e=3.2406*a+r*-1.5372+o*-.4986,n=a*-.9689+1.8758*r+.0415*o,i=.0557*a+r*-.204+1.057*o,e=e>.0031308?1.055*Math.pow(e,1/2.4)-.055:e*=12.92,n=n>.0031308?1.055*Math.pow(n,1/2.4)-.055:n*=12.92,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:i*=12.92,e=Math.min(Math.max(0,e),1),n=Math.min(Math.max(0,n),1),i=Math.min(Math.max(0,i),1),[255*e,255*n,255*i]}function L(t){var e,n,i,a=t[0],r=t[1],o=t[2];return a/=95.047,r/=100,o/=108.883,a=a>.008856?Math.pow(a,1/3):7.787*a+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,e=116*r-16,n=500*(a-r),i=200*(r-o),[e,n,i]}function V(t){return Y(L(t))}function W(t){var e,n,i,a,r=t[0],o=t[1],s=t[2];return r<=8?(n=100*r/903.3,a=7.787*(n/100)+16/116):(n=100*Math.pow((r+16)/116,3),a=Math.pow(n/100,1/3)),e=e/95.047<=.008856?e=95.047*(o/500+a-16/116)/7.787:95.047*Math.pow(o/500+a,3),i=i/108.883<=.008859?i=108.883*(a-s/200-16/116)/7.787:108.883*Math.pow(a-s/200,3),[e,n,i]}function Y(t){var e,n,i,a=t[0],r=t[1],o=t[2];return e=Math.atan2(o,r),n=360*e/2/Math.PI,n<0&&(n+=360),i=Math.sqrt(r*r+o*o),[a,i,n]}function B(t){return R(W(t))}function z(t){var e,n,i,a=t[0],r=t[1],o=t[2];return i=o/360*2*Math.PI,e=r*Math.cos(i),n=r*Math.sin(i),[a,e,n]}function N(t){return W(z(t))}function H(t){return B(z(t))}function E(t){return J[t]}function U(t){return i(E(t))}function j(t){return a(E(t))}function G(t){return o(E(t))}function q(t){return s(E(t))}function Z(t){return d(E(t))}function X(t){return u(E(t))}e.exports={rgb2hsl:i,rgb2hsv:a,rgb2hwb:o,rgb2cmyk:s,rgb2keyword:l,rgb2xyz:u,rgb2lab:d,rgb2lch:c,hsl2rgb:h,hsl2hsv:f,hsl2hwb:m,hsl2cmyk:p,hsl2keyword:v,hsv2rgb:y,hsv2hsl:x,hsv2hwb:k,hsv2cmyk:_,hsv2keyword:w,hwb2rgb:S,hwb2hsl:M,hwb2hsv:D,hwb2cmyk:C,hwb2keyword:T,cmyk2rgb:P,cmyk2hsl:I,cmyk2hsv:A,cmyk2hwb:F,cmyk2keyword:O,keyword2rgb:E,keyword2hsl:U,keyword2hsv:j,keyword2hwb:G,keyword2cmyk:q,keyword2lab:Z,keyword2xyz:X,xyz2rgb:R,xyz2lab:L,xyz2lch:V,lab2xyz:W,lab2rgb:B,lab2lch:Y,lch2lab:z,lch2xyz:N,lch2rgb:H};var J={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},K={};for(var Q in J)K[JSON.stringify(J[Q])]=Q},{}],4:[function(t,e,n){var i=t(3),a=function(){return new u};for(var r in i){a[r+"Raw"]=function(t){return function(e){return"number"==typeof e&&(e=Array.prototype.slice.call(arguments)),i[t](e)}}(r);var o=/(\w+)2(\w+)/.exec(r),s=o[1],l=o[2];a[s]=a[s]||{},a[s][l]=a[r]=function(t){return function(e){"number"==typeof e&&(e=Array.prototype.slice.call(arguments));var n=i[t](e);if("string"==typeof n||void 0===n)return n;for(var a=0;a0)for(n in xi)i=xi[n],a=e[i],v(a)||(t[i]=a);return t}function y(e){b(this,e),this._d=new Date(null!=e._d?e._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),ki===!1&&(ki=!0,t.updateOffset(this),ki=!1)}function x(t){return t instanceof y||null!=t&&null!=t._isAMomentObject}function k(t){return t<0?Math.ceil(t)||0:Math.floor(t)}function _(t){var e=+t,n=0;return 0!==e&&isFinite(e)&&(n=k(e)),n}function w(t,e,n){var i,a=Math.min(t.length,e.length),r=Math.abs(t.length-e.length),o=0;for(i=0;i0?"future":"past"];return C(n)?n(e):n.replace(/%s/i,e)}function W(t,e){var n=t.toLowerCase();Fi[n]=Fi[n+"s"]=Fi[e]=t}function Y(t){return"string"==typeof t?Fi[t]||Fi[t.toLowerCase()]:void 0}function B(t){var e,n,i={};for(n in t)d(t,n)&&(e=Y(n),e&&(i[e]=t[n]));return i}function z(t,e){Oi[t]=e}function N(t){var e=[];for(var n in t)e.push({unit:n,priority:Oi[n]});return e.sort(function(t,e){return t.priority-e.priority}),e}function H(e,n){return function(i){return null!=i?(U(this,e,i),t.updateOffset(this,n),this):E(this,e)}}function E(t,e){return t.isValid()?t._d["get"+(t._isUTC?"UTC":"")+e]():NaN}function U(t,e,n){t.isValid()&&t._d["set"+(t._isUTC?"UTC":"")+e](n)}function j(t){return t=Y(t),C(this[t])?this[t]():this}function G(t,e){if("object"==typeof t){t=B(t);for(var n=N(t),i=0;i=0;return(r?n?"+":"":"-")+Math.pow(10,Math.max(0,a)).toString().substr(1)+i}function Z(t,e,n,i){var a=i;"string"==typeof i&&(a=function(){return this[i]()}),t&&(Wi[t]=a),e&&(Wi[e[0]]=function(){return q(a.apply(this,arguments),e[1],e[2])}),n&&(Wi[n]=function(){return this.localeData().ordinal(a.apply(this,arguments),t)})}function X(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function J(t){var e,n,i=t.match(Ri);for(e=0,n=i.length;e=0&&Li.test(t);)t=t.replace(Li,n),Li.lastIndex=0,i-=1;return t}function $(t,e,n){ea[t]=C(e)?e:function(t,i){return t&&n?n:e}}function tt(t,e){return d(ea,t)?ea[t](e._strict,e._locale):new RegExp(et(t))}function et(t){return nt(t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,n,i,a){return e||n||i||a}))}function nt(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function it(t,e){var n,i=e;for("string"==typeof t&&(t=[t]),s(e)&&(i=function(t,n){n[e]=_(t)}),n=0;n=0&&isFinite(s.getFullYear())&&s.setFullYear(t),s}function kt(t){var e=new Date(Date.UTC.apply(null,arguments));return t<100&&t>=0&&isFinite(e.getUTCFullYear())&&e.setUTCFullYear(t),e}function _t(t,e,n){var i=7+e-n,a=(7+kt(t,0,i).getUTCDay()-e)%7;return-a+i-1}function wt(t,e,n,i,a){var r,o,s=(7+n-i)%7,l=_t(t,i,a),u=1+7*(e-1)+s+l;return u<=0?(r=t-1,o=vt(r)+u):u>vt(t)?(r=t+1,o=u-vt(t)):(r=t,o=u),{year:r,dayOfYear:o}}function St(t,e,n){var i,a,r=_t(t.year(),e,n),o=Math.floor((t.dayOfYear()-r-1)/7)+1;return o<1?(a=t.year()-1,i=o+Mt(a,e,n)):o>Mt(t.year(),e,n)?(i=o-Mt(t.year(),e,n),a=t.year()+1):(a=t.year(),i=o),{week:i,year:a}}function Mt(t,e,n){var i=_t(t,e,n),a=_t(t+1,e,n);return(vt(t)-i+a)/7}function Dt(t){return St(t,this._week.dow,this._week.doy).week}function Ct(){return this._week.dow}function Tt(){return this._week.doy}function Pt(t){var e=this.localeData().week(this);return null==t?e:this.add(7*(t-e),"d")}function It(t){var e=St(this,1,4).week;return null==t?e:this.add(7*(t-e),"d")}function At(t,e){return"string"!=typeof t?t:isNaN(t)?(t=e.weekdaysParse(t),"number"==typeof t?t:null):parseInt(t,10)}function Ft(t,e){return"string"==typeof t?e.weekdaysParse(t)%7||7:isNaN(t)?null:t}function Ot(t,e){return t?a(this._weekdays)?this._weekdays[t.day()]:this._weekdays[this._weekdays.isFormat.test(e)?"format":"standalone"][t.day()]:this._weekdays}function Rt(t){return t?this._weekdaysShort[t.day()]:this._weekdaysShort}function Lt(t){return t?this._weekdaysMin[t.day()]:this._weekdaysMin}function Vt(t,e,n){var i,a,r,o=t.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],i=0;i<7;++i)r=h([2e3,1]).day(i),this._minWeekdaysParse[i]=this.weekdaysMin(r,"").toLocaleLowerCase(),this._shortWeekdaysParse[i]=this.weekdaysShort(r,"").toLocaleLowerCase(),this._weekdaysParse[i]=this.weekdays(r,"").toLocaleLowerCase();return n?"dddd"===e?(a=ha.call(this._weekdaysParse,o),a!==-1?a:null):"ddd"===e?(a=ha.call(this._shortWeekdaysParse,o),a!==-1?a:null):(a=ha.call(this._minWeekdaysParse,o),a!==-1?a:null):"dddd"===e?(a=ha.call(this._weekdaysParse,o),a!==-1?a:(a=ha.call(this._shortWeekdaysParse,o),a!==-1?a:(a=ha.call(this._minWeekdaysParse,o),a!==-1?a:null))):"ddd"===e?(a=ha.call(this._shortWeekdaysParse,o),a!==-1?a:(a=ha.call(this._weekdaysParse,o),a!==-1?a:(a=ha.call(this._minWeekdaysParse,o),a!==-1?a:null))):(a=ha.call(this._minWeekdaysParse,o),a!==-1?a:(a=ha.call(this._weekdaysParse,o),a!==-1?a:(a=ha.call(this._shortWeekdaysParse,o),a!==-1?a:null)))}function Wt(t,e,n){var i,a,r;if(this._weekdaysParseExact)return Vt.call(this,t,e,n);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),i=0;i<7;i++){if(a=h([2e3,1]).day(i),n&&!this._fullWeekdaysParse[i]&&(this._fullWeekdaysParse[i]=new RegExp("^"+this.weekdays(a,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[i]=new RegExp("^"+this.weekdaysShort(a,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[i]=new RegExp("^"+this.weekdaysMin(a,"").replace(".",".?")+"$","i")),this._weekdaysParse[i]||(r="^"+this.weekdays(a,"")+"|^"+this.weekdaysShort(a,"")+"|^"+this.weekdaysMin(a,""),this._weekdaysParse[i]=new RegExp(r.replace(".",""),"i")),n&&"dddd"===e&&this._fullWeekdaysParse[i].test(t))return i;if(n&&"ddd"===e&&this._shortWeekdaysParse[i].test(t))return i;if(n&&"dd"===e&&this._minWeekdaysParse[i].test(t))return i;if(!n&&this._weekdaysParse[i].test(t))return i}}function Yt(t){if(!this.isValid())return null!=t?this:NaN;var e=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?(t=At(t,this.localeData()),this.add(t-e,"d")):e}function Bt(t){if(!this.isValid())return null!=t?this:NaN;var e=(this.day()+7-this.localeData()._week.dow)%7;return null==t?e:this.add(t-e,"d")}function zt(t){if(!this.isValid())return null!=t?this:NaN;if(null!=t){var e=Ft(t,this.localeData());return this.day(this.day()%7?e:e-7)}return this.day()||7}function Nt(t){return this._weekdaysParseExact?(d(this,"_weekdaysRegex")||Ut.call(this),t?this._weekdaysStrictRegex:this._weekdaysRegex):(d(this,"_weekdaysRegex")||(this._weekdaysRegex=wa),this._weekdaysStrictRegex&&t?this._weekdaysStrictRegex:this._weekdaysRegex)}function Ht(t){return this._weekdaysParseExact?(d(this,"_weekdaysRegex")||Ut.call(this),t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(d(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=Sa),this._weekdaysShortStrictRegex&&t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)}function Et(t){return this._weekdaysParseExact?(d(this,"_weekdaysRegex")||Ut.call(this),t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(d(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=Ma),this._weekdaysMinStrictRegex&&t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)}function Ut(){function t(t,e){return e.length-t.length}var e,n,i,a,r,o=[],s=[],l=[],u=[];for(e=0;e<7;e++)n=h([2e3,1]).day(e),i=this.weekdaysMin(n,""),a=this.weekdaysShort(n,""),r=this.weekdays(n,""),o.push(i),s.push(a),l.push(r),u.push(i),u.push(a),u.push(r);for(o.sort(t),s.sort(t),l.sort(t),u.sort(t),e=0;e<7;e++)s[e]=nt(s[e]),l[e]=nt(l[e]),u[e]=nt(u[e]);this._weekdaysRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+l.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+s.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+o.join("|")+")","i")}function jt(){return this.hours()%12||12}function Gt(){return this.hours()||24}function qt(t,e){Z(t,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)})}function Zt(t,e){return e._meridiemParse}function Xt(t){return"p"===(t+"").toLowerCase().charAt(0)}function Jt(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"}function Kt(t){return t?t.toLowerCase().replace("_","-"):t}function Qt(t){for(var e,n,i,a,r=0;r0;){if(i=$t(a.slice(0,e).join("-")))return i;if(n&&n.length>=e&&w(a,n,!0)>=e-1)break;e--}r++}return null}function $t(t){var i=null;if(!Ia[t]&&"undefined"!=typeof n&&n&&n.exports)try{i=Da._abbr,e("./locale/"+t),te(i)}catch(t){}return Ia[t]}function te(t,e){var n;return t&&(n=v(e)?ie(t):ee(t,e),n&&(Da=n)),Da._abbr}function ee(t,e){if(null!==e){var n=Pa;if(e.abbr=t,null!=Ia[t])D("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),n=Ia[t]._config;else if(null!=e.parentLocale){if(null==Ia[e.parentLocale])return Aa[e.parentLocale]||(Aa[e.parentLocale]=[]),Aa[e.parentLocale].push({name:t,config:e}),null;n=Ia[e.parentLocale]._config}return Ia[t]=new I(P(n,e)),Aa[t]&&Aa[t].forEach(function(t){ee(t.name,t.config)}),te(t),Ia[t]}return delete Ia[t],null}function ne(t,e){if(null!=e){var n,i=Pa;null!=Ia[t]&&(i=Ia[t]._config),e=P(i,e),n=new I(e),n.parentLocale=Ia[t],Ia[t]=n,te(t)}else null!=Ia[t]&&(null!=Ia[t].parentLocale?Ia[t]=Ia[t].parentLocale:null!=Ia[t]&&delete Ia[t]);return Ia[t]}function ie(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return Da;if(!a(t)){if(e=$t(t))return e;t=[t]}return Qt(t)}function ae(){return Mi(Ia)}function re(t){var e,n=t._a;return n&&g(t).overflow===-2&&(e=n[aa]<0||n[aa]>11?aa:n[ra]<1||n[ra]>ot(n[ia],n[aa])?ra:n[oa]<0||n[oa]>24||24===n[oa]&&(0!==n[sa]||0!==n[la]||0!==n[ua])?oa:n[sa]<0||n[sa]>59?sa:n[la]<0||n[la]>59?la:n[ua]<0||n[ua]>999?ua:-1,g(t)._overflowDayOfYear&&(era)&&(e=ra),g(t)._overflowWeeks&&e===-1&&(e=da),g(t)._overflowWeekday&&e===-1&&(e=ca),g(t).overflow=e),t}function oe(t){var e,n,i,a,r,o,s=t._i,l=Fa.exec(s)||Oa.exec(s);if(l){for(g(t).iso=!0,e=0,n=La.length;evt(a)&&(g(t)._overflowDayOfYear=!0),n=kt(a,0,t._dayOfYear),t._a[aa]=n.getUTCMonth(),t._a[ra]=n.getUTCDate()),e=0;e<3&&null==t._a[e];++e)t._a[e]=r[e]=i[e];for(;e<7;e++)t._a[e]=r[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[oa]&&0===t._a[sa]&&0===t._a[la]&&0===t._a[ua]&&(t._nextDay=!0,t._a[oa]=0),t._d=(t._useUTC?kt:xt).apply(null,r),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[oa]=24)}}function ce(t){var e,n,i,a,r,o,s,l;if(e=t._w,null!=e.GG||null!=e.W||null!=e.E)r=1,o=4,n=le(e.GG,t._a[ia],St(xe(),1,4).year),i=le(e.W,1),a=le(e.E,1),(a<1||a>7)&&(l=!0);else{r=t._locale._week.dow,o=t._locale._week.doy;var u=St(xe(),r,o);n=le(e.gg,t._a[ia],u.year),i=le(e.w,u.week),null!=e.d?(a=e.d,(a<0||a>6)&&(l=!0)):null!=e.e?(a=e.e+r,(e.e<0||e.e>6)&&(l=!0)):a=r}i<1||i>Mt(n,r,o)?g(t)._overflowWeeks=!0:null!=l?g(t)._overflowWeekday=!0:(s=wt(n,i,a,r,o),t._a[ia]=s.year,t._dayOfYear=s.dayOfYear)}function he(e){if(e._f===t.ISO_8601)return void oe(e);e._a=[],g(e).empty=!0;var n,i,a,r,o,s=""+e._i,l=s.length,u=0;for(a=Q(e._f,e._locale).match(Ri)||[],n=0;n0&&g(e).unusedInput.push(o),s=s.slice(s.indexOf(i)+i.length),u+=i.length),Wi[r]?(i?g(e).empty=!1:g(e).unusedTokens.push(r),rt(r,i,e)):e._strict&&!i&&g(e).unusedTokens.push(r);g(e).charsLeftOver=l-u,s.length>0&&g(e).unusedInput.push(s),e._a[oa]<=12&&g(e).bigHour===!0&&e._a[oa]>0&&(g(e).bigHour=void 0),g(e).parsedDateParts=e._a.slice(0),g(e).meridiem=e._meridiem,e._a[oa]=fe(e._locale,e._a[oa],e._meridiem),de(e),re(e)}function fe(t,e,n){var i;return null==n?e:null!=t.meridiemHour?t.meridiemHour(e,n):null!=t.isPM?(i=t.isPM(n),i&&e<12&&(e+=12),i||12!==e||(e=0),e):e}function ge(t){var e,n,i,a,r;if(0===t._f.length)return g(t).invalidFormat=!0,void(t._d=new Date(NaN));for(a=0;athis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Ye(){if(!v(this._isDSTShifted))return this._isDSTShifted;var t={};if(b(t,this),t=ve(t),t._a){var e=t._isUTC?h(t._a):xe(t._a);this._isDSTShifted=this.isValid()&&w(t._a,e.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Be(){return!!this.isValid()&&!this._isUTC}function ze(){return!!this.isValid()&&this._isUTC}function Ne(){return!!this.isValid()&&(this._isUTC&&0===this._offset)}function He(t,e){var n,i,a,r=t,o=null;return Me(t)?r={ms:t._milliseconds,d:t._days,M:t._months}:s(t)?(r={},e?r[e]=t:r.milliseconds=t):(o=Ha.exec(t))?(n="-"===o[1]?-1:1,r={y:0,d:_(o[ra])*n,h:_(o[oa])*n,m:_(o[sa])*n,s:_(o[la])*n,ms:_(De(1e3*o[ua]))*n}):(o=Ea.exec(t))?(n="-"===o[1]?-1:1,r={y:Ee(o[2],n),M:Ee(o[3],n),w:Ee(o[4],n),d:Ee(o[5],n),h:Ee(o[6],n),m:Ee(o[7],n),s:Ee(o[8],n)}):null==r?r={}:"object"==typeof r&&("from"in r||"to"in r)&&(a=je(xe(r.from),xe(r.to)),r={},r.ms=a.milliseconds,r.M=a.months),i=new Se(r),Me(t)&&d(t,"_locale")&&(i._locale=t._locale),i}function Ee(t,e){var n=t&&parseFloat(t.replace(",","."));return(isNaN(n)?0:n)*e}function Ue(t,e){var n={milliseconds:0,months:0};return n.months=e.month()-t.month()+12*(e.year()-t.year()),t.clone().add(n.months,"M").isAfter(e)&&--n.months,n.milliseconds=+e-+t.clone().add(n.months,"M"),n}function je(t,e){var n;return t.isValid()&&e.isValid()?(e=Pe(e,t),t.isBefore(e)?n=Ue(t,e):(n=Ue(e,t),n.milliseconds=-n.milliseconds,n.months=-n.months),n):{milliseconds:0,months:0}}function Ge(t,e){return function(n,i){var a,r;return null===i||isNaN(+i)||(D(e,"moment()."+e+"(period, number) is deprecated. Please use moment()."+e+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),r=n,n=i,i=r),n="string"==typeof n?+n:n,a=He(n,i),qe(this,a,t),this}}function qe(e,n,i,a){var r=n._milliseconds,o=De(n._days),s=De(n._months);e.isValid()&&(a=null==a||a,r&&e._d.setTime(e._d.valueOf()+r*i),o&&U(e,"Date",E(e,"Date")+o*i),s&&ct(e,E(e,"Month")+s*i),a&&t.updateOffset(e,o||s))}function Ze(t,e){var n=t.diff(e,"days",!0);return n<-6?"sameElse":n<-1?"lastWeek":n<0?"lastDay":n<1?"sameDay":n<2?"nextDay":n<7?"nextWeek":"sameElse"}function Xe(e,n){var i=e||xe(),a=Pe(i,this).startOf("day"),r=t.calendarFormat(this,a)||"sameElse",o=n&&(C(n[r])?n[r].call(this,i):n[r]);return this.format(o||this.localeData().calendar(r,this,xe(i)))}function Je(){return new y(this)}function Ke(t,e){var n=x(t)?t:xe(t);return!(!this.isValid()||!n.isValid())&&(e=Y(v(e)?"millisecond":e),"millisecond"===e?this.valueOf()>n.valueOf():n.valueOf()r&&(e=r),Rn.call(this,t,e,n,i,a))}function Rn(t,e,n,i,a){var r=wt(t,e,n,i,a),o=kt(r.year,0,r.dayOfYear);return this.year(o.getUTCFullYear()),this.month(o.getUTCMonth()),this.date(o.getUTCDate()),this}function Ln(t){return null==t?Math.ceil((this.month()+1)/3):this.month(3*(t-1)+this.month()%3)}function Vn(t){var e=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==t?e:this.add(t-e,"d")}function Wn(t,e){e[ua]=_(1e3*("0."+t))}function Yn(){return this._isUTC?"UTC":""}function Bn(){return this._isUTC?"Coordinated Universal Time":""}function zn(t){return xe(1e3*t)}function Nn(){return xe.apply(null,arguments).parseZone()}function Hn(t){return t}function En(t,e,n,i){var a=ie(),r=h().set(i,e);return a[n](r,t)}function Un(t,e,n){if(s(t)&&(e=t,t=void 0),t=t||"",null!=e)return En(t,e,n,"month");var i,a=[];for(i=0;i<12;i++)a[i]=En(t,i,n,"month");return a}function jn(t,e,n,i){"boolean"==typeof t?(s(e)&&(n=e,e=void 0),e=e||""):(e=t,n=e,t=!1,s(e)&&(n=e,e=void 0),e=e||"");var a=ie(),r=t?a._week.dow:0;if(null!=n)return En(e,(n+r)%7,i,"day");var o,l=[];for(o=0;o<7;o++)l[o]=En(e,(o+r)%7,i,"day");return l}function Gn(t,e){return Un(t,e,"months")}function qn(t,e){return Un(t,e,"monthsShort")}function Zn(t,e,n){return jn(t,e,n,"weekdays")}function Xn(t,e,n){return jn(t,e,n,"weekdaysShort")}function Jn(t,e,n){return jn(t,e,n,"weekdaysMin")}function Kn(){var t=this._data;return this._milliseconds=tr(this._milliseconds),this._days=tr(this._days),this._months=tr(this._months),t.milliseconds=tr(t.milliseconds),t.seconds=tr(t.seconds),t.minutes=tr(t.minutes),t.hours=tr(t.hours),t.months=tr(t.months),t.years=tr(t.years),this}function Qn(t,e,n,i){var a=He(e,n);return t._milliseconds+=i*a._milliseconds,t._days+=i*a._days,t._months+=i*a._months,t._bubble()}function $n(t,e){return Qn(this,t,e,1)}function ti(t,e){return Qn(this,t,e,-1)}function ei(t){return t<0?Math.floor(t):Math.ceil(t)}function ni(){var t,e,n,i,a,r=this._milliseconds,o=this._days,s=this._months,l=this._data;return r>=0&&o>=0&&s>=0||r<=0&&o<=0&&s<=0||(r+=864e5*ei(ai(s)+o),o=0,s=0),l.milliseconds=r%1e3,t=k(r/1e3),l.seconds=t%60,e=k(t/60),l.minutes=e%60,n=k(e/60),l.hours=n%24,o+=k(n/24),a=k(ii(o)),s+=a,o-=ei(ai(a)),i=k(s/12),s%=12,l.days=o,l.months=s,l.years=i,this}function ii(t){return 4800*t/146097}function ai(t){return 146097*t/4800}function ri(t){var e,n,i=this._milliseconds;if(t=Y(t),"month"===t||"year"===t)return e=this._days+i/864e5,n=this._months+ii(e),"month"===t?n:n/12;switch(e=this._days+Math.round(ai(this._months)),t){case"week":return e/7+i/6048e5;case"day":return e+i/864e5;case"hour":return 24*e+i/36e5;case"minute":return 1440*e+i/6e4;case"second":return 86400*e+i/1e3;case"millisecond":return Math.floor(864e5*e)+i;default:throw new Error("Unknown unit "+t)}}function oi(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*_(this._months/12)}function si(t){return function(){return this.as(t)}}function li(t){return t=Y(t),this[t+"s"]()}function ui(t){return function(){return this._data[t]}}function di(){return k(this.days()/7)}function ci(t,e,n,i,a){return a.relativeTime(e||1,!!n,t,i)}function hi(t,e,n){var i=He(t).abs(),a=pr(i.as("s")),r=pr(i.as("m")),o=pr(i.as("h")),s=pr(i.as("d")),l=pr(i.as("M")),u=pr(i.as("y")),d=a0,d[4]=n,ci.apply(null,d)}function fi(t){return void 0===t?pr:"function"==typeof t&&(pr=t,!0)}function gi(t,e){return void 0!==vr[t]&&(void 0===e?vr[t]:(vr[t]=e,!0))}function mi(t){var e=this.localeData(),n=hi(this,!t,e);return t&&(n=e.pastFuture(+this,n)),e.postformat(n)}function pi(){var t,e,n,i=br(this._milliseconds)/1e3,a=br(this._days),r=br(this._months);t=k(i/60),e=k(t/60),i%=60,t%=60,n=k(r/12),r%=12;var o=n,s=r,l=a,u=e,d=t,c=i,h=this.asSeconds();return h?(h<0?"-":"")+"P"+(o?o+"Y":"")+(s?s+"M":"")+(l?l+"D":"")+(u||d||c?"T":"")+(u?u+"H":"")+(d?d+"M":"")+(c?c+"S":""):"P0D"}var vi,bi;bi=Array.prototype.some?Array.prototype.some:function(t){for(var e=Object(this),n=e.length>>>0,i=0;i68?1900:2e3)};var ba=H("FullYear",!0);Z("w",["ww",2],"wo","week"),Z("W",["WW",2],"Wo","isoWeek"),W("week","w"),W("isoWeek","W"),z("week",5),z("isoWeek",5),$("w",Ei),$("ww",Ei,Bi),$("W",Ei),$("WW",Ei,Bi),at(["w","ww","W","WW"],function(t,e,n,i){e[i.substr(0,1)]=_(t)});var ya={dow:0,doy:6};Z("d",0,"do","day"),Z("dd",0,0,function(t){return this.localeData().weekdaysMin(this,t)}),Z("ddd",0,0,function(t){return this.localeData().weekdaysShort(this,t)}),Z("dddd",0,0,function(t){return this.localeData().weekdays(this,t)}),Z("e",0,0,"weekday"),Z("E",0,0,"isoWeekday"),W("day","d"),W("weekday","e"),W("isoWeekday","E"),z("day",11),z("weekday",11),z("isoWeekday",11),$("d",Ei),$("e",Ei),$("E",Ei),$("dd",function(t,e){return e.weekdaysMinRegex(t)}),$("ddd",function(t,e){return e.weekdaysShortRegex(t)}),$("dddd",function(t,e){return e.weekdaysRegex(t)}),at(["dd","ddd","dddd"],function(t,e,n,i){var a=n._locale.weekdaysParse(t,i,n._strict);null!=a?e.d=a:g(n).invalidWeekday=t}),at(["d","e","E"],function(t,e,n,i){e[i]=_(t)});var xa="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),ka="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),_a="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),wa=ta,Sa=ta,Ma=ta;Z("H",["HH",2],0,"hour"),Z("h",["hh",2],0,jt),Z("k",["kk",2],0,Gt),Z("hmm",0,0,function(){return""+jt.apply(this)+q(this.minutes(),2)}),Z("hmmss",0,0,function(){return""+jt.apply(this)+q(this.minutes(),2)+q(this.seconds(),2)}),Z("Hmm",0,0,function(){return""+this.hours()+q(this.minutes(),2)}),Z("Hmmss",0,0,function(){return""+this.hours()+q(this.minutes(),2)+q(this.seconds(),2)}),qt("a",!0),qt("A",!1),W("hour","h"),z("hour",13),$("a",Zt),$("A",Zt),$("H",Ei),$("h",Ei),$("HH",Ei,Bi),$("hh",Ei,Bi),$("hmm",Ui),$("hmmss",ji),$("Hmm",Ui),$("Hmmss",ji),it(["H","HH"],oa),it(["a","A"],function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t}),it(["h","hh"],function(t,e,n){e[oa]=_(t),g(n).bigHour=!0}),it("hmm",function(t,e,n){var i=t.length-2;e[oa]=_(t.substr(0,i)),e[sa]=_(t.substr(i)),g(n).bigHour=!0}),it("hmmss",function(t,e,n){var i=t.length-4,a=t.length-2;e[oa]=_(t.substr(0,i)),e[sa]=_(t.substr(i,2)),e[la]=_(t.substr(a)),g(n).bigHour=!0}),it("Hmm",function(t,e,n){var i=t.length-2;e[oa]=_(t.substr(0,i)),e[sa]=_(t.substr(i))}),it("Hmmss",function(t,e,n){var i=t.length-4,a=t.length-2;e[oa]=_(t.substr(0,i)),e[sa]=_(t.substr(i,2)),e[la]=_(t.substr(a))});var Da,Ca=/[ap]\.?m?\.?/i,Ta=H("Hours",!0),Pa={calendar:Di,longDateFormat:Ci,invalidDate:Ti,ordinal:Pi,ordinalParse:Ii,relativeTime:Ai,months:ga,monthsShort:ma,week:ya,weekdays:xa,weekdaysMin:_a,weekdaysShort:ka,meridiemParse:Ca},Ia={},Aa={},Fa=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Oa=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Ra=/Z|[+-]\d\d(?::?\d\d)?/,La=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Va=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Wa=/^\/?Date\((\-?\d+)/i; +t.createFromInputFallback=M("value provided is not in a recognized ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.",function(t){t._d=new Date(t._i+(t._useUTC?" UTC":""))}),t.ISO_8601=function(){};var Ya=M("moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var t=xe.apply(null,arguments);return this.isValid()&&t.isValid()?tthis?this:t:p()}),za=function(){return Date.now?Date.now():+new Date};Ce("Z",":"),Ce("ZZ",""),$("Z",Qi),$("ZZ",Qi),it(["Z","ZZ"],function(t,e,n){n._useUTC=!0,n._tzm=Te(Qi,t)});var Na=/([\+\-]|\d\d)/gi;t.updateOffset=function(){};var Ha=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/,Ea=/^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/;He.fn=Se.prototype;var Ua=Ge(1,"add"),ja=Ge(-1,"subtract");t.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",t.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var Ga=M("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(t){return void 0===t?this.localeData():this.locale(t)});Z(0,["gg",2],0,function(){return this.weekYear()%100}),Z(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Tn("gggg","weekYear"),Tn("ggggg","weekYear"),Tn("GGGG","isoWeekYear"),Tn("GGGGG","isoWeekYear"),W("weekYear","gg"),W("isoWeekYear","GG"),z("weekYear",1),z("isoWeekYear",1),$("G",Ji),$("g",Ji),$("GG",Ei,Bi),$("gg",Ei,Bi),$("GGGG",qi,Ni),$("gggg",qi,Ni),$("GGGGG",Zi,Hi),$("ggggg",Zi,Hi),at(["gggg","ggggg","GGGG","GGGGG"],function(t,e,n,i){e[i.substr(0,2)]=_(t)}),at(["gg","GG"],function(e,n,i,a){n[a]=t.parseTwoDigitYear(e)}),Z("Q",0,"Qo","quarter"),W("quarter","Q"),z("quarter",7),$("Q",Yi),it("Q",function(t,e){e[aa]=3*(_(t)-1)}),Z("D",["DD",2],"Do","date"),W("date","D"),z("date",9),$("D",Ei),$("DD",Ei,Bi),$("Do",function(t,e){return t?e._ordinalParse:e._ordinalParseLenient}),it(["D","DD"],ra),it("Do",function(t,e){e[ra]=_(t.match(Ei)[0],10)});var qa=H("Date",!0);Z("DDD",["DDDD",3],"DDDo","dayOfYear"),W("dayOfYear","DDD"),z("dayOfYear",4),$("DDD",Gi),$("DDDD",zi),it(["DDD","DDDD"],function(t,e,n){n._dayOfYear=_(t)}),Z("m",["mm",2],0,"minute"),W("minute","m"),z("minute",14),$("m",Ei),$("mm",Ei,Bi),it(["m","mm"],sa);var Za=H("Minutes",!1);Z("s",["ss",2],0,"second"),W("second","s"),z("second",15),$("s",Ei),$("ss",Ei,Bi),it(["s","ss"],la);var Xa=H("Seconds",!1);Z("S",0,0,function(){return~~(this.millisecond()/100)}),Z(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),Z(0,["SSS",3],0,"millisecond"),Z(0,["SSSS",4],0,function(){return 10*this.millisecond()}),Z(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),Z(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),Z(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),Z(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),Z(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),W("millisecond","ms"),z("millisecond",16),$("S",Gi,Yi),$("SS",Gi,Bi),$("SSS",Gi,zi);var Ja;for(Ja="SSSS";Ja.length<=9;Ja+="S")$(Ja,Xi);for(Ja="S";Ja.length<=9;Ja+="S")it(Ja,Wn);var Ka=H("Milliseconds",!1);Z("z",0,0,"zoneAbbr"),Z("zz",0,0,"zoneName");var Qa=y.prototype;Qa.add=Ua,Qa.calendar=Xe,Qa.clone=Je,Qa.diff=an,Qa.endOf=vn,Qa.format=un,Qa.from=dn,Qa.fromNow=cn,Qa.to=hn,Qa.toNow=fn,Qa.get=j,Qa.invalidAt=Dn,Qa.isAfter=Ke,Qa.isBefore=Qe,Qa.isBetween=$e,Qa.isSame=tn,Qa.isSameOrAfter=en,Qa.isSameOrBefore=nn,Qa.isValid=Sn,Qa.lang=Ga,Qa.locale=gn,Qa.localeData=mn,Qa.max=Ba,Qa.min=Ya,Qa.parsingFlags=Mn,Qa.set=G,Qa.startOf=pn,Qa.subtract=ja,Qa.toArray=kn,Qa.toObject=_n,Qa.toDate=xn,Qa.toISOString=sn,Qa.inspect=ln,Qa.toJSON=wn,Qa.toString=on,Qa.unix=yn,Qa.valueOf=bn,Qa.creationData=Cn,Qa.year=ba,Qa.isLeapYear=yt,Qa.weekYear=Pn,Qa.isoWeekYear=In,Qa.quarter=Qa.quarters=Ln,Qa.month=ht,Qa.daysInMonth=ft,Qa.week=Qa.weeks=Pt,Qa.isoWeek=Qa.isoWeeks=It,Qa.weeksInYear=Fn,Qa.isoWeeksInYear=An,Qa.date=qa,Qa.day=Qa.days=Yt,Qa.weekday=Bt,Qa.isoWeekday=zt,Qa.dayOfYear=Vn,Qa.hour=Qa.hours=Ta,Qa.minute=Qa.minutes=Za,Qa.second=Qa.seconds=Xa,Qa.millisecond=Qa.milliseconds=Ka,Qa.utcOffset=Ae,Qa.utc=Oe,Qa.local=Re,Qa.parseZone=Le,Qa.hasAlignedHourOffset=Ve,Qa.isDST=We,Qa.isLocal=Be,Qa.isUtcOffset=ze,Qa.isUtc=Ne,Qa.isUTC=Ne,Qa.zoneAbbr=Yn,Qa.zoneName=Bn,Qa.dates=M("dates accessor is deprecated. Use date instead.",qa),Qa.months=M("months accessor is deprecated. Use month instead",ht),Qa.years=M("years accessor is deprecated. Use year instead",ba),Qa.zone=M("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",Fe),Qa.isDSTShifted=M("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",Ye);var $a=I.prototype;$a.calendar=A,$a.longDateFormat=F,$a.invalidDate=O,$a.ordinal=R,$a.preparse=Hn,$a.postformat=Hn,$a.relativeTime=L,$a.pastFuture=V,$a.set=T,$a.months=st,$a.monthsShort=lt,$a.monthsParse=dt,$a.monthsRegex=mt,$a.monthsShortRegex=gt,$a.week=Dt,$a.firstDayOfYear=Tt,$a.firstDayOfWeek=Ct,$a.weekdays=Ot,$a.weekdaysMin=Lt,$a.weekdaysShort=Rt,$a.weekdaysParse=Wt,$a.weekdaysRegex=Nt,$a.weekdaysShortRegex=Ht,$a.weekdaysMinRegex=Et,$a.isPM=Xt,$a.meridiem=Jt,te("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10,n=1===_(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return t+n}}),t.lang=M("moment.lang is deprecated. Use moment.locale instead.",te),t.langData=M("moment.langData is deprecated. Use moment.localeData instead.",ie);var tr=Math.abs,er=si("ms"),nr=si("s"),ir=si("m"),ar=si("h"),rr=si("d"),or=si("w"),sr=si("M"),lr=si("y"),ur=ui("milliseconds"),dr=ui("seconds"),cr=ui("minutes"),hr=ui("hours"),fr=ui("days"),gr=ui("months"),mr=ui("years"),pr=Math.round,vr={s:45,m:45,h:22,d:26,M:11},br=Math.abs,yr=Se.prototype;return yr.abs=Kn,yr.add=$n,yr.subtract=ti,yr.as=ri,yr.asMilliseconds=er,yr.asSeconds=nr,yr.asMinutes=ir,yr.asHours=ar,yr.asDays=rr,yr.asWeeks=or,yr.asMonths=sr,yr.asYears=lr,yr.valueOf=oi,yr._bubble=ni,yr.get=li,yr.milliseconds=ur,yr.seconds=dr,yr.minutes=cr,yr.hours=hr,yr.days=fr,yr.weeks=di,yr.months=gr,yr.years=mr,yr.humanize=mi,yr.toISOString=pi,yr.toString=pi,yr.toJSON=pi,yr.locale=gn,yr.localeData=mn,yr.toIsoString=M("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",pi),yr.lang=Ga,Z("X",0,0,"unix"),Z("x",0,0,"valueOf"),$("x",Ji),$("X",$i),it("X",function(t,e,n){n._d=new Date(1e3*parseFloat(t,10))}),it("x",function(t,e,n){n._d=new Date(_(t))}),t.version="2.17.1",i(xe),t.fn=Qa,t.min=_e,t.max=we,t.now=za,t.utc=h,t.unix=zn,t.months=Gn,t.isDate=l,t.locale=te,t.invalid=p,t.duration=He,t.isMoment=x,t.weekdays=Zn,t.parseZone=Nn,t.localeData=ie,t.isDuration=Me,t.monthsShort=qn,t.weekdaysMin=Jn,t.defineLocale=ee,t.updateLocale=ne,t.locales=ae,t.weekdaysShort=Xn,t.normalizeUnits=Y,t.relativeTimeRounding=fi,t.relativeTimeThreshold=gi,t.calendarFormat=Ze,t.prototype=Qa,t})},{}],7:[function(t,e,n){var i=t(28)();t(26)(i),t(42)(i),t(22)(i),t(31)(i),t(25)(i),t(21)(i),t(23)(i),t(24)(i),t(29)(i),t(33)(i),t(34)(i),t(32)(i),t(35)(i),t(30)(i),t(27)(i),t(36)(i),t(37)(i),t(38)(i),t(39)(i),t(40)(i),t(45)(i),t(43)(i),t(44)(i),t(46)(i),t(47)(i),t(48)(i),t(15)(i),t(16)(i),t(17)(i),t(18)(i),t(19)(i),t(20)(i),t(8)(i),t(9)(i),t(10)(i),t(11)(i),t(12)(i),t(13)(i),t(14)(i),window.Chart=e.exports=i},{10:10,11:11,12:12,13:13,14:14,15:15,16:16,17:17,18:18,19:19,20:20,21:21,22:22,23:23,24:24,25:25,26:26,27:27,28:28,29:29,30:30,31:31,32:32,33:33,34:34,35:35,36:36,37:37,38:38,39:39,40:40,42:42,43:43,44:44,45:45,46:46,47:47,48:48,8:8,9:9}],8:[function(t,e,n){"use strict";e.exports=function(t){t.Bar=function(e,n){return n.type="bar",new t(e,n)}}},{}],9:[function(t,e,n){"use strict";e.exports=function(t){t.Bubble=function(e,n){return n.type="bubble",new t(e,n)}}},{}],10:[function(t,e,n){"use strict";e.exports=function(t){t.Doughnut=function(e,n){return n.type="doughnut",new t(e,n)}}},{}],11:[function(t,e,n){"use strict";e.exports=function(t){t.Line=function(e,n){return n.type="line",new t(e,n)}}},{}],12:[function(t,e,n){"use strict";e.exports=function(t){t.PolarArea=function(e,n){return n.type="polarArea",new t(e,n)}}},{}],13:[function(t,e,n){"use strict";e.exports=function(t){t.Radar=function(e,n){return n.type="radar",new t(e,n)}}},{}],14:[function(t,e,n){"use strict";e.exports=function(t){var e={hover:{mode:"single"},scales:{xAxes:[{type:"linear",position:"bottom",id:"x-axis-1"}],yAxes:[{type:"linear",position:"left",id:"y-axis-1"}]},tooltips:{callbacks:{title:function(){return""},label:function(t){return"("+t.xLabel+", "+t.yLabel+")"}}}};t.defaults.scatter=e,t.controllers.scatter=t.controllers.line,t.Scatter=function(e,n){return n.type="scatter",new t(e,n)}}},{}],15:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.bar={hover:{mode:"label"},scales:{xAxes:[{type:"category",categoryPercentage:.8,barPercentage:.9,gridLines:{offsetGridLines:!0}}],yAxes:[{type:"linear"}]}},t.controllers.bar=t.DatasetController.extend({dataElementType:t.elements.Rectangle,initialize:function(e,n){t.DatasetController.prototype.initialize.call(this,e,n);var i=this,a=i.getMeta(),r=i.getDataset();a.stack=r.stack,a.bar=!0},getStackCount:function(){var t=this,n=t.getMeta(),i=t.getScaleForId(n.yAxisID),a=[];return e.each(t.chart.data.datasets,function(e,n){var r=t.chart.getDatasetMeta(n);r.bar&&t.chart.isDatasetVisible(n)&&(i.options.stacked===!1||i.options.stacked===!0&&a.indexOf(r.stack)===-1||void 0===i.options.stacked&&(void 0===r.stack||a.indexOf(r.stack)===-1))&&a.push(r.stack)},t),a.length},update:function(t){var n=this;e.each(n.getMeta().data,function(e,i){n.updateElement(e,i,t)},n)},updateElement:function(t,n,i){var a=this,r=a.getMeta(),o=a.getScaleForId(r.xAxisID),s=a.getScaleForId(r.yAxisID),l=s.getBasePixel(),u=a.chart.options.elements.rectangle,d=t.custom||{},c=a.getDataset();t._xScale=o,t._yScale=s,t._datasetIndex=a.index,t._index=n;var h=a.getRuler(n);t._model={x:a.calculateBarX(n,a.index,h),y:i?l:a.calculateBarY(n,a.index),label:a.chart.data.labels[n],datasetLabel:c.label,horizontal:!1,base:i?l:a.calculateBarBase(a.index,n),width:a.calculateBarWidth(h),backgroundColor:d.backgroundColor?d.backgroundColor:e.getValueAtIndexOrDefault(c.backgroundColor,n,u.backgroundColor),borderSkipped:d.borderSkipped?d.borderSkipped:u.borderSkipped,borderColor:d.borderColor?d.borderColor:e.getValueAtIndexOrDefault(c.borderColor,n,u.borderColor),borderWidth:d.borderWidth?d.borderWidth:e.getValueAtIndexOrDefault(c.borderWidth,n,u.borderWidth)},t.pivot()},calculateBarBase:function(t,e){var n=this,i=n.getMeta(),a=n.getScaleForId(i.yAxisID),r=a.getBaseValue(),o=r;if(a.options.stacked===!0||void 0===a.options.stacked&&void 0!==i.stack){for(var s=n.chart,l=s.data.datasets,u=Number(l[t].data[e]),d=0;d0&&(t[0].yLabel?n=t[0].yLabel:e.labels.length>0&&t[0].index');var n=t.data,i=n.datasets,a=n.labels;if(i.length)for(var r=0;r'),a[r]&&e.push(a[r]),e.push("");return e.push(""),e.join("")},legend:{labels:{generateLabels:function(t){var n=t.data;return n.labels.length&&n.datasets.length?n.labels.map(function(i,a){var r=t.getDatasetMeta(0),o=n.datasets[0],s=r.data[a],l=s&&s.custom||{},u=e.getValueAtIndexOrDefault,d=t.options.elements.arc,c=l.backgroundColor?l.backgroundColor:u(o.backgroundColor,a,d.backgroundColor),h=l.borderColor?l.borderColor:u(o.borderColor,a,d.borderColor),f=l.borderWidth?l.borderWidth:u(o.borderWidth,a,d.borderWidth);return{text:i,fillStyle:c,strokeStyle:h,lineWidth:f,hidden:isNaN(o.data[a])||r.data[a].hidden,index:a}}):[]}},onClick:function(t,e){var n,i,a,r=e.index,o=this.chart;for(n=0,i=(o.data.datasets||[]).length;n=Math.PI?-1:g<-Math.PI?1:0);var m=g+f,p={x:Math.cos(g),y:Math.sin(g)},v={x:Math.cos(m),y:Math.sin(m)},b=g<=0&&0<=m||g<=2*Math.PI&&2*Math.PI<=m,y=g<=.5*Math.PI&&.5*Math.PI<=m||g<=2.5*Math.PI&&2.5*Math.PI<=m,x=g<=-Math.PI&&-Math.PI<=m||g<=Math.PI&&Math.PI<=m,k=g<=.5*-Math.PI&&.5*-Math.PI<=m||g<=1.5*Math.PI&&1.5*Math.PI<=m,_=h/100,w={x:x?-1:Math.min(p.x*(p.x<0?1:_),v.x*(v.x<0?1:_)),y:k?-1:Math.min(p.y*(p.y<0?1:_),v.y*(v.y<0?1:_))},S={x:b?1:Math.max(p.x*(p.x>0?1:_),v.x*(v.x>0?1:_)),y:y?1:Math.max(p.y*(p.y>0?1:_),v.y*(v.y>0?1:_))},M={width:.5*(S.x-w.x),height:.5*(S.y-w.y)};u=Math.min(s/M.width,l/M.height),d={x:(S.x+w.x)*-.5,y:(S.y+w.y)*-.5}}i.borderWidth=n.getMaxBorderWidth(c.data),i.outerRadius=Math.max((u-i.borderWidth)/2,0),i.innerRadius=Math.max(h?i.outerRadius/100*h:0,0),i.radiusLength=(i.outerRadius-i.innerRadius)/i.getVisibleDatasetCount(),i.offsetX=d.x*i.outerRadius,i.offsetY=d.y*i.outerRadius,c.total=n.calculateTotal(),n.outerRadius=i.outerRadius-i.radiusLength*n.getRingIndex(n.index),n.innerRadius=Math.max(n.outerRadius-i.radiusLength,0),e.each(c.data,function(e,i){n.updateElement(e,i,t)})},updateElement:function(t,n,i){var a=this,r=a.chart,o=r.chartArea,s=r.options,l=s.animation,u=(o.left+o.right)/2,d=(o.top+o.bottom)/2,c=s.rotation,h=s.rotation,f=a.getDataset(),g=i&&l.animateRotate?0:t.hidden?0:a.calculateCircumference(f.data[n])*(s.circumference/(2*Math.PI)),m=i&&l.animateScale?0:a.innerRadius,p=i&&l.animateScale?0:a.outerRadius,v=e.getValueAtIndexOrDefault;e.extend(t,{_datasetIndex:a.index,_index:n,_model:{x:u+r.offsetX,y:d+r.offsetY,startAngle:c,endAngle:h,circumference:g,outerRadius:p,innerRadius:m,label:v(f.label,n,r.data.labels[n])}});var b=t._model;this.removeHoverStyle(t),i&&l.animateRotate||(0===n?b.startAngle=s.rotation:b.startAngle=a.getMeta().data[n-1]._model.endAngle,b.endAngle=b.startAngle+b.circumference),t.pivot()},removeHoverStyle:function(e){t.DatasetController.prototype.removeHoverStyle.call(this,e,this.chart.options.elements.arc)},calculateTotal:function(){var t,n=this.getDataset(),i=this.getMeta(),a=0;return e.each(i.data,function(e,i){t=n.data[i],isNaN(t)||e.hidden||(a+=Math.abs(t))}),a},calculateCircumference:function(t){var e=this.getMeta().total;return e>0&&!isNaN(t)?2*Math.PI*(t/e):0},getMaxBorderWidth:function(t){for(var e,n,i=0,a=this.index,r=t.length,o=0;oi?e:i,i=n>i?n:i;return i}})}},{}],18:[function(t,e,n){"use strict";e.exports=function(t){function e(t,e){return n.getValueOrDefault(t.showLine,e.showLines)}var n=t.helpers;t.defaults.line={showLines:!0,spanGaps:!1,hover:{mode:"label"},scales:{xAxes:[{type:"category",id:"x-axis-0"}],yAxes:[{type:"linear",id:"y-axis-0"}]}},t.controllers.line=t.DatasetController.extend({datasetElementType:t.elements.Line,dataElementType:t.elements.Point,update:function(t){var i,a,r,o=this,s=o.getMeta(),l=s.dataset,u=s.data||[],d=o.chart.options,c=d.elements.line,h=o.getScaleForId(s.yAxisID),f=o.getDataset(),g=e(f,d);for(g&&(r=l.custom||{},void 0!==f.tension&&void 0===f.lineTension&&(f.lineTension=f.tension),l._scale=h,l._datasetIndex=o.index,l._children=u,l._model={spanGaps:f.spanGaps?f.spanGaps:d.spanGaps,tension:r.tension?r.tension:n.getValueOrDefault(f.lineTension,c.tension),backgroundColor:r.backgroundColor?r.backgroundColor:f.backgroundColor||c.backgroundColor,borderWidth:r.borderWidth?r.borderWidth:f.borderWidth||c.borderWidth,borderColor:r.borderColor?r.borderColor:f.borderColor||c.borderColor,borderCapStyle:r.borderCapStyle?r.borderCapStyle:f.borderCapStyle||c.borderCapStyle,borderDash:r.borderDash?r.borderDash:f.borderDash||c.borderDash,borderDashOffset:r.borderDashOffset?r.borderDashOffset:f.borderDashOffset||c.borderDashOffset,borderJoinStyle:r.borderJoinStyle?r.borderJoinStyle:f.borderJoinStyle||c.borderJoinStyle,fill:r.fill?r.fill:void 0!==f.fill?f.fill:c.fill,steppedLine:r.steppedLine?r.steppedLine:n.getValueOrDefault(f.steppedLine,c.stepped),cubicInterpolationMode:r.cubicInterpolationMode?r.cubicInterpolationMode:n.getValueOrDefault(f.cubicInterpolationMode,c.cubicInterpolationMode),scaleTop:h.top,scaleBottom:h.bottom,scaleZero:h.getBasePixel()},l.pivot()),i=0,a=u.length;i');var n=t.data,i=n.datasets,a=n.labels;if(i.length)for(var r=0;r'),a[r]&&e.push(a[r]),e.push("");return e.push(""),e.join(""); +},legend:{labels:{generateLabels:function(t){var n=t.data;return n.labels.length&&n.datasets.length?n.labels.map(function(i,a){var r=t.getDatasetMeta(0),o=n.datasets[0],s=r.data[a],l=s.custom||{},u=e.getValueAtIndexOrDefault,d=t.options.elements.arc,c=l.backgroundColor?l.backgroundColor:u(o.backgroundColor,a,d.backgroundColor),h=l.borderColor?l.borderColor:u(o.borderColor,a,d.borderColor),f=l.borderWidth?l.borderWidth:u(o.borderWidth,a,d.borderWidth);return{text:i,fillStyle:c,strokeStyle:h,lineWidth:f,hidden:isNaN(o.data[a])||r.data[a].hidden,index:a}}):[]}},onClick:function(t,e){var n,i,a,r=e.index,o=this.chart;for(n=0,i=(o.data.datasets||[]).length;n0&&!isNaN(t)?2*Math.PI/e:0}})}},{}],20:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.radar={aspectRatio:1,scale:{type:"radialLinear"},elements:{line:{tension:0}}},t.controllers.radar=t.DatasetController.extend({datasetElementType:t.elements.Line,dataElementType:t.elements.Point,linkScales:e.noop,update:function(t){var n=this,i=n.getMeta(),a=i.dataset,r=i.data,o=a.custom||{},s=n.getDataset(),l=n.chart.options.elements.line,u=n.chart.scale;void 0!==s.tension&&void 0===s.lineTension&&(s.lineTension=s.tension),e.extend(i.dataset,{_datasetIndex:n.index,_children:r,_loop:!0,_model:{tension:o.tension?o.tension:e.getValueOrDefault(s.lineTension,l.tension),backgroundColor:o.backgroundColor?o.backgroundColor:s.backgroundColor||l.backgroundColor,borderWidth:o.borderWidth?o.borderWidth:s.borderWidth||l.borderWidth,borderColor:o.borderColor?o.borderColor:s.borderColor||l.borderColor,fill:o.fill?o.fill:void 0!==s.fill?s.fill:l.fill,borderCapStyle:o.borderCapStyle?o.borderCapStyle:s.borderCapStyle||l.borderCapStyle,borderDash:o.borderDash?o.borderDash:s.borderDash||l.borderDash,borderDashOffset:o.borderDashOffset?o.borderDashOffset:s.borderDashOffset||l.borderDashOffset,borderJoinStyle:o.borderJoinStyle?o.borderJoinStyle:s.borderJoinStyle||l.borderJoinStyle,scaleTop:u.top,scaleBottom:u.bottom,scaleZero:u.getBasePosition()}}),i.dataset.pivot(),e.each(r,function(e,i){n.updateElement(e,i,t)},n),n.updateBezierControlPoints()},updateElement:function(t,n,i){var a=this,r=t.custom||{},o=a.getDataset(),s=a.chart.scale,l=a.chart.options.elements.point,u=s.getPointPositionForValue(n,o.data[n]);e.extend(t,{_datasetIndex:a.index,_index:n,_scale:s,_model:{x:i?s.xCenter:u.x,y:i?s.yCenter:u.y,tension:r.tension?r.tension:e.getValueOrDefault(o.lineTension,a.chart.options.elements.line.tension),radius:r.radius?r.radius:e.getValueAtIndexOrDefault(o.pointRadius,n,l.radius),backgroundColor:r.backgroundColor?r.backgroundColor:e.getValueAtIndexOrDefault(o.pointBackgroundColor,n,l.backgroundColor),borderColor:r.borderColor?r.borderColor:e.getValueAtIndexOrDefault(o.pointBorderColor,n,l.borderColor),borderWidth:r.borderWidth?r.borderWidth:e.getValueAtIndexOrDefault(o.pointBorderWidth,n,l.borderWidth),pointStyle:r.pointStyle?r.pointStyle:e.getValueAtIndexOrDefault(o.pointStyle,n,l.pointStyle),hitRadius:r.hitRadius?r.hitRadius:e.getValueAtIndexOrDefault(o.hitRadius,n,l.hitRadius)}}),t._model.skip=r.skip?r.skip:isNaN(t._model.x)||isNaN(t._model.y)},updateBezierControlPoints:function(){var t=this.chart.chartArea,n=this.getMeta();e.each(n.data,function(i,a){var r=i._model,o=e.splineCurve(e.previousItem(n.data,a,!0)._model,r,e.nextItem(n.data,a,!0)._model,r.tension);r.controlPointPreviousX=Math.max(Math.min(o.previous.x,t.right),t.left),r.controlPointPreviousY=Math.max(Math.min(o.previous.y,t.bottom),t.top),r.controlPointNextX=Math.max(Math.min(o.next.x,t.right),t.left),r.controlPointNextY=Math.max(Math.min(o.next.y,t.bottom),t.top),i.pivot()})},draw:function(t){var n=this.getMeta(),i=t||1;e.each(n.data,function(t){t.transition(i)}),n.dataset.transition(i).draw(),e.each(n.data,function(t){t.draw()})},setHoverStyle:function(t){var n=this.chart.data.datasets[t._datasetIndex],i=t.custom||{},a=t._index,r=t._model;r.radius=i.hoverRadius?i.hoverRadius:e.getValueAtIndexOrDefault(n.pointHoverRadius,a,this.chart.options.elements.point.hoverRadius),r.backgroundColor=i.hoverBackgroundColor?i.hoverBackgroundColor:e.getValueAtIndexOrDefault(n.pointHoverBackgroundColor,a,e.getHoverColor(r.backgroundColor)),r.borderColor=i.hoverBorderColor?i.hoverBorderColor:e.getValueAtIndexOrDefault(n.pointHoverBorderColor,a,e.getHoverColor(r.borderColor)),r.borderWidth=i.hoverBorderWidth?i.hoverBorderWidth:e.getValueAtIndexOrDefault(n.pointHoverBorderWidth,a,r.borderWidth)},removeHoverStyle:function(t){var n=this.chart.data.datasets[t._datasetIndex],i=t.custom||{},a=t._index,r=t._model,o=this.chart.options.elements.point;r.radius=i.radius?i.radius:e.getValueAtIndexOrDefault(n.radius,a,o.radius),r.backgroundColor=i.backgroundColor?i.backgroundColor:e.getValueAtIndexOrDefault(n.pointBackgroundColor,a,o.backgroundColor),r.borderColor=i.borderColor?i.borderColor:e.getValueAtIndexOrDefault(n.pointBorderColor,a,o.borderColor),r.borderWidth=i.borderWidth?i.borderWidth:e.getValueAtIndexOrDefault(n.pointBorderWidth,a,o.borderWidth)}})}},{}],21:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.animation={duration:1e3,easing:"easeOutQuart",onProgress:e.noop,onComplete:e.noop},t.Animation=t.Element.extend({currentStep:null,numSteps:60,easing:"",render:null,onAnimationProgress:null,onAnimationComplete:null}),t.animationService={frameDuration:17,animations:[],dropFrames:0,request:null,addAnimation:function(t,e,n,i){var a=this;i||(t.animating=!0);for(var r=0;r1&&(n=Math.floor(t.dropFrames),t.dropFrames=t.dropFrames%1);for(var i=0;it.animations[i].animationObject.numSteps&&(t.animations[i].animationObject.currentStep=t.animations[i].animationObject.numSteps),t.animations[i].animationObject.render(t.animations[i].chartInstance,t.animations[i].animationObject),t.animations[i].animationObject.onAnimationProgress&&t.animations[i].animationObject.onAnimationProgress.call&&t.animations[i].animationObject.onAnimationProgress.call(t.animations[i].chartInstance,t.animations[i]),t.animations[i].animationObject.currentStep===t.animations[i].animationObject.numSteps?(t.animations[i].animationObject.onAnimationComplete&&t.animations[i].animationObject.onAnimationComplete.call&&t.animations[i].animationObject.onAnimationComplete.call(t.animations[i].chartInstance,t.animations[i]),t.animations[i].chartInstance.animating=!1,t.animations.splice(i,1)):++i;var a=Date.now(),r=(a-e)/t.frameDuration;t.dropFrames+=r,t.animations.length>0&&t.requestAnimationFrame()}}}},{}],22:[function(t,e,n){"use strict";e.exports=function(t){var e=t.canvasHelpers={};e.drawPoint=function(e,n,i,a,r){var o,s,l,u,d,c;if("object"==typeof n&&(o=n.toString(),"[object HTMLImageElement]"===o||"[object HTMLCanvasElement]"===o))return void e.drawImage(n,a-n.width/2,r-n.height/2);if(!(isNaN(i)||i<=0)){switch(n){default:e.beginPath(),e.arc(a,r,i,0,2*Math.PI),e.closePath(),e.fill();break;case"triangle":e.beginPath(),s=3*i/Math.sqrt(3),d=s*Math.sqrt(3)/2,e.moveTo(a-s/2,r+d/3),e.lineTo(a+s/2,r+d/3),e.lineTo(a,r-2*d/3),e.closePath(),e.fill();break;case"rect":c=1/Math.SQRT2*i,e.beginPath(),e.fillRect(a-c,r-c,2*c,2*c),e.strokeRect(a-c,r-c,2*c,2*c);break;case"rectRounded":var h=i/Math.SQRT2,f=a-h,g=r-h,m=Math.SQRT2*i;t.helpers.drawRoundedRectangle(e,f,g,m,m,i/2),e.fill();break;case"rectRot":c=1/Math.SQRT2*i,e.beginPath(),e.moveTo(a-c,r),e.lineTo(a,r+c),e.lineTo(a+c,r),e.lineTo(a,r-c),e.closePath(),e.fill();break;case"cross":e.beginPath(),e.moveTo(a,r+i),e.lineTo(a,r-i),e.moveTo(a-i,r),e.lineTo(a+i,r),e.closePath();break;case"crossRot":e.beginPath(),l=Math.cos(Math.PI/4)*i,u=Math.sin(Math.PI/4)*i,e.moveTo(a-l,r-u),e.lineTo(a+l,r+u),e.moveTo(a-l,r+u),e.lineTo(a+l,r-u),e.closePath();break;case"star":e.beginPath(),e.moveTo(a,r+i),e.lineTo(a,r-i),e.moveTo(a-i,r),e.lineTo(a+i,r),l=Math.cos(Math.PI/4)*i,u=Math.sin(Math.PI/4)*i,e.moveTo(a-l,r-u),e.lineTo(a+l,r+u),e.moveTo(a-l,r+u),e.lineTo(a+l,r-u),e.closePath();break;case"line":e.beginPath(),e.moveTo(a-i,r),e.lineTo(a+i,r),e.closePath();break;case"dash":e.beginPath(),e.moveTo(a,r),e.lineTo(a+i,r),e.closePath()}e.stroke()}},e.clipArea=function(t,e){t.save(),t.beginPath(),t.rect(e.left,e.top,e.right-e.left,e.bottom-e.top),t.clip()},e.unclipArea=function(t){t.restore()}}},{}],23:[function(t,e,n){"use strict";e.exports=function(t){function e(e){e=e||{};var n=e.data=e.data||{};return n.datasets=n.datasets||[],n.labels=n.labels||[],e.options=i.configMerge(t.defaults.global,t.defaults[e.type],e.options||{}),e}function n(t){var e=t.options;e.scale?t.scale.options=e.scale:e.scales&&e.scales.xAxes.concat(e.scales.yAxes).forEach(function(e){t.scales[e.id].options=e}),t.tooltip._options=e.tooltips}var i=t.helpers,a=t.plugins,r=t.platform;t.types={},t.instances={},t.controllers={},t.Controller=function(n,a,o){var s=this;a=e(a);var l=r.acquireContext(n,a),u=l&&l.canvas,d=u&&u.height,c=u&&u.width;return o.ctx=l,o.canvas=u,o.config=a,o.width=c,o.height=d,o.aspectRatio=d?c/d:null,s.id=i.uid(),s.chart=o,s.config=a,s.options=a.options,s._bufferedRender=!1,t.instances[s.id]=s,Object.defineProperty(s,"data",{get:function(){return s.config.data}}),l&&u?(s.initialize(),s.update(),s):(console.error("Failed to create chart: can't acquire context from the given item"),s)},i.extend(t.Controller.prototype,{initialize:function(){var t=this;return a.notify(t,"beforeInit"),i.retinaScale(t.chart),t.bindEvents(),t.options.responsive&&t.resize(!0),t.ensureScalesHaveIDs(),t.buildScales(),t.initToolTip(),a.notify(t,"afterInit"),t},clear:function(){return i.clear(this.chart),this},stop:function(){return t.animationService.cancelAnimation(this),this},resize:function(t){var e=this,n=e.chart,r=e.options,o=n.canvas,s=r.maintainAspectRatio&&n.aspectRatio||null,l=Math.floor(i.getMaximumWidth(o)),u=Math.floor(s?l/s:i.getMaximumHeight(o));if((n.width!==l||n.height!==u)&&(o.width=n.width=l,o.height=n.height=u,o.style.width=l+"px",o.style.height=u+"px",i.retinaScale(n),!t)){var d={width:l,height:u};a.notify(e,"resize",[d]),e.options.onResize&&e.options.onResize(e,d),e.stop(),e.update(e.options.responsiveAnimationDuration)}},ensureScalesHaveIDs:function(){var t=this.options,e=t.scales||{},n=t.scale;i.each(e.xAxes,function(t,e){t.id=t.id||"x-axis-"+e}),i.each(e.yAxes,function(t,e){t.id=t.id||"y-axis-"+e}),n&&(n.id=n.id||"scale")},buildScales:function(){var e=this,n=e.options,a=e.scales={},r=[];n.scales&&(r=r.concat((n.scales.xAxes||[]).map(function(t){return{options:t,dtype:"category"}}),(n.scales.yAxes||[]).map(function(t){return{options:t,dtype:"linear"}}))),n.scale&&r.push({options:n.scale,dtype:"radialLinear",isDefault:!0}),i.each(r,function(n){var r=n.options,o=i.getValueOrDefault(r.type,n.dtype),s=t.scaleService.getScaleConstructor(o);if(s){var l=new s({id:r.id,options:r,ctx:e.chart.ctx,chart:e});a[l.id]=l,n.isDefault&&(e.scale=l)}}),t.scaleService.addScalesToLayout(this)},buildOrUpdateControllers:function(){var e=this,n=[],a=[];if(i.each(e.data.datasets,function(i,r){var o=e.getDatasetMeta(r);o.type||(o.type=i.type||e.config.type),n.push(o.type),o.controller?o.controller.updateIndex(r):(o.controller=new t.controllers[o.type](e,r),a.push(o.controller))},e),n.length>1)for(var r=1;r0||(a.forEach(function(e){delete t[e]}),delete t._chartjs)}}var i=t.helpers,a=["push","pop","shift","splice","unshift"];t.DatasetController=function(t,e){this.initialize(t,e)},i.extend(t.DatasetController.prototype,{datasetElementType:null,dataElementType:null,initialize:function(t,e){var n=this;n.chart=t,n.index=e,n.linkScales(),n.addElements()},updateIndex:function(t){this.index=t},linkScales:function(){var t=this,e=t.getMeta(),n=t.getDataset();null===e.xAxisID&&(e.xAxisID=n.xAxisID||t.chart.options.scales.xAxes[0].id),null===e.yAxisID&&(e.yAxisID=n.yAxisID||t.chart.options.scales.yAxes[0].id)},getDataset:function(){return this.chart.data.datasets[this.index]},getMeta:function(){return this.chart.getDatasetMeta(this.index)},getScaleForId:function(t){return this.chart.scales[t]},reset:function(){this.update(!0)},destroy:function(){this._data&&n(this._data,this)},createMetaDataset:function(){var t=this,e=t.datasetElementType;return e&&new e({_chart:t.chart.chart,_datasetIndex:t.index})},createMetaData:function(t){var e=this,n=e.dataElementType;return n&&new n({_chart:e.chart.chart,_datasetIndex:e.index,_index:t})},addElements:function(){var t,e,n=this,i=n.getMeta(),a=n.getDataset().data||[],r=i.data;for(t=0,e=a.length;ti&&t.insertElements(i,a-i)},insertElements:function(t,e){for(var n=0;n=0;a--)e.call(n,t[a],a);else for(a=0;a=i[n].length||!i[n][a].type?i[n].push(r.configMerge(s,e)):e.type&&e.type!==i[n][a].type?i[n][a]=r.configMerge(i[n][a],s,e):i[n][a]=r.configMerge(i[n][a],e)}):(i[n]=[],r.each(e,function(e){var a=r.getValueOrDefault(e.type,"xAxes"===n?"category":"linear");i[n].push(r.configMerge(t.scaleService.getScaleDefaults(a),e))})):i.hasOwnProperty(n)&&"object"==typeof i[n]&&null!==i[n]&&"object"==typeof e?i[n]=r.configMerge(i[n],e):i[n]=e}),i},r.getValueAtIndexOrDefault=function(t,e,n){return void 0===t||null===t?n:r.isArray(t)?e=0;i--){var a=t[i];if(e(a))return a}},r.inherits=function(t){var e=this,n=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return e.apply(this,arguments)},i=function(){this.constructor=n};return i.prototype=e.prototype,n.prototype=new i,n.extend=r.inherits,t&&r.extend(n.prototype,t),n.__super__=e.prototype,n},r.noop=function(){},r.uid=function(){var t=0;return function(){return t++}}(),r.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},r.almostEquals=function(t,e,n){return Math.abs(t-e)t},r.max=function(t){return t.reduce(function(t,e){return isNaN(e)?t:Math.max(t,e)},Number.NEGATIVE_INFINITY)},r.min=function(t){return t.reduce(function(t,e){return isNaN(e)?t:Math.min(t,e)},Number.POSITIVE_INFINITY)},r.sign=Math.sign?function(t){return Math.sign(t)}:function(t){return t=+t,0===t||isNaN(t)?t:t>0?1:-1},r.log10=Math.log10?function(t){return Math.log10(t)}:function(t){return Math.log(t)/Math.LN10},r.toRadians=function(t){return t*(Math.PI/180)},r.toDegrees=function(t){return t*(180/Math.PI)},r.getAngleFromPoint=function(t,e){var n=e.x-t.x,i=e.y-t.y,a=Math.sqrt(n*n+i*i),r=Math.atan2(i,n);return r<-.5*Math.PI&&(r+=2*Math.PI),{angle:r,distance:a}},r.distanceBetweenPoints=function(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))},r.aliasPixel=function(t){return t%2===0?0:.5},r.splineCurve=function(t,e,n,i){var a=t.skip?e:t,r=e,o=n.skip?e:n,s=Math.sqrt(Math.pow(r.x-a.x,2)+Math.pow(r.y-a.y,2)),l=Math.sqrt(Math.pow(o.x-r.x,2)+Math.pow(o.y-r.y,2)),u=s/(s+l),d=l/(s+l);u=isNaN(u)?0:u,d=isNaN(d)?0:d;var c=i*u,h=i*d;return{previous:{x:r.x-c*(o.x-a.x),y:r.y-c*(o.y-a.y)},next:{x:r.x+h*(o.x-a.x),y:r.y+h*(o.y-a.y)}}},r.EPSILON=Number.EPSILON||1e-14,r.splineCurveMonotone=function(t){var e,n,i,a,o=(t||[]).map(function(t){return{model:t._model,deltaK:0,mK:0}}),s=o.length;for(e=0;e0?o[e-1]:null,a=e0?o[e-1]:null,a=e=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},r.previousItem=function(t,e,n){return n?e<=0?t[t.length-1]:t[e-1]:e<=0?t[0]:t[e-1]},r.niceNum=function(t,e){var n,i=Math.floor(r.log10(t)),a=t/Math.pow(10,i);return n=e?a<1.5?1:a<3?2:a<7?5:10:a<=1?1:a<=2?2:a<=5?5:10,n*Math.pow(10,i)};var o=r.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return 1*((t=t/1-1)*t*t+1)},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-1*((t=t/1-1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return 1*(t/=1)*t*t*t*t},easeOutQuint:function(t){return 1*((t=t/1-1)*t*t*t*t+1)},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return-1*Math.cos(t/1*(Math.PI/2))+1},easeOutSine:function(t){return 1*Math.sin(t/1*(Math.PI/2))},easeInOutSine:function(t){return-.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===(t/=1)?1:(n||(n=.3),i0?(n=l[0].clientX,i=l[0].clientY):(n=a.clientX,i=a.clientY);var u=parseFloat(r.getStyle(o,"padding-left")),d=parseFloat(r.getStyle(o,"padding-top")),c=parseFloat(r.getStyle(o,"padding-right")),h=parseFloat(r.getStyle(o,"padding-bottom")),f=s.right-s.left-u-c,g=s.bottom-s.top-d-h;return n=Math.round((n-s.left-u)/f*o.width/e.currentDevicePixelRatio),i=Math.round((i-s.top-d)/g*o.height/e.currentDevicePixelRatio),{x:n,y:i}},r.addEvent=function(t,e,n){t.addEventListener?t.addEventListener(e,n):t.attachEvent?t.attachEvent("on"+e,n):t["on"+e]=n},r.removeEvent=function(t,e,n){t.removeEventListener?t.removeEventListener(e,n,!1):t.detachEvent?t.detachEvent("on"+e,n):t["on"+e]=r.noop},r.getConstraintWidth=function(t){return a(t,"max-width","clientWidth")},r.getConstraintHeight=function(t){return a(t,"max-height","clientHeight")},r.getMaximumWidth=function(t){var e=t.parentNode,n=parseInt(r.getStyle(e,"padding-left"),10),i=parseInt(r.getStyle(e,"padding-right"),10),a=e.clientWidth-n-i,o=r.getConstraintWidth(t);return isNaN(o)?a:Math.min(a,o)},r.getMaximumHeight=function(t){var e=t.parentNode,n=parseInt(r.getStyle(e,"padding-top"),10),i=parseInt(r.getStyle(e,"padding-bottom"),10),a=e.clientHeight-n-i,o=r.getConstraintHeight(t);return isNaN(o)?a:Math.min(a,o)},r.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},r.retinaScale=function(t){var e=t.currentDevicePixelRatio=window.devicePixelRatio||1;if(1!==e){var n=t.canvas,i=t.height,a=t.width;n.height=i*e,n.width=a*e,t.ctx.scale(e,e),n.style.height=i+"px",n.style.width=a+"px"}},r.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},r.fontString=function(t,e,n){return e+" "+t+"px "+n},r.longestText=function(t,e,n,i){i=i||{};var a=i.data=i.data||{},o=i.garbageCollect=i.garbageCollect||[];i.font!==e&&(a=i.data={},o=i.garbageCollect=[],i.font=e),t.font=e;var s=0;r.each(n,function(e){void 0!==e&&null!==e&&r.isArray(e)!==!0?s=r.measureText(t,a,o,s,e):r.isArray(e)&&r.each(e,function(e){void 0===e||null===e||r.isArray(e)||(s=r.measureText(t,a,o,s,e))})});var l=o.length/2;if(l>n.length){for(var u=0;ui&&(i=r),i},r.numberOfLabelLines=function(t){var e=1;return r.each(t,function(t){r.isArray(t)&&t.length>e&&(e=t.length)}),e},r.drawRoundedRectangle=function(t,e,n,i,a,r){t.beginPath(),t.moveTo(e+r,n),t.lineTo(e+i-r,n),t.quadraticCurveTo(e+i,n,e+i,n+r),t.lineTo(e+i,n+a-r),t.quadraticCurveTo(e+i,n+a,e+i-r,n+a),t.lineTo(e+r,n+a),t.quadraticCurveTo(e,n+a,e,n+a-r),t.lineTo(e,n+r),t.quadraticCurveTo(e,n,e+r,n),t.closePath()},r.color=function(e){return i?i(e instanceof CanvasGradient?t.defaults.global.defaultColor:e):(console.error("Color.js not found!"),e)},r.isArray=Array.isArray?function(t){return Array.isArray(t)}:function(t){return"[object Array]"===Object.prototype.toString.call(t)},r.arrayEquals=function(t,e){var n,i,a,o;if(!t||!e||t.length!==e.length)return!1;for(n=0,i=t.length;n0&&(s=t.getDatasetMeta(s[0]._datasetIndex).data),s},"x-axis":function(t,e){return r(t,e,!0)},point:function(t,n){var a=e(n,t.chart);return i(t,a)},nearest:function(t,n,i){var r=e(n,t.chart),o=a(t,r,i.intersect);return o.length>1&&o.sort(function(t,e){var n=t.getArea(),i=e.getArea(),a=n-i;return 0===a&&(a=t._datasetIndex-e._datasetIndex),a}),o.slice(0,1)},x:function(t,i,a){var r=e(i,t.chart),o=[],s=!1;return n(t,function(t){t.inXRange(r.x)&&o.push(t),t.inRange(r.x,r.y)&&(s=!0)}),a.intersect&&!s&&(o=[]),o},y:function(t,i,a){var r=e(i,t.chart),o=[],s=!1;return n(t,function(t){t.inYRange(r.y)&&o.push(t),t.inRange(r.x,r.y)&&(s=!0)}),a.intersect&&!s&&(o=[]),o}}}}},{}],28:[function(t,e,n){"use strict";e.exports=function(){var t=function(e,n){return this.controller=new t.Controller(e,n,this),this.controller};return t.defaults={global:{responsive:!0,responsiveAnimationDuration:0,maintainAspectRatio:!0,events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"nearest",intersect:!0,animationDuration:400},onClick:null,defaultColor:"rgba(0,0,0,0.1)",defaultFontColor:"#666",defaultFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",defaultFontSize:12,defaultFontStyle:"normal",showLines:!0,elements:{},legendCallback:function(t){var e=[];e.push('
      ');for(var n=0;n'),t.data.datasets[n].label&&e.push(t.data.datasets[n].label),e.push("");return e.push("
    "),e.join("")}}},t.Chart=t,t}},{}],29:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers;t.layoutService={defaults:{},addBox:function(t,e){t.boxes||(t.boxes=[]),t.boxes.push(e)},removeBox:function(t,e){t.boxes&&t.boxes.splice(t.boxes.indexOf(e),1)},update:function(t,n,i){function a(t){var e,n=t.isHorizontal();n?(e=t.update(t.options.fullWidth?y:M,S),D-=e.height):(e=t.update(w,_),M-=e.width),C.push({horizontal:n,minSize:e,box:t})}function r(t){var n=e.findNextWhere(C,function(e){return e.box===t});if(n)if(t.isHorizontal()){var i={left:Math.max(F,T),right:Math.max(O,P),top:0,bottom:0};t.update(t.options.fullWidth?y:M,x/2,i)}else t.update(n.minSize.width,D)}function o(t){var n=e.findNextWhere(C,function(e){return e.box===t}),i={left:0,right:0,top:R,bottom:L};n&&t.update(n.minSize.width,D,i)}function s(t){t.isHorizontal()?(t.left=t.options.fullWidth?d:F,t.right=t.options.fullWidth?n-c:F+M,t.top=N,t.bottom=N+t.height,N=t.bottom):(t.left=z,t.right=z+t.width,t.top=R,t.bottom=R+D,z=t.right)}if(t){var l=t.options.layout,u=l?l.padding:null,d=0,c=0,h=0,f=0;isNaN(u)?(d=u.left||0,c=u.right||0,h=u.top||0,f=u.bottom||0):(d=u,c=u,h=u,f=u);var g=e.where(t.boxes,function(t){return"left"===t.options.position}),m=e.where(t.boxes,function(t){return"right"===t.options.position}),p=e.where(t.boxes,function(t){return"top"===t.options.position}),v=e.where(t.boxes,function(t){return"bottom"===t.options.position}),b=e.where(t.boxes,function(t){return"chartArea"===t.options.position});p.sort(function(t,e){return(e.options.fullWidth?1:0)-(t.options.fullWidth?1:0)}),v.sort(function(t,e){return(t.options.fullWidth?1:0)-(e.options.fullWidth?1:0)});var y=n-d-c,x=i-h-f,k=y/2,_=x/2,w=(n-k)/(g.length+m.length),S=(i-_)/(p.length+v.length),M=y,D=x,C=[];e.each(g.concat(m,p,v),a);var T=0,P=0,I=0,A=0;e.each(p.concat(v),function(t){if(t.getPadding){var e=t.getPadding();T=Math.max(T,e.left),P=Math.max(P,e.right)}}),e.each(g.concat(m),function(t){if(t.getPadding){var e=t.getPadding();I=Math.max(I,e.top),A=Math.max(A,e.bottom)}});var F=d,O=c,R=h,L=f;e.each(g.concat(m),r),e.each(g,function(t){F+=t.width}),e.each(m,function(t){O+=t.width}),e.each(p.concat(v),r),e.each(p,function(t){R+=t.height}),e.each(v,function(t){L+=t.height}),e.each(g.concat(m),o),F=d,O=c,R=h,L=f,e.each(g,function(t){F+=t.width}),e.each(m,function(t){O+=t.width}),e.each(p,function(t){R+=t.height}),e.each(v,function(t){L+=t.height});var V=Math.max(T-F,0);F+=V,O+=Math.max(P-O,0);var W=Math.max(I-R,0);R+=W,L+=Math.max(A-L,0);var Y=i-R-L,B=n-F-O;B===M&&Y===D||(e.each(g,function(t){t.height=Y}),e.each(m,function(t){t.height=Y}),e.each(p,function(t){t.options.fullWidth||(t.width=B)}),e.each(v,function(t){t.options.fullWidth||(t.width=B)}),D=Y,M=B);var z=d+V,N=h+W;e.each(g.concat(p),s),z+=M,N+=D,e.each(m,s),e.each(v,s),t.chartArea={left:F,top:R,right:F+M,bottom:R+D},e.each(b,function(e){e.left=t.chartArea.left,e.top=t.chartArea.top,e.right=t.chartArea.right,e.bottom=t.chartArea.bottom,e.update(M,D)})}}}}},{}],30:[function(t,e,n){"use strict";e.exports=function(t){function e(t,e){return t.usePointStyle?e*Math.SQRT2:t.boxWidth}function n(e,n){var i=new t.Legend({ctx:e.chart.ctx,options:n,chart:e});e.legend=i,t.layoutService.addBox(e,i)}var i=t.helpers,a=i.noop;t.defaults.global.legend={display:!0,position:"top",fullWidth:!0,reverse:!1,onClick:function(t,e){var n=e.datasetIndex,i=this.chart,a=i.getDatasetMeta(n);a.hidden=null===a.hidden?!i.data.datasets[n].hidden:null,i.update()},onHover:null,labels:{boxWidth:40,padding:10,generateLabels:function(t){var e=t.data;return i.isArray(e.datasets)?e.datasets.map(function(e,n){return{text:e.label,fillStyle:i.isArray(e.backgroundColor)?e.backgroundColor[0]:e.backgroundColor,hidden:!t.isDatasetVisible(n),lineCap:e.borderCapStyle,lineDash:e.borderDash,lineDashOffset:e.borderDashOffset,lineJoin:e.borderJoinStyle,lineWidth:e.borderWidth,strokeStyle:e.borderColor,pointStyle:e.pointStyle,datasetIndex:n}},this):[]}}},t.Legend=t.Element.extend({initialize:function(t){i.extend(this,t),this.legendHitBoxes=[],this.doughnutMode=!1},beforeUpdate:a,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:a,beforeSetDimensions:a,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:a,beforeBuildLabels:a,buildLabels:function(){var t=this,e=t.options.labels,n=e.generateLabels.call(t,t.chart);e.filter&&(n=n.filter(function(n){return e.filter(n,t.chart.data)})),t.options.reverse&&n.reverse(),t.legendItems=n},afterBuildLabels:a,beforeFit:a,fit:function(){var n=this,a=n.options,r=a.labels,o=a.display,s=n.ctx,l=t.defaults.global,u=i.getValueOrDefault,d=u(r.fontSize,l.defaultFontSize),c=u(r.fontStyle,l.defaultFontStyle),h=u(r.fontFamily,l.defaultFontFamily),f=i.fontString(d,c,h),g=n.legendHitBoxes=[],m=n.minSize,p=n.isHorizontal();if(p?(m.width=n.maxWidth,m.height=o?10:0):(m.width=o?10:0,m.height=n.maxHeight),o)if(s.font=f,p){var v=n.lineWidths=[0],b=n.legendItems.length?d+r.padding:0;s.textAlign="left",s.textBaseline="top",i.each(n.legendItems,function(t,i){var a=e(r,d),o=a+d/2+s.measureText(t.text).width;v[v.length-1]+o+r.padding>=n.width&&(b+=d+r.padding,v[v.length]=n.left),g[i]={left:0,top:0,width:o,height:d},v[v.length-1]+=o+r.padding}),m.height+=b}else{var y=r.padding,x=n.columnWidths=[],k=r.padding,_=0,w=0,S=d+y;i.each(n.legendItems,function(t,n){var i=e(r,d),a=i+d/2+s.measureText(t.text).width;w+S>m.height&&(k+=_+r.padding,x.push(_),_=0,w=0),_=Math.max(_,a),w+=S,g[n]={left:0,top:0,width:a,height:d}}),k+=_,x.push(_),m.width+=k}n.width=m.width,n.height=m.height},afterFit:a,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var n=this,a=n.options,r=a.labels,o=t.defaults.global,s=o.elements.line,l=n.width,u=n.lineWidths;if(a.display){var d,c=n.ctx,h=i.getValueOrDefault,f=h(r.fontColor,o.defaultFontColor),g=h(r.fontSize,o.defaultFontSize),m=h(r.fontStyle,o.defaultFontStyle),p=h(r.fontFamily,o.defaultFontFamily),v=i.fontString(g,m,p);c.textAlign="left",c.textBaseline="top",c.lineWidth=.5,c.strokeStyle=f,c.fillStyle=f,c.font=v;var b=e(r,g),y=n.legendHitBoxes,x=function(e,n,i){if(!(isNaN(b)||b<=0)){c.save(),c.fillStyle=h(i.fillStyle,o.defaultColor),c.lineCap=h(i.lineCap,s.borderCapStyle),c.lineDashOffset=h(i.lineDashOffset,s.borderDashOffset),c.lineJoin=h(i.lineJoin,s.borderJoinStyle),c.lineWidth=h(i.lineWidth,s.borderWidth),c.strokeStyle=h(i.strokeStyle,o.defaultColor);var r=0===h(i.lineWidth,s.borderWidth);if(c.setLineDash&&c.setLineDash(h(i.lineDash,s.borderDash)),a.labels&&a.labels.usePointStyle){var l=g*Math.SQRT2/2,u=l/Math.SQRT2,d=e+u,f=n+u;t.canvasHelpers.drawPoint(c,i.pointStyle,l,d,f)}else r||c.strokeRect(e,n,b,g),c.fillRect(e,n,b,g);c.restore()}},k=function(t,e,n,i){c.fillText(n.text,b+g/2+t,e),n.hidden&&(c.beginPath(),c.lineWidth=2,c.moveTo(b+g/2+t,e+g/2),c.lineTo(b+g/2+t+i,e+g/2),c.stroke())},_=n.isHorizontal();d=_?{x:n.left+(l-u[0])/2,y:n.top+r.padding,line:0}:{x:n.left+r.padding,y:n.top+r.padding,line:0};var w=g+r.padding;i.each(n.legendItems,function(t,e){var i=c.measureText(t.text).width,a=b+g/2+i,o=d.x,s=d.y;_?o+a>=l&&(s=d.y+=w,d.line++,o=d.x=n.left+(l-u[d.line])/2):s+w>n.bottom&&(o=d.x=o+n.columnWidths[d.line]+r.padding,s=d.y=n.top+r.padding,d.line++),x(o,s,t),y[e].left=o,y[e].top=s,k(o,s,t,i),_?d.x+=a+r.padding:d.y+=w})}},handleEvent:function(t){var e=this,n=e.options,i="mouseup"===t.type?"click":t.type,a=!1;if("mousemove"===i){if(!n.onHover)return}else{if("click"!==i)return;if(!n.onClick)return}var r=t.x,o=t.y;if(r>=e.left&&r<=e.right&&o>=e.top&&o<=e.bottom)for(var s=e.legendHitBoxes,l=0;l=u.left&&r<=u.left+u.width&&o>=u.top&&o<=u.top+u.height){if("click"===i){n.onClick.call(e,t.native,e.legendItems[l]),a=!0;break}if("mousemove"===i){n.onHover.call(e,t.native,e.legendItems[l]),a=!0;break}}}return a}}),t.plugins.register({beforeInit:function(t){var e=t.options.legend;e&&n(t,e)},beforeUpdate:function(e){var a=e.options.legend;a?(a=i.configMerge(t.defaults.global.legend,a),e.legend?e.legend.options=a:n(e,a)):(t.layoutService.removeBox(e,e.legend),delete e.legend)},afterEvent:function(t,e){var n=t.legend;n&&n.handleEvent(e)}})}},{}],31:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.plugins={},t.plugins={_plugins:[],_cacheId:0,register:function(t){var e=this._plugins;[].concat(t).forEach(function(t){e.indexOf(t)===-1&&e.push(t)}),this._cacheId++},unregister:function(t){var e=this._plugins;[].concat(t).forEach(function(t){var n=e.indexOf(t);n!==-1&&e.splice(n,1)}),this._cacheId++},clear:function(){this._plugins=[],this._cacheId++},count:function(){return this._plugins.length},getAll:function(){return this._plugins},notify:function(t,e,n){var i,a,r,o,s,l=this.descriptors(t),u=l.length;for(i=0;ic&&ot.maxHeight){o--;break}o++,d=s*u}t.labelRotation=o},afterCalculateTickRotation:function(){i.callCallback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){i.callCallback(this.options.beforeFit,[this])},fit:function(){var t=this,a=t.minSize={width:0,height:0},r=t.options,o=r.ticks,s=r.scaleLabel,l=r.gridLines,u=r.display,d=t.isHorizontal(),c=n(o),h=1.5*n(s).size,f=r.gridLines.tickMarkLength;if(d?a.width=t.isFullWidth()?t.maxWidth-t.margins.left-t.margins.right:t.maxWidth:a.width=u&&l.drawTicks?f:0,d?a.height=u&&l.drawTicks?f:0:a.height=t.maxHeight,s.display&&u&&(d?a.height+=h:a.width+=h),o.display&&u){var g=i.longestText(t.ctx,c.font,t.ticks,t.longestTextCache),m=i.numberOfLabelLines(t.ticks),p=.5*c.size;if(d){t.longestLabelWidth=g;var v=i.toRadians(t.labelRotation),b=Math.cos(v),y=Math.sin(v),x=y*g+c.size*m+p*m;a.height=Math.min(t.maxHeight,a.height+x),t.ctx.font=c.font;var k=t.ticks[0],_=e(t.ctx,k,c.font),w=t.ticks[t.ticks.length-1],S=e(t.ctx,w,c.font);0!==t.labelRotation?(t.paddingLeft="bottom"===r.position?b*_+3:b*p+3,t.paddingRight="bottom"===r.position?b*p+3:b*S+3):(t.paddingLeft=_/2+3,t.paddingRight=S/2+3)}else o.mirror?g=0:g+=t.options.ticks.padding,a.width+=g,t.paddingTop=c.size/2,t.paddingBottom=c.size/2}t.handleMargins(),t.width=a.width,t.height=a.height},handleMargins:function(){var t=this;t.margins&&(t.paddingLeft=Math.max(t.paddingLeft-t.margins.left,0),t.paddingTop=Math.max(t.paddingTop-t.margins.top,0),t.paddingRight=Math.max(t.paddingRight-t.margins.right,0),t.paddingBottom=Math.max(t.paddingBottom-t.margins.bottom,0))},afterFit:function(){i.callCallback(this.options.afterFit,[this])},isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},isFullWidth:function(){return this.options.fullWidth},getRightValue:function(t){return null===t||"undefined"==typeof t?NaN:"number"!=typeof t||isFinite(t)?"object"==typeof t?t instanceof Date||t.isValid?t:this.getRightValue(this.isHorizontal()?t.x:t.y):t:NaN},getLabelForIndex:i.noop,getPixelForValue:i.noop,getValueForPixel:i.noop,getPixelForTick:function(t,e){var n=this;if(n.isHorizontal()){var i=n.width-(n.paddingLeft+n.paddingRight),a=i/Math.max(n.ticks.length-(n.options.gridLines.offsetGridLines?0:1),1),r=a*t+n.paddingLeft;e&&(r+=a/2);var o=n.left+Math.round(r);return o+=n.isFullWidth()?n.margins.left:0}var s=n.height-(n.paddingTop+n.paddingBottom);return n.top+t*(s/(n.ticks.length-1))},getPixelForDecimal:function(t){var e=this;if(e.isHorizontal()){var n=e.width-(e.paddingLeft+e.paddingRight),i=n*t+e.paddingLeft,a=e.left+Math.round(i);return a+=e.isFullWidth()?e.margins.left:0}return e.top+t*e.height},getBasePixel:function(){return this.getPixelForValue(this.getBaseValue())},getBaseValue:function(){var t=this,e=t.min,n=t.max;return t.beginAtZero?0:e<0&&n<0?n:e>0&&n>0?e:0},draw:function(e){var a=this,r=a.options;if(r.display){var o,s,l=a.ctx,u=t.defaults.global,d=r.ticks,c=r.gridLines,h=r.scaleLabel,f=0!==a.labelRotation,g=d.autoSkip,m=a.isHorizontal();d.maxTicksLimit&&(s=d.maxTicksLimit);var p=i.getValueOrDefault(d.fontColor,u.defaultFontColor),v=n(d),b=c.drawTicks?c.tickMarkLength:0,y=i.getValueOrDefault(c.borderDash,u.borderDash),x=i.getValueOrDefault(c.borderDashOffset,u.borderDashOffset),k=i.getValueOrDefault(h.fontColor,u.defaultFontColor),_=n(h),w=i.toRadians(a.labelRotation),S=Math.cos(w),M=a.longestLabelWidth*S;l.fillStyle=p;var D=[];if(m){if(o=!1,f&&(M/=2),(M+d.autoSkipPadding)*a.ticks.length>a.width-(a.paddingLeft+a.paddingRight)&&(o=1+Math.floor((M+d.autoSkipPadding)*a.ticks.length/(a.width-(a.paddingLeft+a.paddingRight)))),s&&a.ticks.length>s)for(;!o||a.ticks.length/(o||1)>s;)o||(o=1),o+=1;g||(o=!1)}var C="right"===r.position?a.left:a.right-b,T="right"===r.position?a.left+b:a.right,P="bottom"===r.position?a.top:a.bottom-b,I="bottom"===r.position?a.top+b:a.bottom;if(i.each(a.ticks,function(t,n){if(void 0!==t&&null!==t){var s=a.ticks.length===n+1,l=o>1&&n%o>0||n%o===0&&n+o>=a.ticks.length;if((!l||s)&&void 0!==t&&null!==t){var u,h;n===("undefined"!=typeof a.zeroLineIndex?a.zeroLineIndex:0)?(u=c.zeroLineWidth,h=c.zeroLineColor):(u=i.getValueAtIndexOrDefault(c.lineWidth,n),h=i.getValueAtIndexOrDefault(c.color,n));var g,p,v,k,_,S,M,A,F,O,R="middle",L="middle";if(m){"bottom"===r.position?(L=f?"middle":"top",R=f?"right":"center",O=a.top+b):(L=f?"middle":"bottom",R=f?"left":"center",O=a.bottom-b);var V=a.getPixelForTick(n)+i.aliasPixel(u);F=a.getPixelForTick(n,c.offsetGridLines)+d.labelOffset,g=v=_=M=V,p=P,k=I,S=e.top,A=e.bottom}else{var W,Y="left"===r.position,B=d.padding;d.mirror?(R=Y?"left":"right",W=B):(R=Y?"right":"left",W=b+B),F=Y?a.right-W:a.left+W;var z=a.getPixelForTick(n);z+=i.aliasPixel(u),O=a.getPixelForTick(n,c.offsetGridLines),g=C,v=T,_=e.left,M=e.right,p=k=S=A=z}D.push({tx1:g,ty1:p,tx2:v,ty2:k,x1:_,y1:S,x2:M,y2:A,labelX:F,labelY:O,glWidth:u,glColor:h,glBorderDash:y,glBorderDashOffset:x,rotation:-1*w,label:t,textBaseline:L,textAlign:R})}}}),i.each(D,function(t){if(c.display&&(l.save(),l.lineWidth=t.glWidth,l.strokeStyle=t.glColor,l.setLineDash&&(l.setLineDash(t.glBorderDash),l.lineDashOffset=t.glBorderDashOffset),l.beginPath(),c.drawTicks&&(l.moveTo(t.tx1,t.ty1),l.lineTo(t.tx2,t.ty2)),c.drawOnChartArea&&(l.moveTo(t.x1,t.y1),l.lineTo(t.x2,t.y2)),l.stroke(),l.restore()),d.display){l.save(),l.translate(t.labelX,t.labelY),l.rotate(t.rotation),l.font=v.font,l.textBaseline=t.textBaseline,l.textAlign=t.textAlign;var e=t.label;if(i.isArray(e))for(var n=0,a=0;n0)i=t.stepSize;else{var r=e.niceNum(n.max-n.min,!1);i=e.niceNum(r/(t.maxTicks-1),!0)}var o=Math.floor(n.min/i)*i,s=Math.ceil(n.max/i)*i;t.min&&t.max&&t.stepSize&&e.almostWhole((t.max-t.min)/t.stepSize,i/1e3)&&(o=t.min,s=t.max);var l=(s-o)/i;l=e.almostEquals(l,Math.round(l),i/1e3)?Math.round(l):Math.ceil(l),a.push(void 0!==t.min?t.min:o);for(var u=1;u3?i[2]-i[1]:i[1]-i[0];Math.abs(a)>1&&t!==Math.floor(t)&&(a=t-Math.floor(t));var r=e.log10(Math.abs(a)),o="";if(0!==t){var s=-1*Math.floor(r);s=Math.max(Math.min(s,20),0),o=t.toFixed(s)}else o="0";return o},logarithmic:function(t,n,i){var a=t/Math.pow(10,Math.floor(e.log10(t)));return 0===t?"0":1===a||2===a||5===a||0===n||n===i.length-1?t.toExponential():""}}}}},{}],35:[function(t,e,n){"use strict";e.exports=function(t){function e(e,n){var i=new t.Title({ctx:e.chart.ctx,options:n,chart:e});e.titleBlock=i,t.layoutService.addBox(e,i)}var n=t.helpers;t.defaults.global.title={display:!1,position:"top",fullWidth:!0,fontStyle:"bold",padding:10,text:""};var i=n.noop;t.Title=t.Element.extend({initialize:function(t){var e=this;n.extend(e,t),e.legendHitBoxes=[]},beforeUpdate:i,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:i,beforeSetDimensions:i,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:i,beforeBuildLabels:i,buildLabels:i,afterBuildLabels:i,beforeFit:i,fit:function(){var e=this,i=n.getValueOrDefault,a=e.options,r=t.defaults.global,o=a.display,s=i(a.fontSize,r.defaultFontSize),l=e.minSize;e.isHorizontal()?(l.width=e.maxWidth,l.height=o?s+2*a.padding:0):(l.width=o?s+2*a.padding:0,l.height=e.maxHeight),e.width=l.width,e.height=l.height},afterFit:i,isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},draw:function(){var e=this,i=e.ctx,a=n.getValueOrDefault,r=e.options,o=t.defaults.global;if(r.display){var s,l,u,d=a(r.fontSize,o.defaultFontSize),c=a(r.fontStyle,o.defaultFontStyle),h=a(r.fontFamily,o.defaultFontFamily),f=n.fontString(d,c,h),g=0,m=e.top,p=e.left,v=e.bottom,b=e.right;i.fillStyle=a(r.fontColor,o.defaultFontColor),i.font=f,e.isHorizontal()?(s=p+(b-p)/2,l=m+(v-m)/2,u=b-p):(s="left"===r.position?p+d/2:b-d/2,l=m+(v-m)/2,u=v-m,g=Math.PI*("left"===r.position?-.5:.5)),i.save(),i.translate(s,l),i.rotate(g),i.textAlign="center",i.textBaseline="middle",i.fillText(r.text,0,0,u),i.restore()}}}),t.plugins.register({beforeInit:function(t){var n=t.options.title;n&&e(t,n)},beforeUpdate:function(i){var a=i.options.title;a?(a=n.configMerge(t.defaults.global.title,a),i.titleBlock?i.titleBlock.options=a:e(i,a)):(t.layoutService.removeBox(i,i.titleBlock),delete i.titleBlock)}})}},{}],36:[function(t,e,n){"use strict";e.exports=function(t){function e(t,e){var n=l.color(t);return n.alpha(e*n.alpha()).rgbaString()}function n(t,e){return e&&(l.isArray(e)?Array.prototype.push.apply(t,e):t.push(e)),t}function i(t){var e=t._xScale,n=t._yScale||t._scale,i=t._index,a=t._datasetIndex;return{xLabel:e?e.getLabelForIndex(i,a):"",yLabel:n?n.getLabelForIndex(i,a):"",index:i,datasetIndex:a,x:t._model.x,y:t._model.y}}function a(e){var n=t.defaults.global,i=l.getValueOrDefault;return{xPadding:e.xPadding,yPadding:e.yPadding,xAlign:e.xAlign,yAlign:e.yAlign,bodyFontColor:e.bodyFontColor,_bodyFontFamily:i(e.bodyFontFamily,n.defaultFontFamily),_bodyFontStyle:i(e.bodyFontStyle,n.defaultFontStyle),_bodyAlign:e.bodyAlign,bodyFontSize:i(e.bodyFontSize,n.defaultFontSize),bodySpacing:e.bodySpacing,titleFontColor:e.titleFontColor,_titleFontFamily:i(e.titleFontFamily,n.defaultFontFamily),_titleFontStyle:i(e.titleFontStyle,n.defaultFontStyle),titleFontSize:i(e.titleFontSize,n.defaultFontSize),_titleAlign:e.titleAlign,titleSpacing:e.titleSpacing, +titleMarginBottom:e.titleMarginBottom,footerFontColor:e.footerFontColor,_footerFontFamily:i(e.footerFontFamily,n.defaultFontFamily),_footerFontStyle:i(e.footerFontStyle,n.defaultFontStyle),footerFontSize:i(e.footerFontSize,n.defaultFontSize),_footerAlign:e.footerAlign,footerSpacing:e.footerSpacing,footerMarginTop:e.footerMarginTop,caretSize:e.caretSize,cornerRadius:e.cornerRadius,backgroundColor:e.backgroundColor,opacity:0,legendColorBackground:e.multiKeyBackground,displayColors:e.displayColors}}function r(t,e){var n=t._chart.ctx,i=2*e.yPadding,a=0,r=e.body,o=r.reduce(function(t,e){return t+e.before.length+e.lines.length+e.after.length},0);o+=e.beforeBody.length+e.afterBody.length;var s=e.title.length,u=e.footer.length,d=e.titleFontSize,c=e.bodyFontSize,h=e.footerFontSize;i+=s*d,i+=s?(s-1)*e.titleSpacing:0,i+=s?e.titleMarginBottom:0,i+=o*c,i+=o?(o-1)*e.bodySpacing:0,i+=u?e.footerMarginTop:0,i+=u*h,i+=u?(u-1)*e.footerSpacing:0;var f=0,g=function(t){a=Math.max(a,n.measureText(t).width+f)};return n.font=l.fontString(d,e._titleFontStyle,e._titleFontFamily),l.each(e.title,g),n.font=l.fontString(c,e._bodyFontStyle,e._bodyFontFamily),l.each(e.beforeBody.concat(e.afterBody),g),f=e.displayColors?c+2:0,l.each(r,function(t){l.each(t.before,g),l.each(t.lines,g),l.each(t.after,g)}),f=0,n.font=l.fontString(h,e._footerFontStyle,e._footerFontFamily),l.each(e.footer,g),a+=2*e.xPadding,{width:a,height:i}}function o(t,e){var n=t._model,i=t._chart,a=t._chartInstance.chartArea,r="center",o="center";n.yi.height-e.height&&(o="bottom");var s,l,u,d,c,h=(a.left+a.right)/2,f=(a.top+a.bottom)/2;"center"===o?(s=function(t){return t<=h},l=function(t){return t>h}):(s=function(t){return t<=e.width/2},l=function(t){return t>=i.width-e.width/2}),u=function(t){return t+e.width>i.width},d=function(t){return t-e.width<0},c=function(t){return t<=f?"top":"bottom"},s(n.x)?(r="left",u(n.x)&&(r="center",o=c(n.y))):l(n.x)&&(r="right",d(n.x)&&(r="center",o=c(n.y)));var g=t._options;return{xAlign:g.xAlign?g.xAlign:r,yAlign:g.yAlign?g.yAlign:o}}function s(t,e,n){var i=t.x,a=t.y,r=t.caretSize,o=t.caretPadding,s=t.cornerRadius,l=n.xAlign,u=n.yAlign,d=r+o,c=s+o;return"right"===l?i-=e.width:"center"===l&&(i-=e.width/2),"top"===u?a+=d:a-="bottom"===u?e.height+d:e.height/2,"center"===u?"left"===l?i+=d:"right"===l&&(i-=d):"left"===l?i-=c:"right"===l&&(i+=c),{x:i,y:a}}var l=t.helpers;t.defaults.global.tooltips={enabled:!0,custom:null,mode:"nearest",position:"average",intersect:!0,backgroundColor:"rgba(0,0,0,0.8)",titleFontStyle:"bold",titleSpacing:2,titleMarginBottom:6,titleFontColor:"#fff",titleAlign:"left",bodySpacing:2,bodyFontColor:"#fff",bodyAlign:"left",footerFontStyle:"bold",footerSpacing:2,footerMarginTop:6,footerFontColor:"#fff",footerAlign:"left",yPadding:6,xPadding:6,caretSize:5,cornerRadius:6,multiKeyBackground:"#fff",displayColors:!0,callbacks:{beforeTitle:l.noop,title:function(t,e){var n="",i=e.labels,a=i?i.length:0;if(t.length>0){var r=t[0];r.xLabel?n=r.xLabel:a>0&&r.indexl;)r-=2*Math.PI;for(;r=s&&r<=l,d=o>=i.innerRadius&&o<=i.outerRadius;return u&&d}return!1},getCenterPoint:function(){var t=this._view,e=(t.startAngle+t.endAngle)/2,n=(t.innerRadius+t.outerRadius)/2;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},getArea:function(){var t=this._view;return Math.PI*((t.endAngle-t.startAngle)/(2*Math.PI))*(Math.pow(t.outerRadius,2)-Math.pow(t.innerRadius,2))},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,n=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},draw:function(){var t=this._chart.ctx,e=this._view,n=e.startAngle,i=e.endAngle;t.beginPath(),t.arc(e.x,e.y,e.outerRadius,n,i),t.arc(e.x,e.y,e.innerRadius,i,n,!0),t.closePath(),t.strokeStyle=e.borderColor,t.lineWidth=e.borderWidth,t.fillStyle=e.backgroundColor,t.fill(),t.lineJoin="bevel",e.borderWidth&&t.stroke()}})}},{}],38:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers,n=t.defaults.global;t.defaults.global.elements.line={tension:.4,backgroundColor:n.defaultColor,borderWidth:3,borderColor:n.defaultColor,borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",capBezierPoints:!0,fill:!0},t.elements.Line=t.Element.extend({draw:function(){function t(t,e){var n=e._view;e._view.steppedLine===!0?(l.lineTo(n.x,t._view.y),l.lineTo(n.x,n.y)):0===e._view.tension?l.lineTo(n.x,n.y):l.bezierCurveTo(t._view.controlPointNextX,t._view.controlPointNextY,n.controlPointPreviousX,n.controlPointPreviousY,n.x,n.y)}var i=this,a=i._view,r=a.spanGaps,o=a.scaleZero,s=i._loop;s||("top"===a.fill?o=a.scaleTop:"bottom"===a.fill&&(o=a.scaleBottom));var l=i._chart.ctx;l.save();var u=i._children.slice(),d=-1;s&&u.length&&u.push(u[0]);var c,h,f,g;if(u.length&&a.fill){for(l.beginPath(),c=0;ce?1:-1,o=1,s=u.borderSkipped||"left"):(e=u.x-u.width/2,n=u.x+u.width/2,i=u.y,a=u.base,r=1,o=a>i?1:-1,s=u.borderSkipped||"bottom"),d){var c=Math.min(Math.abs(e-n),Math.abs(i-a));d=d>c?c:d;var h=d/2,f=e+("left"!==s?h*r:0),g=n+("right"!==s?-h*r:0),m=i+("top"!==s?h*o:0),p=a+("bottom"!==s?-h*o:0);f!==g&&(i=m,a=p),m!==p&&(e=f,n=g)}l.beginPath(),l.fillStyle=u.backgroundColor,l.strokeStyle=u.borderColor,l.lineWidth=d;var v=[[e,a],[e,i],[n,i],[n,a]],b=["bottom","left","top","right"],y=b.indexOf(s,0);y===-1&&(y=0);var x=t(0);l.moveTo(x[0],x[1]);for(var k=1;k<4;k++)x=t(k),l.lineTo(x[0],x[1]);l.fill(),d&&l.stroke()},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){var i=!1;if(this._view){var a=n(this);i=t>=a.left&&t<=a.right&&e>=a.top&&e<=a.bottom}return i},inLabelRange:function(t,i){var a=this;if(!a._view)return!1;var r=!1,o=n(a);return r=e(a)?t>=o.left&&t<=o.right:i>=o.top&&i<=o.bottom},inXRange:function(t){var e=n(this);return t>=e.left&&t<=e.right},inYRange:function(t){var e=n(this);return t>=e.top&&t<=e.bottom},getCenterPoint:function(){var t,n,i=this._view;return e(this)?(t=i.x,n=(i.y+i.base)/2):(t=(i.x+i.base)/2,n=i.y),{x:t,y:n}},getArea:function(){var t=this._view;return t.width*Math.abs(t.y-t.base)},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}})}},{}],41:[function(t,e,n){"use strict";e.exports=function(t){function e(t,e){var n=l.getStyle(t,e),i=n&&n.match(/(\d+)px/);return i?Number(i[1]):void 0}function n(t,n){var i=t.style,a=t.getAttribute("height"),r=t.getAttribute("width");if(t._chartjs={initial:{height:a,width:r,style:{display:i.display,height:i.height,width:i.width}}},i.display=i.display||"block",null===r||""===r){var o=e(t,"width");void 0!==o&&(t.width=o)}if(null===a||""===a)if(""===t.style.height)t.height=t.width/(n.options.aspectRatio||2);else{var s=e(t,"height");void 0!==o&&(t.height=s)}return t}function i(t,e,n,i,a){return{type:t,chart:e,native:a||null,x:void 0!==n?n:null,y:void 0!==i?i:null}}function a(t,e){var n=u[t.type]||t.type,a=l.getRelativePosition(t,e);return i(n,e,a.x,a.y,t)}function r(t){var e=document.createElement("iframe");return e.className="chartjs-hidden-iframe",e.style.cssText="display:block;overflow:hidden;border:0;margin:0;top:0;left:0;bottom:0;right:0;height:100%;width:100%;position:absolute;pointer-events:none;z-index:-1;",e.tabIndex=-1,l.addEvent(e,"load",function(){l.addEvent(e.contentWindow||e,"resize",t),t()}),e}function o(t,e,n){var a=t._chartjs={ticking:!1},o=function(){a.ticking||(a.ticking=!0,l.requestAnimFrame.call(window,function(){if(a.resizer)return a.ticking=!1,e(i("resize",n))}))};a.resizer=r(o),t.insertBefore(a.resizer,t.firstChild)}function s(t){if(t&&t._chartjs){var e=t._chartjs.resizer;e&&(e.parentNode.removeChild(e),t._chartjs.resizer=null),delete t._chartjs}}var l=t.helpers,u={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"};return{acquireContext:function(t,e){if("string"==typeof t?t=document.getElementById(t):t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas),t instanceof HTMLCanvasElement){var i=t.getContext&&t.getContext("2d");if(i instanceof CanvasRenderingContext2D)return n(t,e),i}return null},releaseContext:function(t){var e=t.canvas;if(e._chartjs){var n=e._chartjs.initial;["height","width"].forEach(function(t){var i=n[t];void 0===i||null===i?e.removeAttribute(t):e.setAttribute(t,i)}),l.each(n.style||{},function(t,n){e.style[n]=t}),e.width=e.width,delete e._chartjs}},addEventListener:function(t,e,n){var i=t.chart.canvas;if("resize"===e)return void o(i.parentNode,n,t.chart);var r=n._chartjs||(n._chartjs={}),s=r.proxies||(r.proxies={}),u=s[t.id+"_"+e]=function(e){n(a(e,t.chart))};l.addEvent(i,e,u)},removeEventListener:function(t,e,n){var i=t.chart.canvas;if("resize"===e)return void s(i.parentNode,n);var a=n._chartjs||{},r=a.proxies||{},o=r[t.id+"_"+e];o&&l.removeEvent(i,e,o)}}}},{}],42:[function(t,e,n){"use strict";var i=t(41);e.exports=function(t){t.platform={acquireContext:function(){},releaseContext:function(){},addEventListener:function(){},removeEventListener:function(){}},t.helpers.extend(t.platform,i(t))}},{41:41}],43:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers,n={position:"bottom"},i=t.Scale.extend({getLabels:function(){var t=this.chart.data;return(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels},determineDataLimits:function(){var t=this,n=t.getLabels();t.minIndex=0,t.maxIndex=n.length-1;var i;void 0!==t.options.ticks.min&&(i=e.indexOf(n,t.options.ticks.min),t.minIndex=i!==-1?i:t.minIndex),void 0!==t.options.ticks.max&&(i=e.indexOf(n,t.options.ticks.max),t.maxIndex=i!==-1?i:t.maxIndex),t.min=n[t.minIndex],t.max=n[t.maxIndex]},buildTicks:function(){var t=this,e=t.getLabels();t.ticks=0===t.minIndex&&t.maxIndex===e.length-1?e:e.slice(t.minIndex,t.maxIndex+1)},getLabelForIndex:function(t,e){var n=this,i=n.chart.data,a=n.isHorizontal();return i.yLabels&&!a?n.getRightValue(i.datasets[e].data[t]):n.ticks[t-n.minIndex]},getPixelForValue:function(t,e,n,i){var a=this,r=Math.max(a.maxIndex+1-a.minIndex-(a.options.gridLines.offsetGridLines?0:1),1);if(void 0!==t&&isNaN(e)){var o=a.getLabels(),s=o.indexOf(t);e=s!==-1?s:e}if(a.isHorizontal()){var l=a.width/r,u=l*(e-a.minIndex);return(a.options.gridLines.offsetGridLines&&i||a.maxIndex===a.minIndex&&i)&&(u+=l/2),a.left+Math.round(u)}var d=a.height/r,c=d*(e-a.minIndex);return a.options.gridLines.offsetGridLines&&i&&(c+=d/2),a.top+Math.round(c)},getPixelForTick:function(t,e){return this.getPixelForValue(this.ticks[t],t+this.minIndex,null,e)},getValueForPixel:function(t){var e,n=this,i=Math.max(n.ticks.length-(n.options.gridLines.offsetGridLines?0:1),1),a=n.isHorizontal(),r=(a?n.width:n.height)/i;return t-=a?n.left:n.top,n.options.gridLines.offsetGridLines&&(t-=r/2),e=t<=0?0:Math.round(t/r)},getBasePixel:function(){return this.bottom}});t.scaleService.registerScaleType("category",i,n)}},{}],44:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers,n={position:"left",ticks:{callback:t.Ticks.formatters.linear}},i=t.LinearScaleBase.extend({determineDataLimits:function(){function t(t){return s?t.xAxisID===n.id:t.yAxisID===n.id}var n=this,i=n.options,a=n.chart,r=a.data,o=r.datasets,s=n.isHorizontal();n.min=null,n.max=null;var l=i.stacked;if(void 0===l&&e.each(o,function(e,n){if(!l){var i=a.getDatasetMeta(n);a.isDatasetVisible(n)&&t(i)&&void 0!==i.stack&&(l=!0)}}),i.stacked||l){var u={};e.each(o,function(r,o){var s=a.getDatasetMeta(o),l=[s.type,void 0===i.stacked&&void 0===s.stack?o:"",s.stack].join(".");void 0===u[l]&&(u[l]={positiveValues:[],negativeValues:[]});var d=u[l].positiveValues,c=u[l].negativeValues;a.isDatasetVisible(o)&&t(s)&&e.each(r.data,function(t,e){var a=+n.getRightValue(t);isNaN(a)||s.data[e].hidden||(d[e]=d[e]||0,c[e]=c[e]||0,i.relativePoints?d[e]=100:a<0?c[e]+=a:d[e]+=a)})}),e.each(u,function(t){var i=t.positiveValues.concat(t.negativeValues),a=e.min(i),r=e.max(i);n.min=null===n.min?a:Math.min(n.min,a),n.max=null===n.max?r:Math.max(n.max,r)})}else e.each(o,function(i,r){var o=a.getDatasetMeta(r);a.isDatasetVisible(r)&&t(o)&&e.each(i.data,function(t,e){var i=+n.getRightValue(t);isNaN(i)||o.data[e].hidden||(null===n.min?n.min=i:in.max&&(n.max=i))})});this.handleTickRangeOptions()},getTickLimit:function(){var n,i=this,a=i.options.ticks;if(i.isHorizontal())n=Math.min(a.maxTicksLimit?a.maxTicksLimit:11,Math.ceil(i.width/50));else{var r=e.getValueOrDefault(a.fontSize,t.defaults.global.defaultFontSize);n=Math.min(a.maxTicksLimit?a.maxTicksLimit:11,Math.ceil(i.height/(2*r)))}return n},handleDirectionalChanges:function(){this.isHorizontal()||this.ticks.reverse()},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},getPixelForValue:function(t){var e,n=this,i=n.start,a=+n.getRightValue(t),r=n.end-i;return n.isHorizontal()?(e=n.left+n.width/r*(a-i),Math.round(e)):(e=n.bottom-n.height/r*(a-i),Math.round(e))},getValueForPixel:function(t){var e=this,n=e.isHorizontal(),i=n?e.width:e.height,a=(n?t-e.left:e.bottom-t)/i;return e.start+(e.end-e.start)*a},getPixelForTick:function(t){return this.getPixelForValue(this.ticksAsNumbers[t])}});t.scaleService.registerScaleType("linear",i,n)}},{}],45:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers,n=e.noop;t.LinearScaleBase=t.Scale.extend({handleTickRangeOptions:function(){var t=this,n=t.options,i=n.ticks;if(i.beginAtZero){var a=e.sign(t.min),r=e.sign(t.max);a<0&&r<0?t.max=0:a>0&&r>0&&(t.min=0)}void 0!==i.min?t.min=i.min:void 0!==i.suggestedMin&&(t.min=Math.min(t.min,i.suggestedMin)),void 0!==i.max?t.max=i.max:void 0!==i.suggestedMax&&(t.max=Math.max(t.max,i.suggestedMax)),t.min===t.max&&(t.max++,i.beginAtZero||t.min--)},getTickLimit:n,handleDirectionalChanges:n,buildTicks:function(){var n=this,i=n.options,a=i.ticks,r=n.getTickLimit();r=Math.max(2,r);var o={maxTicks:r,min:a.min,max:a.max,stepSize:e.getValueOrDefault(a.fixedStepSize,a.stepSize)},s=n.ticks=t.Ticks.generators.linear(o,n);n.handleDirectionalChanges(),n.max=e.max(s),n.min=e.min(s),a.reverse?(s.reverse(),n.start=n.max,n.end=n.min):(n.start=n.min,n.end=n.max)},convertTicksToLabels:function(){var e=this;e.ticksAsNumbers=e.ticks.slice(),e.zeroLineIndex=e.ticks.indexOf(0),t.Scale.prototype.convertTicksToLabels.call(e)}})}},{}],46:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers,n={position:"left",ticks:{callback:t.Ticks.formatters.logarithmic}},i=t.Scale.extend({determineDataLimits:function(){function t(t){return u?t.xAxisID===n.id:t.yAxisID===n.id}var n=this,i=n.options,a=i.ticks,r=n.chart,o=r.data,s=o.datasets,l=e.getValueOrDefault,u=n.isHorizontal();n.min=null,n.max=null,n.minNotZero=null;var d=i.stacked;if(void 0===d&&e.each(s,function(e,n){if(!d){var i=r.getDatasetMeta(n);r.isDatasetVisible(n)&&t(i)&&void 0!==i.stack&&(d=!0)}}),i.stacked||d){var c={};e.each(s,function(a,o){var s=r.getDatasetMeta(o),l=[s.type,void 0===i.stacked&&void 0===s.stack?o:"",s.stack].join(".");r.isDatasetVisible(o)&&t(s)&&(void 0===c[l]&&(c[l]=[]),e.each(a.data,function(t,e){var a=c[l],r=+n.getRightValue(t);isNaN(r)||s.data[e].hidden||(a[e]=a[e]||0,i.relativePoints?a[e]=100:a[e]+=r)}))}),e.each(c,function(t){var i=e.min(t),a=e.max(t);n.min=null===n.min?i:Math.min(n.min,i),n.max=null===n.max?a:Math.max(n.max,a)})}else e.each(s,function(i,a){var o=r.getDatasetMeta(a);r.isDatasetVisible(a)&&t(o)&&e.each(i.data,function(t,e){var i=+n.getRightValue(t);isNaN(i)||o.data[e].hidden||(null===n.min?n.min=i:in.max&&(n.max=i),0!==i&&(null===n.minNotZero||ia?{start:e-n-5,end:e}:{start:e,end:e+n+5}}function r(t){var r,o,s,l=n(t),u=Math.min(t.height/2,t.width/2),d={l:t.width,r:0,t:t.height,b:0},c={};t.ctx.font=l.font,t._pointLabelSizes=[];var h=e(t);for(r=0;rd.r&&(d.r=p.end,c.r=g),v.startd.b&&(d.b=v.end,c.b=g)}t.setReductions(u,d,c)}function o(t){var e=Math.min(t.height/2,t.width/2);t.drawingArea=Math.round(e),t.setCenterPoint(0,0,0,0)}function s(t){return 0===t||180===t?"center":t<180?"left":"right"}function l(t,e,n,i){if(f.isArray(e))for(var a=n.y,r=1.5*i,o=0;o270||t<90)&&(n.y-=e.h)}function d(t){var i=t.ctx,a=f.getValueOrDefault,r=t.options,o=r.angleLines,d=r.pointLabels;i.lineWidth=o.lineWidth,i.strokeStyle=o.color;var c=t.getDistanceFromCenterForValue(r.reverse?t.min:t.max),h=n(t);i.textBaseline="top";for(var m=e(t)-1;m>=0;m--){if(o.display){var p=t.getPointPosition(m,c);i.beginPath(),i.moveTo(t.xCenter,t.yCenter),i.lineTo(p.x,p.y),i.stroke(),i.closePath()}var v=t.getPointPosition(m,c+5),b=a(d.fontColor,g.defaultFontColor);i.font=h.font,i.fillStyle=b;var y=t.getIndexAngle(m),x=f.toDegrees(y);i.textAlign=s(x),u(x,t._pointLabelSizes[m],v),l(i,t.pointLabels[m]||"",v,h.size)}}function c(t,n,i,a){var r=t.ctx;if(r.strokeStyle=f.getValueAtIndexOrDefault(n.color,a-1),r.lineWidth=f.getValueAtIndexOrDefault(n.lineWidth,a-1),t.options.lineArc)r.beginPath(),r.arc(t.xCenter,t.yCenter,i,0,2*Math.PI),r.closePath(),r.stroke();else{var o=e(t);if(0===o)return;r.beginPath();var s=t.getPointPosition(0,i);r.moveTo(s.x,s.y);for(var l=1;l0&&n>0?e:0)},draw:function(){var t=this,e=t.options,n=e.gridLines,i=e.ticks,a=f.getValueOrDefault; +if(e.display){var r=t.ctx,o=a(i.fontSize,g.defaultFontSize),s=a(i.fontStyle,g.defaultFontStyle),l=a(i.fontFamily,g.defaultFontFamily),u=f.fontString(o,s,l);f.each(t.ticks,function(s,l){if(l>0||e.reverse){var d=t.getDistanceFromCenterForValue(t.ticksAsNumbers[l]),h=t.yCenter-d;if(n.display&&0!==l&&c(t,n,d,l),i.display){var f=a(i.fontColor,g.defaultFontColor);if(r.font=u,i.showLabelBackdrop){var m=r.measureText(s).width;r.fillStyle=i.backdropColor,r.fillRect(t.xCenter-m/2-i.backdropPaddingX,h-o/2-i.backdropPaddingY,m+2*i.backdropPaddingX,o+2*i.backdropPaddingY)}r.textAlign="center",r.textBaseline="middle",r.fillStyle=f,r.fillText(s,t.xCenter,h)}}}),e.lineArc||d(t)}}});t.scaleService.registerScaleType("radialLinear",p,m)}},{}],48:[function(t,e,n){"use strict";var i=t(6);i="function"==typeof i?i:window.moment,e.exports=function(t){var e=t.helpers,n={units:[{name:"millisecond",steps:[1,2,5,10,20,50,100,250,500]},{name:"second",steps:[1,2,5,10,30]},{name:"minute",steps:[1,2,5,10,30]},{name:"hour",steps:[1,2,3,6,12]},{name:"day",steps:[1,2,5]},{name:"week",maxStep:4},{name:"month",maxStep:3},{name:"quarter",maxStep:4},{name:"year",maxStep:!1}]},a={position:"bottom",time:{parser:!1,format:!1,unit:!1,round:!1,displayFormat:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm:ss a",hour:"MMM D, hA",day:"ll",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"}},ticks:{autoSkip:!1}},r=t.Scale.extend({initialize:function(){if(!i)throw new Error("Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com");t.Scale.prototype.initialize.call(this)},getLabelMoment:function(t,e){return null===t||null===e?null:"undefined"!=typeof this.labelMoments[t]?this.labelMoments[t][e]:null},getLabelDiff:function(t,e){var n=this;return null===t||null===e?null:(void 0===n.labelDiffs&&n.buildLabelDiffs(),"undefined"!=typeof n.labelDiffs[t]?n.labelDiffs[t][e]:null)},getMomentStartOf:function(t){var e=this;return"week"===e.options.time.unit&&e.options.time.isoWeekday!==!1?t.clone().startOf("isoWeek").isoWeekday(e.options.time.isoWeekday):t.clone().startOf(e.tickUnit)},determineDataLimits:function(){var t=this;t.labelMoments=[];var n=[];t.chart.data.labels&&t.chart.data.labels.length>0?(e.each(t.chart.data.labels,function(e){var i=t.parseTime(e);i.isValid()&&(t.options.time.round&&i.startOf(t.options.time.round),n.push(i))},t),t.firstTick=i.min.call(t,n),t.lastTick=i.max.call(t,n)):(t.firstTick=null,t.lastTick=null),e.each(t.chart.data.datasets,function(a,r){var o=[],s=t.chart.isDatasetVisible(r);"object"==typeof a.data[0]&&null!==a.data[0]?e.each(a.data,function(e){var n=t.parseTime(t.getRightValue(e));n.isValid()&&(t.options.time.round&&n.startOf(t.options.time.round),o.push(n),s&&(t.firstTick=null!==t.firstTick?i.min(t.firstTick,n):n,t.lastTick=null!==t.lastTick?i.max(t.lastTick,n):n))},t):o=n,t.labelMoments.push(o)},t),t.options.time.min&&(t.firstTick=t.parseTime(t.options.time.min)),t.options.time.max&&(t.lastTick=t.parseTime(t.options.time.max)),t.firstTick=(t.firstTick||i()).clone(),t.lastTick=(t.lastTick||i()).clone()},buildLabelDiffs:function(){var t=this;t.labelDiffs=[];var n=[];t.chart.data.labels&&t.chart.data.labels.length>0&&e.each(t.chart.data.labels,function(e){var i=t.parseTime(e);i.isValid()&&(t.options.time.round&&i.startOf(t.options.time.round),n.push(i.diff(t.firstTick,t.tickUnit,!0)))},t),e.each(t.chart.data.datasets,function(i){var a=[];"object"==typeof i.data[0]&&null!==i.data[0]?e.each(i.data,function(e){var n=t.parseTime(t.getRightValue(e));n.isValid()&&(t.options.time.round&&n.startOf(t.options.time.round),a.push(n.diff(t.firstTick,t.tickUnit,!0)))},t):a=n,t.labelDiffs.push(a)},t)},buildTicks:function(){var i=this;i.ctx.save();var a=e.getValueOrDefault(i.options.ticks.fontSize,t.defaults.global.defaultFontSize),r=e.getValueOrDefault(i.options.ticks.fontStyle,t.defaults.global.defaultFontStyle),o=e.getValueOrDefault(i.options.ticks.fontFamily,t.defaults.global.defaultFontFamily),s=e.fontString(a,r,o);if(i.ctx.font=s,i.ticks=[],i.unitScale=1,i.scaleSizeInUnits=0,i.options.time.unit)i.tickUnit=i.options.time.unit||"day",i.displayFormat=i.options.time.displayFormats[i.tickUnit],i.scaleSizeInUnits=i.lastTick.diff(i.firstTick,i.tickUnit,!0),i.unitScale=e.getValueOrDefault(i.options.time.unitStepSize,1);else{var l=i.isHorizontal()?i.width:i.height,u=i.tickFormatFunction(i.firstTick,0,[]),d=i.ctx.measureText(u).width,c=Math.cos(e.toRadians(i.options.ticks.maxRotation)),h=Math.sin(e.toRadians(i.options.ticks.maxRotation));d=d*c+a*h;var f=l/d;i.tickUnit=i.options.time.minUnit,i.scaleSizeInUnits=i.lastTick.diff(i.firstTick,i.tickUnit,!0),i.displayFormat=i.options.time.displayFormats[i.tickUnit];for(var g=0,m=n.units[g];g=Math.ceil(i.scaleSizeInUnits/f)){i.unitScale=e.getValueOrDefault(i.options.time.unitStepSize,m.steps[p]);break}break}if(m.maxStep===!1||Math.ceil(i.scaleSizeInUnits/f)=0&&(i.lastTick=x),i.scaleSizeInUnits=i.lastTick.diff(i.firstTick,i.tickUnit,!0)}i.options.time.displayFormat&&(i.displayFormat=i.options.time.displayFormat),i.ticks.push(i.firstTick.clone());for(var _=i.unitScale;_<=i.scaleSizeInUnits;_+=i.unitScale){var w=y.clone().add(_,i.tickUnit);if(i.options.time.max&&w.diff(i.lastTick,i.tickUnit,!0)>=0)break;i.ticks.push(w)}var S=i.ticks[i.ticks.length-1].diff(i.lastTick,i.tickUnit);0===S&&0!==i.scaleSizeInUnits||(i.options.time.max?(i.ticks.push(i.lastTick.clone()),i.scaleSizeInUnits=i.lastTick.diff(i.ticks[0],i.tickUnit,!0)):(i.ticks.push(i.lastTick.clone()),i.scaleSizeInUnits=i.lastTick.diff(i.firstTick,i.tickUnit,!0))),i.ctx.restore(),i.labelDiffs=void 0},getLabelForIndex:function(t,e){var n=this,i=n.chart.data.labels&&tsetAcceptLanguageString($acceptLanguageString); + } + } + + /** + * @param string $acceptLanguageString + * + * @return $this + */ + public function setAcceptLanguageString($acceptLanguageString) + { + $this->acceptLanguageString = $acceptLanguageString; + + return $this; + } + + /** + * @return string + */ + public function getAcceptLanguageString() + { + if (null === $this->acceptLanguageString) { + $this->createAcceptLanguageString(); + } + + return $this->acceptLanguageString; + } + + /** + * @return string + */ + public function createAcceptLanguageString() + { + $acceptLanguageString = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : null; + $this->setAcceptLanguageString($acceptLanguageString); + + return $acceptLanguageString; + } +} diff --git a/inc/modules/statistics/phpbrowserdetector/Browser.php b/inc/modules/statistics/phpbrowserdetector/Browser.php new file mode 100644 index 0000000..e36a681 --- /dev/null +++ b/inc/modules/statistics/phpbrowserdetector/Browser.php @@ -0,0 +1,322 @@ +setUserAgent($userAgent); + } elseif (null === $userAgent || is_string($userAgent)) { + $this->setUserAgent(new UserAgent($userAgent)); + } else { + throw new InvalidArgumentException(); + } + } + + /** + * Set the name of the OS. + * + * @param string $name + * + * @return $this + */ + public function setName($name) + { + $this->name = (string)$name; + + return $this; + } + + /** + * Return the name of the Browser. + * + * @return string + */ + public function getName() + { + if (!isset($this->name)) { + BrowserDetector::detect($this, $this->getUserAgent()); + } + + return $this->name; + } + + /** + * Check to see if the specific browser is valid. + * + * @param string $name + * + * @return bool + */ + public function isBrowser($name) + { + return (0 == strcasecmp($this->getName(), trim($name))); + } + + /** + * Set the version of the browser. + * + * @param string $version + * + * @return $this + */ + public function setVersion($version) + { + $this->version = (string)$version; + + return $this; + } + + /** + * The version of the browser. + * + * @return string + */ + public function getVersion() + { + if (!isset($this->name)) { + BrowserDetector::detect($this, $this->getUserAgent()); + } + + return (string) $this->version; + } + + /** + * Set the Browser to be a robot. + * + * @param bool $isRobot + * + * @return $this + */ + public function setIsRobot($isRobot) + { + $this->isRobot = (bool)$isRobot; + + return $this; + } + + /** + * Is the browser from a robot (ex Slurp,GoogleBot)? + * + * @return bool + */ + public function getIsRobot() + { + if (!isset($this->name)) { + BrowserDetector::detect($this, $this->getUserAgent()); + } + + return $this->isRobot; + } + + /** + * @return bool + */ + public function isRobot() + { + return $this->getIsRobot(); + } + + /** + * @param bool $isChromeFrame + * + * @return $this + */ + public function setIsChromeFrame($isChromeFrame) + { + $this->isChromeFrame = (bool)$isChromeFrame; + + return $this; + } + + /** + * Used to determine if the browser is actually "chromeframe". + * + * @return bool + */ + public function getIsChromeFrame() + { + if (!isset($this->name)) { + BrowserDetector::detect($this, $this->getUserAgent()); + } + + return $this->isChromeFrame; + } + + /** + * @return bool + */ + public function isChromeFrame() + { + return $this->getIsChromeFrame(); + } + + /** + * @param bool $isFacebookWebView + * + * @return $this + */ + public function setIsFacebookWebView($isFacebookWebView) + { + $this->isFacebookWebView = (bool) $isFacebookWebView; + + return $this; + } + + /** + * Used to determine if the browser is actually "facebook". + * + * @return bool + */ + public function getIsFacebookWebView() + { + if (!isset($this->name)) { + BrowserDetector::detect($this, $this->getUserAgent()); + } + + return $this->isFacebookWebView; + } + + /** + * @return bool + */ + public function isFacebookWebView() + { + return $this->getIsFacebookWebView(); + } + + /** + * @param UserAgent $userAgent + * + * @return $this + */ + public function setUserAgent(UserAgent $userAgent) + { + $this->userAgent = $userAgent; + + return $this; + } + + /** + * @return UserAgent + */ + public function getUserAgent() + { + return $this->userAgent; + } + + /** + * @param bool + * + * @return $this + */ + public function setIsCompatibilityMode($isCompatibilityMode) + { + $this->isCompatibilityMode = $isCompatibilityMode; + + return $this; + } + + /** + * @return bool + */ + public function isCompatibilityMode() + { + return $this->isCompatibilityMode; + } + + /** + * Render pages outside of IE's compatibility mode. + */ + public function endCompatibilityMode() + { + header('X-UA-Compatible: IE=edge'); + } +} diff --git a/inc/modules/statistics/phpbrowserdetector/BrowserDetector.php b/inc/modules/statistics/phpbrowserdetector/BrowserDetector.php new file mode 100644 index 0000000..43cb43e --- /dev/null +++ b/inc/modules/statistics/phpbrowserdetector/BrowserDetector.php @@ -0,0 +1,1006 @@ +getUserAgent(); + } + self::$userAgentString = $userAgent->getUserAgentString(); + + self::$browser->setName(Browser::UNKNOWN); + self::$browser->setVersion(Browser::VERSION_UNKNOWN); + + self::checkChromeFrame(); + self::checkFacebookWebView(); + + foreach (self::$browsersList as $browserName) { + $funcName = self::FUNC_PREFIX . $browserName; + + if (self::$funcName()) { + return true; + } + } + + return false; + } + + /** + * Determine if the user is using Chrome Frame. + * + * @return bool + */ + public static function checkChromeFrame() + { + if (strpos(self::$userAgentString, 'chromeframe') !== false) { + self::$browser->setIsChromeFrame(true); + + return true; + } + + return false; + } + + /** + * Determine if the user is using Facebook. + * + * @return bool + */ + public static function checkFacebookWebView() + { + if (strpos(self::$userAgentString, 'FBAV') !== false) { + self::$browser->setIsFacebookWebView(true); + + return true; + } + + return false; + } + + /** + * Determine if the user is using a BlackBerry. + * + * @return bool + */ + public static function checkBrowserBlackBerry() + { + if (stripos(self::$userAgentString, 'blackberry') !== false) { + if (stripos(self::$userAgentString, 'Version/') !== false) { + $aresult = explode('Version/', self::$userAgentString); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + self::$browser->setVersion($aversion[0]); + } + } else { + $aresult = explode('/', stristr(self::$userAgentString, 'BlackBerry')); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + self::$browser->setVersion($aversion[0]); + } + } + self::$browser->setName(Browser::BLACKBERRY); + + return true; + } elseif (stripos(self::$userAgentString, 'BB10') !== false) { + $aresult = explode('Version/10.', self::$userAgentString); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + self::$browser->setVersion('10.' . $aversion[0]); + } + self::$browser->setName(Browser::BLACKBERRY); + return true; + } + + return false; + } + + /** + * Determine if the browser is a robot. + * + * @return bool + */ + public static function checkBrowserRobot() + { + if (stripos(self::$userAgentString, 'bot') !== false || + stripos(self::$userAgentString, 'spider') !== false || + stripos(self::$userAgentString, 'crawler') !== false + ) { + self::$browser->setIsRobot(true); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Internet Explorer. + * + * @return bool + */ + public static function checkBrowserInternetExplorer() + { + // Test for v1 - v1.5 IE + if (stripos(self::$userAgentString, 'microsoft internet explorer') !== false) { + self::$browser->setName(Browser::IE); + self::$browser->setVersion('1.0'); + $aresult = stristr(self::$userAgentString, '/'); + if (preg_match('/308|425|426|474|0b1/i', $aresult)) { + self::$browser->setVersion('1.5'); + } + + return true; + } // Test for versions > 1.5 and < 11 and some cases of 11 + else { + if (stripos(self::$userAgentString, 'msie') !== false && stripos(self::$userAgentString, 'opera') === false + ) { + // See if the browser is the odd MSN Explorer + if (stripos(self::$userAgentString, 'msnb') !== false) { + $aresult = explode(' ', stristr(str_replace(';', '; ', self::$userAgentString), 'MSN')); + self::$browser->setName(Browser::MSN); + if (isset($aresult[1])) { + self::$browser->setVersion(str_replace(array('(', ')', ';'), '', $aresult[1])); + } + + return true; + } + $aresult = explode(' ', stristr(str_replace(';', '; ', self::$userAgentString), 'msie')); + self::$browser->setName(Browser::IE); + if (isset($aresult[1])) { + self::$browser->setVersion(str_replace(array('(', ')', ';'), '', $aresult[1])); + } + // See https://msdn.microsoft.com/en-us/library/ie/hh869301%28v=vs.85%29.aspx + // Might be 11, anyway ! + if (stripos(self::$userAgentString, 'trident') !== false) { + preg_match('/rv:(\d+\.\d+)/', self::$userAgentString, $matches); + if (isset($matches[1])) { + self::$browser->setVersion($matches[1]); + } + + // At this poing in the method, we know the MSIE and Trident + // strings are present in the $userAgentString. If we're in + // compatibility mode, we need to determine the true version. + // If the MSIE version is 7.0, we can look at the Trident + // version to *approximate* the true IE version. If we don't + // find a matching pair, ( e.g. MSIE 7.0 && Trident/7.0 ) + // we're *not* in compatibility mode and the browser really + // is version 7.0. + if (stripos(self::$userAgentString, 'MSIE 7.0;')) { + if (stripos(self::$userAgentString, 'Trident/7.0;')) { + // IE11 in compatibility mode + self::$browser->setVersion('11.0'); + self::$browser->setIsCompatibilityMode(true); + } elseif (stripos(self::$userAgentString, 'Trident/6.0;')) { + // IE10 in compatibility mode + self::$browser->setVersion('10.0'); + self::$browser->setIsCompatibilityMode(true); + } elseif (stripos(self::$userAgentString, 'Trident/5.0;')) { + // IE9 in compatibility mode + self::$browser->setVersion('9.0'); + self::$browser->setIsCompatibilityMode(true); + } elseif (stripos(self::$userAgentString, 'Trident/4.0;')) { + // IE8 in compatibility mode + self::$browser->setVersion('8.0'); + self::$browser->setIsCompatibilityMode(true); + } + } + } + + return true; + } // Test for versions >= 11 + else { + if (stripos(self::$userAgentString, 'trident') !== false) { + self::$browser->setName(Browser::IE); + + preg_match('/rv:(\d+\.\d+)/', self::$userAgentString, $matches); + if (isset($matches[1])) { + self::$browser->setVersion($matches[1]); + + return true; + } else { + return false; + } + } // Test for Pocket IE + else { + if (stripos(self::$userAgentString, 'mspie') !== false || + stripos( + self::$userAgentString, + 'pocket' + ) !== false + ) { + $aresult = explode(' ', stristr(self::$userAgentString, 'mspie')); + self::$browser->setName(Browser::POCKET_IE); + + if (stripos(self::$userAgentString, 'mspie') !== false) { + if (isset($aresult[1])) { + self::$browser->setVersion($aresult[1]); + } + } else { + $aversion = explode('/', self::$userAgentString); + if (isset($aversion[1])) { + self::$browser->setVersion($aversion[1]); + } + } + + return true; + } + } + } + } + + return false; + } + + /** + * Determine if the browser is Opera. + * + * @return bool + */ + public static function checkBrowserOpera() + { + if (stripos(self::$userAgentString, 'opera mini') !== false) { + $resultant = stristr(self::$userAgentString, 'opera mini'); + if (preg_match('/\//', $resultant)) { + $aresult = explode('/', $resultant); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + self::$browser->setVersion($aversion[0]); + } + } else { + $aversion = explode(' ', stristr($resultant, 'opera mini')); + if (isset($aversion[1])) { + self::$browser->setVersion($aversion[1]); + } + } + self::$browser->setName(Browser::OPERA_MINI); + + return true; + } elseif (stripos(self::$userAgentString, 'OPiOS') !== false) { + $aresult = explode('/', stristr(self::$userAgentString, 'OPiOS')); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + self::$browser->setVersion($aversion[0]); + } + self::$browser->setName(Browser::OPERA_MINI); + + return true; + } elseif (stripos(self::$userAgentString, 'opera') !== false) { + $resultant = stristr(self::$userAgentString, 'opera'); + if (preg_match('/Version\/(1[0-2].*)$/', $resultant, $matches)) { + if (isset($matches[1])) { + self::$browser->setVersion($matches[1]); + } + } elseif (preg_match('/\//', $resultant)) { + $aresult = explode('/', str_replace('(', ' ', $resultant)); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + self::$browser->setVersion($aversion[0]); + } + } else { + $aversion = explode(' ', stristr($resultant, 'opera')); + self::$browser->setVersion(isset($aversion[1]) ? $aversion[1] : ''); + } + self::$browser->setName(Browser::OPERA); + + return true; + } elseif (stripos(self::$userAgentString, ' OPR/') !== false) { + self::$browser->setName(Browser::OPERA); + if (preg_match('/OPR\/([\d\.]*)/', self::$userAgentString, $matches)) { + if (isset($matches[1])) { + self::$browser->setVersion($matches[1]); + } + } + + return true; + } + + return false; + } + + /** + * Determine if the browser is Samsung. + * + * @return bool + */ + public static function checkBrowserSamsung() + { + if (stripos(self::$userAgentString, 'SamsungBrowser') !== false) { + $aresult = explode('/', stristr(self::$userAgentString, 'SamsungBrowser')); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + self::$browser->setVersion($aversion[0]); + } + self::$browser->setName(Browser::SAMSUNG_BROWSER); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Chrome. + * + * @return bool + */ + public static function checkBrowserChrome() + { + if (stripos(self::$userAgentString, 'Chrome') !== false) { + $aresult = explode('/', stristr(self::$userAgentString, 'Chrome')); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + self::$browser->setVersion($aversion[0]); + } + self::$browser->setName(Browser::CHROME); + + return true; + } elseif (stripos(self::$userAgentString, 'CriOS') !== false) { + $aresult = explode('/', stristr(self::$userAgentString, 'CriOS')); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + self::$browser->setVersion($aversion[0]); + } + self::$browser->setName(Browser::CHROME); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Vivaldi. + * + * @return bool + */ + public static function checkBrowserVivaldi() + { + if (stripos(self::$userAgentString, 'Vivaldi') !== false) { + $aresult = explode('/', stristr(self::$userAgentString, 'Vivaldi')); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + self::$browser->setVersion($aversion[0]); + } + self::$browser->setName(Browser::VIVALDI); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Microsoft Edge. + * + * @return bool + */ + public static function checkBrowserEdge() + { + if (stripos(self::$userAgentString, 'Edge') !== false) { + $version = explode('Edge/', self::$userAgentString); + if (isset($version[1])) { + self::$browser->setVersion((float)$version[1]); + } + self::$browser->setName(Browser::EDGE); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Google Search Appliance. + * + * @return bool + */ + public static function checkBrowserGsa() + { + if (stripos(self::$userAgentString, 'GSA') !== false) { + $aresult = explode('/', stristr(self::$userAgentString, 'GSA')); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + self::$browser->setVersion($aversion[0]); + } + self::$browser->setName(Browser::GSA); + + return true; + } + + return false; + } + + /** + * Determine if the browser is WebTv. + * + * @return bool + */ + public static function checkBrowserWebTv() + { + if (stripos(self::$userAgentString, 'webtv') !== false) { + $aresult = explode('/', stristr(self::$userAgentString, 'webtv')); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + self::$browser->setVersion($aversion[0]); + } + self::$browser->setName(Browser::WEBTV); + + return true; + } + + return false; + } + + /** + * Determine if the browser is NetPositive. + * + * @return bool + */ + public static function checkBrowserNetPositive() + { + if (stripos(self::$userAgentString, 'NetPositive') !== false) { + $aresult = explode('/', stristr(self::$userAgentString, 'NetPositive')); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + self::$browser->setVersion(str_replace(array('(', ')', ';'), '', $aversion[0])); + } + self::$browser->setName(Browser::NETPOSITIVE); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Galeon. + * + * @return bool + */ + public static function checkBrowserGaleon() + { + if (stripos(self::$userAgentString, 'galeon') !== false) { + $aresult = explode(' ', stristr(self::$userAgentString, 'galeon')); + $aversion = explode('/', $aresult[0]); + if (isset($aversion[1])) { + self::$browser->setVersion($aversion[1]); + } + self::$browser->setName(Browser::GALEON); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Konqueror. + * + * @return bool + */ + public static function checkBrowserKonqueror() + { + if (stripos(self::$userAgentString, 'Konqueror') !== false) { + $aresult = explode(' ', stristr(self::$userAgentString, 'Konqueror')); + $aversion = explode('/', $aresult[0]); + if (isset($aversion[1])) { + self::$browser->setVersion($aversion[1]); + } + self::$browser->setName(Browser::KONQUEROR); + + return true; + } + + return false; + } + + /** + * Determine if the browser is iCab. + * + * @return bool + */ + public static function checkBrowserIcab() + { + if (stripos(self::$userAgentString, 'icab') !== false) { + $aversion = explode(' ', stristr(str_replace('/', ' ', self::$userAgentString), 'icab')); + if (isset($aversion[1])) { + self::$browser->setVersion($aversion[1]); + } + self::$browser->setName(Browser::ICAB); + + return true; + } + + return false; + } + + /** + * Determine if the browser is OmniWeb. + * + * @return bool + */ + public static function checkBrowserOmniWeb() + { + if (stripos(self::$userAgentString, 'omniweb') !== false) { + $aresult = explode('/', stristr(self::$userAgentString, 'omniweb')); + $aversion = explode(' ', isset($aresult[1]) ? $aresult[1] : ''); + self::$browser->setVersion($aversion[0]); + self::$browser->setName(Browser::OMNIWEB); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Phoenix. + * + * @return bool + */ + public static function checkBrowserPhoenix() + { + if (stripos(self::$userAgentString, 'Phoenix') !== false) { + $aversion = explode('/', stristr(self::$userAgentString, 'Phoenix')); + if (isset($aversion[1])) { + self::$browser->setVersion($aversion[1]); + } + self::$browser->setName(Browser::PHOENIX); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Firebird. + * + * @return bool + */ + public static function checkBrowserFirebird() + { + if (stripos(self::$userAgentString, 'Firebird') !== false) { + $aversion = explode('/', stristr(self::$userAgentString, 'Firebird')); + if (isset($aversion[1])) { + self::$browser->setVersion($aversion[1]); + } + self::$browser->setName(Browser::FIREBIRD); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Netscape Navigator 9+. + * + * @return bool + */ + public static function checkBrowserNetscapeNavigator9Plus() + { + if (stripos(self::$userAgentString, 'Firefox') !== false && + preg_match('/Navigator\/([^ ]*)/i', self::$userAgentString, $matches) + ) { + if (isset($matches[1])) { + self::$browser->setVersion($matches[1]); + } + self::$browser->setName(Browser::NETSCAPE_NAVIGATOR); + + return true; + } elseif (stripos(self::$userAgentString, 'Firefox') === false && + preg_match('/Netscape6?\/([^ ]*)/i', self::$userAgentString, $matches) + ) { + if (isset($matches[1])) { + self::$browser->setVersion($matches[1]); + } + self::$browser->setName(Browser::NETSCAPE_NAVIGATOR); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Shiretoko. + * + * @return bool + */ + public static function checkBrowserShiretoko() + { + if (stripos(self::$userAgentString, 'Mozilla') !== false && + preg_match('/Shiretoko\/([^ ]*)/i', self::$userAgentString, $matches) + ) { + if (isset($matches[1])) { + self::$browser->setVersion($matches[1]); + } + self::$browser->setName(Browser::SHIRETOKO); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Ice Cat. + * + * @return bool + */ + public static function checkBrowserIceCat() + { + if (stripos(self::$userAgentString, 'Mozilla') !== false && + preg_match('/IceCat\/([^ ]*)/i', self::$userAgentString, $matches) + ) { + if (isset($matches[1])) { + self::$browser->setVersion($matches[1]); + } + self::$browser->setName(Browser::ICECAT); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Nokia. + * + * @return bool + */ + public static function checkBrowserNokia() + { + if (preg_match("/Nokia([^\/]+)\/([^ SP]+)/i", self::$userAgentString, $matches)) { + self::$browser->setVersion($matches[2]); + if (stripos(self::$userAgentString, 'Series60') !== false || + strpos(self::$userAgentString, 'S60') !== false + ) { + self::$browser->setName(Browser::NOKIA_S60); + } else { + self::$browser->setName(Browser::NOKIA); + } + + return true; + } + + return false; + } + + /** + * Determine if the browser is Firefox. + * + * @return bool + */ + public static function checkBrowserFirefox() + { + if (stripos(self::$userAgentString, 'safari') === false) { + if (preg_match("/Firefox[\/ \(]([^ ;\)]+)/i", self::$userAgentString, $matches)) { + if (isset($matches[1])) { + self::$browser->setVersion($matches[1]); + } + self::$browser->setName(Browser::FIREFOX); + + return true; + } elseif (preg_match('/Firefox$/i', self::$userAgentString, $matches)) { + self::$browser->setVersion(''); + self::$browser->setName(Browser::FIREFOX); + + return true; + } + } + + return false; + } + + /** + * Determine if the browser is SeaMonkey. + * + * @return bool + */ + public static function checkBrowserSeaMonkey() + { + if (stripos(self::$userAgentString, 'safari') === false) { + if (preg_match("/SeaMonkey[\/ \(]([^ ;\)]+)/i", self::$userAgentString, $matches)) { + if (isset($matches[1])) { + self::$browser->setVersion($matches[1]); + } + self::$browser->setName(Browser::SEAMONKEY); + + return true; + } elseif (preg_match('/SeaMonkey$/i', self::$userAgentString, $matches)) { + self::$browser->setVersion(''); + self::$browser->setName(Browser::SEAMONKEY); + + return true; + } + } + + return false; + } + + /** + * Determine if the browser is Iceweasel. + * + * @return bool + */ + public static function checkBrowserIceweasel() + { + if (stripos(self::$userAgentString, 'Iceweasel') !== false) { + $aresult = explode('/', stristr(self::$userAgentString, 'Iceweasel')); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + self::$browser->setVersion($aversion[0]); + } + self::$browser->setName(Browser::ICEWEASEL); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Mozilla. + * + * @return bool + */ + public static function checkBrowserMozilla() + { + if (stripos(self::$userAgentString, 'mozilla') !== false && + preg_match('/rv:[0-9].[0-9][a-b]?/i', self::$userAgentString) && + stripos(self::$userAgentString, 'netscape') === false + ) { + $aversion = explode(' ', stristr(self::$userAgentString, 'rv:')); + preg_match('/rv:[0-9].[0-9][a-b]?/i', self::$userAgentString, $aversion); + self::$browser->setVersion(str_replace('rv:', '', $aversion[0])); + self::$browser->setName(Browser::MOZILLA); + + return true; + } elseif (stripos(self::$userAgentString, 'mozilla') !== false && + preg_match('/rv:[0-9]\.[0-9]/i', self::$userAgentString) && + stripos(self::$userAgentString, 'netscape') === false + ) { + $aversion = explode('', stristr(self::$userAgentString, 'rv:')); + self::$browser->setVersion(str_replace('rv:', '', $aversion[0])); + self::$browser->setName(Browser::MOZILLA); + + return true; + } elseif (stripos(self::$userAgentString, 'mozilla') !== false && + preg_match('/mozilla\/([^ ]*)/i', self::$userAgentString, $matches) && + stripos(self::$userAgentString, 'netscape') === false + ) { + if (isset($matches[1])) { + self::$browser->setVersion($matches[1]); + } + self::$browser->setName(Browser::MOZILLA); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Lynx. + * + * @return bool + */ + public static function checkBrowserLynx() + { + if (stripos(self::$userAgentString, 'lynx') !== false) { + $aresult = explode('/', stristr(self::$userAgentString, 'Lynx')); + $aversion = explode(' ', (isset($aresult[1]) ? $aresult[1] : '')); + self::$browser->setVersion($aversion[0]); + self::$browser->setName(Browser::LYNX); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Amaya. + * + * @return bool + */ + public static function checkBrowserAmaya() + { + if (stripos(self::$userAgentString, 'amaya') !== false) { + $aresult = explode('/', stristr(self::$userAgentString, 'Amaya')); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + self::$browser->setVersion($aversion[0]); + } + self::$browser->setName(Browser::AMAYA); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Safari. + * + * @return bool + */ + public static function checkBrowserWkhtmltopdf() + { + if (stripos(self::$userAgentString, 'wkhtmltopdf') !== false) { + self::$browser->setName(Browser::WKHTMLTOPDF); + return true; + } + + return false; + } + /** + * Determine if the browser is Safari. + * + * @return bool + */ + public static function checkBrowserSafari() + { + if (stripos(self::$userAgentString, 'Safari') !== false) { + $aresult = explode('/', stristr(self::$userAgentString, 'Version')); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + self::$browser->setVersion($aversion[0]); + } else { + self::$browser->setVersion(Browser::VERSION_UNKNOWN); + } + self::$browser->setName(Browser::SAFARI); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Yandex. + * + * @return bool + */ + public static function checkBrowserYandex() + { + if (stripos(self::$userAgentString, 'YaBrowser') !== false) { + $aresult = explode('/', stristr(self::$userAgentString, 'YaBrowser')); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + self::$browser->setVersion($aversion[0]); + } + self::$browser->setName(Browser::YANDEX); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Comodo Dragon / Ice Dragon / Chromodo. + * + * @return bool + */ + public static function checkBrowserDragon() + { + if (stripos(self::$userAgentString, 'Dragon') !== false) { + $aresult = explode('/', stristr(self::$userAgentString, 'Dragon')); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + self::$browser->setVersion($aversion[0]); + } + self::$browser->setName(Browser::DRAGON); + + return true; + } + + return false; + } + + /** + * Determine if the browser is Android. + * + * @return bool + */ + public static function checkBrowserAndroid() + { + // Navigator + if (stripos(self::$userAgentString, 'Android') !== false) { + if (preg_match('/Version\/([\d\.]*)/i', self::$userAgentString, $matches)) { + if (isset($matches[1])) { + self::$browser->setVersion($matches[1]); + } + } else { + self::$browser->setVersion(Browser::VERSION_UNKNOWN); + } + self::$browser->setName(Browser::NAVIGATOR); + + return true; + } + + return false; + } +} diff --git a/inc/modules/statistics/phpbrowserdetector/DetectorInterface.php b/inc/modules/statistics/phpbrowserdetector/DetectorInterface.php new file mode 100644 index 0000000..703cfba --- /dev/null +++ b/inc/modules/statistics/phpbrowserdetector/DetectorInterface.php @@ -0,0 +1,7 @@ +setUserAgent($userAgent); + } elseif (null === $userAgent || is_string($userAgent)) { + $this->setUserAgent(new UserAgent($userAgent)); + } else { + throw new InvalidArgumentException(); + } + } + + /** + * @param UserAgent $userAgent + * + * @return $this + */ + public function setUserAgent(UserAgent $userAgent) + { + $this->userAgent = $userAgent; + + return $this; + } + + /** + * @return UserAgent + */ + public function getUserAgent() + { + return $this->userAgent; + } + + /** + * @return string + */ + public function getName() + { + if (!isset($this->name)) { + DeviceDetector::detect($this, $this->getUserAgent()); + } + + return $this->name; + } + + /** + * @param string $name + * + * @return $this + */ + public function setName($name) + { + $this->name = (string)$name; + + return $this; + } +} diff --git a/inc/modules/statistics/phpbrowserdetector/DeviceDetector.php b/inc/modules/statistics/phpbrowserdetector/DeviceDetector.php new file mode 100644 index 0000000..45ed221 --- /dev/null +++ b/inc/modules/statistics/phpbrowserdetector/DeviceDetector.php @@ -0,0 +1,96 @@ +setName($device::UNKNOWN); + + return ( + self::checkIpad($device, $userAgent) || + self::checkIphone($device, $userAgent) || + self::checkWindowsPhone($device, $userAgent) || + self::checkSamsungPhone($device, $userAgent) + ); + } + + /** + * Determine if the device is iPad. + * + * @param Device $device + * @param UserAgent $userAgent + * @return bool + */ + private static function checkIpad(Device $device, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), 'ipad') !== false) { + $device->setName(Device::IPAD); + return true; + } + + return false; + } + + /** + * Determine if the device is iPhone. + * + * @param Device $device + * @param UserAgent $userAgent + * @return bool + */ + private static function checkIphone(Device $device, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), 'iphone;') !== false) { + $device->setName(Device::IPHONE); + return true; + } + + return false; + } + + /** + * Determine if the device is Windows Phone. + * + * @param Device $device + * @param UserAgent $userAgent + * @return bool + */ + private static function checkWindowsPhone(Device $device, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), 'Windows Phone') !== false) { + if (preg_match('/Microsoft; (Lumia [^)]*)\)/', $userAgent->getUserAgentString(), $matches)) { + $device->setName($matches[1]); + return true; + } + + $device->setName($device::WINDOWS_PHONE); + return true; + } + return false; + } + + /** + * Determine if the device is Windows Phone. + * + * @param Device $device + * @param UserAgent $userAgent + * @return bool + */ + private static function checkSamsungPhone(Device $device, UserAgent $userAgent) + { + if (preg_match('/SAMSUNG SM-([^ ]*)/i', $userAgent->getUserAgentString(), $matches)) { + $device->setName(str_ireplace('SAMSUNG', 'Samsung', $matches[0])); + return true; + } + return false; + } +} diff --git a/inc/modules/statistics/phpbrowserdetector/InvalidArgumentException.php b/inc/modules/statistics/phpbrowserdetector/InvalidArgumentException.php new file mode 100644 index 0000000..5cba91d --- /dev/null +++ b/inc/modules/statistics/phpbrowserdetector/InvalidArgumentException.php @@ -0,0 +1,7 @@ +setAcceptLanguage($acceptLanguage); + } elseif (null === $acceptLanguage || is_string($acceptLanguage)) { + $this->setAcceptLanguage(new AcceptLanguage($acceptLanguage)); + } else { + throw new InvalidArgumentException(); + } + } + + /** + * Get all user's languages. + * + * @return array + */ + public function getLanguages() + { + if (!is_array($this->languages)) { + LanguageDetector::detect($this, $this->getAcceptLanguage()); + } + + return $this->languages; + } + + /** + * Set languages. + * + * @param array $languages + * + * @return $this + */ + public function setLanguages($languages) + { + $this->languages = $languages; + + return $this; + } + + /** + * Get a user's language. + * + * @return string + */ + public function getLanguage() + { + if (!is_array($this->languages)) { + LanguageDetector::detect($this, $this->getAcceptLanguage()); + } + + return strtolower(substr(reset($this->languages), 0, 2)); + } + + /** + * Get a user's language and locale. + * + * @param string $separator + * + * @return string + */ + public function getLanguageLocale($separator = '-') + { + if (!is_array($this->languages)) { + LanguageDetector::detect($this, $this->getAcceptLanguage()); + } + + $userLanguage = $this->getLanguage(); + foreach ($this->languages as $language) { + if (strlen($language) === 5 && strpos($language, $userLanguage) === 0) { + $locale = substr($language, -2); + break; + } + } + + if (!empty($locale)) { + return $userLanguage . $separator . strtoupper($locale); + } else { + return $userLanguage; + } + } + + /** + * @param AcceptLanguage $acceptLanguage + * + * @return $this + */ + public function setAcceptLanguage(AcceptLanguage $acceptLanguage) + { + $this->acceptLanguage = $acceptLanguage; + + return $this; + } + + /** + * @return AcceptLanguage + */ + public function getAcceptLanguage() + { + return $this->acceptLanguage; + } +} diff --git a/inc/modules/statistics/phpbrowserdetector/LanguageDetector.php b/inc/modules/statistics/phpbrowserdetector/LanguageDetector.php new file mode 100644 index 0000000..7019481 --- /dev/null +++ b/inc/modules/statistics/phpbrowserdetector/LanguageDetector.php @@ -0,0 +1,45 @@ +getAcceptLanguageString(); + $languages = array(); + $language->setLanguages($languages); + + if (!empty($acceptLanguageString)) { + $httpLanguages = preg_split( + '/q=([\d\.]*)/', + $acceptLanguageString, + -1, + PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE + ); + + $key = 0; + foreach (array_reverse($httpLanguages) as $value) { + $value = trim($value, ',; .'); + if (is_numeric($value)) { + $key = $value; + } else { + $languages[$key] = explode(',', $value); + } + } + krsort($languages); + + foreach ($languages as $value) { + $language->setLanguages(array_merge($language->getLanguages(), $value)); + } + } + } +} diff --git a/inc/modules/statistics/phpbrowserdetector/Os.php b/inc/modules/statistics/phpbrowserdetector/Os.php new file mode 100644 index 0000000..f771a91 --- /dev/null +++ b/inc/modules/statistics/phpbrowserdetector/Os.php @@ -0,0 +1,176 @@ +setUserAgent($userAgent); + } elseif (null === $userAgent || is_string($userAgent)) { + $this->setUserAgent(new UserAgent($userAgent)); + } else { + throw new InvalidArgumentException(); + } + } + + /** + * Return the name of the OS. + * + * @return string + */ + public function getName() + { + if (!isset($this->name)) { + OsDetector::detect($this, $this->getUserAgent()); + } + + return $this->name; + } + + /** + * Set the name of the OS. + * + * @param string $name + * + * @return $this + */ + public function setName($name) + { + $this->name = (string)$name; + + return $this; + } + + /** + * Return the version of the OS. + * + * @return string + */ + public function getVersion() + { + if (isset($this->version)) { + return (string)$this->version; + } else { + OsDetector::detect($this, $this->getUserAgent()); + + return (string)$this->version; + } + } + + /** + * Set the version of the OS. + * + * @param string $version + * + * @return $this + */ + public function setVersion($version) + { + $this->version = (string)$version; + + return $this; + } + + /** + * Is the browser from a mobile device? + * + * @return bool + */ + public function getIsMobile() + { + if (!isset($this->name)) { + OsDetector::detect($this, $this->getUserAgent()); + } + + return $this->isMobile; + } + + /** + * @return bool + */ + public function isMobile() + { + return $this->getIsMobile(); + } + + /** + * Set the Browser to be mobile. + * + * @param bool $isMobile + */ + public function setIsMobile($isMobile = true) + { + $this->isMobile = (bool)$isMobile; + } + + /** + * @param UserAgent $userAgent + * + * @return $this + */ + public function setUserAgent(UserAgent $userAgent) + { + $this->userAgent = $userAgent; + + return $this; + } + + /** + * @return UserAgent + */ + public function getUserAgent() + { + return $this->userAgent; + } +} diff --git a/inc/modules/statistics/phpbrowserdetector/OsDetector.php b/inc/modules/statistics/phpbrowserdetector/OsDetector.php new file mode 100644 index 0000000..1ec4e86 --- /dev/null +++ b/inc/modules/statistics/phpbrowserdetector/OsDetector.php @@ -0,0 +1,508 @@ +setName($os::UNKNOWN); + $os->setVersion($os::VERSION_UNKNOWN); + $os->setIsMobile(false); + + self::checkMobileBrowsers($os, $userAgent); + + return ( + // Chrome OS before OS X + self::checkChromeOs($os, $userAgent) || + // iOS before OS X + self::checkIOS($os, $userAgent) || + self::checkOSX($os, $userAgent) || + self::checkSymbOS($os, $userAgent) || + self::checkWindows($os, $userAgent) || + self::checkWindowsPhone($os, $userAgent) || + self::checkFreeBSD($os, $userAgent) || + self::checkOpenBSD($os, $userAgent) || + self::checkNetBSD($os, $userAgent) || + self::checkOpenSolaris($os, $userAgent) || + self::checkSunOS($os, $userAgent) || + self::checkOS2($os, $userAgent) || + self::checkBeOS($os, $userAgent) || + // Android before Linux + self::checkAndroid($os, $userAgent) || + self::checkLinux($os, $userAgent) || + self::checkNokia($os, $userAgent) || + self::checkBlackBerry($os, $userAgent) + ); + } + + /** + * Determine if the user's browser is on a mobile device. + * + * @param Os $os + * @param UserAgent $userAgent + * + * @return bool + */ + public static function checkMobileBrowsers(Os $os, UserAgent $userAgent) + { + // Check for Opera Mini + if (stripos($userAgent->getUserAgentString(), 'opera mini') !== false) { + $os->setIsMobile(true); + } // Set is mobile for Pocket IE + elseif (stripos($userAgent->getUserAgentString(), 'mspie') !== false || + stripos($userAgent->getUserAgentString(), 'pocket') !== false) { + $os->setIsMobile(true); + } + } + + /** + * Determine if the user's operating system is iOS. + * + * @param Os $os + * @param UserAgent $userAgent + * + * @return bool + */ + private static function checkIOS(Os $os, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), 'CPU OS') !== false || + stripos($userAgent->getUserAgentString(), 'iPhone OS') !== false && + stripos($userAgent->getUserAgentString(), 'OS X')) { + $os->setName($os::IOS); + if (preg_match('/CPU( iPhone)? OS ([\d_]*)/i', $userAgent->getUserAgentString(), $matches)) { + $os->setVersion(str_replace('_', '.', $matches[2])); + } + $os->setIsMobile(true); + + return true; + } + + return false; + } + + /** + * Determine if the user's operating system is Chrome OS. + * + * @param Os $os + * @param UserAgent $userAgent + * + * @return bool + */ + private static function checkChromeOs(Os $os, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), ' CrOS') !== false || + stripos($userAgent->getUserAgentString(), 'CrOS ') !== false + ) { + $os->setName($os::CHROME_OS); + if (preg_match('/Chrome\/([\d\.]*)/i', $userAgent->getUserAgentString(), $matches)) { + $os->setVersion($matches[1]); + } + return true; + } + + return false; + } + + /** + * Determine if the user's operating system is OS X. + * + * @param Os $os + * @param UserAgent $userAgent + * + * @return bool + */ + private static function checkOSX(Os $os, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), 'OS X') !== false) { + $os->setName($os::OSX); + if (preg_match('/OS X ([\d\._]*)/i', $userAgent->getUserAgentString(), $matches)) { + if (isset($matches[1])) { + $os->setVersion(str_replace('_', '.', $matches[1])); + } + } + + return true; + } + + return false; + } + + /** + * Determine if the user's operating system is Windows. + * + * @param Os $os + * @param UserAgent $userAgent + * + * @return bool + */ + private static function checkWindows(Os $os, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), 'Windows NT') !== false) { + $os->setName($os::WINDOWS); + // Windows version + if (preg_match('/Windows NT ([\d\.]*)/i', $userAgent->getUserAgentString(), $matches)) { + if (isset($matches[1])) { + switch (str_replace('_', '.', $matches[1])) { + case '6.3': + $os->setVersion('8.1'); + break; + case '6.2': + $os->setVersion('8'); + break; + case '6.1': + $os->setVersion('7'); + break; + case '6.0': + $os->setVersion('Vista'); + break; + case '5.2': + case '5.1': + $os->setVersion('XP'); + break; + case '5.01': + case '5.0': + $os->setVersion('2000'); + break; + case '4.0': + $os->setVersion('NT 4.0'); + break; + default: + if ((float)$matches[1] >= 10.0) { + $os->setVersion($matches[1]); + } + break; + } + } + } + + return true; + } // Windows Me, Windows 98, Windows 95, Windows CE + elseif (preg_match( + '/(Windows 98; Win 9x 4\.90|Windows 98|Windows 95|Windows CE)/i', + $userAgent->getUserAgentString(), + $matches + )) { + $os->setName($os::WINDOWS); + switch (strtolower($matches[0])) { + case 'windows 98; win 9x 4.90': + $os->setVersion('Me'); + break; + case 'windows 98': + $os->setVersion('98'); + break; + case 'windows 95': + $os->setVersion('95'); + break; + case 'windows ce': + $os->setVersion('CE'); + break; + } + + return true; + } + + return false; + } + + /** + * Determine if the user's operating system is Windows Phone. + * + * @param Os $os + * @param UserAgent $userAgent + * + * @return bool + */ + private static function checkWindowsPhone(Os $os, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), 'Windows Phone') !== false) { + $os->setIsMobile(true); + $os->setName($os::WINDOWS_PHONE); + // Windows version + if (preg_match('/Windows Phone ([\d\.]*)/i', $userAgent->getUserAgentString(), $matches)) { + if (isset($matches[1])) { + $os->setVersion((float)$matches[1]); + } + } + + return true; + } + return false; + } + + /** + * Determine if the user's operating system is SymbOS. + * + * @param Os $os + * @param UserAgent $userAgent + * + * @return bool + */ + private static function checkSymbOS(Os $os, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), 'SymbOS') !== false) { + $os->setName($os::SYMBOS); + + return true; + } + + return false; + } + + /** + * Determine if the user's operating system is Linux. + * + * @param Os $os + * @param UserAgent $userAgent + * + * @return bool + */ + private static function checkLinux(Os $os, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), 'Linux') !== false) { + $os->setVersion($os::VERSION_UNKNOWN); + $os->setName($os::LINUX); + + return true; + } + + return false; + } + + /** + * Determine if the user's operating system is Nokia. + * + * @param Os $os + * @param UserAgent $userAgent + * + * @return bool + */ + private static function checkNokia(Os $os, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), 'Nokia') !== false) { + $os->setVersion($os::VERSION_UNKNOWN); + $os->setName($os::NOKIA); + $os->setIsMobile(true); + + return true; + } + + return false; + } + + /** + * Determine if the user's operating system is BlackBerry. + * + * @param Os $os + * @param UserAgent $userAgent + * + * @return bool + */ + private static function checkBlackBerry(Os $os, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), 'BlackBerry') !== false) { + if (stripos($userAgent->getUserAgentString(), 'Version/') !== false) { + $aresult = explode('Version/', $userAgent->getUserAgentString()); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + $os->setVersion($aversion[0]); + } + } else { + $os->setVersion($os::VERSION_UNKNOWN); + } + $os->setName($os::BLACKBERRY); + $os->setIsMobile(true); + + return true; + } elseif (stripos($userAgent->getUserAgentString(), 'BB10') !== false) { + $aresult = explode('Version/10.', $userAgent->getUserAgentString()); + if (isset($aresult[1])) { + $aversion = explode(' ', $aresult[1]); + $os->setVersion('10.' . $aversion[0]); + } else { + $os->setVersion('10'); + } + $os->setName($os::BLACKBERRY); + $os->setIsMobile(true); + + return true; + } + + return false; + } + + /** + * Determine if the user's operating system is Android. + * + * @param Os $os + * @param UserAgent $userAgent + * + * @return bool + */ + private static function checkAndroid(Os $os, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), 'Android') !== false) { + if (preg_match('/Android ([\d\.]*)/i', $userAgent->getUserAgentString(), $matches)) { + if (isset($matches[1])) { + $os->setVersion($matches[1]); + } + } else { + $os->setVersion($os::VERSION_UNKNOWN); + } + $os->setName($os::ANDROID); + $os->setIsMobile(true); + + return true; + } + + return false; + } + + /** + * Determine if the user's operating system is FreeBSD. + * + * @param Os $os + * @param UserAgent $userAgent + * + * @return bool + */ + private static function checkFreeBSD(Os $os, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), 'FreeBSD') !== false) { + $os->setVersion($os::VERSION_UNKNOWN); + $os->setName($os::FREEBSD); + + return true; + } + + return false; + } + + /** + * Determine if the user's operating system is OpenBSD. + * + * @param Os $os + * @param UserAgent $userAgent + * + * @return bool + */ + private static function checkOpenBSD(Os $os, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), 'OpenBSD') !== false) { + $os->setVersion($os::VERSION_UNKNOWN); + $os->setName($os::OPENBSD); + + return true; + } + + return false; + } + + /** + * Determine if the user's operating system is SunOS. + * + * @param Os $os + * @param UserAgent $userAgent + * + * @return bool + */ + private static function checkSunOS(Os $os, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), 'SunOS') !== false) { + $os->setVersion($os::VERSION_UNKNOWN); + $os->setName($os::SUNOS); + + return true; + } + + return false; + } + + /** + * Determine if the user's operating system is NetBSD. + * + * @param Os $os + * @param UserAgent $userAgent + * + * @return bool + */ + private static function checkNetBSD(Os $os, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), 'NetBSD') !== false) { + $os->setVersion($os::VERSION_UNKNOWN); + $os->setName($os::NETBSD); + + return true; + } + + return false; + } + + /** + * Determine if the user's operating system is OpenSolaris. + * + * @param Os $os + * @param UserAgent $userAgent + * + * @return bool + */ + private static function checkOpenSolaris(Os $os, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), 'OpenSolaris') !== false) { + $os->setVersion($os::VERSION_UNKNOWN); + $os->setName($os::OPENSOLARIS); + + return true; + } + + return false; + } + + /** + * Determine if the user's operating system is OS2. + * + * @param Os $os + * @param UserAgent $userAgent + * + * @return bool + */ + private static function checkOS2(Os $os, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), 'OS\/2') !== false) { + $os->setVersion($os::VERSION_UNKNOWN); + $os->setName($os::OS2); + + return true; + } + + return false; + } + + /** + * Determine if the user's operating system is BeOS. + * + * @param Os $os + * @param UserAgent $userAgent + * + * @return bool + */ + private static function checkBeOS(Os $os, UserAgent $userAgent) + { + if (stripos($userAgent->getUserAgentString(), 'BeOS') !== false) { + $os->setVersion($os::VERSION_UNKNOWN); + $os->setName($os::BEOS); + + return true; + } + + return false; + } +} diff --git a/inc/modules/statistics/phpbrowserdetector/UserAgent.php b/inc/modules/statistics/phpbrowserdetector/UserAgent.php new file mode 100644 index 0000000..36a2e42 --- /dev/null +++ b/inc/modules/statistics/phpbrowserdetector/UserAgent.php @@ -0,0 +1,56 @@ +setUserAgentString($userAgentString); + } + } + + /** + * @param string $userAgentString + * + * @return $this + */ + public function setUserAgentString($userAgentString) + { + $this->userAgentString = (string)$userAgentString; + + return $this; + } + + /** + * @return string + */ + public function getUserAgentString() + { + if (null === $this->userAgentString) { + $this->createUserAgentString(); + } + + return $this->userAgentString; + } + + /** + * @return string + */ + public function createUserAgentString() + { + $userAgentString = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : null; + $this->setUserAgentString($userAgentString); + + return $userAgentString; + } +} diff --git a/inc/modules/statistics/src/Chart.php b/inc/modules/statistics/src/Chart.php new file mode 100644 index 0000000..c76cf4d --- /dev/null +++ b/inc/modules/statistics/src/Chart.php @@ -0,0 +1,119 @@ +db('statistics') + ->select([ + 'count' => 'COUNT(*)', + 'count_unique' => 'COUNT(DISTINCT uniqhash)', + 'formatedDate' => "strftime('%Y-%m-%d', datetime(created_at, 'unixepoch', 'localtime'))", + ]) + ->where('bot', 0) + ->where('created_at', '>=', $time) + ->group(['formatedDate']) + ->asc('formatedDate'); + + if (!empty($url)) { + $query->where('url', $url); + } + + if (!empty($referrer)) { + $query->where('referrer', $referrer); + } + + $data = $query->toArray(); + + $return = [ + 'labels' => [], + 'uniques' => [], + 'visits' => [], + ]; + + while ($time < (time() - ($offset * 86400))) { + $return['labels'][] = '"'.date("Y-m-d", $time).'"'; + $return['readable'][] = '"'.date("d M Y", $time).'"'; + $return['uniques'][] = 0; + $return['visits'][] = 0; + + $time = strtotime('+1 day', $time); + } + + foreach ($data as $day) { + $index = array_search('"'.$day['formatedDate'].'"', $return['labels']); + if ($index === false) { + continue; + } + + $return['uniques'][$index] = $day['count_unique']; + $return['visits'][$index] = $day['count']; + } + + return $return; + } + + public function getOperatingSystems($url = null, $referrer = null) + { + return $this->getPopularBy('platform', $url, $referrer); + } + + public function getBrowsers($url = null, $referrer = null) + { + return $this->getPopularBy('browser', $url, $referrer); + } + + public function getCountries($url = null, $referrer = null) + { + return $this->getPopularBy('country', $url, $referrer); + } + + public function getPages($url = null, $referrer = null) + { + return $this->getPopularBy('url', $url, $referrer); + } + + public function getReferrers($url = null, $referrer = null) + { + return $this->getPopularBy('referrer', $url, $referrer); + } + + protected function getPopularBy($group, $url = null, $referrer = null) + { + $data = $this->db('statistics') + ->select([ + $group, + 'count' => 'COUNT(DISTINCT uniqhash)', + ]) + ->where('bot', 0) + ->group([$group]) + ->asc('count'); + + if (!empty($url)) { + $data->where('url', $url); + } + if (!empty($referrer)) { + $data->where('referrer', $referrer); + } + + $data = $data->toArray(); + + return [ + 'labels' => array_map(function (&$value) { + return '"'.$value.'"'; + }, array_column($data, $group)), + 'data' => array_column($data, 'count'), + ]; + } + + protected function db($table) + { + return new DB($table); + } +} diff --git a/inc/modules/statistics/src/Statistics.php b/inc/modules/statistics/src/Statistics.php new file mode 100644 index 0000000..2a2d2ce --- /dev/null +++ b/inc/modules/statistics/src/Statistics.php @@ -0,0 +1,124 @@ +db('statistics') + ->select([ + 'referrer', + 'count_unique' => 'COUNT(DISTINCT uniqhash)', + 'count' => 'COUNT(uniqhash)', + ]) + ->where('bot', $bot ? 1 : 0) + ->group(['referrer']) + ->desc('count'); + + if (!empty($url)) { + $query->where('url', $url); + } + if ($limit !== false) { + $query->limit($limit); + } + + $urls = $query->toArray(); + + return $urls; + } + + public function getPages($referrer = null, $limit = 15) + { + $query = $this->db('statistics') + ->select([ + 'url', + 'count_unique' => 'COUNT(DISTINCT uniqhash)', + 'count' => 'COUNT(uniqhash)', + ]) + ->group(['url']) + ->desc('count'); + + if ($limit !== false) { + $query->limit($limit); + } + + if (!empty($referrer)) { + $query->where('referrer', $referrer); + } + + $urls = $query->toArray(); + + return $urls; + } + + public function countCurrentOnline($margin = "-5 minutes") + { + $online = $this->db('statistics') + ->select([ + 'count' => 'COUNT(DISTINCT uniqhash)', + ]) + ->where('bot', 0) + ->where('created_at', '>', strtotime($margin)) + ->oneArray(); + + return $online['count']; + } + + public function countAllVisits($date = 'TODAY', $days = 1, $url = null, $referrer = null) + { + $query = $this->db('statistics') + ->select([ + 'count' => 'COUNT(uniqhash)', + ]) + ->where('bot', 0); + + if ($date != 'ALL') { + $date = strtotime($date); + $query->where('created_at', '>=', $date)->where('created_at', '<', $date + $days * 86400); + } + + if (!empty($url)) { + $query->where('url', $url); + } + if (!empty($referrer)) { + $query->where('referrer', $referrer); + } + + $all = $query->oneArray(); + + return $all['count']; + } + + public function countUniqueVisits($date = 'TODAY', $days = 1, $url = null, $referrer = null) + { + $query = $this->db('statistics') + ->select([ + 'count' => 'COUNT(DISTINCT uniqhash)', + ]) + ->where('bot', 0); + + if ($date != 'ALL') { + $date = strtotime($date); + $query->where('created_at', '>=', $date)->where('created_at', '<', $date + $days * 86400); + } + + if (!empty($url)) { + $query->where('url', $url); + } + if (!empty($referrer)) { + $query->where('referrer', $referrer); + } + + $record = $query->oneArray(); + + return $record['count']; + } + + protected function db($table) + { + return new DB($table); + } +} diff --git a/inc/modules/statistics/view/admin/dashboard.html b/inc/modules/statistics/view/admin/dashboard.html new file mode 100644 index 0000000..88ecb5c --- /dev/null +++ b/inc/modules/statistics/view/admin/dashboard.html @@ -0,0 +1,226 @@ +
    +
    + +
    +
    +

    {$lang.statistics.unique_today}

    +
    +
    +

    {?= $visitors.unique ?}

    +
    +
    + +
    +
    + +
    +
    +

    {$lang.statistics.today_visits}

    +
    +
    +

    {?= $visitors.visits.today ?}

    +
    +
    + +
    +
    + +
    +
    +

    {$lang.statistics.online}

    +
    +
    +

    {?= $visitors.online ?}

    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    +

    {$lang.statistics.yesterday}

    +
    +
    +

    {?= $visitors.visits.yesterday ?}

    +
    +
    + +
    + +
    + +
    +
    +

    {$lang.statistics.7days}

    +
    +
    +

    {?= $visitors.visits.7days ?}

    +
    +
    + +
    + +
    + +
    +
    +

    {$lang.statistics.30days}

    +
    +
    +

    {?= $visitors.visits.30days ?}

    +
    +
    + +
    + +
    + +
    +
    +

    {$lang.statistics.total}

    +
    +
    +

    {?= $visitors.visits.all ?}

    +
    +
    + +
    +
    + +
    +
    +
    +
    +

    {$lang.statistics.os}

    +
    +
    + +
    +
    +
    +
    +
    +
    +

    {$lang.statistics.browsers}

    +
    +
    + +
    +
    +
    +
    +
    +
    +

    {$lang.statistics.countries}

    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +

    {$lang.statistics.referrals}

    + {$lang.statistics.show} +
    +
    + + + + + + + {loop: $visitors.referrers} + + {if: $value.referrer == NULL} + + {else} + + {/if} + + + + {/loop} +
    {$lang.statistics.url}{$lang.statistics.visits}{$lang.statistics.unique}
    (direct visit){$value.referrer}{$value.count}{$value.count_unique}
    +
    +
    +
    +
    +
    +
    +

    {$lang.statistics.pages}

    + {$lang.statistics.show} +
    +
    + + + + + + + {loop: $visitors.pages} + + + + + + {/loop} +
    {$lang.statistics.url}{$lang.statistics.visits}{$lang.statistics.unique}
    {$value.url}{$value.count}{$value.count_unique}
    +
    +
    +
    +
    diff --git a/inc/modules/statistics/view/admin/pages.html b/inc/modules/statistics/view/admin/pages.html new file mode 100644 index 0000000..2baf830 --- /dev/null +++ b/inc/modules/statistics/view/admin/pages.html @@ -0,0 +1,44 @@ +

    {$lang.statistics.all_pages}

    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +

    {$lang.statistics.pages}

    +
    +
    + + + + + + + {loop: $pages.list} + + + + + + {/loop} +
    {$lang.statistics.url}{$lang.statistics.visits}{$lang.statistics.unique}
    {$value.url}{$value.count}{$value.count_unique}
    +
    +
    +
    +
    diff --git a/inc/modules/statistics/view/admin/referrer.html b/inc/modules/statistics/view/admin/referrer.html new file mode 100644 index 0000000..7cccc1c --- /dev/null +++ b/inc/modules/statistics/view/admin/referrer.html @@ -0,0 +1,133 @@ +

    {$lang.statistics.referrals_from} {$url}

    +
    +
    + +
    +
    +

    {$lang.statistics.all_unique}

    +
    +
    +

    {?= $visitors.unique ?}

    +
    +
    + +
    +
    + +
    +
    +

    {$lang.statistics.all_visits}

    +
    +
    +

    {?= $visitors.all ?}

    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +

    {$lang.statistics.os}

    +
    +
    + +
    +
    +
    +
    +
    +
    +

    {$lang.statistics.browsers}

    +
    +
    + +
    +
    +
    +
    +
    +
    +

    {$lang.statistics.countries}

    +
    +
    + +
    +
    +
    +
    +
    +
    +

    {$lang.statistics.target}

    +
    +
    + + + + + + + {loop: $visitors.referrers} + + {if: $value.url == NULL} + + {else} + + {/if} + + + + {/loop} +
    {$lang.statistics.url}{$lang.statistics.visits}{$lang.statistics.unique}
    ({$lang.statistics.direct_visit}){$value.url}{$value.count}{$value.count_unique}
    +
    +
    +
    +
    diff --git a/inc/modules/statistics/view/admin/referrers.html b/inc/modules/statistics/view/admin/referrers.html new file mode 100644 index 0000000..5cba24c --- /dev/null +++ b/inc/modules/statistics/view/admin/referrers.html @@ -0,0 +1,48 @@ +

    {$lang.statistics.all_referrals}

    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +

    {$lang.statistics.from_address}

    +
    +
    + + + + + + + {loop: $referrers.list} + + {if: $value.referrer == NULL} + + {else} + + {/if} + + + + {/loop} +
    {$lang.statistics.url}{$lang.statistics.visits}{$lang.statistics.unique}
    ({$lang.statistics.direct_visit}){$value.referrer}{$value.count}{$value.count_unique}
    +
    +
    +
    +
    diff --git a/inc/modules/statistics/view/admin/url.html b/inc/modules/statistics/view/admin/url.html new file mode 100644 index 0000000..3cbfe5b --- /dev/null +++ b/inc/modules/statistics/view/admin/url.html @@ -0,0 +1,133 @@ +

    {$lang.statistics.stats_page} {$url}

    +
    +
    + +
    +
    +

    {$lang.statistics.all_unique}

    +
    +
    +

    {?= $visitors.unique ?}

    +
    +
    + +
    +
    + +
    +
    +

    {$lang.statistics.all_visits}

    +
    +
    +

    {?= $visitors.all ?}

    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +

    {$lang.statistics.os}

    +
    +
    + +
    +
    +
    +
    +
    +
    +

    {$lang.statistics.browsers}

    +
    +
    + +
    +
    +
    +
    +
    +
    +

    {$lang.statistics.countries}

    +
    +
    + +
    +
    +
    +
    +
    +
    +

    {$lang.statistics.referrals}

    +
    +
    + + + + + + + {loop: $visitors.referrers} + + {if: $value.referrer == NULL} + + {else} + + {/if} + + + + {/loop} +
    {$lang.statistics.url}{$lang.statistics.visits}{$lang.statistics.unique}
    ({$lang.statistics.direct_visit}){$value.referrer}{$value.count}{$value.count_unique}
    +
    +
    +
    +
    diff --git a/inc/modules/users/Admin.php b/inc/modules/users/Admin.php new file mode 100644 index 0000000..98b735f --- /dev/null +++ b/inc/modules/users/Admin.php @@ -0,0 +1,261 @@ + + * @author Wojciech Król + * @copyright 2017 Paweł Klockiewicz, Wojciech Król + * @license https://batflat.org/license + * @link https://batflat.org + */ + +namespace Inc\Modules\Users; + +use Inc\Core\AdminModule; + +class Admin extends AdminModule +{ + private $assign = []; + + public function navigation() + { + return [ + $this->lang('manage', 'general') => 'manage', + $this->lang('add_new') => 'add' + ]; + } + + /** + * users list + */ + public function getManage() + { + $rows = $this->db('users')->toArray(); + foreach ($rows as &$row) { + if (empty($row['fullname'])) { + $row['fullname'] = '----'; + } + $row['editURL'] = url([ADMIN, 'users', 'edit', $row['id']]); + $row['delURL'] = url([ADMIN, 'users', 'delete', $row['id']]); + } + + return $this->draw('manage.html', ['myId' => $this->core->getUserInfo('id'), 'users' => $rows]); + } + + /** + * add new user + */ + public function getAdd() + { + if (!empty($redirectData = getRedirectData())) { + $this->assign['form'] = filter_var_array($redirectData, FILTER_SANITIZE_STRING); + } else { + $this->assign['form'] = ['username' => '', 'email' => '', 'fullname' => '', 'description' => '']; + } + + + $this->assign['title'] = $this->lang('new_user'); + $this->assign['modules'] = $this->_getModules('all'); + $this->assign['avatarURL'] = url(MODULES.'/users/img/default.png'); + + return $this->draw('form.html', ['users' => $this->assign]); + } + + /** + * edit user + */ + public function getEdit($id) + { + $user = $this->db('users')->oneArray($id); + + if (!empty($user)) { + $this->assign['form'] = $user; + $this->assign['title'] = $this->lang('edit_user'); + $this->assign['modules'] = $this->_getModules($user['access']); + $this->assign['avatarURL'] = url(UPLOADS.'/users/'.$user['avatar']); + + return $this->draw('form.html', ['users' => $this->assign]); + } else { + redirect(url([ADMIN, 'users', 'manage'])); + } + } + + /** + * save user data + */ + public function postSave($id = null) + { + $errors = 0; + + // location to redirect + if (!$id) { + $location = url([ADMIN, 'users', 'add']); + } else { + $location = url([ADMIN, 'users', 'edit', $id]); + } + + // admin + if ($id == 1) { + $_POST['access'] = ['all']; + } + + // check if required fields are empty + if (checkEmptyFields(['username', 'email', 'access'], $_POST)) { + $this->notify('failure', $this->lang('empty_inputs', 'general')); + redirect($location, $_POST); + } + + // check if user already exists + if ($this->_userAlreadyExists($id)) { + $errors++; + $this->notify('failure', $this->lang('user_already_exists')); + } + // chech if e-mail adress is correct + $_POST['email'] = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL); + if (!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) { + $errors++; + $this->notify('failure', $this->lang('wrong_email')); + } + // check if password is longer than 5 characters + if (isset($_POST['password']) && strlen($_POST['password']) < 5) { + $errors++; + $this->notify('failure', $this->lang('too_short_pswd')); + } + // access to modules + if ((count($_POST['access']) == count($this->_getModules())) || ($id == 1)) { + $_POST['access'] = 'all'; + } else { + $_POST['access'][] = 'dashboard'; + $_POST['access'] = implode(',', $_POST['access']); + } + + // CREATE / EDIT + if (!$errors) { + unset($_POST['save']); + + if (!empty($_POST['password'])) { + $_POST['password'] = password_hash($_POST['password'], PASSWORD_BCRYPT); + } + + if (($photo = isset_or($_FILES['photo']['tmp_name'], false)) || !$id) { + $img = new \Inc\Core\Lib\Image; + + if (empty($photo) && !$id) { + $photo = MODULES.'/users/img/default.png'; + } + if ($img->load($photo)) { + if ($img->getInfos('width') < $img->getInfos('height')) { + $img->crop(0, 0, $img->getInfos('width'), $img->getInfos('width')); + } else { + $img->crop(0, 0, $img->getInfos('height'), $img->getInfos('height')); + } + + if ($img->getInfos('width') > 512) { + $img->resize(512, 512); + } + + if ($id) { + $user = $this->db('users')->oneArray($id); + } + + $_POST['avatar'] = uniqid('avatar').".".$img->getInfos('type'); + } + } + + if (!$id) { // new + $query = $this->db('users')->save($_POST); + } else { // edit + $query = $this->db('users')->where('id', $id)->save($_POST); + } + + if ($query) { + if (isset($img) && $img->getInfos('width')) { + if (isset($user)) { + unlink(UPLOADS."/users/".$user['avatar']); + } + + $img->save(UPLOADS."/users/".$_POST['avatar']); + } + + $this->notify('success', $this->lang('save_success')); + } else { + $this->notify('failure', $this->lang('save_failure')); + } + + redirect($location); + } + + redirect($location, $_POST); + } + + /** + * remove user + */ + public function getDelete($id) + { + if ($id != 1 && $this->core->getUserInfo('id') != $id && ($user = $this->db('users')->oneArray($id))) { + if ($this->db('users')->delete($id)) { + if (!empty($user['avatar'])) { + unlink(UPLOADS."/users/".$user['avatar']); + } + + $this->notify('success', $this->lang('delete_success')); + } else { + $this->notify('failure', $this->lang('delete_failure')); + } + } + redirect(url([ADMIN, 'users', 'manage'])); + } + + /** + * list of active modules + * @return array + */ + private function _getModules($access = null) + { + $result = []; + $rows = $this->db('modules')->toArray(); + + if (!$access) { + $accessArray = []; + } else { + $accessArray = explode(',', $access); + } + + foreach ($rows as $row) { + if ($row['dir'] != 'dashboard') { + $details = $this->core->getModuleInfo($row['dir']); + + if (empty($accessArray)) { + $attr = ''; + } else { + if (in_array($row['dir'], $accessArray) || ($accessArray[0] == 'all')) { + $attr = 'selected'; + } else { + $attr = ''; + } + } + $result[] = ['dir' => $row['dir'], 'name' => $details['name'], 'icon' => $details['icon'], 'attr' => $attr]; + } + } + return $result; + } + + /** + * check if user already exists + * @return array + */ + private function _userAlreadyExists($id = null) + { + if (!$id) { // new + $count = $this->db('users')->where('username', $_POST['username'])->count(); + } else { // edit + $count = $this->db('users')->where('username', $_POST['username'])->where('id', '<>', $id)->count(); + } + if ($count > 0) { + return true; + } else { + return false; + } + } +} diff --git a/inc/modules/users/Info.php b/inc/modules/users/Info.php new file mode 100644 index 0000000..6afec23 --- /dev/null +++ b/inc/modules/users/Info.php @@ -0,0 +1,62 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +return [ + 'name' => $core->lang['users']['module_name'], + 'description' => $core->lang['users']['module_desc'], + 'author' => 'Sruu.pl', + 'version' => '1.1', + 'compatibility' => '1.3.*', + 'icon' => 'user', + + 'install' => function () use ($core) { + $core->db()->pdo()->exec("CREATE TABLE IF NOT EXISTS `users` ( + `id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, + `username` text NOT NULL, + `fullname` text NULL, + `description` text NULL, + `password` text NOT NULL, + `avatar` text NOT NULL, + `email` text NOT NULL, + `role` text NOT NULL DEFAULT 'admin', + `access` text NOT NULL DEFAULT 'all' + )"); + + $core->db()->pdo()->exec("CREATE TABLE `login_attempts` ( + `ip` TEXT NOT NULL, + `attempts` INTEGER NOT NULL, + `expires` INTEGER NOT NULL DEFAULT 0 + )"); + + $core->db()->pdo()->exec("CREATE TABLE IF NOT EXISTS `remember_me` ( + `id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, + `token` text NOT NULL, + `user_id` integer NOT NULL REFERENCES users(id) ON DELETE CASCADE, + `expiry` integer NOT NULL + )"); + + $avatar = uniqid('avatar').'.png'; + $core->db()->pdo()->exec('INSERT INTO `users` (`username`, `fullname`, `description`, `password`, `avatar`, `email`, `role`, `access`) + VALUES ("admin", "Selina Kyle", "My name is Selina Kyle but I speak for Catwoman… A mon who can offer you a path. Someone like you is only here by choice. You have been exploring the criminal fraternity but whatever your original intentions you have to become truly lost.", "$2y$10$pgRnDiukCbiYVqsamMM3ROWViSRqbyCCL33N8.ykBKZx0dlplXe9i", "'.$avatar.'", "admin@localhost", "admin", "all")'); + + if (!is_dir(UPLOADS."/users")) { + mkdir(UPLOADS."/users", 0777); + } + + copy(MODULES.'/users/img/default.png', UPLOADS.'/users/'.$avatar); + }, + 'uninstall' => function () use ($core) { + $core->db()->pdo()->exec("DROP TABLE `users`"); + $core->db()->pdo()->exec("DROP TABLE `login_attempts`"); + $core->db()->pdo()->exec("DROP TABLE `remember_me`"); + deleteDir(UPLOADS."/users"); + } +]; diff --git a/inc/modules/users/Site.php b/inc/modules/users/Site.php new file mode 100644 index 0000000..542d924 --- /dev/null +++ b/inc/modules/users/Site.php @@ -0,0 +1,31 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +namespace Inc\Modules\Users; + +use Inc\Core\SiteModule; + +class Site extends SiteModule +{ + public function init() + { + $this->tpl->set('users', function () { + $result = []; + $users = $this->db('users')->select(['id', 'username', 'fullname', 'description', 'avatar', 'email'])->toArray(); + + foreach ($users as $key => $value) { + $result[$value['id']] = $users[$key]; + $result[$value['id']]['avatar'] = url('uploads/users/' . $value['avatar']); + } + return $result; + }); + } +} diff --git a/inc/modules/users/img/default.png b/inc/modules/users/img/default.png new file mode 100644 index 0000000..eeae0e0 Binary files /dev/null and b/inc/modules/users/img/default.png differ diff --git a/inc/modules/users/lang/admin/en_english.ini b/inc/modules/users/lang/admin/en_english.ini new file mode 100644 index 0000000..540bd62 --- /dev/null +++ b/inc/modules/users/lang/admin/en_english.ini @@ -0,0 +1,23 @@ +module_name = "Users" +module_desc = "Management user accounts." + +add_new = "Add user" +new_user = "New user" +edit_user = "Edit user" +display_name = "Displayed name" +description = "Short description about you" +photo = "Avatar / Photo" +email = "E-mail" +new_password = "New password..." +access = "Access" +role = "Role" +user = "User" +admin = "Administrator" +save_success = "User successfully saved." +save_failure = "Failed to save user." +delete_success = "User successfully deleted." +delete_failure = "Unable to delete user." +delete_confirm = "Are you sure you want to delete selected user?" +user_already_exists = "Username already exist." +wrong_email = "E-mail is incorrect!" +too_short_pswd = "Minimum password length is 5 characters." \ No newline at end of file diff --git a/inc/modules/users/lang/admin/pl_polski.ini b/inc/modules/users/lang/admin/pl_polski.ini new file mode 100644 index 0000000..cf5fa59 --- /dev/null +++ b/inc/modules/users/lang/admin/pl_polski.ini @@ -0,0 +1,23 @@ +module_name = "Użytkownicy" +module_desc = "Zarządzanie kontami użytkowników." + +add_new = "Dodaj nowego" +new_user = "Nowy użytkownik" +edit_user = "Edycja użytkownika" +display_name = "Nazwa wyświetlana" +description = "Krótki opis Ciebie" +photo = "Avatar / Zdjęcie" +email = "Adres e-mail" +new_password = "Nowe hasło..." +access = "Dostęp" +role = "Rola" +user = "Użytkownik" +admin = "Administrator" +save_success = "Pomyślnie zapisano użytkownika." +save_failure = "Nie udało się zapisać użytkownika." +delete_success = "Pomyślnie usunięto użytkownika." +delete_failure = "Nie udało się usunąć użytkownika." +delete_confirm = "Na pewno chcesz usunąć wybranego użytkownika?" +user_already_exists = "Użytkownik o takim loginie już istnieje." +wrong_email = "Wprowadzono błędny adres e-mail!" +too_short_pswd = "Hasło musi zawierać minimum 5 znaków." \ No newline at end of file diff --git a/inc/modules/users/view/admin/form.html b/inc/modules/users/view/admin/form.html new file mode 100644 index 0000000..bb93527 --- /dev/null +++ b/inc/modules/users/view/admin/form.html @@ -0,0 +1,71 @@ +
    +
    +
    +
    +

    {$users.title}

    +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    + +
    + + +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/inc/modules/users/view/admin/manage.html b/inc/modules/users/view/admin/manage.html new file mode 100644 index 0000000..55ebf1b --- /dev/null +++ b/inc/modules/users/view/admin/manage.html @@ -0,0 +1,40 @@ + \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..0b19ab9 --- /dev/null +++ b/index.php @@ -0,0 +1,30 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +header('Content-Type:text/html;charset=utf-8'); + +define('BASE_DIR', __DIR__); +require_once('inc/core/defines.php'); + +if (DEV_MODE) { + error_reporting(E_ALL); + ini_set('display_errors', 1); +} else { + error_reporting(0); +} + +require_once('inc/core/lib/Autoloader.php'); +ob_start(base64_decode('XEluY1xDb3JlXE1haW46OnZlcmlmeUxpY2Vuc2U=')); + +// Site core init +$core = new Inc\Core\Site; + +ob_end_flush(); diff --git a/themes/admin/css/style.css b/themes/admin/css/style.css new file mode 100644 index 0000000..f32d0ed --- /dev/null +++ b/themes/admin/css/style.css @@ -0,0 +1,810 @@ +@charset "UTF-8"; +@import url('//cdn.jsdelivr.net/animatecss/3.5.0/animate.min.css'); + +html { + height: 100%; +} + body { + padding-bottom: 30px; + position: relative; + min-height: 100%; + } + +a, .btn { + transition: background 0.2s, color 0.2s; +} + a:hover, + a:focus { + text-decoration: none; + } + +/* 1. MAIN WRAPPER +--------------------------------------------------------- */ +#wrapper { + padding-left: 0; + transition: all 0.5s ease; + position: relative; +} +#wrapper.toggled { + padding-left: 250px; +} + +/* 2. SIDEBAR +--------------------------------------------------------- */ +#sidebar-wrapper { + z-index: 1000; + position: fixed; + left: 250px; + width: 0; + height: 100%; + margin-left: -250px; + overflow-y: auto; + overflow-x: hidden; + background: #222; + transition: all 0.5s ease; +} +#wrapper.toggled #sidebar-wrapper { + width: 250px; +} + +.sidebar-brand { + position: absolute; + top: 0; + width: 250px; + text-align: center; + padding: 20px 0; +} + .sidebar-brand img { + width: auto; + height: 30px; + display: inline-block; + margin-right: 2px; + } + .sidebar-brand h2 { + display: inline-block; + margin: 0; + vertical-align: middle; + font-weight: 600; + font-size: 24px; + } + .sidebar-brand h2 span { + color: #fff; + } + +.sidebar-nav { + position: absolute; + top: 75px; + width: 250px; + margin: 0; + padding: 0; + list-style: none; +} + .sidebar-nav > li { + text-indent: 10px; + line-height: 42px; + } + .sidebar-nav > li.sortable-placeholder { + background: rgba(255,255,255,0.03); + border-top: 1px solid rgba(255,255,255,0.075); + border-bottom: 1px solid rgba(255,255,255,0.075); + padding: 21px 0; + } + .sidebar-nav > li a { + display: block; + text-decoration: none; + color: #757575; + font-weight: 600; + } + .sidebar-nav > li > a:hover, + .sidebar-nav > li.active > a { + text-decoration: none; + color: #fff; + background: #F8BE12; + } + .sidebar-nav > li > a:active, + .sidebar-nav > li > a:focus { + text-decoration: none; + } + .sidebar-nav > li > a i.fa { + padding-right: 10px; + font-size: 14px; + cursor: move; + } + .sidebar-nav > li > ul { + background: rgba(255,255,255,0.03); + list-style: none; + margin: 0; + padding: 0 0 0 24px; + border-left: 1px solid #222; + position: relative; + } + .sidebar-nav > li > ul:before { + content: ""; + height: 100%; + width: 1px; + position: absolute; + background: rgba(255,255,255,0.075); + left: 24px; + top: 0; + } + .sidebar-nav > li > ul li { + line-height: 35px; + } + .sidebar-nav > li > ul li a { + font-weight: 400; + font-size: 90%; + padding: 0 8px; + position: relative; + } + .sidebar-nav > li > ul li a:hover { + color: #fff; + } + .sidebar-nav > li > ul li a:before { + content: ""; + width: 8px; + height: 1px; + background: rgba(255,255,255,0.075); + position: absolute; + left: 1px; + top: 17px; + } + .sidebar-nav > li > ul li.active a { + color: #fff; + } + +/* 3. NAVBAR +--------------------------------------------------------- */ +#navbar-wrapper { + width: 100%; + position: absolute; + z-index: 2; +} +#wrapper.toggled #navbar-wrapper { + position: absolute; + margin-right: -250px; +} + #navbar-wrapper .navbar { + padding: 10px 15px; + border-width: 0 0 0 0; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + font-size: 24px; + margin-bottom: 0; + } + #navbar-wrapper .navbar .navbar-header { + float: left; + } + #navbar-wrapper .navbar .navbar-header .navbar-brand { + font-size: 24px; + } + + #navbar-wrapper .navbar ul.navbar-nav { + float: right; + } + #navbar-wrapper .navbar ul.navbar-nav li { + float: left; + } + #navbar-wrapper .navbar ul.navbar-nav li:not(.hello) a { + color: #F8BE12; + } + #navbar-wrapper .navbar ul.navbar-nav li:not(.hello) a:hover { + color: #656565; + } + #navbar-wrapper .navbar ul.navbar-nav li.hello a { + pointer-events: none; + } + #navbar-wrapper .navbar ul.navbar-nav li.hello a span { + font-size: 70%; + position: relative; + bottom: 2px; + } + +/* 4. CONTENT +--------------------------------------------------------- */ +#content-wrapper { + width: 100%; + position: absolute; + padding: 15px; + top: 100px; +} +#wrapper.toggled #content-wrapper { + position: absolute; + margin-right: -250px; +} +.content-title { + margin: 11px 0 30px 0; +} +h3.panel-title { + font-weight: 400; +} + h3.panel-title .btn { + color: #fff; + } +.panel-heading { + position: relative; +} +.panel-heading .nav-tabs { + position: absolute; + bottom: 0; + right: 13px; + border-bottom: 0; +} + .panel-heading .nav-tabs > li a { + padding: 5px 10px; + margin-left: 2px; + } + .panel-heading .nav-tabs > li.active a { + border-bottom: 1px solid #fff; + background-color: #fff; + color: #616262; + } + +#notify { + display: none; + width: 100%; + position: absolute; + padding: 12px 30px; + color: #fff; + box-sizing: border-box; + margin: 0; + top: 75px; +} + #notify.alert-danger:before { + content: '\f071'; + font-family: 'FontAwesome'; + padding-right: 1em; + } + #notify.alert-success:before { + content: '\f00c'; + font-family: 'FontAwesome'; + padding-right: 1em; + } + #notify .close { + position: static; + } + + +/* 5. FOOTER +--------------------------------------------------------- */ +footer { + position: absolute; + right: 10px; + bottom: 5px; + height: 20px; + width: calc(100% - 15px); + text-align: right; + font-size: 12px; +} + +/* 6. LOGIN +--------------------------------------------------------- */ +#login { + width: 90%; + max-width: 420px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + -webkit-transform: translate(-50%, -50%); +} + #login .panel { + border: 0; + box-shadow: none; + background: none; + } + + #login .panel-heading { + background-color: #222; + color: #F8BE12; + text-align: center; + border: 0; + margin-top: -1px; + } + #login .panel-heading img { + height: 25px; + width: auto; + display: inline-block; + vertical-align: middle; + margin-right: 2px; + } + #login .panel-heading h2 { + font-weight: 600; + display: inline-block; + vertical-align: middle; + margin: 0; + font-size: 21px; + } + #login .panel-heading h2 span { + color: #FFF; + } + + #login .panel-body { + background: #FFF; + } + #login .panel-body .remember-me { + margin-top: 8px; + font-weight: 400; + font-size: 14px; + } + + #login #notify { + display: block; + position: static; + width: 100%; + display: block; + margin-top: 20px; + text-align: center; + } + #login .fa-lock { + padding: 0 1px 0 1px; + } + +/* 7. CUSTOM CHECKBOXES & RADIO BUTTONS +--------------------------------------------------------- */ +.toggle, +.toggleR { + position: relative; + background: #fff; + border: 1px solid #ccc; + display: inline-block; + vertical-align: middle; + width: 18px; + height: 18px; + padding: 2px; + margin-right: 5px; +} + .toggle.checked:after { + content: '\2713'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: #F8BE12; + font-weight: bold; + } +.toggleR { + border-radius: 50%; +} + .toggleR.checked:after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 10px; + height: 10px; + margin: -5px 0 0 -5px; + background-color: #F8BE12; + border-radius: 50%; + } +.toggle.disabled, +.toggleR.disabled { + opacity: .7; +} +.checkbox-inline .toggle, +.radio-inline .toggleR { + margin-left: -20px; +} + +/* 8. CUSTOM SELECTORS +--------------------------------------------------------- */ +#selectator_dimmer { + background-color: rgba(0, 0, 0, 0); + width: 100%; + height: 100%; + left: 0; + top: 0; + bottom: 0; + right: 0; + position: fixed; + z-index: 100; +} +.selectator_element { + width: 100%; + border: 1px solid #ccc; + box-sizing: border-box; + background-color: #fff; + display: inline-block; + text-decoration: none; + vertical-align: middle; + box-shadow: inset 0 1px 1px rgba(0,0,0,.075); +} +.selectator_element * { + box-sizing: border-box; + text-decoration: none; +} +.selectator_element img { + display: block; +} +.selectator_element.disabled { + background-color: #f5f5f5; + cursor: not-allowed; +} +.selectator_element.disabled * { + cursor: not-allowed !important; +} +.selectator_element.multiple { + padding: 4px 20px 4px 12px !important; +} +.selectator_element.single { + height: 39px; + padding: 9px 12px !important; +} +.selectator_element.focused { + border: 1px solid #ffce54; + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(255, 206, 84, 0.6); +} +.selectator_element:after { + position: absolute; + content: '\25BC'; + font-size: 70%; + transform: scaleY(0.75); + right: 4px; + color: #434a54; + top: 50%; + line-height: 0; +} +.selectator_element.loading:before { + border: 3px solid rgba(0, 0, 0, 0.1); + border-top: 3px solid rgba(0, 0, 0, 0.5); + border-radius: 50%; + width: 14px; + line-height: 0; + height: 14px; + margin-top: -10px; + animation: selectator_spinner 500ms linear infinite; + content: ''; + position: absolute; + right: 20px; + top: 50%; +} + +.selectator_selected_items .selectator_placeholder { + font-size: 80%; + color: rgba(0, 0, 0, 0.5); +} +.single .selectator_selected_items { + display: block; +} +.multiple .selectator_selected_items { + display: inline; +} +.selectator_selected_items .selectator_selected_item { + color: rgba(0, 0, 0, 0.75); + position: relative; + vertical-align: top; +} +.single .selectator_selected_items .selectator_selected_item { + background-color: transparent; + display: block; + margin: 0; + padding: 0; + font-size: inherit; + text-decoration: none; +} +.multiple .selectator_selected_items .selectator_selected_item { + background-color: #f5f5f5; + display: inline-block; + margin: 2.5px 5px 2.5px 0; + padding: 0 20px 0 5px; + font-size: 80%; + border: 1px solid #ccc; + line-height: 22px; +} +.selectator_selected_items .selectator_selected_item .selectator_selected_item_left { + float: left; +} +.single .selectator_selected_items .selectator_selected_item .selectator_selected_item_left { + float: left; + width: 30px; +} +.single .selectator_selected_items .selectator_selected_item .selectator_selected_item_left img { + height: 22px; +} +.multiple .selectator_selected_items .selectator_selected_item .selectator_selected_item_left { + float: left; + width: 22px; +} +.multiple .selectator_selected_items .selectator_selected_item .selectator_selected_item_left img { + height: 18px; +} +.single .selectator_selected_items .selectator_selected_item .selectator_selected_item_title { + height: auto; + line-height: 1.3; +} +.multiple .selectator_selected_items .selectator_selected_item .selectator_selected_item_title { + float: left; + font-weight: 700; +} +.selectator_selected_items .selectator_selected_item .selectator_selected_item_subtitle { + display: none; +} +.single .selectator_selected_items .selectator_selected_item .selectator_selected_item_right { + float: right; + background-color: #ac6; + color: #fff; + font-weight: bold; + font-size: 80%; + text-align: center; + line-height: 16px; + border-radius: 12px; + padding: 2px 12px; + margin-right: 40px; +} +.multiple .selectator_selected_items .selectator_selected_item .selectator_selected_item_right { + display: none; +} +.single .selectator_selected_items .selectator_selected_item .selectator_selected_item_remove { + display: block; + position: absolute; + right: 16px; + cursor: pointer; + font-size: 75%; + font-weight: bold; + color: rgba(0, 0, 0, 0.75); + padding: 2px; + line-height: 0; + top: 50%; + transform: scaleX(1.2); +} +.multiple .selectator_selected_items .selectator_selected_item .selectator_selected_item_remove { + display: inline-block; + font-weight: bold; + color: rgba(0, 0, 0, 0.75); + margin: 0 0 0 5px; + cursor: pointer; + font-size: 100%; + font-weight: 700; + line-height: 22px; + vertical-align: top; + border-radius: 0 2px 2px 0; + position: absolute; + right: 0; + top: 0; + bottom: 0; + padding: 0 5px 0 5px; +} +.multiple .selectator_selected_items .selectator_selected_item .selectator_selected_item_remove:hover { + background-color: rgba(0, 0, 0, 0.1); +} + +.selectator_input, .selectator_textlength { + border: 0; + display: inline-block; + margin: 0; + background-color: transparent; + font-size: 13px; + outline: none; +} +.multiple .selectator_input, .multiple .selectator_textlength { + padding: 3px 0 0 0; + margin: 3px 0 2px 5px; +} +.single .selectator_input { + border: 1px solid #ccc; + position: absolute; + bottom: -41px; + left: -1px; + z-index: 101; + padding: 9px 12px; + width: 100%; + width: calc(100% + 2px); + border-bottom: 0; + background-color: #f5f5f5; + color: #333; + font-size: inherit; +} +.single.options-hidden .selectator_input { + opacity: 0; + position: absolute; + left: -10000px; +} +.single.options-visible .selectator_input { + opacity: 1; +} +.disable_search .selectator_input { + opacity: 0; + padding: 0 1px 1px 0 !important; +} + +.selectator_options { + margin: 0; + padding: 0; + border: 1px solid #ccc; + position: absolute; + box-sizing: border-box; + z-index: 101; + background-color: #fff; + overflow-y: auto; + list-style: none; + left: -1px; + right: -1px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); +} +.disable_search .selectator_options { + border-top: 1px solid #ccc; +} +.single.disable_search .selectator_options { + padding-top: 0; +} +.options-hidden .selectator_options { + display: none; +} +.selectator_options .selectator_group { + padding: 5px; + font-weight: bold; +} +.selectator_options .selectator_option { + padding: 5px; + cursor: pointer; + color: #434a54; +} +.selectator_options .selectator_option.selectator_group_option { + padding-left: 20px; +} +.selectator_options .selectator_option:before, .selectator_options .selectator_option:after { + content: ""; + display: table; +} +.selectator_options .selectator_option:after { + clear: both; +} +.selectator_options .selectator_option .selectator_option_left { + float: left; + margin: 0 8px 0 12px; +} +.selectator_options .selectator_option .selectator_option_left img { + height: 30px; + border-radius: 2px; +} +.selectator_options .selectator_option .selectator_option_title { + margin-left: 12px; +} +.selectator_options .selectator_option .selectator_option_subtitle { + font-size: 70%; + color: rgba(0, 0, 0, 0.5); + margin-top: -4px; + margin-bottom: -1px; + margin-left: 12px; +} +.selectator_options .selectator_option .selectator_option_right { + float: right; + color: #434a54; + font-weight: bold; + font-size: 80%; + text-align: center; + line-height: 16px; + border-radius: 12px; + padding: 2px 12px; + margin-top: 4px; +} +.selectator_options .selectator_option.active { + background-color: #f5f5f5; +} +.selectator_options .selectator_option.active .selectator_option_subtitle { + color: rgba(255, 255, 255, 0.6); +} +@keyframes selectator_spinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +/* 9. OTHER STUFF +--------------------------------------------------------- */ +[class*="bg-"] { + padding: 15px; +} +.well .label { + font-size: 80%; + padding: .4em .9em .4em; +} +.tooltip-inner { + white-space: pre; +} +.table>tbody>tr>td { + vertical-align: middle; +} +.table a:not(.btn), +table a:not(.btn) { + text-decoration: none; +} +.pagination { + margin: 21px 0 0 0; +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: #f5f5f5; +} +select[multiple].form-control { + height: 39px; +} +.text-muted { + color:#bababa; +} +.modal-title { + font-weight: 400; +} + +/* 10. HELPERS +--------------------------------------------------------- */ +.no-margin { + margin: 0; +} +.no-top-margin { + margin-top: 0; +} +.no-bottom-margin { + margin-bottom: 0; +} +.margin-tiny { + margin-bottom: 1rem; +} +.margin-small { + margin-bottom: 2rem; +} +.margin-medium { + margin-bottom: 4rem; +} +.margin-large { + margin-bottom: 6rem; +} + +/* 11. MEDIA QUERIES +--------------------------------------------------------- */ +.panel-heading .tinynav { + position: absolute; + top: 7px; + right: 15px; + display: block +} +.panel-heading .nav-tabs { + display: none +} + +@media(min-width: 768px) { + #wrapper { + padding-left: 250px; + } + + #wrapper.toggled { + padding-left: 0; + } + + #sidebar-wrapper { + width: 250px; + } + + #wrapper.toggled #sidebar-wrapper { + width: 0; + } + + #navbar-wrapper { + position: relative; + } + + #content-wrapper { + position: relative; + top: 0; + } + + #wrapper.toggled #navbar-wrapper, + #wrapper.toggled #content-wrapper { + position: relative; + margin-right: 0; + } + + #notify { + position: relative; + top: 0; + } + + .panel-heading .tinynav { + display: none; + } + .panel-heading .nav-tabs { + display: block; + } +} \ No newline at end of file diff --git a/themes/admin/img/logo.svg b/themes/admin/img/logo.svg new file mode 100644 index 0000000..35b23b3 --- /dev/null +++ b/themes/admin/img/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/themes/admin/img/unknown_theme.png b/themes/admin/img/unknown_theme.png new file mode 100644 index 0000000..adf94c5 Binary files /dev/null and b/themes/admin/img/unknown_theme.png differ diff --git a/themes/admin/index.html b/themes/admin/index.html new file mode 100644 index 0000000..d7cb515 --- /dev/null +++ b/themes/admin/index.html @@ -0,0 +1,98 @@ + + + + + + + Batflat - {$module.name} + + + + + + + + + + + + + {loop: $bat.header}{$value}{/loop} + + + + +
    + + + + + + {if: $bat.notify} + + {/if} + +
    +
    +
    +
    +

    {$module.name}

    + {$module.content} +
    +
    +
    +
    + +
    + + + + + {loop: $bat.footer}{$value}{/loop} + + \ No newline at end of file diff --git a/themes/admin/js/kalypto.min.js b/themes/admin/js/kalypto.min.js new file mode 100644 index 0000000..b93cc28 --- /dev/null +++ b/themes/admin/js/kalypto.min.js @@ -0,0 +1 @@ +!function(e){"use strict";function t(t){function s(){var s=e(this);if(void 0===s.data("kalypto")){var n=new e.kalypto(this,t);s.data("kalypto",n)}else console.log("Kalypto is already defined on this element.",s)}return this.each(s)}function s(t,s){function n(){d.settings=e.extend({},o,s),i(),a()}function i(){function e(){var e=d.settings.toggleClass;if(d.settings.copyInputClasses){var t=g.attr("class");t&&(e+=" "+t)}return d.settings.customClasses.length&&(e+=" "+d.settings.customClasses),g.is("[disabled]")&&(e+=" disabled"),g.is(":checked")?''+d.settings.dataLabel+"":''+d.settings.dataLabel+""}g.next().hasClass(d.settings.toggleClass)||(g.after(e),d.settings.hideInputs&&g.hide(),l=g.next(),g.trigger(d.settings.elBuiltEvent))}function a(){g.next().bind("click",c),g.bind("change",c)}function c(t){function s(){"radio"===g.attr("type")&&i.each(n),g.trigger(g.is(":checked")?d.settings.checkedEvent:d.settings.uncheckedEvent),g.next().toggleClass(d.settings.checkedClass)}function n(t,s){var n=e(s);n.next().removeClass(d.settings.checkedClass),n.is(":checked")||d.lastClickedEl===n.next().get(0)||n.trigger(d.settings.uncheckedEvent)}var i="radio"===g.attr("type")?e('input[name="'+g.attr("name")+'"]'):g;if("INPUT"!==this.tagName){if(t.preventDefault(),g.is("[disabled]"))return;d.lastClickedEl=this,g.trigger("click")}else setTimeout(s,0)}var l,d=this,g=e(t),o={toggleClass:"toggle",checkedClass:"checked",hideInputs:!0,copyInputClasses:!0,dataLabel:g.data("label")||"",checkedEvent:"k_checked",uncheckedEvent:"k_unchecked",elBuiltEvent:"k_elbuilt",customClasses:""};d.settings={},d.init=n,d.init()}e.kalypto=s,e.fn.kalypto=t}(jQuery); \ No newline at end of file diff --git a/themes/admin/js/scripts.js b/themes/admin/js/scripts.js new file mode 100644 index 0000000..769ec35 --- /dev/null +++ b/themes/admin/js/scripts.js @@ -0,0 +1,147 @@ +/* 1. TOGGLE SIDEBAR +--------------------------------------------------------- */ +$("#sidebar-toggle").click(function(e) { + e.preventDefault(); + $("#wrapper").toggleClass("toggled"); +}); + +/* 2. COLLAPSE LINKS IN SIDEBAR +--------------------------------------------------------- */ +$('.sidebar-nav li a').click(function(e) +{ + if($('li:hidden', $(this).next()).length) + { + e.preventDefault(); + $('.sidebar-nav li ul.in').collapse('hide'); + $(this).next('ul').collapse('show'); + } + else if($('li:visible', $(this).next()).length) + { + e.preventDefault(); + $(this).next('ul').collapse('hide'); + } +}); +$('.sidebar-nav li.active ul').addClass('in'); + +/* 3. CONFIRM BOX +--------------------------------------------------------- */ +$(document).on('click touchstart', '[data-confirm]:not(.disabled):not([disabled])', function(evt) +{ + evt.preventDefault(); + var text = $(this).attr('data-confirm'); + var source = $(this); + + bootbox.confirm({ + message: text, + callback: function(result) { + if(result) + { + if(source.is('[type="submit"]')) + { + $(document).off('click touchstart', '[data-confirm]:not(.disabled):not([disabled])'); + source.click(); + } + else if(source.is('a')) + { + $(location).attr('href', source.attr('href')); + } + } + } + }); +}); + +/* 4. TOOLTIP ACTIVATION +--------------------------------------------------------- */ +$(function () { + $("[data-toggle='tooltip']").tooltip(); + $("[data-toggle='popover']").popover(); +}); + +/* 5. NOTIFICATION +--------------------------------------------------------- */ +$(function () { + if($('#notify').length) + { + $('#notify').slideDown(500); + if($( window ).width() < 768) + $('#content-wrapper').animate({'top' : '+=46'}, 500); + + setTimeout(function() { + $('#notify').slideUp(500); + if($( window ).width() < 768) + $('#content-wrapper').animate({'top' : '-=46'}, 500); + }, 8000); + } +}); + +/* 6. SORTABLE SIDEBAR +--------------------------------------------------------- */ +$(function () { + sortable('.sidebar-nav', {handle:'i'})[0].addEventListener('sortupdate', function(e) { + var baseURL = batflat.url + '/' + batflat.admin; + var items = {}; + + $(e.detail.endparent).children('li').each(function(index, element) { + var module = $(element).data('module'); + items[module] = index; + }); + + $.ajax({ + url: baseURL + '/settings/changeOrderOfNavItem?t=' + batflat.token, + type: 'POST', + cache: false, + data: items, + success: function(respond) { + console.log(respond); + } + }); + }); +}); + +/* 7. TINYNAV +--------------------------------------------------------- */ +$(function () { + $('.panel-heading .nav-tabs').tinyNav({ + active: 'active' + }); +}); + +/* 8. CUSTOM CHECKBOXES & RADIO BUTTONS +--------------------------------------------------------- */ +$(':checkbox').kalypto(); +$(':radio').kalypto({toggleClass: 'toggleR'}); + +/* 9. REMOTE MODAL +--------------------------------------------------------- */ +$('a[data-toggle="modal"]').on('click', function(e) { + var target_modal = $(e.currentTarget).data('target'); + var remote_content = $(e.currentTarget).attr('href'); + + if(remote_content.indexOf('#') === 0) return; + + var modal = $(target_modal); + var modalContent = $(target_modal + ' .modal-content'); + + modal.off('show.bs.modal'); + modal.on('show.bs.modal', function () { + modalContent.load(remote_content); + }).modal(); + + return false; +}); + +/* 10. CUSTOM SELECT +--------------------------------------------------------- */ +$('select').each(function () { + var options = { + useDimmer: true, + useSearch: false, + labels: { + search: '...' + } + }; + $.each($(this).data(), function (key, value) { + options[key] = value; + }); + $(this).selectator(options); +}); \ No newline at end of file diff --git a/themes/admin/js/selectator.min.js b/themes/admin/js/selectator.min.js new file mode 100644 index 0000000..9aee847 --- /dev/null +++ b/themes/admin/js/selectator.min.js @@ -0,0 +1,9 @@ + /* + Selectator jQuery Plugin + version 3.1, 30.04.2017 + by Ingi P. Jacobsen (Faroe Media) & Sruu.pl + + The MIT License (MIT) + */ + +!function(a){"use strict";a.selectator=function(b,c){var d={prefix:"selectator_",width:"100%",height:"auto",optionsHeight:"158px",useDimmer:!1,useSearch:!0,useOnMobile:!1,showAllOptionsOnFocus:!1,selectFirstOptionOnSearch:!0,keepOpen:!1,submitCallback:function(){},load:null,delay:0,minSearchLength:0,valueField:"value",textField:"text",searchFields:["value","text"],placeholder:"",render:{selected_item:function(a,b){var c="";return void 0!==a.left&&(c+='
    '+a.left+"
    "),void 0!==a.right&&(c+='
    '+a.right+"
    "),c+='
    '+(void 0!==a.text?b(a.text):"")+"
    ",void 0!==a.subtitle&&(c+='
    '+b(a.subtitle)+"
    "),c+='
    x
    '},option:function(a,b){var c="";return void 0!==a.left&&(c+='
    '+a.left+"
    "),void 0!==a.right&&(c+='
    '+a.right+"
    "),c+='
    '+(void 0!==a.text?b(a.text):"")+"
    ",void 0!==a.subtitle&&(c+='
    '+b(a.subtitle)+"
    "),c}},labels:{search:"Search..."}},e=this;e.options={},e.$source_element=a(b),e.$container_element=null,e.$selecteditems_element=null,e.$input_element=null,e.$textlength_element=null,e.$options_element=null,e.usefilterResults=!0;var f=void 0===e.$source_element.attr("multiple"),g=!f,h=e.$source_element.is(":disabled"),i=/Mobi/i.test(navigator.userAgent),j=!1,k=!0,l=null,m={backspace:8,tab:9,enter:13,shift:16,ctrl:17,alt:18,capslock:20,escape:27,pageup:33,pagedown:34,end:35,home:36,left:37,up:38,right:39,down:40};e.init=function(){if(e.options=a.extend(!0,{},d,c),a.each(e.$source_element.data(),function(a,b){"selectator"==a.substring(0,10)&&(e.options[a.substring(10,11).toLowerCase()+a.substring(11)]=b)}),e.options.searchFields="string"==typeof e.options.searchFields?e.options.searchFields.split(" "):e.options.searchFields,e.$source_element.find("option").each(function(){a(this).data("value",this.value),a(this).data("text",this.text)}),j=null!==e.options.load,e.options.useDimmer&&0===a("#"+e.options.prefix+"dimmer").length){var b=a(document.createElement("div"));b.attr("id",e.options.prefix+"dimmer"),b.hide(),a(document.body).prepend(b)}e.$source_element.addClass("selectator"),e.$source_element.attr("placeholder")&&(e.options.placeholder=e.$source_element.attr("placeholder")),e.$container_element=a(document.createElement("div")),void 0!==e.$source_element.attr("id")&&e.$container_element.attr("id",e.options.prefix+e.$source_element.attr("id")),e.$container_element.addClass(e.options.prefix+"element "+(g?"multiple ":"single ")+"options-hidden"),e.options.useSearch||e.$container_element.addClass("disable_search"),h&&e.$container_element.addClass("disabled"),e.$container_element.css({width:e.options.width,minHeight:"auto"===e.options.height?e.$source_element.css("height"):e.options.height,padding:e.$source_element.css("padding"),"flex-grow":e.$source_element.css("flex-grow"),position:"relative"}),"element"===e.options.height&&e.$container_element.css({height:e.$source_element.outerHeight()+"px"}),e.$textlength_element=a(document.createElement("span")),e.$textlength_element.addClass(e.options.prefix+"textlength"),e.$textlength_element.css({position:"absolute",visibility:"hidden"}),e.$container_element.append(e.$textlength_element),e.$selecteditems_element=a(document.createElement("div")),e.$selecteditems_element.addClass(e.options.prefix+"selected_items"),e.$container_element.append(e.$selecteditems_element),e.$input_element=a(document.createElement("input")),e.$input_element.addClass(e.options.prefix+"input"),e.$input_element.attr("tabindex",e.$source_element.attr("tabindex")),e.options.useSearch?f?e.$input_element.attr("placeholder",e.options.labels.search):(""!=e.options.placeholder&&e.$input_element.attr("placeholder",e.options.placeholder),e.$input_element.width(20)):(e.$input_element.attr("readonly",!0),e.$input_element.css({width:"0px",height:"0px",overflow:"hidden",border:0,padding:0,position:"absolute"})),e.$input_element.attr("autocomplete","false"),e.$container_element.append(e.$input_element),e.$options_element=a(document.createElement("ul")),e.$options_element.addClass(e.options.prefix+"options"),e.$options_element.css("max-height",e.options.optionsHeight),e.$container_element.append(e.$options_element),e.$source_element.after(e.$container_element),e.$source_element.hide(),"undefined"!=typeof Scrollator&&e.$options_element.scrollator({zIndex:1001,customClass:"ease_preventOverlay"}),e.$source_element.change(function(){o()}),e.$container_element.not(".disabled").on("focus",function(){e.$input_element.focus(),e.$input_element.trigger("focus")}),e.$container_element.not(".disabled").on("mousedown",function(a){if(a.preventDefault(),e.$input_element.focus(),e.$input_element.trigger("focus"),e.$input_element[0].setSelectionRange)e.$input_element[0].setSelectionRange(e.$input_element.val().length,e.$input_element.val().length);else if(e.$input_element[0].createTextRange){var b=e.$input_element[0].createTextRange();b.collapse(!0),b.moveEnd("character",e.$input_element.val().length),b.moveStart("character",e.$input_element.val().length),b.select()}}),e.$container_element.not(".disabled").on("click",function(){e.$input_element.focus(),e.$input_element.trigger("focus")}),e.$container_element.not(".disabled").on("dblclick",function(){e.$input_element.select(),e.$input_element.trigger("focus")}),e.$input_element.on("keydown",function(a){var b=a.keyCode||a.which,c=null,d=null;switch(b){case m.up:a.preventDefault(),t(),c=e.$options_element.find(".active"),0!==c.length?(d=c.prevUntil("."+e.options.prefix+"option:visible").add(c).first().prev("."+e.options.prefix+"option:visible"),c.removeClass("active"),d.addClass("active")):e.$options_element.find("."+e.options.prefix+"option").filter(":visible").last().addClass("active"),v();break;case m.down:a.preventDefault(),t(),c=e.$options_element.find(".active"),0!==c.length?(d=c.nextUntil("."+e.options.prefix+"option:visible").add(c).last().next("."+e.options.prefix+"option:visible"),c.removeClass("active"),d.addClass("active")):e.$options_element.find("."+e.options.prefix+"option").filter(":visible").first().addClass("active"),v();break;case m.escape:a.preventDefault();break;case m.enter:a.preventDefault(),c=e.$options_element.find(".active"),0!==c.length?w():""!==e.$input_element.val()&&e.options.submitCallback(e.$input_element.val()),n();break;case m.backspace:e.options.useSearch?(""===e.$input_element.val()&&g&&e.$source_element.find("option:selected").length&&(e.$source_element.find("option:selected").last()[0].removeAttribute("selected"),e.$source_element.find("option:selected").last()[0].selected=!1,e.$source_element.trigger("change"),o()),n()):a.preventDefault();break;default:n()}}),e.$input_element.on("keyup",function(a){a.preventDefault(),a.stopPropagation();var b=a.which;switch(b){case m.escape:u();break;case m.enter:e.options.keepOpen||u();break;case m.left:case m.right:case m.up:case m.down:case m.tab:case m.shift:break;default:r()}!e.$container_element.hasClass("options-hidden")||b!==m.left&&b!==m.right&&b!==m.up&&b!==m.down||t(),n()}),e.$input_element.on("focus",function(){e.$container_element.addClass("focused"),(f||e.options.showAllOptionsOnFocus||!e.options.useSearch)&&t()}),e.$input_element.on("blur",function(){e.$container_element.removeClass("focused"),u()}),e.$container_element.not(".disabled").on("mouseup","."+e.options.prefix+"selected_item_remove",function(){var b=a(this).closest("."+e.options.prefix+"selected_item").data("source_item_element");b.removeAttribute("selected"),b.selected=!1,f&&e.$source_element.find('[value=""]').length&&(e.$source_element.find('[value=""]')[0].selected=!0,e.$source_element.find('[value=""]')[0].removeAttribute("selected")),e.$source_element.trigger("change"),s(e.usefilterResults),p(),o()}),e.$container_element.on("mouseover","."+e.options.prefix+"option",function(){e.$options_element.find(".active").removeClass("active"),a(this).addClass("active")}),e.$container_element.on("mousedown","."+e.options.prefix+"option",function(a){a.preventDefault(),a.stopPropagation()}),e.$container_element.on("mouseup","."+e.options.prefix+"option",function(){w()}),e.$container_element.on("click","."+e.options.prefix+"option",function(a){a.stopPropagation()}),e.options.$source_element=e.$source_element,e.options.$container_element=e.$container_element,e.options.$selecteditems_element=e.$selecteditems_element,e.options.$input_element=e.$input_element,e.options.$textlength_element=e.$textlength_element,e.options.$options_element=e.$options_element,p(),o(),n()};var n=function(){if(g){e.$textlength_element.text(""===e.$input_element.val()&&""!==e.options.placeholder?e.options.placeholder:e.$input_element.val());var a=e.$textlength_element.width()>e.$container_element.width()-30?e.$container_element.width()-30:e.$textlength_element.width()+30;e.$input_element.css({width:a+"px"})}},o=function(){e.$selecteditems_element.empty(),e.$source_element.find("option").each(function(){var b=a(this);if(this.selected){var c=a(document.createElement("div"));c.data("source_item_element",this),c.addClass(e.options.prefix+"selected_item"),c.addClass(e.options.prefix+"value_"+b.val().replace(/\W/g,"")),void 0!==b.attr("class")&&c.addClass(b.attr("class"));var d={value:this.value,text:this.text};a.each(this.attributes,function(){this.specified&&(d[this.name.replace("data-","")]=this.value)}),a.extend(d,a(this).data("item_data")),c.append(e.options.render.selected_item(d,x)),!f||""!=d[e.options.valueField]&&void 0!==d[e.options.valueField]&&0!==e.$source_element.find('[value=""]').length||c.find("."+e.options.prefix+"selected_item_remove").remove(),e.$selecteditems_element.append(c)}}),f&&(""==e.options.placeholder||""!==e.$source_element.val()&&null!==e.$source_element.val()?e.$selecteditems_element.find("."+e.options.prefix+"placeholder").remove():(e.$selecteditems_element.empty(),e.$selecteditems_element.append('
    '+e.options.placeholder+"
    ")))},p=function(){e.$options_element.empty();var b=[];e.$source_element.children().each(function(){if("optgroup"===a(this).prop("tagName").toLowerCase()){var c=a(this);if(0!==c.children("option").length){var d=[];c.children("option").each(function(){d.push({type:"option",text:a(this).html(),element:this})}),b.push({type:"group",text:c.attr("label"),options:d,element:c})}}else b.push({type:"option",text:a(this).html(),element:this})}),a(b).each(function(){if("group"===this.type){var b=a(document.createElement("li"));b.addClass(e.options.prefix+"group"),void 0!==a(this.element).attr("class")&&b.addClass(a(this.element).attr("class")),b.html(a(this.element).attr("label")),e.$options_element.append(b),a(this.options).each(function(){var a=q.call(this.element,!0);e.$options_element.append(a)})}else{var c=q.call(this.element,!1);e.$options_element.append(c)}}),s(e.usefilterResults)},q=function(b){var c=a(document.createElement("li"));c.data("source_option_element",this),c.addClass(e.options.prefix+"option"),c.addClass(e.options.prefix+"value_"+a(this).val().replace(/\W/g,"")),b&&c.addClass(e.options.prefix+"group_option"),this.selected&&c.addClass("active"),void 0!==a(this).attr("class")&&c.addClass(a(this).attr("class"));var d={value:this.value,text:this.text};return a.each(this.attributes,function(){this.specified&&(d[this.name.replace("data-","")]=this.value)}),a.extend(d,a(this).data("item_data")),g&&this.selected&&c.hide(),c.append(e.options.render.option(d,x)),c},r=function(){clearTimeout(l),l=setTimeout(function(){e.$container_element.addClass("loading"),j?e.options.load(e.$input_element.val(),function(b,c){if(e.usefilterResults=void 0!==c&&c,e.$source_element.children("option").not(":selected").not('[value=""]').remove(),void 0!==b){var d=[];if(a.each(e.$source_element.children("option:selected"),function(a,b){d.push(b.value)}),f&&0===e.$source_element.find('[value=""]').length&&e.$source_element.prepend(a('')),e.$input_element.val().replace(/\s/g,"").length>=e.options.minSearchLength)for(var g=0;g'+h[e.options.textField]+"");e.$source_element.append(i),i.data("item_data",h)}}}p(),e.$container_element.removeClass("loading"),s(e.usefilterResults)}):(e.$container_element.removeClass("loading"),s(e.usefilterResults))},e.options.delay)},s=function(b){b=void 0!==b&&b;var c=e.$input_element.val().replace(/\s/g,"").length0&&e.$options_element.scrollTop(e.$options_element.scrollTop()+a.position().top-e.$options_element.height()/2+a.height()/2)},w=function(){var a=e.$options_element.find(".active");a.data("source_option_element").selected=!0,a.data("source_option_element").removeAttribute("selected"),e.$source_element.trigger("change"),e.options.keepOpen||e.$input_element.val(""),s(e.usefilterResults),o(),n(),j&&!e.options.keepOpen&&(e.$source_element.children("option").not(":selected").not('[value=""]').remove(),p(),u()),e.options.keepOpen||u()},x=function(a){return(a+"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""")};e.refresh=function(){o()},e.destroy=function(){e.$container_element.remove(),a.removeData(b,"selectator"),e.$source_element.show(),0===a("."+e.options.prefix+"element").length&&a("#"+e.options.prefix+"dimmer").remove()},e.init(),!e.options.useOnMobile&&i&&e.destroy()},a.fn.selectator=function(b){var c=void 0!==b?b:{};return this.each(function(){var b=a(this);if("object"==typeof c){if(void 0===b.data("selectator")){var d=new a.selectator(this,c);b.data("selectator",d)}}else b.data("selectator")[c]?b.data("selectator")[c].apply(this,Array.prototype.slice.call(arguments,1)):a.error("Method "+c+" does not exist in $.selectator")})}}(jQuery); \ No newline at end of file diff --git a/themes/admin/login.html b/themes/admin/login.html new file mode 100644 index 0000000..7270a0c --- /dev/null +++ b/themes/admin/login.html @@ -0,0 +1,56 @@ + + + + + + + Batflat + + + + + + + + + + +
    +
    +
    + logo

    batflat

    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    + {if: $bat.notify} +
    {$bat.notify.text}
    + {/if} +
    + + + + \ No newline at end of file diff --git a/themes/batblog/blog.html b/themes/batblog/blog.html new file mode 100644 index 0000000..e79e6b0 --- /dev/null +++ b/themes/batblog/blog.html @@ -0,0 +1,80 @@ +{template: inc/header.html} + + +
    +
    +
    +
    +
    +

    {$blog.title}

    +
    + {$blog.desc} +
    +
    +
    +
    +
    + + +
    +
    +
    + {if: $bat.notify} +
    {$bat.notify.text}
    + {/if} + + {loop: $blog.posts} + + {/loop} + + +
    + {template: inc/sidebar.html} +
    +
    + +{template: inc/footer.html} \ No newline at end of file diff --git a/themes/batblog/css/style.css b/themes/batblog/css/style.css new file mode 100644 index 0000000..92c8a7f --- /dev/null +++ b/themes/batblog/css/style.css @@ -0,0 +1,645 @@ +@import url('//fonts.googleapis.com/css?family=Roboto:300,400,700,800&subset=latin-ext'); +body { + font-family: 'Roboto', sans-serif; + font-size: 16px; + font-weight: 300; + color: #888; + background-color: #fff; +} +p { + line-height: 1.5; + margin: 30px 0; +} +p a { + text-decoration: underline +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: 'Roboto', sans-serif; + font-weight: 800; +} +a { + color: #404040 +} +a:hover, +a:focus { + color: #f8be12 +} +blockquote { + color: #808080; + font-style: italic; +} +hr.small { + max-width: 100px; + margin: 10px auto; + border-width: 1px; + border-color: white; +} +.navbar-custom { + position: absolute; + left: 0; + width: 100%; + z-index: 3; + font-family: 'Roboto', sans-serif; + border:0; + border-radius: 0; +} +.navbar-custom .navbar-brand { + font-weight: 800 +} +.navbar-custom .nav > li > a { + font-size: 16px; + font-weight: 400; + letter-spacing: 1px; + border-bottom: 1px solid transparent; +} +.navbar-custom .nav > li.active > a { + color: #f8be12; + border-color: transparent; +} +.navbar-default .navbar-nav>.open>a, +.navbar-default .navbar-nav>.open>a:hover, +.navbar-default .navbar-nav>.open>a:focus { + background-color: transparent +} +@media only screen and (min-width: 768px) { + .navbar-custom { + background: transparent; + border-bottom: 1px solid transparent; + } + .navbar-custom .navbar-brand { + color: white; + padding: 20px; + } + .navbar-custom .navbar-brand:hover, + .navbar-custom .navbar-brand:focus { + color: rgba(255, 255, 255, 0.8) + } + .navbar-custom .nav > li > a { + color: white; + padding: 20px; + font-weight: 300; + } + .navbar-custom .nav > li.active > a { + color: #fff; + border-color: #fff; + } + .navbar-custom .nav > li.active > a:hover { + color: #fff; + opacity: .85; + } + .navbar-custom .nav > li > form { + padding: 12px; + } + .navbar-custom .nav > li > form > select { + background:transparent; + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' height='10px' width='15px'%3E%3Ctext x='0' y='10' fill='white'%3E%E2%96%BE%3C/text%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: center right; + padding:6px 22px 6px 12px; + border:1px solid #FFF; + color:#FFF; + border-radius: 0; + -moz-appearance: none; + -webkit-appearance: none; + } + .navbar-custom .nav > li > form > select:focus { + box-shadow: none; + } + .navbar-custom .nav > li > form > select > option { + color: initial; + } + .navbar-custom .nav > li > a:hover, + .navbar-custom .nav > li > a:focus { + color: rgba(255, 255, 255, 0.8); + background-color: transparent; + } +} +@media only screen and (min-width: 1170px) { + .navbar-custom { + -webkit-transition: all 0.3s; + transition: all 0.3s; + } + .navbar-custom.is-fixed { + position: fixed; + background-color: rgba(255, 255, 255, 0.9); + border-bottom: 1px solid #f2f2f2; + } + .navbar-custom.is-fixed .navbar-brand { + color: #404040 + } + .navbar-custom.is-fixed .navbar-brand:hover, + .navbar-custom.is-fixed .navbar-brand:focus { + color: #f8be12 + } + .navbar-custom.is-fixed .nav > li > a { + color: #404040 + } + .navbar-custom.is-fixed .nav > li > a:hover, + .navbar-custom.is-fixed .nav > li > a:focus, + .navbar-custom.is-fixed .nav > li.active > a { + color: #f8be12 + } + .navbar-custom.is-fixed .nav > li > form > select { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' height='10px' width='15px'%3E%3Ctext x='0' y='10' fill='#404040'%3E%E2%96%BE%3C/text%3E%3C/svg%3E"); + border:1px solid #404040; + color:#404040; + } +} +.navbar-default .navbar-nav>.active>a, +.navbar-default .navbar-nav>.active>a:focus, +.navbar-default .navbar-nav>.active>a:hover, +.navbar-default .navbar-nav>li>a:focus, +.navbar-default .navbar-nav>li>a:hover { + background-color: transparent +} +.intro-header { + background-color: #808080; + background-attachment: fixed; + background-position: bottom center; + background-repeat: no-repeat; + background-size: cover; + margin-bottom: 50px; +} +.intro-header .site-heading, +.intro-header .post-heading, +.intro-header .page-heading { + padding: 100px 0 50px; + color: white; +} +@media only screen and (min-width: 768px) { + .intro-header .site-heading, + .intro-header .post-heading, + .intro-header .page-heading { + padding: 120px 0 + } +} +.intro-header .site-heading, +.intro-header .page-heading { + text-align: center +} +.intro-header .site-heading h1, +.intro-header .page-heading h1 { + margin-top: 0; + font-weight:300; + font-size: 38px; +} +.intro-header .site-heading .subheading, +.intro-header .page-heading .subheading { + font-size: 16px; + line-height: 1.1; + display: block; + font-family: 'Roboto', sans-serif; + font-weight: 300; + margin: 10px 0 0; +} +@media only screen and (min-width: 768px) { + .intro-header .site-heading h1, + .intro-header .page-heading h1 { + font-size: 38px; + font-weight:300; + } +} +.intro-header .post-heading h1 { + font-size: 35px; + font-weight:300; +} +.intro-header .post-heading .subheading, +.intro-header .post-heading .meta { + line-height: 1.1; + display: block; +} +.intro-header .post-heading .subheading { + font-family: 'Roboto', sans-serif; + font-size: 24px; + margin: 10px 0 30px; + font-weight: 600; +} +.intro-header .post-heading .meta { + font-family: 'Roboto', sans-serif; + font-style: italic; + font-weight: 300; + font-size: 20px; +} +.intro-header .post-heading .meta a { + color: white +} +@media only screen and (min-width: 768px) { + .intro-header .post-heading h1 { + font-size: 55px + } + .intro-header .post-heading .subheading { + font-size: 30px + } +} +.post-preview > a { + color: #404040 +} +.post-preview > a:hover, +.post-preview > a:focus { + text-decoration: none; + color: #f8be12; +} +.post-preview > a > .post-cover { + max-width: 100% +} +.post-preview > a > .post-title { + font-size: 30px; + margin-top: 30px; + margin-bottom: 10px; +} +.post-preview > a > .post-subtitle { + margin: 0; + font-weight: 300; + margin-bottom: 10px; +} +.post-preview > .post-meta { + color: #808080; + font-size: 18px; + font-style: italic; + margin-top: 0; +} +.post-preview > .post-meta > a { + text-decoration: none; + color: #404040; +} +.post-preview > .post-meta > a:hover, +.post-preview > .post-meta > a:focus { + color: #f8be12; + text-decoration: underline; +} +@media only screen and (min-width: 768px) { + .post-preview > a > .post-title { + font-size: 36px + } +} +.section-heading { + font-size: 36px; + margin-top: 60px; + font-weight: 700; +} +.caption { + text-align: center; + font-size: 14px; + padding: 10px; + font-style: italic; + margin: 0; + display: block; + border-bottom-right-radius: 5px; + border-bottom-left-radius: 5px; +} +.thumbnail .caption h3 { + margin-top: 0; + margin-bottom: 0; +} +.thumbnail .caption p { + margin-top: 10px; + margin-bottom: 0; + line-height: 1.2; +} +footer { + padding: 50px 0 65px +} +footer .list-inline { + margin: 0; + padding: 0; +} +footer .copyright { + font-size: 14px; + text-align: center; + margin-bottom: 0; +} +.form-control { + border-radius: 0; + box-shadow: none; +} +.form-control:focus { + border-color: #f8be12; + box-shadow: 0 0 8px rgba(249, 190, 18, .6); +} +.form-group { + font-size: 14px; + position: relative; + margin-bottom: 0; + padding-bottom: 1.5em; + padding-right: 15px; + padding-left: 15px; + border-bottom: 1px solid #eeeeee; +} +form .form-group:last-of-type { + margin-bottom: 25px +} +form .btn { + margin-left: 15px +} +.form-group input, +.form-group textarea { + z-index: 1; + position: relative; + padding-right: 0; + padding-left: 0; + border: none; + font-size: 1.5em; + background: none; + box-shadow: none !important; + resize: none; +} +.form-group label { + display: block; + z-index: 0; + position: relative; + top: 2em; + margin: 0; + font-size: 0.85em; + line-height: 1.764705882em; + opacity: 0; + -webkit-transition: top 0.3s ease, opacity 0.3s ease; + transition: top 0.3s ease, opacity 0.3s ease; +} +.form-group::not(:first-child) { + padding-left: 14px; + border-left: 1px solid #eeeeee; +} +.form-group-with-value label { + top: 0; + opacity: 1; +} +.form-group-with-focus label { + color: #f8be12 +} +form .row:first-child .form-group { + border-top: 1px solid #eeeeee +} +input::-webkit-input-placeholder, +textarea::-webkit-input-placeholder { + color: #AAB2BD !important +} +input::-moz-placeholder, +textarea::-moz-placeholder { + color: #AAB2BD !important +} +input:-ms-input-placeholder, +textarea:-ms-input-placeholder { + color: #AAB2BD !important +} +.btn { + font-family: 'Roboto', sans-serif; + text-transform: uppercase; + font-size: 14px; + font-weight: 800; + letter-spacing: 1px; + border-radius: 0; + padding: 15px 25px; +} +.btn-lg { + font-size: 16px; + padding: 25px 35px; +} +.btn-default { + background-color: #fff +} +.btn-default:hover, +.btn-default:focus { + background-color: #f8be12; + border: 1px solid #f8be12; + color: white; +} +.input-group .btn { + padding: 6px 12px; + font-size: 14px; + font-weight: 400; +} +.pager { + margin: 20px 0 0 +} +.pager li > a, +.pager li > span { + font-family: 'Roboto', sans-serif; + text-transform: uppercase; + font-size: 14px; + font-weight: 800; + letter-spacing: 1px; + padding: 15px 25px; + background-color: white; + border-radius: 0; +} +.pager li > a:hover, +.pager li > a:focus { + color: white; + background-color: #f8be12; + border: 1px solid #f8be12; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #808080; + background-color: #404040; + cursor: not-allowed; +} +.pagination > li > a, +.pagination > li > span { + color: inherit; +} +.pagination > li > a:hover, +.pagination > li > span:hover + color: inherit; +} +.pagination > li:first-child > a, +.pagination > li:last-child > a, +.pagination > li:first-child > span, +.pagination > li:last-child > span { + border-radius: 0; +} +.pagination > .active > a, +.pagination > .active > a:focus, +.pagination > .active > a:hover, +.pagination > .active > span, +.pagination > .active > span:focus, +.pagination > .active > span:hover { + border-color: #f8be12; + background: #f8be12; +} +::-moz-selection { + color: white; + text-shadow: none; + background: #f8be12; +} +::selection { + color: white; + text-shadow: none; + background: #f8be12; +} +img::selection { + color: white; + background: transparent; +} +img::-moz-selection { + color: white; + background: transparent; +} +h1.page-title { + color:#333; + position:relative; + text-align: center; + padding:0 0 15px; + margin:0 0 15px; +} +h1.page-title:after { + content:""; + position:absolute; + height:1px; + background:#888; + bottom:0; + left:40%; + right:40%; +} +.posts article { + margin-bottom:45px; +} +.posts article .post-image img { + width:100%; + border-radius:5px; +} +.posts article .post-header h2 { + font-size:22px; + font-weight: 200; + padding-bottom:10px; + margin:0; +} +.posts article .post-content { + font-size:16px; + line-height: 30px; + color:#888; + padding:20px 0px; + text-align: justify; +} +.posts article .post-content p { + margin:10px 0; +} +.posts article .post-meta { + padding-top:0px; + padding-bottom:10px; + font-size:12px; +} +.posts article .post-footer { + border:1px solid #e3e3e3; + padding:10px; + font-size:12px; +} +.posts article .post-footer a { + font-size:14px; + margin:0 5px; + text-decoration: none; +} +.posts article .post-profile { + margin:15px 0; + padding:10px; + border:1px solid #e3e3e3; +} +.posts article .post-profile .row div:first-of-type { + text-align: center; +} +.posts article .post-profile img { + width:64px; + float:left; + margin-right:15px; + margin-left:0px; + margin-bottom:15px; + border-radius:50%; +} +.posts article .post-profile h2 { + font-size:22px; + color:#333; + margin:20px 0 10px; + padding:0 +} +.sidebar { + padding-left:20px; + padding-bottom:60px; +} +@media only screen and (max-width: 767px) { + .sidebar { + padding-left:0; + } +} +.sidebar .sidebar-content { + list-style: none; + padding:0; + margin:0; +} +.sidebar .sidebar-content .widget { + margin-bottom:30px; + font-size:16px; +} +.sidebar .sidebar-content .widget a { + text-decoration: none; +} +.sidebar .sidebar-content .widget h3 { + padding:20px 0; + margin:0; + font-size:18px; + font-weight: 300; + text-transform: uppercase; + border-bottom:1px solid #e3e3e3; + color:#333; +} +.sidebar .sidebar-content .widget:first-of-type h3 { + padding-top:0; +} +.sidebar .sidebar-content .widget ul { + list-style: none; + padding:0; + margin:0; +} +.sidebar .sidebar-content .widget ul li { + padding:7px 10px; + border-bottom:1px solid #e9e9e9; +} +.sidebar .sidebar-content .widget ul li a { + padding:0; + margin:0; + font-size:16px; + color:#888; +} +.sidebar .sidebar-content .widget.profile { + text-align: center; +} +.sidebar .sidebar-content .widget.profile img { + padding:25px; + border-radius:50%; + max-width:80%; +} +#disqus_thread { + margin-top:30px; +} +.gallery > div[class*="col-"] .thumbnail { + position:relative; + overflow:hidden; + margin-bottom:10px; +} +.gallery > div[class*="col-"] .thumbnail img { + position:absolute; + margin:auto; + top:0; + right:0; + bottom:0; + left:0; + transform:scale(1.1); + transition:all 1s; + max-width:none; +} +.gallery > div[class*="col-"] .thumbnail img:hover { + transform:scale(1.2); +} +body.modal-open > *:not(.modal):not(.modal-backdrop) { + -webkit-filter: blur(5px); + -moz-filter: blur(5px); + -o-filter: blur(5px); + -ms-filter: blur(5px); + filter: blur(5px); +} \ No newline at end of file diff --git a/themes/batblog/img/default-bg.jpg b/themes/batblog/img/default-bg.jpg new file mode 100644 index 0000000..51506df Binary files /dev/null and b/themes/batblog/img/default-bg.jpg differ diff --git a/themes/batblog/img/profile.jpg b/themes/batblog/img/profile.jpg new file mode 100644 index 0000000..b40fbcd Binary files /dev/null and b/themes/batblog/img/profile.jpg differ diff --git a/themes/batblog/inc/footer.html b/themes/batblog/inc/footer.html new file mode 100644 index 0000000..4613deb --- /dev/null +++ b/themes/batblog/inc/footer.html @@ -0,0 +1,21 @@ +
    + + +
    +
    +
    +
    + +
    +
    +
    +
    + + {loop: $bat.footer}{$value}{/loop} + + + + \ No newline at end of file diff --git a/themes/batblog/inc/header.html b/themes/batblog/inc/header.html new file mode 100644 index 0000000..85cfd8e --- /dev/null +++ b/themes/batblog/inc/header.html @@ -0,0 +1,45 @@ + + + + + + + + + + {$page.title} | {$settings.title} + + + + + + + {loop: $bat.header}{$value}{/loop} + + + + + + + \ No newline at end of file diff --git a/themes/batblog/inc/sidebar.html b/themes/batblog/inc/sidebar.html new file mode 100644 index 0000000..0daf7c2 --- /dev/null +++ b/themes/batblog/inc/sidebar.html @@ -0,0 +1,56 @@ + +
    + +
    \ No newline at end of file diff --git a/themes/batblog/index.html b/themes/batblog/index.html new file mode 100644 index 0000000..9dab16f --- /dev/null +++ b/themes/batblog/index.html @@ -0,0 +1,31 @@ +{template: inc/header.html} + + +
    +
    +
    +
    +
    +

    {$page.title}

    +
    + {if: $page.desc}{$page.desc}{else}{$settings.description}{/if} +
    +
    +
    +
    +
    +
    +
    +
    + {if: $bat.notify} +
    {$bat.notify.text}
    + {/if} +
    + {$page.content} +
    +
    + {template: inc/sidebar.html} +
    +
    + +{template: inc/footer.html} \ No newline at end of file diff --git a/themes/batblog/js/theme.js b/themes/batblog/js/theme.js new file mode 100644 index 0000000..e6ccd72 --- /dev/null +++ b/themes/batblog/js/theme.js @@ -0,0 +1,28 @@ +$(function() { + $("body").on("input propertychange", ".form-group", function(e) { + $(this).toggleClass("form-group-with-value", !!$(e.target).val()); + }).on("focus", ".form-group", function() { + $(this).addClass("form-group-with-focus"); + }).on("blur", ".form-group", function() { + $(this).removeClass("form-group-with-focus"); + }); +}); + +jQuery(document).ready(function($) { + $(window).scroll(function() { + if ($(window).scrollTop() > 0) + $(".navbar").addClass("is-fixed"); + else + $(".navbar").removeClass("is-fixed"); + }); + + $("article .post-footer .pull-left a").click(function(e) { + window.open($(this).attr('href'), "Share", "status = 1, height = 400, width = 640, resizable = 1") + e.preventDefault(); + }); + $(window).resize(function() { + $('.gallery > div').each(function() { + $('.thumbnail', this).height( $(this).width() * 1 ); + }); + }).resize(); +}); diff --git a/themes/batblog/manifest.json b/themes/batblog/manifest.json new file mode 100644 index 0000000..de81d1c --- /dev/null +++ b/themes/batblog/manifest.json @@ -0,0 +1,7 @@ +{ + "name": "BatBlog", + "version": "1.0", + "author": "Sruu.pl", + "email": "support@batflat.org", + "thumb": "preview.png" +} \ No newline at end of file diff --git a/themes/batblog/post.html b/themes/batblog/post.html new file mode 100644 index 0000000..f039ca6 --- /dev/null +++ b/themes/batblog/post.html @@ -0,0 +1,75 @@ +{template: inc/header.html} + + +
    +
    +
    +
    +
    +

    {$blog.title}

    +
    + {$blog.desc} +
    +
    +
    +
    +
    + +
    +
    +
    + {if: $bat.notify} +
    {$bat.notify.text}
    + {/if} + +
    +
    +

    + {$post.title} +

    +
    + + {if: $post.cover_photo} +
    + + {$post.title} + +
    + {/if} +
    + {$post.content} +
    +
    +
    + +

    {$post.author.name}

    +
    +
    {$post.author.description}
    +
    + +
    +
    + +
    +
    +
    + {template: inc/sidebar.html} +
    +
    + +{template: inc/footer.html} \ No newline at end of file diff --git a/themes/batblog/preview.png b/themes/batblog/preview.png new file mode 100644 index 0000000..c47f790 Binary files /dev/null and b/themes/batblog/preview.png differ diff --git a/themes/default/blog.html b/themes/default/blog.html new file mode 100644 index 0000000..44d0915 --- /dev/null +++ b/themes/default/blog.html @@ -0,0 +1,58 @@ +{template: inc/header.html} + + +
    +
    +
    +
    +
    +

    {$blog.title}

    +
    + {$blog.desc} +
    +
    +
    +
    +
    + + +
    +
    +
    + {if: $bat.notify} +
    {$bat.notify.text}
    + {/if} + {loop: $blog.posts} +
    + + {if: $value.cover_photo} + + {/if} +

    + {$value.title} +

    +
    +

    {$value.content}

    + +
    +
    {/loop} + + +
    +
    +
    +{template: inc/footer.html} \ No newline at end of file diff --git a/themes/default/css/style.css b/themes/default/css/style.css new file mode 100644 index 0000000..131f28c --- /dev/null +++ b/themes/default/css/style.css @@ -0,0 +1,477 @@ +@import url('//fonts.googleapis.com/css?family=Lora:400,700|Open+Sans:300,400,700,800&subset=latin-ext'); +body { + font-family: 'Lora', 'Times New Roman', serif; + font-size: 20px; + color: #404040; + background-color: #fff; +} +p { + line-height: 1.5; + margin: 30px 0; +} +p a { + text-decoration: underline +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: 800; +} +a { + color: #404040 +} +a:hover, +a:focus { + color: #f8be12 +} +a img:hover, +a img:focus { + cursor: zoom-in +} +blockquote { + color: #808080; + font-style: italic; +} +hr.small { + max-width: 100px; + margin: 15px auto; + border-width: 4px; + border-color: white; +} +.navbar-custom { + position: absolute; + top: 0; + left: 0; + width: 100%; + z-index: 3; + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} +.navbar-custom .navbar-brand { + font-weight: 800 +} +.navbar-custom .nav > li > a { + text-transform: uppercase; + font-size: 12px; + font-weight: 800; + letter-spacing: 1px; +} +.navbar-custom .nav > li.active > a { + color: #fff; +} +.navbar-custom .nav > li.active > a:hover { + color: rgba(255, 255, 255, 0.8); +} +.navbar-default .navbar-nav>.open>a, +.navbar-default .navbar-nav>.open>a:hover, +.navbar-default .navbar-nav>.open>a:focus { + background-color: transparent +} +@media only screen and (min-width: 768px) { + .navbar-custom { + background: transparent; + border-bottom: 1px solid transparent; + } + .navbar-custom .navbar-brand { + color: white; + padding: 20px; + } + .navbar-custom .navbar-brand:hover, + .navbar-custom .navbar-brand:focus { + color: rgba(255, 255, 255, 0.8) + } + .navbar-custom .nav > li > a { + color: white; + padding: 20px; + } + .navbar-custom .nav > li > form { + padding: 12px; + } + .navbar-custom .nav > li > form > select { + background:transparent; + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' height='10px' width='15px'%3E%3Ctext x='0' y='10' fill='white'%3E%E2%96%BE%3C/text%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: center right; + padding:6px 22px 6px 12px; + border:1px solid #FFF; + color:#FFF; + border-radius: 0; + -moz-appearance: none; + -webkit-appearance: none; + } + .navbar-custom .nav > li > form > select:focus { + box-shadow: none; + } + .navbar-custom .nav > li > form > select > option { + color: initial; + } + .navbar-custom .nav > li > a:hover, + .navbar-custom .nav > li > a:focus { + color: rgba(255, 255, 255, 0.8); + background-color: transparent; + } +} +@media only screen and (min-width: 1170px) { + .navbar-custom { + -webkit-transition: all 0.3s; + transition: all 0.3s; + } + .navbar-custom.is-fixed { + position: fixed; + background-color: rgba(255, 255, 255, 0.9); + border-bottom: 1px solid #f2f2f2; + } + .navbar-custom.is-fixed .navbar-brand { + color: #404040 + } + .navbar-custom.is-fixed .navbar-brand:hover, + .navbar-custom.is-fixed .navbar-brand:focus { + color: #f8be12 + } + .navbar-custom.is-fixed .nav > li > a { + color: #404040 + } + .navbar-custom.is-fixed .nav > li > a:hover, + .navbar-custom.is-fixed .nav > li > a:focus, + .navbar-custom.is-fixed .nav > li.active > a { + color: #f8be12 + } + .navbar-custom.is-fixed .nav > li > form > select { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' height='10px' width='15px'%3E%3Ctext x='0' y='10' fill='#404040'%3E%E2%96%BE%3C/text%3E%3C/svg%3E"); + border:1px solid #404040; + color:#404040; + } +} +.navbar-default .navbar-nav>.active>a, +.navbar-default .navbar-nav>.active>a:focus, +.navbar-default .navbar-nav>.active>a:hover, +.navbar-default .navbar-nav>li>a:focus, +.navbar-default .navbar-nav>li>a:hover { + background-color: transparent +} +.intro-header { + background-color: #808080; + background-attachment: fixed; + background-position: center; + background-repeat: no-repeat; + background-size: cover; + margin-bottom: 50px; +} +.intro-header .site-heading, +.intro-header .post-heading, +.intro-header .page-heading { + padding: 100px 0 50px; + color: white; +} +@media only screen and (min-width: 768px) { + .intro-header .site-heading, + .intro-header .post-heading, + .intro-header .page-heading { + padding: 150px 0 + } +} +.intro-header .site-heading, +.intro-header .page-heading { + text-align: center +} +.intro-header .site-heading h1, +.intro-header .page-heading h1 { + margin-top: 0; + font-size: 50px; +} +.intro-header .site-heading .subheading, +.intro-header .page-heading .subheading { + font-size: 24px; + line-height: 1.1; + display: block; + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: 300; + margin: 10px 0 0; +} +@media only screen and (min-width: 768px) { + .intro-header .site-heading h1, + .intro-header .page-heading h1 { + font-size: 80px + } +} +.intro-header .post-heading h1 { + font-size: 35px +} +.intro-header .post-heading .subheading, +.intro-header .post-heading .meta { + line-height: 1.1; + display: block; +} +.intro-header .post-heading .subheading { + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 24px; + margin: 10px 0 30px; + font-weight: 600; +} +.intro-header .post-heading .meta { + font-family: 'Lora', 'Times New Roman', serif; + font-style: italic; + font-weight: 300; + font-size: 20px; +} +.intro-header .post-heading .meta a { + color: white +} +@media only screen and (min-width: 768px) { + .intro-header .post-heading h1 { + font-size: 55px + } + .intro-header .post-heading .subheading { + font-size: 30px + } +} +.post-preview > a { + color: #404040 +} +.post-preview > a:hover, +.post-preview > a:focus { + text-decoration: none; + color: #f8be12; +} +.post-preview > a > .post-cover { + max-width: 100% +} +.post-preview > a > .post-title { + font-size: 30px; + margin-top: 30px; + margin-bottom: 10px; +} +.post-preview > a > .post-subtitle { + margin: 0; + font-weight: 300; + margin-bottom: 10px; +} +.post-preview > .post-meta { + color: #808080; + font-size: 18px; + font-style: italic; + margin-top: 0; +} +.post-preview > .post-meta > a { + text-decoration: none; + color: #404040; +} +.post-preview > .post-meta > a:hover, +.post-preview > .post-meta > a:focus { + color: #f8be12; + text-decoration: underline; +} +@media only screen and (min-width: 768px) { + .post-preview > a > .post-title { + font-size: 36px + } +} +.section-heading { + font-size: 36px; + margin-top: 60px; + font-weight: 700; +} +.caption { + text-align: center; + font-size: 14px; + padding: 10px; + font-style: italic; + margin: 0; + display: block; + border-bottom-right-radius: 5px; + border-bottom-left-radius: 5px; +} +.thumbnail .caption h3 { + margin-top: 0; + margin-bottom: 0; +} +.thumbnail .caption p { + margin-top: 10px; + margin-bottom: 0; + line-height: 1.2; +} +footer { + padding: 50px 0 65px +} +footer .list-inline { + margin: 0; + padding: 0; +} +footer .copyright { + font-size: 14px; + text-align: center; + margin-bottom: 0; +} +.form-control { + border-radius: 0; + box-shadow: none; +} +.form-control:focus { + border-color: #f8be12; + box-shadow: 0 0 8px rgba(249, 190, 18, .6); +} +.form-group { + font-size: 14px; + position: relative; + margin-bottom: 0; + padding-bottom: 1.5em; + padding-right: 15px; + padding-left: 15px; + border-bottom: 1px solid #eeeeee; +} +form .form-group:last-of-type { + margin-bottom: 25px +} +form .btn { + margin-left: 15px +} +.form-group input, +.form-group textarea { + z-index: 1; + position: relative; + padding-right: 0; + padding-left: 0; + border: none; + font-size: 1.5em; + background: none; + box-shadow: none !important; + resize: none; +} +.form-group label { + display: block; + z-index: 0; + position: relative; + top: 2em; + margin: 0; + font-size: 0.85em; + line-height: 1.764705882em; + opacity: 0; + -webkit-transition: top 0.3s ease, opacity 0.3s ease; + transition: top 0.3s ease, opacity 0.3s ease; +} +.form-group::not(:first-child) { + padding-left: 14px; + border-left: 1px solid #eeeeee; +} +.form-group-with-value label { + top: 0; + opacity: 1; +} +.form-group-with-focus label { + color: #f8be12 +} +form .row:first-child .form-group { + border-top: 1px solid #eeeeee +} +input::-webkit-input-placeholder, +textarea::-webkit-input-placeholder { + color: #AAB2BD !important +} +input::-moz-placeholder, +textarea::-moz-placeholder { + color: #AAB2BD !important +} +input:-ms-input-placeholder, +textarea:-ms-input-placeholder { + color: #AAB2BD !important +} +.btn { + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + text-transform: uppercase; + font-size: 14px; + font-weight: 800; + letter-spacing: 1px; + border-radius: 0; + padding: 15px 25px; +} +.btn-lg { + font-size: 16px; + padding: 25px 35px; +} +.btn-default { + background-color: #fff +} +.btn-default:hover, +.btn-default:focus { + background-color: #f8be12; + border: 1px solid #f8be12; + color: white; +} +.input-group .btn { + padding: 6px 12px; + font-size: 14px; + font-weight: 400; +} +.pager { + margin: 20px 0 0 +} +.pager li > a, +.pager li > span { + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + text-transform: uppercase; + font-size: 14px; + font-weight: 800; + letter-spacing: 1px; + padding: 15px 25px; + background-color: white; + border-radius: 0; +} +.pager li > a:hover, +.pager li > a:focus { + color: white; + background-color: #f8be12; + border: 1px solid #f8be12; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #808080; + background-color: #404040; + cursor: not-allowed; +} +.pagination > li > a, +.pagination > li > span { + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + color: inherit; +} +.pagination > li > a:hover, +.pagination > li > span:hover { + color: inherit; +} +.pagination > li:first-child > a, +.pagination > li:last-child > a, +.pagination > li:first-child > span, +.pagination > li:last-child > span { + border-radius: 0; +} +.pagination > .active > a, +.pagination > .active > a:focus, +.pagination > .active > a:hover, +.pagination > .active > span, +.pagination > .active > span:focus, +.pagination > .active > span:hover { + border-color: #f8be12; + background: #f8be12; +} +::-moz-selection { + color: white; + text-shadow: none; + background: #f8be12; +} +::selection { + color: white; + text-shadow: none; + background: #f8be12; +} +img::selection { + color: white; + background: transparent; +} +img::-moz-selection { + color: white; + background: transparent; +} \ No newline at end of file diff --git a/themes/default/img/default-bg.jpg b/themes/default/img/default-bg.jpg new file mode 100644 index 0000000..867dc9a Binary files /dev/null and b/themes/default/img/default-bg.jpg differ diff --git a/themes/default/inc/footer.html b/themes/default/inc/footer.html new file mode 100644 index 0000000..6f3b5ad --- /dev/null +++ b/themes/default/inc/footer.html @@ -0,0 +1,47 @@ +
    + + + + + {loop: $bat.footer}{$value}{/loop} + + + + \ No newline at end of file diff --git a/themes/default/inc/header.html b/themes/default/inc/header.html new file mode 100644 index 0000000..99065d1 --- /dev/null +++ b/themes/default/inc/header.html @@ -0,0 +1,44 @@ + + + + + + + + + + {$page.title} - {$settings.title} + + + + + + + {loop: $bat.header}{$value}{/loop} + + + + + + diff --git a/themes/default/index.html b/themes/default/index.html new file mode 100644 index 0000000..9d1cfff --- /dev/null +++ b/themes/default/index.html @@ -0,0 +1,30 @@ +{template: inc/header.html} + + +
    +
    +
    +
    +
    +

    {$page.title}

    +
    + {if: $page.desc}{$page.desc}{else}{$settings.description}{/if} +
    +
    +
    +
    +
    + + +
    +
    +
    + {if: $bat.notify} +
    {$bat.notify.text}
    + {/if} + {$page.content} +
    +
    +
    + +{template: inc/footer.html} \ No newline at end of file diff --git a/themes/default/js/theme.js b/themes/default/js/theme.js new file mode 100644 index 0000000..4629402 --- /dev/null +++ b/themes/default/js/theme.js @@ -0,0 +1,18 @@ +$(function() { + $("body").on("input propertychange", ".form-group", function(e) { + $(this).toggleClass("form-group-with-value", !!$(e.target).val()); + }).on("focus", ".form-group", function() { + $(this).addClass("form-group-with-focus"); + }).on("blur", ".form-group", function() { + $(this).removeClass("form-group-with-focus"); + }); +}); + +jQuery(document).ready(function($) { + $(window).scroll(function() { + if ($(window).scrollTop() > 0) + $(".navbar").addClass("is-fixed"); + else + $(".navbar").removeClass("is-fixed"); + }); +}); diff --git a/themes/default/manifest.json b/themes/default/manifest.json new file mode 100644 index 0000000..cba1684 --- /dev/null +++ b/themes/default/manifest.json @@ -0,0 +1,7 @@ +{ + "name": "Hello", + "version": "1.1", + "author": "Sruu.pl", + "email": "support@batflat.org", + "thumb": "preview.png" +} \ No newline at end of file diff --git a/themes/default/post.html b/themes/default/post.html new file mode 100644 index 0000000..dad3730 --- /dev/null +++ b/themes/default/post.html @@ -0,0 +1,43 @@ +{template: inc/header.html} + + +{if: $post.cover_photo} +
    +{else} +
    +{/if} +
    +
    +
    +
    +

    {$post.title}

    + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    + {if: $bat.notify} +
    {$bat.notify.text}
    + {/if} + {$post.content} +

    {loop: $post.tags}{if: $key != 0}, {/if}{$value.name}{/loop}

    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + +{template: inc/footer.html} diff --git a/themes/default/preview.png b/themes/default/preview.png new file mode 100644 index 0000000..85e08ce Binary files /dev/null and b/themes/default/preview.png differ diff --git a/upgrade.php b/upgrade.php new file mode 100644 index 0000000..02a9e2e --- /dev/null +++ b/upgrade.php @@ -0,0 +1,208 @@ + +* @author Wojciech Król +* @copyright 2017 Paweł Klockiewicz, Wojciech Król +* @license https://batflat.org/license +* @link https://batflat.org +*/ + +if (!defined("UPGRADABLE")) { + exit(); +} + +function rrmdir($dir) +{ + $files = array_diff(scandir($dir), array('.','..')); + foreach ($files as $file) { + if (is_dir("$dir/$file")) { + rrmdir("$dir/$file"); + } else { + unlink("$dir/$file"); + } + } + return rmdir($dir); +} + +switch ($version) { + case '1.0.0': + /* + Change homepage id to slug + */ + $homepage = $this->core->getSettings('settings', 'homepage'); + $homepage = $this->core->db('pages')->where('id', $homepage)->oneArray(); + $this->core->db('settings')->where('field', 'homepage')->save(['value' => $homepage['slug']]); + + /* + Add 404 pages if does not exist + */ + if (!$this->core->db('pages')->where('slug', '404')->where('lang', 'en_english')->oneArray()) { + // 404 - EN + $this->core->db()->pdo()->exec("INSERT INTO `pages` (`title`, `slug`, `desc`, `lang`, `template`, `date`, `content`) + VALUES ('404', '404', 'Not found', 'en_english', 'index.html', datetime('now'), + '

    Sorry, page does not exist.

    ') + "); + } + if (!$this->core->db('pages')->where('slug', '404')->where('lang', 'pl_polski')->oneArray()) { + // 404 -PL + $this->core->db()->pdo()->exec("INSERT INTO `pages` (`title`, `slug`, `desc`, `lang`, `template`, `date`, `content`) + VALUES ('404', '404', 'Not found', 'pl_polski', 'index.html', datetime('now'), + '

    Niestety taka strona nie istnieje.

    ') + "); + } + + /* + Remove LESS directory + */ + deleteDir('inc/less'); + + // Upgrade version + $return = '1.0.1'; + + case '1.0.1': + $return = "1.0.2"; + + case '1.0.2': + $return = "1.0.3"; + + case '1.0.3': + // Add columns for markdown flag - blog and pages + $this->core->db()->pdo()->exec("ALTER TABLE blog ADD COLUMN markdown INTEGER DEFAULT 0"); + $this->core->db()->pdo()->exec("ALTER TABLE pages ADD COLUMN markdown INTEGER DEFAULT 0"); + $this->core->db()->pdo()->exec("CREATE TABLE `login_attempts` ( + `ip` TEXT NOT NULL, + `attempts` INTEGER NOT NULL, + `expires` INTEGER NOT NULL DEFAULT 0 + )"); + $this->rcopy(BASE_DIR.'/tmp/update/admin', BASE_DIR.'/admin'); + $return = "1.0.4"; + + case '1.0.4': + $return = '1.0.4a'; + + case '1.0.4a': + $this->core->db()->pdo()->exec("ALTER TABLE modules ADD COLUMN sequence INTEGER DEFAULT 0"); + $this->rcopy(BASE_DIR.'/tmp/update/admin', BASE_DIR.'/admin'); + $this->rcopy(BASE_DIR.'/tmp/update/.htaccess', BASE_DIR.'/.htaccess'); + $this->rcopy(BASE_DIR.'/tmp/update/inc/fonts', BASE_DIR.'/inc/fonts'); + $this->rcopy(BASE_DIR.'/tmp/update/themes/admin', BASE_DIR.'/themes/admin'); + $return = '1.0.5'; + + case '1.0.5': + if (file_exists(BASE_DIR.'/themes/default')) { + $this->rcopy(BASE_DIR.'/tmp/update/themes/default/preview.png', BASE_DIR.'/themes/default/preview.png'); + $this->rcopy(BASE_DIR.'/tmp/update/themes/default/manifest.json', BASE_DIR.'/themes/default/manifest.json'); + $this->rcopy(BASE_DIR.'/tmp/update/themes/admin', BASE_DIR.'/themes/admin'); + } + $return = '1.1.0'; + + case '1.1.0': + $this->core->db()->pdo()->exec('CREATE TABLE "blog_tags" ( + `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + `name` TEXT, + `slug` TEXT + );'); + $this->core->db()->pdo()->exec('CREATE TABLE `blog_tags_relationship` ( + `blog_id` INTEGER NOT NULL, + `tag_id` INTEGER NOT NULL + );'); + $this->core->db()->pdo()->exec("INSERT INTO `settings` + (`module`, `field`, `value`) + VALUES + ('contact', 'email', 1), + ('contact', 'driver', 'mail'), + ('contact', 'phpmailer.server', 'smtp.example.com'), + ('contact', 'phpmailer.port', '587'), + ('contact', 'phpmailer.username', 'login@example.com'), + ('contact', 'phpmailer.name', 'Batflat contact'), + ('contact', 'phpmailer.password', 'yourpassword')"); + + $this->rcopy(BASE_DIR.'/tmp/update/inc/core', BASE_DIR.'/inc/core'); + $this->rcopy(BASE_DIR.'/tmp/update/themes/admin', BASE_DIR.'/themes/admin'); + $this->rcopy(BASE_DIR.'/tmp/update/admin', BASE_DIR.'/admin'); + $this->rcopy(BASE_DIR.'/tmp/update/index.php', BASE_DIR.'/index.php'); + $return = '1.2.0'; + + case '1.2.0': + $return = '1.2.1'; + + case '1.2.1': + register_shutdown_function(function () { + sleep(2); + redirect(url([ADMIN, 'settings', 'updates'])); + }); + + $lang = $this->core->getSettings('settings', 'lang_site'); + + $this->rcopy(BASE_DIR.'/tmp/update/admin', BASE_DIR.'/admin'); + $this->rcopy(BASE_DIR.'/tmp/update/index.php', BASE_DIR.'/index.php'); + $this->rcopy(BASE_DIR.'/tmp/update/LICENSE.txt', BASE_DIR.'/LICENSE.txt'); + $this->rcopy(BASE_DIR.'/tmp/update/themes/admin', BASE_DIR.'/themes/admin'); + $this->rcopy(BASE_DIR.'/tmp/update/themes/batblog', BASE_DIR.'/themes/batblog'); + + // Settings + $this->core->db()->pdo()->exec("INSERT INTO `settings` (`module`, `field`, `value`) VALUES ('settings', 'timezone', '".date_default_timezone_get()."')"); + $this->core->db()->pdo()->exec("INSERT INTO `settings` (`module`, `field`, `value`) VALUES ('settings', 'license', '')"); + $this->core->db()->pdo()->exec("INSERT INTO `settings` (`module`, `field`, `value`) VALUES ('blog', 'latestPostsCount', '5')"); + + // Users + $this->core->db()->pdo()->exec("ALTER TABLE users ADD COLUMN description TEXT NULL"); + $this->core->db()->pdo()->exec("ALTER TABLE users ADD COLUMN avatar TEXT NULL"); + $this->core->db()->pdo()->exec("CREATE TABLE IF NOT EXISTS `remember_me` ( + `id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, + `token` text NOT NULL, + `user_id` integer NOT NULL REFERENCES users(id) ON DELETE CASCADE, + `expiry` integer NOT NULL + )"); + if (!is_dir(UPLOADS."/users")) { + mkdir(UPLOADS."/users", 0777); + } + + $users = $this->core->db('users')->toArray(); + foreach ($users as $user) { + $avatar = uniqid('avatar').'.png'; + copy(MODULES.'/users/img/default.png', UPLOADS.'/users/'.$avatar); + $this->core->db('users')->where('id', $user['id'])->save(['avatar' => $avatar]); + } + + // Blog + $this->core->db()->pdo()->exec("ALTER TABLE blog ADD COLUMN lang TEXT NULL"); + $this->core->db()->pdo()->exec("UPDATE blog SET lang = '".$lang."'"); + + // Snippets + $snippets = $this->core->db('snippets')->toArray(); + foreach ($snippets as $snippet) { + $this->core->db('snippets')->where('id', $snippet['id'])->save(['content' => '{lang: '.$lang.'}'.$snippet['content'].'{/lang}']); + } + $return = '1.3.0'; + + case '1.3.0': + $this->core->db()->pdo()->exec("ALTER TABLE navs_items ADD COLUMN class TEXT NULL"); + $return = '1.3.1'; + + case '1.3.1': + $this->rcopy(BASE_DIR.'/backup/'.$backup_date.'/inc/core/defines.php', BASE_DIR.'/inc/core/defines.php'); + $this->rcopy(BASE_DIR.'/tmp/update/themes/admin', BASE_DIR.'/themes/admin'); + $return = '1.3.1a'; + + case '1.3.1a': + $return = '1.3.1b'; + + case '1.3.1b': + $return = '1.3.2'; + + case '1.3.2': + $this->rcopy(BASE_DIR.'/tmp/update/admin', BASE_DIR.'/admin'); + $this->rcopy(BASE_DIR.'/tmp/update/themes/admin', BASE_DIR.'/themes/admin'); + $this->core->db()->pdo()->exec("INSERT INTO modules (`dir`) VALUES ('devbar')"); + $return = '1.3.3'; + + case '1.3.3': + $this->rcopy(BASE_DIR.'/tmp/update/admin', BASE_DIR.'/admin'); + $this->rcopy(BASE_DIR.'/tmp/update/themes/admin', BASE_DIR.'/themes/admin'); + $return = '1.3.4'; +} + +return $return; diff --git a/uploads/.htaccess b/uploads/.htaccess new file mode 100644 index 0000000..0e70384 --- /dev/null +++ b/uploads/.htaccess @@ -0,0 +1,4 @@ +Options -Indexes + +SetHandler default-handler +AddType text/plain php \ No newline at end of file diff --git a/uploads/index.html b/uploads/index.html new file mode 100644 index 0000000..e69de29