/** * Get warnings for active modules * * @return array */ public static function getWarnings() { // init vars $warnings = array(); $activeModules = BackendModel::getModules(true); // add warnings $warnings = array_merge($warnings, BackendModel::checkSettings()); // loop active modules foreach ($activeModules as $module) { // model class $class = 'Backend' . SpoonFilter::toCamelCase($module) . 'Model'; // model file exists if (SpoonFile::exists(BACKEND_MODULES_PATH . '/' . $module . '/engine/model.php')) { // require class require_once BACKEND_MODULES_PATH . '/' . $module . '/engine/model.php'; } // method exists if (is_callable(array($class, 'checkSettings'))) { // add possible warnings $warnings = array_merge($warnings, call_user_func(array($class, 'checkSettings'))); } } // return return (array) $warnings; }
/** * Execute the action * * @return void */ public function execute() { // call parent, this will probably add some general CSS/JS or other required files parent::execute(); // user is god? $isGod = BackendAuthentication::getUser()->isGod(); // get possible languages if ($isGod) { $possibleLanguages = array_unique(array_merge(BL::getWorkingLanguages(), BL::getInterfaceLanguages())); } else { $possibleLanguages = BL::getWorkingLanguages(); } // get parameters $language = SpoonFilter::getPostValue('language', array_keys($possibleLanguages), null, 'string'); $module = SpoonFilter::getPostValue('module', BackendModel::getModules(false), null, 'string'); $name = SpoonFilter::getPostValue('name', null, null, 'string'); $type = SpoonFilter::getPostValue('type', BackendModel::getDB()->getEnumValues('locale', 'type'), null, 'string'); $application = SpoonFilter::getPostValue('application', array('backend', 'frontend'), null, 'string'); $value = SpoonFilter::getPostValue('value', null, null, 'string'); // validate values if (trim($value) == '' || $language == '' || $module == '' || $type == '' || $application == '' || $application == 'frontend' && $module != 'core') { $error = BL::err('InvalidValue'); } // in case this is a 'act' type, there are special rules concerning possible values if ($type == 'act' && !isset($error)) { if (!SpoonFilter::isValidAgainstRegexp('|^([a-z0-9\\-\\_])+$|', $value)) { $error = BL::err('InvalidActionValue', $this->getModule()); } } // no error? if (!isset($error)) { // build item $item['language'] = $language; $item['module'] = $module; $item['name'] = $name; $item['type'] = $type; $item['application'] = $application; $item['value'] = $value; $item['edited_on'] = BackendModel::getUTCDate(); $item['user_id'] = BackendAuthentication::getUser()->getUserId(); // does the translation exist? if (BackendLocaleModel::existsByName($name, $type, $module, $language, $application)) { // add the id to the item $item['id'] = (int) BackendLocaleModel::getByName($name, $type, $module, $language, $application); // update in db BackendLocaleModel::update($item); } else { // insert in db BackendLocaleModel::insert($item); } // output OK $this->output(self::OK); } else { $this->output(self::ERROR, null, $error); } }
/** * Load the datagrid * * @return void */ private function loadDataGrid() { // init var $items = array(); // get active modules $modules = BackendModel::getModules(); // loop active modules foreach ($modules as $module) { // check if their is a model-file if (SpoonFile::exists(BACKEND_MODULES_PATH . '/' . $module . '/engine/model.php')) { // require the model-file require_once BACKEND_MODULES_PATH . '/' . $module . '/engine/model.php'; // build class name $className = SpoonFilter::toCamelCase('backend_' . $module . '_model'); // check if the getByTag-method is available if (is_callable(array($className, 'getByTag'))) { // make the call and get the item $moduleItems = (array) call_user_func(array($className, 'getByTag'), $this->id); // loop items foreach ($moduleItems as $row) { // check if needed fields are available if (isset($row['url'], $row['name'], $row['module'])) { // add $items[] = array('module' => ucfirst(BL::lbl(SpoonFilter::toCamelCase($row['module']))), 'name' => $row['name'], 'url' => $row['url']); } } } } } // create datagrid $this->dgUsage = new BackendDataGridArray($items); // disable paging $this->dgUsage->setPaging(false); // hide columns $this->dgUsage->setColumnsHidden(array('url')); // set headers $this->dgUsage->setHeaderLabels(array('name' => ucfirst(BL::lbl('Title')), 'url' => '')); // set url $this->dgUsage->setColumnURL('name', '[url]', ucfirst(BL::lbl('Edit'))); // add use column $this->dgUsage->addColumn('edit', null, ucfirst(BL::lbl('Edit')), '[url]', BL::lbl('Edit')); }
/** * Loops all backend modules, and builds a list of those that have an * api.php file in their engine. */ protected function loadModules() { $modules = BackendModel::getModules(); foreach ($modules as &$module) { /* * check if the api.php file exists for this module, and load it so our methods are * accessible by the Reflection API. */ $moduleAPIFile = BACKEND_MODULES_PATH . '/' . $module . '/engine/api.php'; if (!file_exists($moduleAPIFile)) { continue; } require_once $moduleAPIFile; // class names of the API file are always based on the name o/t module $className = 'Backend' . SpoonFilter::toCamelCase($module) . 'API'; $methods = get_class_methods($className); // we will need the parameters + PHPDoc to generate our textfields foreach ($methods as $key => $method) { $methods[$key] = array('name' => $method, 'parameters' => $this->loadParameters($className, $method)); } // properly format so an iteration can do the work for us $this->modules[] = array('name' => $module, 'methods' => $methods); } }
/** * Get the widgets * * @return void */ private function getWidgets() { // get all active modules $modules = BackendModel::getModules(true); // loop all modules foreach ($modules as $module) { // you have sufficient rights? if (BackendAuthentication::isAllowedModule($module)) { // build pathName $pathName = BACKEND_MODULES_PATH . '/' . $module; // check if the folder exists if (SpoonDirectory::exists($pathName . '/widgets')) { // get widgets $widgets = (array) SpoonFile::getList($pathName . '/widgets', '/(.*)\\.php/i'); // loop through widgets foreach ($widgets as $widget) { // require the classes require_once $pathName . '/widgets/' . $widget; // init var $widgetName = str_replace('.php', '', $widget); // build classname $className = 'Backend' . SpoonFilter::toCamelCase($module) . 'Widget' . SpoonFilter::toCamelCase($widgetName); // validate if the class exists if (!class_exists($className)) { // throw exception throw new BackendException('The widgetfile is present, but the classname should be: ' . $className . '.'); } else { // add to array $this->widgetInstances[] = array('module' => $module, 'widget' => $widgetName, 'className' => $className); // create reflection class $reflection = new ReflectionClass('Backend' . SpoonFilter::toCamelCase($module) . 'Widget' . SpoonFilter::toCamelCase($widgetName)); // get the offset $offset = strpos($reflection->getDocComment(), '*', 7); // get the first sentence $description = substr($reflection->getDocComment(), 0, $offset); // replacements $description = str_replace('*', '', $description); $description = trim(str_replace('/', '', $description)); } // check if model file exists if (SpoonFile::exists($pathName . '/engine/model.php')) { // require model require_once $pathName . '/engine/model.php'; } // add to array $this->widgets[] = array('label' => SpoonFilter::toCamelCase($widgetName), 'value' => $widgetName, 'description' => $description); } } } } }
/** * Is the given action allowed for the current user * * @param string $action The action to check for. * @param string $module The module wherin the action is located. * @return bool */ public static function isAllowedAction($action = null, $module = null) { // GOD's rule them all! if (self::getUser()->isGod()) { return true; } // always allowed actions (yep, hardcoded, because we don't want other people to f**k up) $alwaysAllowed = array('dashboard' => array('index' => 7), 'core' => array('generate_url' => 7, 'content_css' => 7), 'error' => array('index' => 7), 'authentication' => array('index' => 7, 'reset_password' => 7, 'logout' => 7)); // grab the URL from the reference $URL = Spoon::get('url'); $action = $action !== null ? (string) $action : $URL->getAction(); $module = $module !== null ? (string) $module : $URL->getModule(); // is this action an action that doesn't require authentication? if (isset($alwaysAllowed[$module][$action])) { return true; } // we will cache everything if (empty(self::$allowedActions)) { // init var $db = BackendModel::getDB(); // get modules $modules = BackendModel::getModules(); // add always allowed foreach ($alwaysAllowed as $allowedModule => $actions) { $modules[] = $allowedModule; } // get allowed actions $allowedActionsRows = (array) $db->getRecords('SELECT gra.module, gra.action, gra.level FROM users_sessions AS us INNER JOIN users AS u ON us.user_id = u.id INNER JOIN users_groups AS ug ON u.id = ug.user_id INNER JOIN groups_rights_actions AS gra ON ug.group_id = gra.group_id WHERE us.session_id = ? AND us.secret_key = ?', array(SpoonSession::getSessionId(), SpoonSession::get('backend_secret_key'))); // add all actions and there level foreach ($allowedActionsRows as $row) { // add if the module is installed if (in_array($row['module'], $modules)) { self::$allowedActions[$row['module']][$row['action']] = (int) $row['level']; } } } // do we know a level for this action if (isset(self::$allowedActions[$module][$action])) { // is the level greater than zero? aka: do we have access? if ((int) self::$allowedActions[$module][$action] > 0) { return true; } } // fallback return false; }
/** * Validate the forms */ private function validateForm() { if ($this->frm->isSubmitted()) { $txtEmail = $this->frm->getField('backend_email'); $txtPassword = $this->frm->getField('backend_password'); // required fields if (!$txtEmail->isFilled() || !$txtPassword->isFilled()) { // add error $this->frm->addError('fields required'); // show error $this->tpl->assign('hasError', true); } // invalid form-token? if ($this->frm->getToken() != $this->frm->getField('form_token')->getValue()) { // set a correct header, so bots understand they can't mess with us. if (!headers_sent()) { header('400 Bad Request', true, 400); } } // all fields are ok? if ($txtEmail->isFilled() && $txtPassword->isFilled() && $this->frm->getToken() == $this->frm->getField('form_token')->getValue()) { // try to login the user if (!BackendAuthentication::loginUser($txtEmail->getValue(), $txtPassword->getValue())) { // add error $this->frm->addError('invalid login'); // store attempt in session $current = SpoonSession::exists('backend_login_attempts') ? (int) SpoonSession::get('backend_login_attempts') : 0; // increment and store SpoonSession::set('backend_login_attempts', ++$current); // show error $this->tpl->assign('hasError', true); } } // check sessions if (SpoonSession::exists('backend_login_attempts') && (int) SpoonSession::get('backend_login_attempts') >= 5) { // get previous attempt $previousAttempt = SpoonSession::exists('backend_last_attempt') ? SpoonSession::get('backend_last_attempt') : time(); // calculate timeout $timeout = 5 * (SpoonSession::get('backend_login_attempts') - 4); // too soon! if (time() < $previousAttempt + $timeout) { // sleep untill the user can login again sleep($timeout); // set a correct header, so bots understand they can't mess with us. if (!headers_sent()) { header('503 Service Unavailable', true, 503); } } else { // increment and store SpoonSession::set('backend_last_attempt', time()); } // too many attempts $this->frm->addEditor('too many attempts'); // show error $this->tpl->assign('hasTooManyAttemps', true); $this->tpl->assign('hasError', false); } // no errors in the form? if ($this->frm->isCorrect()) { // cleanup sessions SpoonSession::delete('backend_login_attempts'); SpoonSession::delete('backend_last_attempt'); // create filter with modules which may not be displayed $filter = array('authentication', 'error', 'core'); // get all modules $modules = array_diff(BackendModel::getModules(), $filter); // loop through modules and break on first allowed module foreach ($modules as $module) { if (BackendAuthentication::isAllowedModule($module)) { break; } } // redirect to the correct URL (URL the user was looking for or fallback) $this->redirect($this->getParameter('querystring', 'string', BackendModel::createUrlForAction(null, $module))); } } // is the form submitted if ($this->frmForgotPassword->isSubmitted()) { // backend email $email = $this->frmForgotPassword->getField('backend_email_forgot')->getValue(); // required fields if ($this->frmForgotPassword->getField('backend_email_forgot')->isEmail(BL::err('EmailIsInvalid'))) { // check if there is a user with the given emailaddress if (!BackendUsersModel::existsEmail($email)) { $this->frmForgotPassword->getField('backend_email_forgot')->addError(BL::err('EmailIsUnknown')); } } // no errors in the form? if ($this->frmForgotPassword->isCorrect()) { // generate the key for the reset link and fetch the user ID for this email $key = BackendAuthentication::getEncryptedString($email, uniqid()); // insert the key and the timestamp into the user settings $userId = BackendUsersModel::getIdByEmail($email); $user = new BackendUser($userId); $user->setSetting('reset_password_key', $key); $user->setSetting('reset_password_timestamp', time()); // variables to parse in the e-mail $variables['resetLink'] = SITE_URL . BackendModel::createURLForAction('reset_password') . '&email=' . $email . '&key=' . $key; // send e-mail to user BackendMailer::addEmail(SpoonFilter::ucfirst(BL::msg('ResetYourPasswordMailSubject')), BACKEND_MODULE_PATH . '/layout/templates/mails/reset_password.tpl', $variables, $email); // clear post-values $_POST['backend_email_forgot'] = ''; // show success message $this->tpl->assign('isForgotPasswordSuccess', true); // show form $this->tpl->assign('showForm', true); } else { $this->tpl->assign('showForm', true); } } }
/** * Get the filetree * * @param string $path The path to get the filetree for. * @param array[optional] $tree An array to hold the results. * @return array */ private static function getTree($path, array $tree = array()) { // paths that should be ignored $ignore = array(BACKEND_CACHE_PATH, BACKEND_CORE_PATH . '/js/ckeditor', BACKEND_CACHE_PATH, BACKEND_CORE_PATH . '/js/ckfinder', FRONTEND_CACHE_PATH); // get modules $modules = BackendModel::getModules(); // get the folder listing $items = SpoonDirectory::getList($path, true, array('.svn', '.git')); // already in the modules? if (substr_count($path, '/modules/') > 0) { // get last chunk $start = strpos($path, '/modules') + 9; $end = strpos($path, '/', $start + 1); if ($end === false) { $moduleName = substr($path, $start); } else { $moduleName = substr($path, $start, $end - $start); } // don't go any deeper if (!in_array($moduleName, $modules)) { return $tree; } } foreach ($items as $item) { // if the path should be ignored, skip it if (in_array($path . '/' . $item, $ignore)) { continue; } // if the item is a directory we should index it also (recursive) if (is_dir($path . '/' . $item)) { $tree = self::getTree($path . '/' . $item, $tree); } else { // if the file has an extension that has to be processed add it into the tree if (in_array(SpoonFile::getExtension($item), array('js', 'php', 'tpl'))) { $tree[] = $path . '/' . $item; } } } return $tree; }
/** * Fetch the list of modules that require Google Maps API key * * @return array */ public static function getModulesThatRequireGoogleMaps() { // init vars $modules = array(); $installedModules = BackendModel::getModules(); // loop modules foreach ($installedModules as $module) { // fetch setting $setting = BackendModel::getModuleSetting($module, 'requires_google_maps', false); // add to the list if ($setting) { $modules[] = $module; } } // return return $modules; }
/** * Import a locale XML file. * * @return array Import statistics. * @param SimpleXMLElement $xml The locale XML. * @param bool[optional] $overwriteConflicts Should we overwrite when there is a conflict? */ public static function importXML(SimpleXMLElement $xml, $overwriteConflicts = false) { // init $overwriteConflicts = (bool) $overwriteConflicts; $statistics = array('total' => 0, 'imported' => 0); // possible values $possibleApplications = array('frontend', 'backend'); $possibleModules = BackendModel::getModules(false); $possibleLanguages = array('frontend' => array_keys(BL::getWorkingLanguages()), 'backend' => array_keys(BL::getInterfaceLanguages())); $possibleTypes = array(); // types $typesShort = (array) BackendModel::getDB()->getEnumValues('locale', 'type'); foreach ($typesShort as $type) { $possibleTypes[$type] = self::getTypeName($type); } // current locale items (used to check for conflicts) $currentLocale = (array) BackendModel::getDB()->getColumn('SELECT CONCAT(application, module, type, language, name) FROM locale'); // applications foreach ($xml as $application => $modules) { // application does not exist if (!in_array($application, $possibleApplications)) { continue; } // modules foreach ($modules as $module => $items) { // module does not exist if (!in_array($module, $possibleModules)) { continue; } // items foreach ($items as $item) { // attributes $attributes = $item->attributes(); $type = SpoonFilter::getValue($attributes['type'], $possibleTypes, ''); $name = SpoonFilter::getValue($attributes['name'], null, ''); // missing attributes if ($type == '' || $name == '') { continue; } // real type (shortened) $type = array_search($type, $possibleTypes); // translations foreach ($item->translation as $translation) { // statistics $statistics['total']++; // attributes $attributes = $translation->attributes(); $language = SpoonFilter::getValue($attributes['language'], $possibleLanguages[$application], ''); // language does not exist if ($language == '') { continue; } // the actual translation $translation = (string) $translation; // locale item $locale['user_id'] = BackendAuthentication::getUser()->getUserId(); $locale['language'] = $language; $locale['application'] = $application; $locale['module'] = $module; $locale['type'] = $type; $locale['name'] = $name; $locale['value'] = $translation; $locale['edited_on'] = BackendModel::getUTCDate(); // found a conflict, overwrite it with the imported translation if ($overwriteConflicts && in_array($application . $module . $type . $language . $name, $currentLocale)) { // statistics $statistics['imported']++; // overwrite BackendModel::getDB(true)->update('locale', $locale, 'application = ? AND module = ? AND type = ? AND language = ? AND name = ?', array($application, $module, $type, $language, $name)); } elseif (!in_array($application . $module . $type . $language . $name, $currentLocale)) { // statistics $statistics['imported']++; // insert BackendModel::getDB(true)->insert('locale', $locale); } } } } } // rebuild cache foreach ($possibleApplications as $application) { foreach ($possibleLanguages[$application] as $language) { self::buildCache($language, $application); } } // return statistics return $statistics; }
/** * Edit an index * * @param string $module The module wherin will be searched. * @param int $otherId The id of the record. * @param array $fields A key/value pair of fields to index. * @param string[optional] $language The frontend language for this entry. */ public static function saveIndex($module, $otherId, array $fields, $language = null) { // module exists? if (!in_array('search', BackendModel::getModules())) { return; } // no fields? if (empty($fields)) { return; } // set language if (!$language) { $language = BL::getWorkingLanguage(); } // get db $db = BackendModel::getDB(true); // insert search index foreach ($fields as $field => $value) { // reformat value $value = strip_tags((string) $value); // update search index $db->execute('INSERT INTO search_index (module, other_id, language, field, value, active) VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value = ?, active = ?', array((string) $module, (int) $otherId, (string) $language, (string) $field, $value, 'Y', $value, 'Y')); } // invalidate the cache for search self::invalidateCache(); }
/** * Remove an index * * @param string $module The module wherin will be searched. * @param int $otherId The id of the record. * @param string[optional] $language The language to use. */ public static function removeIndex($module, $otherId, $language = null) { // module exists? if (!in_array('search', BackendModel::getModules())) { return; } // set language if (!$language) { $language = BL::getWorkingLanguage(); } // delete indexes BackendModel::getDB(true)->delete('search_index', 'module = ? AND other_id = ? AND language = ?', array((string) $module, (int) $otherId, (string) $language)); // invalidate the cache for search self::invalidateCache(); }
/** * Load the data */ private function loadData() { // get all modules $modules = BackendModel::getModules(); // get user sequence $userSequence = BackendAuthentication::getUser()->getSetting('dashboard_sequence'); // user sequence does not exist? if (!isset($userSequence)) { // get group ID of user $groupId = BackendAuthentication::getUser()->getGroupId(); // get group preset $userSequence = BackendGroupsModel::getSetting($groupId, 'dashboard_sequence'); } // loop all modules foreach ($modules as $module) { // you have sufficient rights? if (BackendAuthentication::isAllowedModule($module)) { // build pathName $pathName = BACKEND_MODULES_PATH . '/' . $module; // check if the folder exists if (SpoonDirectory::exists($pathName . '/widgets')) { // get widgets $widgets = (array) SpoonFile::getList($pathName . '/widgets', '/(.*)\\.php/i'); // loop widgets foreach ($widgets as $widget) { // require the class require_once $pathName . '/widgets/' . $widget; // init var $widgetName = str_replace('.php', '', $widget); // build classname $className = 'Backend' . SpoonFilter::toCamelCase($module) . 'Widget' . SpoonFilter::toCamelCase($widgetName); // validate if the class exists if (!class_exists($className)) { throw new BackendException('The widgetfile is present, but the classname should be: ' . $className . '.'); } // check if model file exists if (SpoonFile::exists($pathName . '/engine/model.php')) { // require model require_once $pathName . '/engine/model.php'; } // present? $present = isset($userSequence[$module][$widgetName]['present']) ? $userSequence[$module][$widgetName]['present'] : false; // if not present, continue if (!$present) { continue; } // create instance $instance = new $className(); // has rights if (!$instance->isAllowed()) { continue; } // hidden? $hidden = isset($userSequence[$module][$widgetName]['hidden']) ? $userSequence[$module][$widgetName]['hidden'] : false; // execute instance if it is not hidden if (!$hidden) { $instance->execute(); } // user sequence provided? $column = isset($userSequence[$module][$widgetName]['column']) ? $userSequence[$module][$widgetName]['column'] : $instance->getColumn(); $position = isset($userSequence[$module][$widgetName]['position']) ? $userSequence[$module][$widgetName]['position'] : $instance->getPosition(); $title = SpoonFilter::ucfirst(BL::lbl(SpoonFilter::toCamelCase($module))) . ': ' . BL::lbl(SpoonFilter::toCamelCase($widgetName)); $templatePath = $instance->getTemplatePath(); // reset template path if ($templatePath == null) { $templatePath = BACKEND_PATH . '/modules/' . $module . '/layout/widgets/' . $widgetName . '.tpl'; } // build item $item = array('template' => $templatePath, 'module' => $module, 'widget' => $widgetName, 'title' => $title, 'hidden' => $hidden); // add on new position if no position is set or if the position is already used if ($position === null || isset($this->widgets[$column][$position])) { $this->widgets[$column][] = $item; } else { $this->widgets[$column][$position] = $item; } } } } } // sort the widgets foreach ($this->widgets as &$column) { ksort($column); } }