/** * Build fake page with error based on fake controller initiation */ protected function buildFakePage() { // initialize fake controller to display page with exception $fakeController = new Controller(); // check if used no layout template if (defined('env_no_layout') && env_no_layout === true) { $fakeController->layout = null; } // add global title tag value $fakeController->setGlobalVar('title', App::$Translate->get('Default', $this->title)); // build error text $rawResponse = 'error'; try { $rawResponse = App::$View->render('native/errors/' . $this->tpl, ['msg' => $this->text]); if (Str::likeEmpty($rawResponse)) { $rawResponse = $this->text; } } catch (SyntaxException $e) { $rawResponse = $this->text; } // set controller body response $fakeController->setOutput($rawResponse); // set status code for header App::$Response->setStatusCode((int) $this->status); // return compiled html output return $fakeController->buildOutput(); }
/** * Check comment add conditions. On bad conditions will be throw'd exception. * @throws JsonException * @return boolean */ public function check() { // check if user is auth'd or guest name is defined if (!App::$User->isAuth() && ((int) $this->_configs['guestAdd'] !== 1 || Str::length($this->guestName) < 2)) { throw new JsonException(__('Guest name is not defined')); } // check if pathway is empty if (Str::likeEmpty($this->pathway)) { throw new JsonException(__('Wrong target pathway')); } // check if message length is correct if (Str::length($this->message) < (int) $this->_configs['minLength'] || Str::length($this->message) > (int) $this->_configs['maxLength']) { throw new JsonException(__('Message length is incorrect. Current: %cur%, min - %min%, max - %max%', ['cur' => Str::length($this->message), 'min' => $this->_configs['minLength'], 'max' => $this->_configs['maxLength']])); } // guest moderation if (!App::$User->isAuth() && (bool) $this->_configs['guestModerate']) { $captcha = App::$Request->request->get('captcha'); if (!App::$Captcha->validate($captcha)) { throw new JsonException(__('Captcha is incorrect! Click on image to refresh and try again')); } } // check delay between 2 comments from 1 user or 1 ip $query = CommentPost::where('user_id', '=', $this->_userId)->orWhere('ip', '=', $this->ip)->orderBy('created_at', 'DESC')->first(); // check if latest post time for this user is founded if ($query !== null) { $postTime = Date::convertToTimestamp($query->created_at); $delay = $postTime + $this->_configs['delay'] - time(); if ($delay > 0) { throw new JsonException(__('Spam protection: please, wait %sec% seconds', ['sec' => $delay])); } } return true; }
/** * Read feedback message and answers and work with add answer model * @param int $id * @param string $hash * @return string * @throws \Ffcms\Core\Exception\NativeException * @throws ForbiddenException * @throws \Ffcms\Core\Exception\SyntaxException */ public function actionRead($id, $hash) { if (!Obj::isLikeInt($id) || Str::length($hash) < 16 || Str::length($hash) > 64) { throw new ForbiddenException(__('The feedback request is not founded')); } // get feedback post record from database $recordPost = FeedbackPost::where('id', '=', $id)->where('hash', '=', $hash)->first(); if ($recordPost === null) { throw new ForbiddenException(__('The feedback request is not founded')); } $userId = App::$User->isAuth() ? App::$User->identity()->getId() : 0; $model = null; // check if feedback post is not closed for answers if ((int) $recordPost->closed === 0) { // init new answer add model $model = new FormAnswerAdd($recordPost, $userId); // if answer is sender lets try to make it model if ($model->send() && $model->validate()) { $model->make(); App::$Session->getFlashBag()->add('success', __('Your answer was added')); $model->clearProperties(); } } // render output view return $this->view->render('read', ['model' => $model, 'post' => $recordPost, 'answers' => $recordPost->getAnswers()->get()]); }
/** * Find all bootatble instances and set it to object map */ public function compileBootableClasses() { // list app root's foreach ($this->appRoots as $app) { $app .= '/Apps/Controller/' . env_name; $files = File::listFiles($app, ['.php'], true); foreach ($files as $file) { // define full class name with namespace $class = 'Apps\\Controller\\' . env_name . '\\' . Str::cleanExtension($file); // check if class exists (must be loaded over autoloader), boot method exist and this is controller instanceof if (class_exists($class) && method_exists($class, 'boot') && is_a($class, 'Ffcms\\Core\\Arch\\Controller', true)) { $this->objects[] = $class; } } } // list widget root's foreach ($this->widgetRoots as $widget) { $widget .= '/Widgets/' . env_name; // widgets are packed in directory, classname should be the same with root directory name $dirs = Directory::scan($widget, GLOB_ONLYDIR, true); if (!Obj::isArray($dirs)) { continue; } foreach ($dirs as $instance) { $class = 'Widgets\\' . env_name . '\\' . $instance . '\\' . $instance; if (class_exists($class) && method_exists($class, 'boot') && is_a($class, 'Ffcms\\Core\\Arch\\Widget', true)) { $this->objects[] = $class; } } } }
/** * Main search method * @return string * @throws \Ffcms\Core\Exception\NativeException * @throws NotFoundException * @throws \Ffcms\Core\Exception\SyntaxException */ public function actionIndex() { // get search query value from GET headers $query = (string) $this->request->query->get('query', null); // strip html tags $query = App::$Security->strip_tags(trim($query)); // get configs $configs = $this->getConfigs(); // check search query length if (Str::likeEmpty($query) || Str::length($query) < (int) $configs['minLength']) { throw new NotFoundException(__('Search query is too short!')); } // prevent sh@t query's with big length if (Str::length($query) > static::QUERY_MAX_LENGTH) { throw new NotFoundException(__('Search query is too long!')); } // initialize search controller model $model = new EntitySearchMain($query, $configs); // try to search by default apps $model->make(); // register search event to allow extend it model results App::$Event->run(static::EVENT_SEARCH_RUNNED, ['model' => $model]); // render output view with search result return $this->view->render('index', ['model' => $model, 'query' => $query]); }
/** * Prepare output comment post information array * @return array|null */ public function make() { if ($this->_record === null) { return null; } // build user data $userName = __('Unknown'); $userAvatar = App::$Alias->scriptUrl . '/upload/user/avatar/small/default.jpg'; $userObject = $this->_record->getUser(); if ($userObject !== null) { $userName = $userObject->getProfile()->getNickname(); $userAvatar = $userObject->getProfile()->getAvatarUrl('small'); } else { if (!Str::likeEmpty($this->_record->guest_name)) { $userName = App::$Security->strip_tags($this->_record->guest_name); } } // return output json data $res = ['type' => $this->_type, 'id' => $this->_record->id, 'text' => $this->_record->message, 'date' => Date::convertToDatetime($this->_record->created_at, Date::FORMAT_TO_HOUR), 'pathway' => $this->_record->pathway, 'moderate' => (int) $this->_record->moderate, 'user' => ['id' => $this->_record->user_id, 'name' => $userName, 'avatar' => $userAvatar]]; if ($this->_type === 'post' && method_exists($this->_record, 'getAnswerCount') && $this->_calcAnswers) { $res['answers'] = $this->_record->getAnswerCount(); } elseif ($this->_type === 'answer') { $res['comment_id'] = $this->_record->comment_id; } return $res; }
/** * Make app installation * @return bool */ public function make() { $cName = ucfirst(Str::lowerCase($this->sysname)); $cPath = 'Apps\\Controller\\Admin\\' . $cName; // if object class is not loaded - prevent install if (!class_exists($cPath) || !defined($cPath . '::VERSION')) { return false; } // get ext version $cVersion = constant($cPath . '::VERSION'); if ($cVersion === null || Str::likeEmpty($cVersion)) { $cVersion = '1.0.0'; } // save row to db $record = new AppRecord(); $record->type = $this->_type; $record->sys_name = $cName; $record->name = ''; $record->disabled = 1; $record->version = $cVersion; $record->save(); // callback to install method in extension if (method_exists($cPath, 'install')) { call_user_func($cPath . '::install'); } return true; }
/** * Generate unique invite string * @return string */ private function makeInvite() { $token = Str::randomLatinNumeric(mt_rand(32, 128)); $find = Invite::where('token', '=', $token)->count(); return $find === 0 ? $token : $this->makeInvite(); // prevent duplication }
/** * Build search results * @return array[AbstractSearchResult] */ public function getResult() { // relevant search by string query $records = Content::where('display', '=', 1)->search($this->query)->take($this->limit)->get(); // check if result is not empty if ($records->count() < 1) { return []; } // build result items $result = []; foreach ($records as $item) { /** @var \Apps\ActiveRecord\Content $item */ $title = $item->getLocaled('title'); $text = App::$Security->strip_tags($item->getLocaled('text')); $snippet = Text::snippet($text); // prevent empty items if (Str::likeEmpty($title)) { continue; } // initialize abstract response pattern $res = new AbstractSearchResult(); $res->setTitle($title); $res->setSnippet($snippet); $res->setDate($item->created_at); $res->setRelevance((int) $item->relevance); $res->setUri('/content/read/' . $item->getPath()); // accumulate response var $result[] = $res; } return $result; }
/** * Build variables and display output html */ public function buildOutput() { $this->after(); // if layout is not required and this is just standalone app if ($this->layout === null) { $content = $this->output; } else { $layoutPath = App::$Alias->currentViewPath . '/layout/' . $this->layout . '.php'; if (!File::exist($layoutPath)) { throw new NativeException('Layout not founded: ' . $layoutPath); } $body = $this->output; // pass global data to config viewer if (App::$Debug !== null) { App::$Debug->bar->getCollector('config')->setData(['Global Vars' => Variables::instance()->getGlobalsArray()]); } // cleanup buffer from random shits after exception throw'd ob_clean(); // start buffering to render layout ob_start(); include $layoutPath; $content = ob_get_clean(); // read buffer content & stop buffering // set custom css library's not included on static call $cssIncludeCode = App::$View->showCodeLink('css'); if (!Str::likeEmpty($cssIncludeCode)) { $content = Str::replace('</head>', $cssIncludeCode . '</head>', $content); } // add debug bar if (App::$Debug !== null) { $content = Str::replace(['</body>', '</head>'], [App::$Debug->renderOut() . '</body>', App::$Debug->renderHead() . '</head>'], $content); } } return $content; }
/** * Normalize local disk-based ABSOLUTE path. * @param string $path * @return string */ public static function diskFullPath($path) { $path = self::diskPath($path); if (!Str::startsWith(root, $path)) { $path = root . DIRECTORY_SEPARATOR . ltrim($path, '\\/'); } return $path; }
/** * Get user nickname. If is empty - return 'id+userId' * @return string */ public function getNickname() { $userNick = $this->nick; if ($userNick === null || Str::likeEmpty($userNick)) { $userNick = 'id' . $this->id; } return $userNick; }
/** * Fast redirect in web environment * @param string $to * @param bool $full */ public function redirect($to, $full = false) { $to = trim($to, '/'); if (false === $full && !Str::startsWith(App::$Alias->baseUrl, $to)) { $to = App::$Alias->baseUrl . '/' . $to; } $redirect = new FoundationRedirect($to); $redirect->send(); exit('Redirecting to ' . $to . ' ...'); }
/** * Save new user group data in database after submit */ public function save() { $this->_role->name = $this->name; if (Str::likeEmpty($this->permissions) || !Str::contains(';', $this->permissions)) { $this->_role->permissions = ''; } else { $this->_role->permissions = implode(';', $this->permissions); } $this->_role->save(); }
/** * Special function for locale stored attributes under serialization. * @param string $attribute * @return array|null|string */ public function getLocaled($attribute) { // if always decoded if (Obj::isArray($this->{$attribute})) { return $this->{$attribute}[App::$Request->getLanguage()]; } if (!Obj::isString($attribute) || Str::likeEmpty($this->{$attribute})) { return null; } return Serialize::getDecodeLocale($this->{$attribute}); }
/** * Get internalization based on called controller * @param string $text * @param array $params * @return string */ public function translate($text, array $params = []) { $index = null; $namespace = 'Apps\\Controller\\' . env_name . '\\'; foreach (@debug_backtrace() as $caller) { if (isset($caller['class']) && Str::startsWith($namespace, $caller['class'])) { $index = Str::sub((string) $caller['class'], Str::length($namespace)); } } return $this->get($index, $text, $params); }
/** * Validate input data from captcha * @param string|null $data * @return bool * @throws SyntaxException */ public static function validate($data = null) { $captchaValue = App::$Session->get('captcha'); // unset session value to prevent duplication. Security fix. App::$Session->remove('captcha'); // check if session has value if ($captchaValue === null || Str::length($captchaValue) < 1) { return false; } return $data === $captchaValue; }
/** * Prepare conditions to build content list * @throws NotFoundException */ public function before() { // check length of passed terms if (!Obj::isString($this->_terms) || Str::length($this->_terms) < self::MIN_QUERY_LENGTH) { throw new NotFoundException(__('Search terms is too short')); } // lets make active record building $this->_records = ContentEntity::whereNotIn('id', $this->_skip)->search($this->_terms)->take(self::MAX_ITEMS)->get(); $this->buildContent(); parent::before(); }
/** * Highlight words in text by current query request. * @param string $text * @param string $tag * @param array $properties * @return string */ public function highlightText($text, $tag, array $properties = []) { $queries = explode(' ', $this->query); $dom = new Dom(); foreach ($queries as $query) { $highlight = $dom->{$tag}(function () use($query) { return $query; }, $properties); $text = Str::ireplace($query, $highlight, $text); } return $text; }
/** * Save configurations build by installer interface */ public function make() { // prepare configurations to save /** @var array $cfg */ $cfg = App::$Properties->getAll('default'); $this->before(); $cfg['baseDomain'] = $this->baseDomain; $cfg['database'] = $this->db; $cfg['adminEmail'] = $this->email; $cfg['singleLanguage'] = $this->singleLanguage; $cfg['multiLanguage'] = (bool) $this->multiLanguage; $cfg['passwordSalt'] = '$2a$07$' . Str::randomLatinNumeric(mt_rand(21, 30)) . '$'; $cfg['debug']['cookie']['key'] = 'fdebug_' . Str::randomLatinNumeric(mt_rand(4, 16)); $cfg['debug']['cookie']['value'] = Str::randomLatinNumeric(mt_rand(32, 128)); // import database tables $connectName = 'install'; include root . '/Private/Database/install.php'; // insert admin user $user = new User(); $user->setConnection('install'); $user->login = $this->user['login']; $user->email = $this->user['email']; $user->role_id = 4; $user->password = App::$Security->password_hash($this->user['password'], $cfg['passwordSalt']); $user->save(); $profile = new Profile(); $profile->setConnection('install'); $profile->user_id = $user->id; $profile->save(); // set installation version $system = new System(); $system->setConnection('install'); $system->var = 'version'; $system->data = Version::VERSION; $system->save(); // write config data App::$Properties->writeConfig('default', $cfg); // make routing configs based on preset property $routing = []; switch ($this->mainpage) { case 'news': $routing = ['Alias' => ['Front' => ['/' => '/content/list/news', '/about' => '/content/read/page/about-page']]]; break; case 'about': $routing = ['Alias' => ['Front' => ['/' => '/content/read/page/about-page']]]; break; } // write routing configurations App::$Properties->writeConfig('routing', $routing); // write installer lock File::write('/Private/Install/install.lock', 'Installation is locked!'); }
/** * List user profiles on website by defined filter * @param string $filter_name * @param null|string|int $filter_value * @return string * @throws \Ffcms\Core\Exception\NativeException * @throws NotFoundException * @throws \Ffcms\Core\Exception\SyntaxException */ public function actionIndex($filter_name, $filter_value = null) { $records = null; // set current page and offset $page = (int) $this->request->query->get('page', 0); $cfgs = $this->application->configs; $userPerPage = (int) $cfgs['usersOnPage']; if ($userPerPage < 1) { $userPerPage = 1; } $offset = $page * $userPerPage; switch ($filter_name) { case 'rating': // rating list, order by rating DESC // check if rating is enabled if ((int) $cfgs['rating'] !== 1) { throw new NotFoundException(); } $records = (new ProfileRecords())->orderBy('rating', 'DESC'); break; case 'hobby': // search by hobby if (Str::likeEmpty($filter_value)) { throw new NotFoundException(); } $records = (new ProfileRecords())->where('hobby', 'like', '%' . $filter_value . '%'); break; case 'city': if (Str::likeEmpty($filter_value)) { throw new NotFoundException(); } $records = (new ProfileRecords())->where('city', '=', $filter_value); break; case 'born': if ($filter_value === null || !Obj::isLikeInt($filter_value)) { throw new NotFoundException(); } $records = (new ProfileRecords())->where('birthday', 'like', $filter_value . '-%'); break; case 'all': $records = (new ProfileRecords())->orderBy('id', 'DESC'); break; default: $this->response->redirect('profile/index/all'); break; } // build pagination $pagination = new SimplePagination(['url' => ['profile/index', $filter_name, $filter_value], 'page' => $page, 'step' => $userPerPage, 'total' => $records->count()]); return $this->view->render('index', ['records' => $records->skip($offset)->take($userPerPage)->get(), 'pagination' => $pagination, 'id' => $filter_name, 'add' => $filter_value, 'ratingOn' => (int) $cfgs['rating']]); }
/** * Check is current instance of application is enabled and can be executed * @return bool */ public function isEnabled() { $appName = App::$Request->getController(); // if app class extend current class we can get origin name $nativeName = Str::lastIn(get_class($this), '\\', true); // check if this controller is enabled $this->application = AppRecord::getItem('app', [$appName, $nativeName]); // not exist? false if ($this->application === null) { return false; } // check if disabled (0 = enabled, anything else = on) return (int) $this->application->disabled === 0; }
/** * Build console controllers. * php console.php Controller/Action index */ public static function run() { global $argv; $output = null; if (!Obj::isArray($argv) || Str::likeEmpty($argv[1])) { $output = 'Console command is unknown! Type "console main/help" to get help guide'; } else { $controller_action = $argv[1]; $arrInput = explode('/', $controller_action); $controller = ucfirst(strtolower($arrInput[0])); $action = ucfirst(strtolower($arrInput[1])); if ($action == null) { $action = 'Index'; } // set action and id $action = 'action' . $action; $id = null; if (isset($argv[2])) { $id = $argv[2]; } try { $controller_path = '/Apps/Controller/' . env_name . '/' . $controller . '.php'; if (file_exists(root . $controller_path) && is_readable(root . $controller_path)) { include_once root . $controller_path; $cname = 'Apps\\Controller\\' . env_name . '\\' . $controller; if (class_exists($cname)) { $load = new $cname(); if (method_exists($cname, $action)) { if ($id !== null) { $output = @$load->{$action}($id); } else { $output = @$load->{$action}(); } } else { throw new NativeException('Method ' . $action . '() not founded in ' . $cname . ' in file {root}' . $controller_path); } unset($load); } else { throw new NativeException('Namespace\\Class - ' . $cname . ' not founded in {root}' . $controller_path); } } else { throw new NativeException('Controller not founded: {root}' . $controller_path); } } catch (NativeException $e) { $e->display($e->getMessage()); } } return self::$Output->write($output); }
/** * Print json response for search query based on standard model * @return string * @throws JsonException */ public function actionIndex() { $this->setJsonHeader(); // get search query as string from request $query = $this->request->query->get('query', null); if (Str::likeEmpty($query) || Str::length($query) < 2) { throw new JsonException('Short query'); } // initialize basic search model $model = new EntitySearchMain($query, ['itemPerApp' => 3]); $model->make(); // build response by relevance as array $response = $model->getRelevanceSortedResult(); return json_encode(['status' => 1, 'count' => count($response), 'data' => $response]); }
/** * Try to find sitemap indexes in storage directory * @throws SyntaxException */ public function before() { if (!Directory::exist(static::INDEX_PATH)) { throw new SyntaxException(__('Directory %dir% for sitemaps is not exists', ['dir' => static::INDEX_PATH])); } $scan = File::listFiles(static::INDEX_PATH, ['.xml'], true); if (Obj::isArray($scan)) { foreach ($scan as $file) { if ($this->_lang !== null && !Str::contains('.' . $this->_lang, $file)) { continue; } $this->files[] = static::INDEX_PATH . '/' . $file; } } }
/** * Find update files with sql queries */ public function findUpdateFiles() { // find all file with update sql queries between $dbVersion<->scriptVersion (dbVer <= x <= scriptVer) $all = File::listFiles('/Private/Database/Updates/', ['.php'], true); foreach ($all as $file) { $file = Str::cleanExtension(basename($file)); // $file="3.0.0-3.0.1" become to $start = 3.0.0,$end=3.0.1 list($start, $end) = explode('-', $file); // true: start <= db & script >= $end if (version_compare($this->dbVersion, $start) !== 1 && version_compare($this->scriptVersion, $end) !== -1) { $this->updateQueries[] = $file; } } sort($this->updateQueries); }
/** * Try user auth after form validate * @return bool */ public function tryAuth() { $password = App::$Security->password_hash($this->password); $search = App::$User->where('password', '=', $password)->where(function ($query) { $query->where('login', '=', $this->login)->orWhere('email', '=', $this->login); }); if ($search->count() === 1) { $object = $search->first(); // check if accounts is approved if ($object->approve_token !== '0' && Str::length($object->approve_token) > 0) { return false; } return $this->openSession($object); } return false; }
/** * Build apps/widgets table in local property */ private function buildExtensions() { $controller = Str::lastIn(get_class($this), '\\', true); foreach ($this->table as $item) { if ($item->type === 'app') { $this->applications[] = $item; if ($this->type === 'app' && $item->sys_name === $controller) { $this->application = $item; } } elseif ($item->type === 'widget') { $this->widgets[] = $item; if ($this->type === 'widget' && $item->sys_name === $controller) { $this->widget = $item; } } } }
/** * List comments as json object with defined offset index * @param int $index * @return string * @throws NotFoundException */ public function actionList($index) { // set header $this->setJsonHeader(); // get configs $configs = AppRecord::getConfigs('widget', 'Comments'); // items per page $perPage = (int) $configs['perPage']; // offset can be only integer $index = (int) $index; $offset = $perPage * $index; // get comment target path and check $path = (string) $this->request->query->get('path'); if (Str::likeEmpty($path)) { throw new NotFoundException('Wrong path'); } // select comments from db and check it $query = CommentPost::where('pathway', '=', $path)->where('moderate', '=', 0); // check if comments is depend of language locale if ((int) $configs['onlyLocale'] === 1) { $query = $query->where('lang', '=', $this->request->getLanguage()); } // get comments with offset and limit $records = $query->skip($offset)->take($perPage)->get(); // check if records is not empty if ($records->count() < 1) { throw new NotFoundException(__('There is no comments found yet. You can be the first!')); } // build output json data as array $data = []; foreach ($records as $comment) { // prepare specified data to output response, based on entity model $commentResponse = new EntityCommentData($comment); // build output json data $data[] = $commentResponse->make(); $commentResponse = null; } // calculate comments left count $count = CommentPost::where('pathway', '=', $path)->where('moderate', '=', 0)->count(); $count -= $offset + $perPage; if ($count < 0) { $count = 0; } return json_encode(['status' => 1, 'data' => $data, 'leftCount' => $count]); }
/** * Browse files from ckeditor * @param string $type * @throws NativeException * @throws \Ffcms\Core\Exception\SyntaxException */ public function actionBrowse($type) { $files = null; $relative = null; // check if request type is defined if ($this->allowedExt[$type] === null || !Obj::isArray($this->allowedExt[$type])) { throw new NativeException('Hack attempt'); } // list files in directory $files = File::listFiles('/upload/' . $type, $this->allowedExt[$type]); // absolute path to relative URI foreach ($files as $file) { $newName = Str::sub($file, Str::length(root) + 1); $relative[] = trim(Str::replace(DIRECTORY_SEPARATOR, '/', $newName), '/'); } // generate response return App::$View->render('editor/browse', ['callbackName' => App::$Security->strip_tags(App::$Request->query->get('CKEditor')), 'callbackId' => (int) App::$Request->query->get('CKEditorFuncNum'), 'files' => $relative, 'type' => $type], __DIR__); }