From 0a935019ccecf733b96c0f1cd9f1b955ae7ba758 Mon Sep 17 00:00:00 2001 From: butschster Date: Wed, 25 Mar 2015 16:22:06 +0300 Subject: [PATCH] init --- classes/Behavior.php | 139 +++ classes/Behavior/Abstract.php | 132 +++ classes/Behavior/Protectedpage.php | 17 + classes/Behavior/Route.php | 156 +++ classes/Behavior/Settings.php | 92 ++ classes/Controller/API/Behavior.php | 42 + classes/Controller/API/Pages.php | 3 + classes/Controller/Front.php | 3 + classes/Controller/Page.php | 3 + classes/KodiCMS/Controller/API/Pages.php | 153 +++ classes/KodiCMS/Controller/Front.php | 153 +++ classes/KodiCMS/Controller/Page.php | 331 ++++++ classes/KodiCMS/Model/API/Page.php | 167 ++++ classes/KodiCMS/Model/API/Page/Tag.php | 44 + classes/KodiCMS/Model/Page.php | 756 ++++++++++++++ classes/KodiCMS/Model/Page/Front.php | 1164 ++++++++++++++++++++++ classes/KodiCMS/Model/Page/Role.php | 11 + classes/KodiCMS/Model/Page/Sitemap.php | 102 ++ classes/Model/API/Page.php | 3 + classes/Model/API/Page/Tag.php | 3 + classes/Model/Page.php | 3 + classes/Model/Page/Behavior.php | 29 + classes/Model/Page/Behavior/Setting.php | 66 ++ classes/Model/Page/Front.php | 3 + classes/Model/Page/Role.php | 3 + classes/Model/Page/Sitemap.php | 3 + composer.json | 24 + config/global.php | 5 + config/permissions.php | 34 + config/sitemap.php | 11 + i18n/ru.php | 57 ++ init.php | 60 ++ install/dump.sql | 2 + install/schema.sql | 40 + media/js/controller/behavior.js | 16 + media/js/controller/page.js | 247 +++++ views/page/blocks/behavior.php | 20 + views/page/blocks/meta.php | 26 + views/page/blocks/search.php | 19 + views/page/blocks/settings.php | 119 +++ views/page/children.php | 87 ++ views/page/edit.php | 133 +++ views/page/index.php | 66 ++ views/page/sort.php | 38 + views/page/sortitem.php | 12 + 45 files changed, 4597 insertions(+) create mode 100644 classes/Behavior.php create mode 100644 classes/Behavior/Abstract.php create mode 100644 classes/Behavior/Protectedpage.php create mode 100644 classes/Behavior/Route.php create mode 100644 classes/Behavior/Settings.php create mode 100644 classes/Controller/API/Behavior.php create mode 100644 classes/Controller/API/Pages.php create mode 100644 classes/Controller/Front.php create mode 100644 classes/Controller/Page.php create mode 100644 classes/KodiCMS/Controller/API/Pages.php create mode 100644 classes/KodiCMS/Controller/Front.php create mode 100644 classes/KodiCMS/Controller/Page.php create mode 100644 classes/KodiCMS/Model/API/Page.php create mode 100644 classes/KodiCMS/Model/API/Page/Tag.php create mode 100644 classes/KodiCMS/Model/Page.php create mode 100644 classes/KodiCMS/Model/Page/Front.php create mode 100644 classes/KodiCMS/Model/Page/Role.php create mode 100644 classes/KodiCMS/Model/Page/Sitemap.php create mode 100644 classes/Model/API/Page.php create mode 100644 classes/Model/API/Page/Tag.php create mode 100644 classes/Model/Page.php create mode 100644 classes/Model/Page/Behavior.php create mode 100644 classes/Model/Page/Behavior/Setting.php create mode 100644 classes/Model/Page/Front.php create mode 100644 classes/Model/Page/Role.php create mode 100644 classes/Model/Page/Sitemap.php create mode 100644 composer.json create mode 100644 config/global.php create mode 100644 config/permissions.php create mode 100644 config/sitemap.php create mode 100644 i18n/ru.php create mode 100644 init.php create mode 100644 install/dump.sql create mode 100644 install/schema.sql create mode 100644 media/js/controller/behavior.js create mode 100644 media/js/controller/page.js create mode 100644 views/page/blocks/behavior.php create mode 100644 views/page/blocks/meta.php create mode 100644 views/page/blocks/search.php create mode 100644 views/page/blocks/settings.php create mode 100644 views/page/children.php create mode 100644 views/page/edit.php create mode 100644 views/page/index.php create mode 100644 views/page/sort.php create mode 100644 views/page/sortitem.php diff --git a/classes/Behavior.php b/classes/Behavior.php new file mode 100644 index 0000000..defe66d --- /dev/null +++ b/classes/Behavior.php @@ -0,0 +1,139 @@ + + * @link http://kodicms.ru + * @copyright (c) 2012-2014 butschster + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + */ +class Behavior { + + /** + * + * @var array + */ + private static $behaviors = array(); + + /** + * + * @param string $behavior_id + * @return Behavior_Abstract + * @throws HTTP_Exception_404 + */ + public static function factory($behavior_id) + { + $behavior_config = self::get($behavior_id); + if ($behavior_config === NULL) + { + throw new HTTP_Exception_404('Behavior :behavior not found!', array( + ':behavior' => $behavior_id + )); + } + + $class_name = Arr::get($behavior_config, 'class', $behavior_id); + + $behavior_class = 'Behavior_' . URL::title($class_name, ''); + + if (!class_exists($behavior_class)) + { + return NULL; + } + + return new $behavior_class($behavior_config); + } + + /** + * Init behaviors + */ + public static function init() + { + $config = Kohana::$config->load('behaviors'); + + foreach ($config as $behavior_id => $data) + { + self::$behaviors[$behavior_id] = $data; + } + } + + /** + * + * @param type $behavior_id + * @return array + */ + public static function get($behavior_id) + { + return Arr::get(self::$behaviors, $behavior_id); + } + + /** + * Load a behavior and return it + * + * @param behavior_id string The Behavior plugin folder name + * @param page object Will be pass to the behavior + * + * + * @return Behavior_Abstract + */ + public static function load($behavior_id, Model_Page_Front &$page, $url, $uri) + { + $behavior = self::factory($behavior_id); + + $uri = substr($uri, strlen($url)); + + return $behavior + ->set_page($page) + ->execute_uri($uri); + } + + /** + * Load a behavior and return it + * + * @param behavior_id string The Behavior plugin folder name + * + * @return string class name of the page + */ + public static function load_page($behavior_id) + { + $behavior_page_class = 'Model_Page_Behavior_' . URL::title($behavior_id, ''); + + if (class_exists($behavior_page_class)) + { + return $behavior_page_class; + } + else + { + return 'Model_Page_Behavior'; + } + } + + /** + * + * Find all active Behaviors id + * @return array + */ + public static function findAll() + { + return array_keys(self::$behaviors); + } + + /** + * + * @param string $name + * @param array|string $selected + * @param array $attributes + * @return string + */ + public static function select_choices() + { + $options = array('' => __('none')); + + foreach (self::findAll() as $behavior) + { + $options[$behavior] = __(ucfirst(Inflector::humanize($behavior))); + } + + return $options; + } + +} diff --git a/classes/Behavior/Abstract.php b/classes/Behavior/Abstract.php new file mode 100644 index 0000000..0e058c9 --- /dev/null +++ b/classes/Behavior/Abstract.php @@ -0,0 +1,132 @@ + + * @link http://kodicms.ru + * @copyright (c) 2012-2014 butschster + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + */ +abstract class Behavior_Abstract { + + /** + * + * @var Model_Page_Front + */ + protected $_page = NULL; + + /** + * + * @var Behavior_Route + */ + protected $_router = NULL; + + /** + * + * @var array + */ + protected $_config = array(); + + /** + * + * @var array + */ + protected $_settings = NULL; + + /** + * + * @param array $config + */ + public function __construct(array $config = array()) + { + $this->_config = $config; + + $routes = $this->routes(); + + if (isset($this->_config['routes']) AND is_array($this->_config['routes'])) + { + $routes = $this->_config['routes'] + $routes; + } + + $this->_router = new Behavior_Route($routes); + } + + /** + * + * @return array + */ + public function routes() + { + return array(); + } + + /** + * + * @return Behavior_Route + */ + public function router() + { + return $this->_router; + } + + /** + * + * @param string $uri + */ + public function execute_uri($uri) + { + $method = $this->_router->find($uri); + + if (strpos($method, '::') !== FALSE) + { + Callback::invoke($method, array($this)); + } + else + { + $this->{$method}(); + } + + return $this; + } + + /** + * + * @param Model_Page_Front $page + * @return \Behavior_Abstract + */ + public function set_page(Model_Page_Front &$page) + { + $this->_page = &$page; + return $this; + } + + /** + * + * @return Model_Page_Front + */ + public function page() + { + return $this->_page; + } + + /** + * + * @return Behavior_Settings + */ + public function settings() + { + if ($this->_settings === NULL) + { + $this->_settings = new Behavior_Settings($this->page()); + } + + return $this->_settings; + } + + public function stub() + { + + } + + abstract public function execute(); +} \ No newline at end of file diff --git a/classes/Behavior/Protectedpage.php b/classes/Behavior/Protectedpage.php new file mode 100644 index 0000000..ecb00bb --- /dev/null +++ b/classes/Behavior/Protectedpage.php @@ -0,0 +1,17 @@ + + * @link http://kodicms.ru + * @copyright (c) 2012-2014 butschster + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + */ +class Behavior_ProtectedPage extends Behavior_Abstract +{ + public function execute() + { + + } +} \ No newline at end of file diff --git a/classes/Behavior/Route.php b/classes/Behavior/Route.php new file mode 100644 index 0000000..8b54a53 --- /dev/null +++ b/classes/Behavior/Route.php @@ -0,0 +1,156 @@ + + * @link http://kodicms.ru + * @copyright (c) 2012-2014 butschster + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + */ +class Behavior_Route { + + // What must be escaped in the route regex + const REGEX_ESCAPE = '[.\\+*?[^\\]${}=!|]'; + + // What can be part of a value + const REGEX_SEGMENT = '[^/.,;?\n]++'; + + /** + * + * @var array + */ + protected $_routes = array(); + + /** + * + * @var array + */ + protected $_params = array(); + + /** + * + * @var string + */ + protected $_matched_route = NULL; + + public function __construct(array $routes) + { + $this->_routes = $routes; + } + + /** + * + * @param string $name + * @return string|NULL + */ + public function __get($name) + { + return $this->param($name); + } + + /** + * + * @return string + */ + public function matched_route() + { + return $this->_matched_route; + } + + /** + * + * @param string $name + * @param mixed $default + * @return string|NULL + */ + public function param($name, $default = NULL) + { + return Arr::get($this->_params, $name, $default); + } + + /** + * + * @return array + */ + public function params() + { + return $this->_params; + } + + /** + * + * @param string $name + * @return boolean + */ + public function __isset($name) + { + return isset($this->_params[$name]); + } + + /** + * + * @param string $uri + * @return \Behavior_Abstract + */ + public function find($uri) + { + $method = $this->_match_route($uri); + + Context::instance()->behavior_router($this); + + return $method; + } + + /** + * + * @return array + */ + public function routes($routes = NULL) + { + return $this->_routes; + } + + /** + * + * @param string $uri + */ + final protected function _match_route($uri) + { + $default_method = 'execute'; + + foreach ($this->routes() as $_uri => $params) + { + if (!isset($params['method'])) + { + $params['method'] = $default_method; + } + + $expression = Route::compile($_uri, Arr::get($params, 'regex')); + if (!preg_match($expression, $uri, $matches)) + { + continue; + } + + foreach ($matches as $key => $value) + { + if (is_int($key)) + { + // Skip all unnamed keys + continue; + } + + // Set the value for all matched keys + $this->_params[$key] = $value; + Context::instance()->set('.' . $key, $value); + } + + $this->_matched_route = $_uri; + + return $params['method']; + } + + $this->_params = preg_split('/\//', $uri, -1, PREG_SPLIT_NO_EMPTY); + + return $default_method; + } +} \ No newline at end of file diff --git a/classes/Behavior/Settings.php b/classes/Behavior/Settings.php new file mode 100644 index 0000000..609821f --- /dev/null +++ b/classes/Behavior/Settings.php @@ -0,0 +1,92 @@ + + * @link http://kodicms.ru + * @copyright (c) 2012-2014 butschster + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + */ +class Behavior_Settings { + + /** + * + * @var array + */ + protected $_data = NULL; + + /** + * + * @var Model_Page + */ + protected $_page = NULL; + + public function __construct($page) + { + $this->_page = $page; + } + + public function __toString() + { + return (string) $this->render(); + } + + /** + * + * @param string $key + * @return string + */ + public function __get($key) + { + return $this->get($key); + } + + /** + * + * @param string $name + * @param mixed $default + * @return string + */ + public function get($key, $default = NULL) + { + $this->_load(); + + return Arr::get($this->_data, $key, $default); + } + + /** + * + * @return Behavior_Settings + * @throws Kohana_Exception + */ + protected function _load() + { + if ($this->_page === NULL) + { + throw new Kohana_Exception('Page must be loaded'); + } + + if ($this->_data === NULL) + { + $this->_data = ORM::factory('Page_Behavior_Setting') + ->find_by_page($this->_page) + ->get('data', array()); + } + + return $this; + } + + /** + * @return View + */ + public function render() + { + $this->_load(); + + return View::factory('behavior/' . $this->_page->behavior_id) + ->set('settings', $this->_data) + ->set('behavior', $this) + ->set('page', $this->_page); + } + +} \ No newline at end of file diff --git a/classes/Controller/API/Behavior.php b/classes/Controller/API/Behavior.php new file mode 100644 index 0000000..82e3941 --- /dev/null +++ b/classes/Controller/API/Behavior.php @@ -0,0 +1,42 @@ + + * @link http://kodicms.ru + * @copyright (c) 2012-2014 butschster + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + */ +class Controller_API_Behavior extends Controller_System_Api { + + public function get_settings() + { + $id = $this->param('id', NULL); + $page_id = $this->param('page_id', NULL, TRUE); + + if (empty($id)) + return; + + $page = ORM::factory('page', (int) $page_id); + + if (!$page->loaded()) + { + throw new HTTP_Exception_404('Page :id not found!', array( + ':id' => $page_id)); + } + + $page->behavior_id = $id; + + try + { + $behavior = new Behavior_Settings($page); + + $this->response((string) $behavior->render()); + } + catch (Kohana_Exception $e) + { + + } + } +} \ No newline at end of file diff --git a/classes/Controller/API/Pages.php b/classes/Controller/API/Pages.php new file mode 100644 index 0000000..007acfa --- /dev/null +++ b/classes/Controller/API/Pages.php @@ -0,0 +1,3 @@ + + * @link http://kodicms.ru + * @copyright (c) 2012-2014 butschster + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + */ +class KodiCMS_Controller_API_Pages extends Controller_System_Api { + + public function get_get() + { + $uids = $this->param('uids'); + $parent = $this->param('pid'); + + $pages = Model_API::factory('api_page') + ->get_all($uids, $parent, $this->fields); + + $this->response($pages); + } + + public function get_tags() + { + $uid = $this->param('uid', NULL, TRUE); + + $tags = Model_API::factory('api_page_tag') + ->get_all(NULL, $this->fields, $uid); + + $this->response($tags); + } + + public function get_by_uri() + { + $uri = $this->param('uri', NULL, TRUE); + + $page = Model::factory('api_page') + ->find_by_uri($uri, $this->fields); + + $this->response($page); + } + + public function get_sort() + { + $pages = Model_Page_Sitemap::get( TRUE )->as_array(); + + $this->response((string) View::factory( 'page/sort', array( + 'pages' => $pages + ))); + } + + public function post_sort() + { + $pages = $this->param('pages', array(), TRUE); + + if (count($pages) > 0) + { + $insert = DB::insert('pages')->columns(array('id', 'parent_id', 'position')); + + foreach ($pages as $page) + { + if (empty($page['parent_id'])) + { + $page['parent_id'] = 1; + } + + $insert->values(array((int) $page['id'], (int) $page['parent_id'], (int) $page['position'])); + } + + $insert = $insert . ' ON DUPLICATE KEY UPDATE parent_id = VALUES(parent_id), position = VALUES(position)'; + + DB::query(Database::INSERT, $insert)->execute(); + + if (Kohana::$caching === TRUE) + { + Cache::instance()->delete_tag('pages'); + } + } + } + + public function get_search() + { + $query = trim($this->param('search', NULL, TRUE)); + + $pages = ORM::factory('page'); + + if (strlen($query) == 2 AND $query[0] == '.') + { + $page_status = array( + 'd' => Model_Page::STATUS_DRAFT, + 's' => Model_Page::STATUS_PASSWORD_PROTECTED, + 'p' => Model_Page::STATUS_PUBLISHED, + 'h' => Model_Page::STATUS_HIDDEN + ); + + if (isset($page_status[$query[1]])) + { + $pages->where('status_id', '=', $page_status[$query[1]]); + } + } + else + { + $pages->like($query); + } + + $childrens = array(); + $pages = $pages->find_all(); + + foreach ($pages as $page) + { + $page->is_expanded = FALSE; + $page->has_children = FALSE; + + $childrens[] = $page; + } + + $this->response((string) View::factory('page/children', array( + 'childrens' => $childrens, + 'level' => 0 + ))); + } + + public function post_change_status() + { + $page_id = $this->param('page_id', NULL, TRUE); + $value = $this->param('value', NULL, TRUE); + + $page = ORM::factory('page', $page_id) + ->set('status_id', $value) + ->update(); + + $this->response($page->get_status()); + } + + public function get_parse_meta() + { + $page_id = $this->param('page_id', NULL, TRUE); + $fields = (array) $this->param('fields', array(), TRUE); + + $response = array(); + $page = Model_Page_Front::findById($page_id); + if ($page instanceof Model_Page_Front) + { + foreach ($fields as $field => $value) + { + $response[$field] = $page->parse_meta($field, $value); + } + } + + $this->response($response); + } +} \ No newline at end of file diff --git a/classes/KodiCMS/Controller/Front.php b/classes/KodiCMS/Controller/Front.php new file mode 100644 index 0000000..f509198 --- /dev/null +++ b/classes/KodiCMS/Controller/Front.php @@ -0,0 +1,153 @@ + + * @link http://kodicms.ru + * @copyright (c) 2012-2014 butschster + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + */ +class KodiCMS_Controller_Front extends Controller_System_Controller +{ + /** + * + * @var Context + */ + protected $_ctx = NULL; + + public function before() + { + parent::before(); + + $this->_ctx = & Context::instance(); + + $this->_ctx + ->request($this->request) + ->response($this->response); + + View::bind_global('ctx', $this->_ctx); + } + + public function action_index() + { + Observer::notify('frontpage_requested', $this->request->uri()); + + $page = Model_Page_Front::find($this->request->uri()); + + if ($page instanceof Model_Page_Front) + { + if ($page->use_redirect AND ! empty($page->redirect_url)) + { + HTTP::redirect($page->redirect_url, 301); + } + + return $this->_render($page); + } + else + { + // Если включен поиск похожей страницы и она найдена, производим + // редирект на найденую страницу + if (Config::get('site', 'find_similar') == Config::YES) + { + if (($uri = Model_Page_Front::find_similar($this->request->uri())) !== FALSE) + { + HTTP::redirect(URL::frontend($uri), 301); + } + } + + Model_Page_Front::not_found(); + } + } + + /** + * + * @param type Model_Page_Front + */ + private function _render( Model_Page_Front $page) + { + View::set_global('page_object', $page); + View::set_global('page', $page); + + $this->_ctx->set_page($page); + + // If page needs login, redirect to login + if ($page->needs_login() == Model_Page::LOGIN_REQUIRED) + { + Observer::notify('frontpage_login_required', $page); + + if (!Auth::is_logged_in()) + { + Flash::set('redirect', $page->url()); + + $this->redirect(Route::get('user')->uri(array( + 'action' => 'login' + ))); + } + } + + Observer::notify('frontpage_found', $page); + + $this->_ctx->set_crumbs($page); + $this->_ctx->build_crumbs(); + + // Если установлен статус 404, то выводим страницу 404 + // Страницу 404 могут выкидывать также Виджеты + if (Request::current()->is_initial() AND $this->response->status() == 404) + { + $message = $this->_ctx->get('throw_message'); + + $this->_ctx = NULL; + + if (!$message) + { + $message = 'Page not found'; + } + + Model_Page_Front::not_found($message); + } + + $html = (string) $page->render_layout(); + + // Если пользователь Администраторо или девелопер, в конец шаблона + // добавляем View 'system/blocks/toolbar', в котором можно добавлять + // собственный HTML, например панель администратора + if (Auth::is_logged_in() AND Auth::has_permissions(array( + 'administrator', 'developer' + ))) + { + $inject_html = (string) View::factory('system/blocks/toolbar'); + + // Insert system HTML before closed tag body + $matches = preg_split('/(<\/body>)/i', $html, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + + if (count($matches) > 1) + { + /* assemble the HTML output back with the iframe code in it */ + $html = $matches[0] . $inject_html . $matches[1] . $matches[2]; + } + } + + // Если в наcтройках выключен режим отладки, то выключить etag кеширование + if (Config::get('site', 'debug') == Config::NO) + { + $this->check_cache(sha1($html)); + $this->response->headers('last-modified', date('r', strtotime($page->updated_on))); + } + + $this->response->headers('Content-Type', $page->mime()); + + if (Config::get('global', 'x_powered_header') == Config::YES) + { + $this->response->headers('X-Powered-CMS', CMS_NAME . '/' . CMS_VERSION); + } + + $this->response->body($html); + } + + public function after() + { + parent::after(); + Observer::notify('frontpage_after_render'); + } +} \ No newline at end of file diff --git a/classes/KodiCMS/Controller/Page.php b/classes/KodiCMS/Controller/Page.php new file mode 100644 index 0000000..f591500 --- /dev/null +++ b/classes/KodiCMS/Controller/Page.php @@ -0,0 +1,331 @@ + + * @link http://kodicms.ru + * @copyright (c) 2012-2014 butschster + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + */ +class KodiCMS_Controller_Page extends Controller_System_Backend { + + public $allowed_actions = array( + 'children' + ); + + public function before() + { + parent::before(); + + $this->breadcrumbs + ->add(__('Pages'), Route::get('backend')->uri(array('controller' => 'page'))); + } + + public function action_index() + { + $this->set_title(__('Pages'), FALSE); + + Assets::package(array('nestable', 'editable')); + + $this->template_js_params['PAGE_STATUSES'] = array_map(function($value, $key) { + return array('id' => $key, 'text' => $value); + }, Model_Page::statuses(), array_keys(Model_Page::statuses())); + + $this->template->content = View::factory('page/index', array( + 'page' => ORM::factory('page', 1), + 'content_children' => $this->children(1, 0, TRUE) + )); + } + + public function action_add() + { + WYSIWYG::load_all(); + Assets::package('backbone'); + + $parent_id = (int) $this->request->param('id', 1); + + $values = Flash::get('page::add::data', array()); + $page = ORM::factory('page')->values($values); + + $page->parent_id = $parent_id; + + // Устанавливаем статус по умолчанию + $page->status_id = Config::get('site', 'default_status_id'); + $page->needs_login = Model_Page::LOGIN_INHERIT; + + $page->published_on = date('Y-m-d H:i:s'); + + // check if trying to save + if ($this->request->method() == Request::POST) + { + return $this->_add($page); + } + + $this->set_page_js_params($page); + $this->set_title(__('Add page')); + + $this->template->content = View::factory('page/edit', array( + 'action' => 'add', + 'parent_id' => $parent_id, + 'page' => $page, + 'permissions' => ORM::factory('role')->find_all()->as_array('id', 'name'), + 'page_permissions' => $page->get_permissions() + )); + } + + private function _add(ORM $page) + { + $page_data = $this->request->post('page'); + + // Сохраняем полученые данные в сесиию + Flash::set('page::add::data', $page_data); + + // Создаем новую страницу + try + { + $page = $page->values($page_data)->create(); + + // Если есть права на управление ролями + if (ACL::check('page.permissions')) + { + $page->save_permissions($this->request->post('page_permissions')); + } + + Messages::success(__('Page has been saved!')); + + Flash::clear('page::add::data'); + } + catch (ORM_Validation_Exception $e) + { + Messages::errors($e->errors('validation')); + $this->go_back(); + } + catch (Kohana_Exception $e) + { + Messages::errors(__('Something went wrong!')); + $this->go_back(); + } + + // save and quit or save and continue editing ? + if ($this->request->post('commit') !== NULL) + { + $this->go(); + } + else + { + $this->go(array( + 'action' => 'edit', + 'id' => $page->id + )); + } + } + + public function action_edit() + { + WYSIWYG::load_all(); + Assets::package('backbone'); + + $page_id = (int) $this->request->param('id'); + + $page = ORM::factory('page', $page_id); + + if (!$page->loaded()) + { + Messages::errors(__('Page not found!')); + $this->go(); + } + + // Проверка пользователя на доступ к редактированию текущей страницы + if (!Auth::has_permissions($page->get_permissions())) + { + Messages::errors(__('You do not have permission to access the requested page!')); + $this->go(); + } + + // check if trying to save + if ($this->request->method() == Request::POST) + { + return $this->_edit($page); + } + + $this->set_page_js_params($page); + $this->set_title($page->title); + + $this->template->content = View::factory('page/edit', array( + 'action' => 'edit', + 'page' => $page, + 'permissions' => ORM::factory('role')->find_all()->as_array('id', 'name'), + 'page_permissions' => $page->get_permissions() + )); + } + + private function _edit(ORM $page) + { + $page_data = $this->request->post('page'); + + $page_data['use_redirect'] = !empty($page_data['use_redirect']); + + try + { + $page = $page->values($page_data)->update(); + + if (ACL::check('page.permissions')) + { + $page->save_permissions($this->request->post('page_permissions')); + } + + Messages::success(__('Page has been saved!')); + } + catch (ORM_Validation_Exception $e) + { + Messages::errors($e->errors('validation')); + $this->go_back(); + } + catch (Kohana_Exception $e) + { + Messages::errors(__('Something went wrong!')); + $this->go_back(); + } + + // save and quit or save and continue editing ? + if ($this->request->post('commit') !== NULL) + { + $this->go(); + } + else + { + $this->go_back(); + } + } + + public function action_delete() + { + $this->auto_render = FALSE; + $page_id = (int) $this->request->param('id'); + + if ($page_id == 1) + { + Messages::errors(__('Root page can not be removed.')); + $this->go_back(); + } + + $page = ORM::factory('page', $page_id); + + if (!$page->loaded()) + { + Messages::errors(__('Page not found!')); + $this->go_back(); + } + + // check for permission to delete this page + if (!Auth::has_permissions($page->get_permissions())) + { + Kohana::$log->add(Log::ALERT, 'Trying to delete page :id by :user', array( + ':id' => $page_id + ))->write(); + + Messages::errors(__('You do not have permission.')); + $this->go_back(); + } + + try + { + $page->delete(); + Messages::success(__('Page has been deleted!')); + } + catch (Kohana_Exception $e) + { + Messages::errors(__('Something went wrong!')); + $this->go_back(); + } + + $this->go(); + } + + public function children($parent_id, $level, $return = FALSE) + { + $expanded_rows = Arr::get($_COOKIE, 'cms_expanded_pages'); + + $expanded_rows = $expanded_rows == NULL ? array() : explode(',', $expanded_rows); + + $page = ORM::factory('page', $parent_id); + + if (!$page->loaded()) + { + return; + } + + $pages = ORM::factory('page')->children_of($parent_id); + $behavior = Behavior::get($page->behavior_id); + + if (!empty($behavior['limit'])) + { + $pages->limit((int) $behavior['limit']); + } + + $childrens = $pages->find_all()->as_array('id'); + + foreach ($childrens as $index => $child) + { + $childrens[$index]->has_children = $child->has_children(); + + $child_behavior = Behavior::get($child->behavior_id); + + if (!empty($child_behavior['link'])) + { + $childrens[$index]->has_children = TRUE; + } + + $childrens[$index]->is_expanded = in_array($child->id, $expanded_rows); + + if ($childrens[$index]->is_expanded === TRUE) + { + $childrens[$index]->children_rows = $this->children($child->id, $level + 1, true); + } + } + + if (!empty($behavior['limit'])) + { + $childrens[] = '...'; + } + + if (!empty($behavior['link'])) + { + $link = strtr($behavior['link'], array(':id' => $parent_id)); + $childrens[] = __(':icon :link', array( + ':icon' => UI::icon('book'), + ':link' => HTML::anchor(URL::backend($link), __(ucfirst($page->behavior_id))) + )); + } + + $content = View::factory('page/children', array( + 'childrens' => $childrens, + 'level' => $level + 1, + 'expanded_rows' => $expanded_rows + )); + + if ($return === TRUE) + { + return $content; + } + + echo $content; + } + + public function action_children() + { + $this->auto_render = FALSE; + + $parent_id = $this->request->query('parent_id'); + $level = $this->request->query('level'); + + return $this->children($parent_id, $level); + } + + public function set_page_js_params(Model_Page $page) + { + $this->template_js_params['PAGE_ID'] = $page->id; + $this->template_js_params['PAGE_OBJECT'] = $page->as_array(); + } +} \ No newline at end of file diff --git a/classes/KodiCMS/Model/API/Page.php b/classes/KodiCMS/Model/API/Page.php new file mode 100644 index 0000000..226cc3e --- /dev/null +++ b/classes/KodiCMS/Model/API/Page.php @@ -0,0 +1,167 @@ + + * @link http://kodicms.ru + * @copyright (c) 2012-2014 butschster + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + */ +class KodiCMS_Model_API_Page extends Model_API { + + protected $_table_name = 'pages'; + + protected $_secured_columns = array( + 'email', 'logins', 'last_login' + ); + + public function get_all($uids, $pid, $fields) + { + $uids = $this->prepare_param($uids, array('Valid', 'numeric')); + $fields = $this->prepare_param($fields); + + $pages = DB::select('id', 'parent_id', 'slug', 'title') + ->select_array( $this->filtered_fields( $fields ) ) + ->from($this->_table_name); + + if (!empty($uids)) + { + $pages->where('id', 'in', $uids); + } + + if (!empty($pid)) + { + $pages->where('parent_id', '=', (int) $pid); + } + + $pages = $pages + ->execute() + ->as_array('id'); + + if (in_array('parts', $fields)) + { + $parts = Model::factory('api_page_part') + ->get(array_keys($pages), array_merge($fields, array('page_id'))); + + if (is_array($parts)) + { + foreach ($parts as $part) + { + if (isset($pages[$part['page_id']])) + { + $pages[$part['page_id']]['parts'][$part['id']] = $part; + unset($pages[$part['page_id']]['parts'][$part['id']]['page_id']); + } + } + } + } + + return $this->create_tree($pages, $fields); + } + + public function create_tree(array $pages, $fields) + { + $indexed = array(); + + // first pass - get the array indexed by the primary id + foreach ($pages as $row) + { + $indexed[$row['id']] = $row; + $indexed[$row['id']]['level'] = 0; + $indexed[$row['id']]['pages'] = array(); + } + + $root = NULL; + foreach ($indexed as $id => $row) + { + $slug = $row['slug']; + + if (isset($indexed[$row['parent_id']])) + { + $indexed[$row['parent_id']]['pages'][] =& $indexed[$id]; + $indexed[$id]['slug'] = $indexed[$row['parent_id']]['slug'] . '/' . $slug; + $indexed[$id]['level'] = $indexed[$row['parent_id']]['level'] + 1; + + if (!$row['parent_id']) + { + $root = $id; + } + } + + $indexed[$id]['url'] = URL::frontend($indexed[$id]['slug'], TRUE); + } + + if ($root == NULL) + { + $indexed = array_shift($indexed); + } + else + { + $indexed = $indexed[$root]; + } + + return array($indexed); + } + + public function find_by_uri($uri, $fields = array()) + { + $fields = $this->prepare_param($fields); + + $page = Model_Page_Front::find($uri); + + if (!$page) + { + throw new HTTP_Exception_404('Page :uri not found', array( + ':uri' => $uri)); + } + + // If page needs login, redirect to login + if ($page->needs_login() == Model_Page::LOGIN_REQUIRED) + { + throw new HTTP_Exception_401('You don`t have access to view page :uri. Please login', array( + ':uri' => $uri )); + } + + $fields = array_merge(array('id', 'title', 'url'), $fields); + + $allowed_fields = array( + 'id', 'title', 'url', 'breadcrumb', 'author', 'author_id', + 'updator', 'updator_id', 'slug', 'keywords', 'description', + 'level', 'tags', 'is_active', 'date', 'breadcrumbs', + 'layout', 'content' + ); + + foreach ($fields as $field) + { + if (strpos($field, 'part::') === 0) + { + $allowed_fields[] = $field; + } + } + + $fields = array_intersect($allowed_fields, $fields); + + $array = array(); + + foreach ($fields as $field) + { + if ($field == 'content') + { + $array['content'] = (string) $page->render_layout(); + continue; + } + elseif (strpos($field, 'part::') === 0) + { + list($part, $name) = explode('::', $field); + $array[$part][$name] = $page->content($name); + } + else if (method_exists($page, $field)) + { + $array[$field] = $page->$field(); + } + } + + return array('page' => $array); + } +} \ No newline at end of file diff --git a/classes/KodiCMS/Model/API/Page/Tag.php b/classes/KodiCMS/Model/API/Page/Tag.php new file mode 100644 index 0000000..6b72f8a --- /dev/null +++ b/classes/KodiCMS/Model/API/Page/Tag.php @@ -0,0 +1,44 @@ + + * @link http://kodicms.ru + * @copyright (c) 2012-2014 butschster + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + */ +class KodiCMS_Model_API_Page_Tag extends Model_API { + + protected $_table_name = 'tags'; + + public function get_all($uids, $fields = array(), $page_id = NULL) + { + $uids = $this->prepare_param($uids, array('Valid', 'numeric')); + $fields = $this->prepare_param($fields); + + $tags = DB::select('id', 'name') + ->select_array( $this->filtered_fields( $fields ) ) + ->from($this->table_name()); + + if (!empty($uids)) + { + $tags->where('id', 'in', $uids); + } + + if ($page_id !== NULL) + { + $tags + ->join('page_tags', 'left') + ->on('page_tags.tag_id', '=', $this->table_name() . '.id') + ->where('page_tags.page_id', '=', (int) $page_id); + } + + return $tags + ->cache_tags(array('page_tags')) + ->cached((int) Config::get('cache', 'tags')) + ->execute() + ->as_array(); + } + +} \ No newline at end of file diff --git a/classes/KodiCMS/Model/Page.php b/classes/KodiCMS/Model/Page.php new file mode 100644 index 0000000..af4c3e7 --- /dev/null +++ b/classes/KodiCMS/Model/Page.php @@ -0,0 +1,756 @@ + + * @link http://kodicms.ru + * @copyright (c) 2012-2014 butschster + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + */ +class KodiCMS_Model_Page extends ORM +{ + const STATUS_DRAFT = 1; + const STATUS_PUBLISHED = 100; + const STATUS_HIDDEN = 101; + const STATUS_PASSWORD_PROTECTED = 200; + + const LOGIN_NOT_REQUIRED = 0; + const LOGIN_REQUIRED = 1; + const LOGIN_INHERIT = 2; + + /** + * Список правил авторищации + * @return array + */ + public static function logins() + { + return array( + static::LOGIN_NOT_REQUIRED => __('Not required'), + static::LOGIN_REQUIRED => __('Required'), + static::LOGIN_INHERIT => __('inherit') + ); + } + + /** + * Список правил для meta robots + * @return array + */ + public static function robots() + { + return array( + 'INDEX, FOLLOW' => 'INDEX, FOLLOW', + 'INDEX, NOFOLLOW' => 'INDEX, NOFOLLOW', + 'NOINDEX, FOLLOW' => 'NOINDEX, FOLLOW', + 'NOINDEX, NOFOLLOW' => 'NOINDEX, NOFOLLOW' + ); + } + + /** + * Список статусов + * @return array + */ + public static function statuses() + { + return array( + static::STATUS_DRAFT => __('Draft'), + static::STATUS_PASSWORD_PROTECTED => __('Password protected'), + static::STATUS_PUBLISHED => __('Published'), + static::STATUS_HIDDEN => __('Hidden') + ); + } + + protected $_created_column = array( + 'format' => 'Y-m-d H:i:s', + 'column' => 'created_on' + ); + + protected $_updated_column = array( + 'format' => 'Y-m-d H:i:s', + 'column' => 'updated_on' + ); + + protected $_belongs_to = array( + 'author' => array( + 'model' => 'user', + 'foreign_key' => 'created_by_id' + ), + 'updator' => array( + 'model' => 'user', + 'foreign_key' => 'updated_by_id' + ), + 'parent' => array( + 'model' => 'page', + 'foreign_key' => 'parent_id' + ) + ); + + protected $_has_many = array ( + 'roles' => array( + 'model' => 'role', + 'through' => 'page_roles' + ) + ); + + /** + * + * @var boolean + */ + public $is_expanded = FALSE; + + /** + * + * @var boolean + */ + public $has_children = FALSE; + + /** + * + * @var array + */ + public $children_rows = NULL; + + /** + * + * @return array + */ + public function labels() + { + return array( + 'title' => __('Page title'), + 'slug' => __('Slug'), + 'breadcrumb' => __('Breadcrumb'), + 'meta_title' => __('Meta title'), + 'meta_keywords' => __('Meta keywords'), + 'meta_description' => __('Meta description'), + 'robots' => __('Robots'), + 'parent_id' => __('Parent page'), + 'layout_file' => __('Layout'), + 'behavior_id' => __('Page type'), + 'status_id' => __('Page status'), + 'password' => __('Page password'), + 'published_on' => __('Published date'), + 'needs_login' => __('Needs login'), + 'page_permissions' => __('Page permissions'), + 'created_by_id' => __('Author'), + 'use_redirect' => __('Use redirect'), + 'redirect_url' => __('Redirect URL') + ); + } + + /** + * + * @return array + */ + public function rules() + { + $rules = array( + 'title' => array( + array('not_empty'), + array('max_length', array(':value', 255)) + ), + 'slug' => array( + array('max_length', array(':value', 100)) + ), + 'status_id' => array( + array('array_key_exists', array(':value', self::statuses())) + ), + 'needs_login' => array( + array('array_key_exists', array(':value', self::logins())) + ), + ); + + if ($this->id > 1) + { + $rules['slug'][] = array('not_empty'); + } + + return $rules; + } + + /** + * + * @return array + */ + public function filters() + { + return array( + 'slug' => array( + array(array($this, 'clean_slug')), + array('strtolower') + ), + 'parent_id' => array( + array('intval') + ), + 'status_id' => array( + array('intval') + ), + 'created_by_id' => array( + array('intval') + ), + 'updated_by_id' => array( + array('intval') + ), + 'position' => array( + array('intval') + ), + 'needs_login' => array( + array('intval') + ), + 'title' => array( + array('trim'), + array('strip_tags') + ), + 'meta_title' => array( + array('trim'), + array('strip_tags') + ), + 'breadcrumb' => array( + array('trim'), + array('strip_tags') + ), + 'meta_keywords' => array( + array('trim'), + array('strip_tags') + ), + 'meta_description' => array( + array('trim'), + array('strip_tags') + ), + 'use_redirect' => array( + array('intval') + ), + 'redirect_url' => array( + array('trim') + ), + ); + } + + /** + * + * @return array + */ + public function form_columns() + { + return array( + 'id' => array( + 'type' => 'input', + 'editable' => FALSE, + 'length' => 10 + ), + 'title' => array( + 'type' => 'input', + 'length' => 100 + ), + 'meta_description' => array( + 'type' => 'textarea' + ), + 'meta_title' => array( + 'type' => 'input' + ), + 'meta_title' => array( + 'type' => 'input' + ), + 'robots' => array( + 'type' => 'select', + 'choices' => 'Model_Page::robots' + ), + 'parent_id' => array( + 'type' => 'select', + 'choices' => array($this, 'get_sitemap') + ), + 'status_id' => array( + 'type' => 'select', + 'choices' => 'Model_Page::statuses' + ), + 'layout_file' => array( + 'type' => 'select', + 'choices' => array($this, 'get_layouts_list') + ), + 'behavior_id' => array( + 'type' => 'select', + 'choices' => 'Behavior::select_choices' + ), + 'needs_login' => array( + 'type' => 'select', + 'choices' => 'Model_Page::logins' + ), + 'created_by_id' => array( + 'type' => 'select', + 'choices' => function() { + return ORM::factory('user') + ->find_all() + ->as_array('id', 'username'); + } + ), + ); + } + + public function before_create() + { + $this->created_by_id = Auth::get_id(); + $this->updated_by_id = $this->created_by_id; + + if (empty($this->status_id)) + { + $this->status_id = Config::get('site', 'default_status_id'); + } + + if ($this->status_id == Model_Page::STATUS_PUBLISHED) + { + $this->published_on = date('Y-m-d H:i:s'); + } + + if (empty($this->use_redirect)) + { + $this->redirect_url = NULL; + } + + if ($this->position == 0) + { + $last_position = DB::select(array(DB::expr('MAX(position)'), 'pos')) + ->from($this->table_name()) + ->where('parent_id', '=', $this->parent_id) + ->execute() + ->get('pos', 0); + + $this->position = ((int) $last_position) + 1; + } + + Observer::notify('page_add_before_save', $this); + + return TRUE; + } + + public function after_create() + { + $page = DB::select('id') + ->from($this->table_name()) + ->where('id', '!=', $this->id) + ->where('slug', '=', $this->slug) + ->where('parent_id', '=', $this->parent_id) + ->execute() + ->get('id'); + + if ($page !== NULL) + { + $this->slug = $this->slug . '-' . $this->id; + $this->update(); + } + + Kohana::$log->add(Log::INFO, 'Page :id added by :user', array( + ':id' => $this->id + ))->write(); + + Observer::notify('page_add_after_save', $this); + + return TRUE; + } + + public function before_update() + { + if (empty($this->published_on) AND $this->status_id == Model_Page::STATUS_PUBLISHED) + { + $this->published_on = date('Y-m-d H:i:s'); + } + + if (empty($this->use_redirect)) + { + $this->redirect_url = NULL; + } + + // Если запрещены теги в Заголовке, удаляем их + if (Config::get('site', 'allow_html_title') == Config::NO) + { + $this->title = strip_tags(trim($this->title)); + } + + $this->updated_by_id = Auth::get_id(); + + Observer::notify('page_edit_before_save', $this); + + return TRUE; + } + + public function after_update() + { + if (Kohana::$caching === TRUE) + { + Cache::instance()->delete_tag('pages'); + } + + Kohana::$log->add(Log::INFO, 'Page :id edited by :user', array( + ':id' => $this->id + ))->write(); + + Observer::notify('page_edit_after_save', $this); + + return $this->after_create(); + } + + public function before_delete() + { + Observer::notify('page_before_delete', $this); + + $this->delete_children(); + + return TRUE; + } + + public function after_delete($id) + { + Kohana::$log->add(Log::INFO, 'Page :id deleted by :user', array( + ':id' => $id + ))->write(); + + Observer::notify('page_after_delete', $id); + + if (Kohana::$caching === TRUE) + { + Cache::instance()->delete_tag('pages'); + } + } + + /** + * Статус страницы + * + * @return string + */ + public function get_status() + { + $status = __('none'); + $label = 'default'; + + switch ($this->status_id) + { + case self::STATUS_DRAFT: + $status = __('Draft'); + $label = 'info'; + break; + case self::STATUS_PASSWORD_PROTECTED: + $status = __('Password protected'); + $label = 'warning'; + break; + case self::STATUS_HIDDEN: + $status = __('Hidden'); + break; + case self::STATUS_PUBLISHED: + if (strtotime($this->published_on) > time()) + { + $status = __('Pending'); + } + else + { + $status = __('Published'); + } + + $label = 'success'; + break; + } + + return UI::label($status, $label . ' editable-status', array('data-value' => $this->status_id)); + } + + /** + * Получение ссылки на страницу + * + * @return string + */ + public function get_public_anchor() + { + return HTML::anchor($this->get_frontend_url(), UI::label(UI::icon('globe') . ' ' . __('View page')), array( + 'class' => 'item-preview', 'target' => '_blank' + )); + } + + /** + * + * + * @return string + */ + public function get_uri() + { + if ($this->parent->loaded()) + { + $result = $this->parent->get_uri() . '/' . $this->slug; + } + else + { + $result = $this->slug; + } + + return $result; + } + + /** + * @return string + */ + public function get_frontend_url() + { + return URL::frontend($this->get_uri(), TRUE); + } + + /** + * Получение ссылки на редактирование страницы + * @return string + */ + public function get_url() + { + return Route::get('backend')->uri(array( + 'controller' => 'page', + 'action' => 'edit', + 'id' => $this->id + )); + } + + /** + * Получение названия шаблона текущей страницы + * @return string + */ + public function layout() + { + if (empty($this->layout_file) AND $this->parent->loaded()) + { + return $this->parent->layout(); + } + + return $this->layout_file; + } + + /** + * + * @param string $keyword + * @return ORM + */ + public function like($keyword) + { + return $this + ->where_open() + ->or_where(DB::expr('LOWER(title)'), 'like', '%:query%') + ->or_where('slug', 'like', '%:query%') + ->or_where('breadcrumb', 'like', '%:query%') + ->or_where('meta_title', 'like', '%:query%') + ->or_where('meta_keywords', 'like', '%:query%') + ->where_close() + ->param(':query', DB::expr($keyword)); + } + + /** + * + * @param integer $id + * @return ORM + */ + public function children_of($id) + { + return $this + ->where('parent_id', '=', (int) $id) + ->order_by('position', 'asc') + ->order_by('page.created_on', 'asc'); + } + + /** + * Проверка на существование внутренних страницы + * + * @return boolean + */ + public function has_children() + { + return (bool) DB::select(array(DB::expr('COUNT(*)'), 'total')) + ->from($this->table_name()) + ->where('parent_id', '=', $this->id) + ->execute() + ->get('total'); + } + + /** + * Удаление внутренних страниц + * + * @return \KodiCMS_Model_Page + */ + public function delete_children() + { + $child_pages = ORM::factory('page') + ->where('parent_id', '=', $this->id) + ->find_all(); + + foreach ($child_pages as $page) + { + $page->delete(); + } + + return $this; + } + + /** + * Получения списка ролей, с которыми разрешен доступ к странице + * + * @param boolean $all + * @return array + */ + public function get_permissions($all = FALSE) + { + if (!$this->loaded() OR $all === TRUE) + { + $roles = ORM::factory('role') + ->where('name', 'in', array('administrator', 'developer', 'editor')); + } + else + { + $roles = $this->roles; + } + + return $roles->find_all()->as_array('id', 'name'); + } + + /** + * Обновление списка ролей, с которым разрешен доступ к странице + * + * @param array $permissions + * @return ORM + */ + public function save_permissions(array $permissions = NULL) + { + if (empty($permissions)) + { + $permissions = array_keys($this->get_permissions(TRUE)); + } + + return $this->update_related_ids('roles', $permissions); + } + + /** + * + * @return array + */ + public function as_array() + { + $object = parent::as_array(); + + $object['layout'] = $this->layout(); + + return $object; + } + + /** + * + * @return array + */ + public function behavior() + { + if (!Valid::not_empty($this->behavior_id)) + { + return NULL; + } + + return Behavior::factory($this->behavior_id); + } + + /** + * + * @param string $slug + * @return string + */ + public function clean_slug($slug) + { + $ext = pathinfo($slug, PATHINFO_EXTENSION); + $slug = pathinfo($slug, PATHINFO_FILENAME); + + $slug = URL::title($slug); + + if (!empty($ext) AND File::mime_by_ext($ext) !== FALSE AND URL_SUFFIX != '.' . $ext) + { + $slug .= '.' . $ext; + } + + return $slug; + } + + /** + * Получение списка страниц за исключением текущей + * + * @return array + */ + public function get_sitemap() + { + $sitemap = Model_Page_Sitemap::get(TRUE); + if ($this->loaded()) + { + $sitemap->exclude(array($this->id)); + } + + return $sitemap->select_choices(); + } + + /** + * Получение списка шаблонов + * + * @return array + */ + public function get_layouts_list() + { + $options = array(); + + if ($this->id != 1) + { + $layout = NULL; + if ($this->parent->loaded()) + { + $layout = $this->parent->layout(); + } + + if (empty($layout)) + { + $layout = __('--- Not set ---'); + } + + $options[0] = __('--- inherit (:layout) ---', array(':layout' => $layout)); + } + else + { + $options[0] = __('--- Not set ---'); + } + + + $layouts = Model_File_Layout::find_all(); + + foreach ($layouts as $layout) + { + $options[$layout->name] = $layout->name; + } + + return $options; + } + + /** + * + * @param array $ids + * @param type $date + */ + public function set_update_date(array $ids, $date = NULL) + { + if (empty($ids)) + { + return FALSE; + } + + if ($date === NULL OR ! Valid::date($date)) + { + $date = date('Y-m-d H:i:s'); + } + + DB::update($this->table_name()) + ->set(array( + 'updated_on' => $date + )) + ->where('id', 'in', $ids) + ->execute($this->_db); + + if (Kohana::$caching === TRUE) + { + Cache::instance()->delete_tag('pages'); + } + + return TRUE; + } + +} // end Model_Page class \ No newline at end of file diff --git a/classes/KodiCMS/Model/Page/Front.php b/classes/KodiCMS/Model/Page/Front.php new file mode 100644 index 0000000..255e633 --- /dev/null +++ b/classes/KodiCMS/Model/Page/Front.php @@ -0,0 +1,1164 @@ + + * @link http://kodicms.ru + * @copyright (c) 2012-2014 butschster + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + */ +class KodiCMS_Model_Page_Front { + + /** + * + * @var array + */ + private static $_pages_cache = array(); + + /** + * + * @var Model_Page_Front + */ + private static $_initial_page = NULL; + + /** + * + * @var string + */ + public $title = ''; + + /** + * + * @var string + */ + public $breadcrumb; + + /** + * + * @var string + */ + public $slug = ''; + + /** + * + * @var string + */ + public $meta_title = ''; + + /** + * + * @var string + */ + public $meta_keywords = ''; + + /** + * + * @var string + */ + public $meta_description = ''; + + /** + * + * @var string + */ + public $url = ''; + + /** + * + * @var integer + */ + public $level = NULL; + + /** + * + * @var integer + */ + public $created_by_id = NULL; + + /** + * + * @var integer + */ + public $updated_by_id = NULL; + + /** + * + * @var boolean + */ + public $needs_login; + + /** + * + * @var Model_User + */ + public $author; + + /** + * + * @var Model_User + */ + public $updator; + + /** + * + * @var Model_Front_Page + */ + protected $_parent = NULL; + + /** + * + * @var array + */ + protected $_blocks = array(); + + /** + * + * @var Model_File_Layout + */ + protected $_layout_object = NULL; + + /** + * + * @var Behavior_Abstract + */ + protected $_behavior = NULL; + + /** + * + * @var array + */ + protected $_meta_params = array(); + + /** + * + * @param string $message + * @param array $params + * @throws HTTP_Exception_404 + */ + public static function not_found($message = 'Page not found', $params = NULL) + { + Observer::notify('page_not_found', $message, $params); + throw new HTTP_Exception_404($message, $params); + } + + /** + * + * @param type $default + * @return Model_Page_Front + */ + public static function initial($default = NULL) + { + return (self::$_initial_page instanceof Model_Page_Front) + ? self::$_initial_page + : $default; + } + + /** + * + * @param array $object + * @param Model_Page_Front $parent + */ + public function __construct($object, $parent) + { + if ($parent instanceof Model_Page_Front) + { + $this->_parent = $parent; + } + + foreach ($object as $key => $value) + { + $this->$key = $value; + } + + if ($this->parent() instanceof Model_Page_Front) + { + $this->_set_url(); + } + + $this->level = $this->level(); + } + + /** + * + * @return integer + */ + public function id() + { + return $this->id; + } + + /** + * + * @return string + */ + public function title() + { + return $this->parse_meta('title'); + } + + /** + * + * @return string + */ + public function meta_title() + { + return $this->parse_meta('meta_title'); + } + + /** + * + * @return string + */ + public function breadcrumb() + { + if (empty($this->breadcrumb)) + { + $this->breadcrumb = $this->title; + } + + return $this->parse_meta('breadcrumb'); + } + + /** + * + * @return Model_User + */ + public function author() + { + if ($this->author instanceof Model_User) + { + return $this->author; + } + + $this->author = ORM::factory('user', $this->created_by_id); + return $this->author; + } + + /** + * + * @return Model_User + */ + public function updator() + { + if ($this->updator instanceof Model_User) + { + return $this->updator; + } + + $this->updator = ORM::factory('user', $this->updated_by_id); + return $this->updator; + } + + /** + * + * @return string + */ + public function slug() + { + return $this->slug; + } + + /** + * + * @param string $default + * @return string + */ + public function meta_keywords($default = NULL) + { + $meta_keywords = $this->parse_meta('meta_keywords'); + return !empty($meta_keywords) + ? $meta_keywords + : $default; + } + + /** + * + * @param string $default + * @return string + */ + public function meta_description($default = NULL) + { + $meta_description = $this->parse_meta('meta_description'); + return ! empty($meta_description) + ? $meta_description + : $default; + } + + /** + * + * @param string|array $key + * @param string $value + * + * @return Model_Page_Front + */ + public function meta_params($key, $value = NULL, $field = NULL) + { + if (is_array($key)) + { + foreach ($key as $key2 => $value) + { + $this->_meta_params[$key2] = $value; + } + } + else + { + $this->_meta_params[$key] = $field === NULL + ? $value + : $this->parse_meta($field, $value); + } + + return $this; + } + + /** + * + * @return string + */ + public function url() + { + $uri = $this->url; + if (!URL::has_suffix($uri) AND $uri != '') + { + $uri .= URL_SUFFIX; + } + + return URL::base(TRUE) . $uri; + } + + /** + * + * @return integer + */ + public function level() + { + if ($this->level === NULL) + { + $this->level = empty($this->url) + ? 0 + : substr_count($this->url, '/') + 1; + } + + return $this->level; + } + /** + * + * @return boolean + */ + public function is_active() + { + if (empty($this->url)) + { + return FALSE; + } + + return (strpos(Request::current()->url(), $this->url) === 1); + } + + /** + * @return boolean + */ + public function is_password_protected() + { + $page_has_access = Session::instance()->get('page_access', array()); + return (!empty($this->password) AND ! array_key_exists($this->id, $page_has_access)); + } + + /** + * + * @param string $label + * @param array $options + * @param boolean $check_current + * @return string + */ + public function link($label = NULL, $options = array(), $check_current = TRUE) + { + if ($label == NULL) + { + $label = $this->title(); + } + + if ($check_current === TRUE) + { + if ($this->is_active()) + { + if (!isset($options['class'])) + { + $options['class'] = ''; + } + + $options['class'] .= ' current'; + } + } + + return HTML::anchor($this->url(), $label, $options); + } + + /** + * + * @param string $format + * @param string $which_one + * @return string + */ + public function date($format = NULL, $which_one = 'publish') + { + if ($which_one == 'update' || $which_one == 'updated') + { + return Date::format($this->updated_on, $format); + } + else if ($which_one == 'publish' || $which_one == 'published') + { + return Date::format($this->published_on, $format); + } + else + { + return Date::format($this->created_on, $format); + } + } + + /** + * Дата создания + * + * @return string + */ + public function created_on() + { + return $this->date(); + } + + /** + * Дата публикации + * + * @return string + */ + public function published_on() + { + return $this->date(NULL, 'published'); + } + + /** + * Дата обновления + * + * @return string + */ + public function updated_on() + { + return $this->date(NULL, 'updated'); + } + + /** + * + * @return Behavior_Abstract + */ + public function behavior() + { + return $this->_behavior; + } + + /** + * + * @param integer $level + * @return array + */ + public function breadcrumbs($level = 0) + { + $crumbs = Breadcrumbs::factory(); + + if ($this->parent() instanceof Model_Page_Front + AND $this->level > $level) + { + $this->parent()->_recurse_breadcrumbs($level, $crumbs); + } + + $crumbs->add($this->breadcrumb(), $this->url, TRUE, NULL, array( + 'id' => $this->id + )); + + return $crumbs; + } + + /** + * + * @return Model_Page_Front + */ + public function previous() + { + if ($this->parent() instanceof Model_Page_Front) + { + $pages = $this->parent()->children(array( + 'where' => array(array('page.id', '<', $this->id)), + 'order_by' => array(array('page.created_on', 'desc')), + 'limit' => 1 + )); + + return isset($pages[0]) + ? $pages[0] + : NULL; + } + + return NULL; + } + + /** + * + * @return Model_Page_Front + */ + public function next() + { + if ($this->parent() instanceof Model_Page_Front) + { + $pages = $this->parent()->children(array( + 'where' => array(array('page.id', '>', $this->id)), + 'order_by' => array(array('page.created_on', 'asc')), + 'limit' => 1 + )); + + return isset($pages[0]) + ? $pages[0] + : NULL; + } + + return NULL; + } + + /** + * + * @param array $clause + * @param array $values + * @param boolean $include_hidden + * @return array + */ + public function children($clause = NULL, $values = array(), $include_hidden = FALSE) + { + $page_class = get_called_class(); + + if (!isset($clause['order_by'])) + { + $clause['order_by'] = array( + array('page.position', 'desc'), + array('page.id', 'asc') + ); + } + + $sql = DB::select('page.*') + ->from(array('pages', 'page')) + ->where('parent_id', '=', $this->id) + ->where('status_id', 'in', self::get_statuses($include_hidden)); + + if (Config::get('page', 'check_date') == Config::YES) + { + $sql->where('published_on', '<=', DB::expr('NOW()')); + } + + $this->custom_filter($sql); + + $sql = Record::_conditions($sql, $clause); + + // hack to be able to redefine the page class with behavior + if (!empty($this->behavior_id)) + { + // will return Page by default (if not found!) + $page_class = Behavior::load_page($this->behavior_id); + } + + $query = $sql + ->cache_tags( array('pages') ) + ->cached((int) Config::get('cache', 'front_page')) + ->execute(); + + $pages = array(); + foreach ($query as $object) + { + $pages[] = new $page_class($object, $this); + } + + return $pages; + } + + /** + * + * @param array $clause + * @param array $values + * @param boolean $include_hidden + * @return integer + */ + public function children_count($clause = NULL, $values = array(), $include_hidden = FALSE) + { + $page_class = get_called_class(); + + if (!isset($clause['order_by'])) + { + $clause['order_by'] = array( + array('page.position', 'desc'), + array('page.id', 'asc') + ); + } + + $sql = DB::select(array(DB::expr('COUNT(*)'), 'total')) + ->from(array('pages', 'page')) + ->where('parent_id', '=', $this->id) + ->where('status_id', 'in', self::get_statuses($include_hidden)); + + if (Config::get('page', 'check_date') == Config::YES) + { + $sql->where('published_on', '<=', DB::expr('NOW()')); + } + + $this->custom_filter($sql); + + // Prepare SQL + $sql = Record::_conditions($sql, $clause); + + return (int) $sql + ->cache_tags( array('pages') ) + ->cached((int) Config::get('cache', 'front_page')) + ->execute() + ->get('total'); + } + + /** + * + * @param Database_Query $sql + * @return void + */ + public function custom_filter(Database_Query & $sql) + { + Observer::notify('frontpage_custom_filter', $sql, $this); + } + + /** + * + * @param string $uri + * @return string|boolean + */ + public static function find_similar($uri) + { + if (empty($uri)) + { + return FALSE; + } + + if (Kohana::$profiling === TRUE) + { + $benchmark = Profiler::start(__CLASS__, __METHOD__); + } + + $uri_slugs = array_merge(array(''), preg_split('/\//', $uri, -1, PREG_SPLIT_NO_EMPTY)); + + $config = Kohana::$config->load('similar'); + $statuses = $config->get('find_in_statuses', array()); + + $slugs = DB::select('id', 'slug') + ->from('pages') + ->where('status_id', 'in', $statuses); + + if (Config::get('page', 'check_date') == Config::YES) + { + $slugs->where('published_on', '<=', DB::expr('NOW()')); + } + + $slugs = $slugs + ->execute() + ->as_array('id', 'slug'); + + $new_slugs = array(); + + foreach ($uri_slugs as $slug) + { + if (in_array($slug, $slugs)) + { + $new_slugs[] = $slug; + continue; + } + + $similar_pages = Text::similar_word($slug, $slugs); + + if (!empty($similar_pages)) + { + $page_id = key($similar_pages); + $page = self::findById($page_id); + $new_slugs[] = $page->slug; + } + } + + if (!$config['return_parent_page'] AND ( count($uri_slugs) != count($new_slugs))) + { + if (isset($benchmark)) + { + Profiler::stop($benchmark); + } + + return FALSE; + } + + $uri = implode('/', $new_slugs); + + $page = self::find($uri); + + if (isset($benchmark)) + { + Profiler::stop($benchmark); + } + + return $page ? $uri : FALSE; + } + + /** + * + * @param string $uri + * @param boolean $include_hidden + * @param Model_Page_Front $parent + * @return \Model_Page_Front + */ + public static function find($uri, $include_hidden = TRUE, Model_Page_Front $parent = NULL) + { + if (Kohana::$profiling === TRUE) + { + $benchmark = Profiler::start(__CLASS__, __METHOD__); + } + + $uri = trim($uri, '/'); + + $urls = preg_split('/\//', $uri, -1, PREG_SPLIT_NO_EMPTY); + + if ($parent === NULL) + { + $urls = array_merge(array(''), $urls); + } + + $url = ''; + + $page = new stdClass; + $page->id = 0; + + foreach ($urls as $page_slug) + { + $url = ltrim($url . '/' . $page_slug, '/'); + + if ($page = self::findBySlug($page_slug, $parent, $include_hidden)) + { + if (!empty($page->behavior_id)) + { + $behavior = Behavior::load($page->behavior_id, $page, $url, $uri); + + if ($behavior !== NULL) + { + $page->_behavior = $behavior; + + if (isset($benchmark)) + { + Profiler::stop($benchmark); + } + + self::$_initial_page = $page; + return $page; + } + } + } + else + { + break; + } + + $parent = $page; + } + + self::$_initial_page = $page; + + if (isset($benchmark)) + { + Profiler::stop($benchmark); + } + + return $page; + } + + /** + * + * @param string $field + * @param string $value + * @param Model_Page_Front $parent + * @param boolean $include_hidden + * @return boolean|\Model_Page_Front + */ + public static function findByField($field, $value, $parent = NULL, $include_hidden = TRUE) + { + $page_cache_id = self::_get_cache_id(array($field, $value), $parent); + + if (isset(self::$_pages_cache[$page_cache_id])) + { + return self::$_pages_cache[$page_cache_id]; + } + + $page_class = get_called_class(); + + $page = DB::select('page.*') + ->from(array('pages', 'page')) + ->where('page.' . $field, '=', $value) + ->where('status_id', 'in', self::get_statuses($include_hidden)) + ->limit(1); + + if (Config::get('page', 'check_date') == Config::YES) + { + $page->where('published_on', '<=', DB::expr('NOW()')); + } + + $parent_id = $parent instanceof Model_Page_Front ? $parent->id : NULL; + if ($parent_id !== NULL) + { + $page->where('parent_id', '=', $parent_id); + } + + $page = $page + ->cache_tags(array('pages')) + ->cached((int) Config::get('cache', 'front_page')) + ->as_object() + ->execute() + ->current(); + + if (!$page) + { + return FALSE; + } + + if ($page->parent_id AND $parent === NULL) + { + $parent = self::findById($page->parent_id); + } + + // hook to be able to redefine the page class with behavior + if ($parent instanceof Model_Page_Front AND !empty($parent->behavior_id)) + { + // will return Page by default (if not found!) + $page_class = Behavior::load_page($parent->behavior_id); + } + + // create the object page + $page = new $page_class($page, $parent); + + self::$_pages_cache[$page_cache_id] = $page; + + return $page; + } + + /** + * + * @param string $slug + * @param Model_Page_Front $parent + * @param boolean $include_hidden + * @return boolean|\Model_Page_Front + */ + public static function findBySlug($slug, $parent = NULL, $include_hidden = TRUE) + { + return self::findByField('slug', $slug, $parent, $include_hidden); + } + + /** + * + * @param integer $id + * @param boolean $include_hidden + * @return \Model_Page_Front|boolean + */ + public static function findById($id, $include_hidden = TRUE) + { + return self::findByField('id', $id, NULL, $include_hidden); + } + + /** + * + * @param integer $level + * @return boolean|\Model_Page_Front + */ + public function parent($level = NULL) + { + if ($level === NULL) + { + return $this->_parent; + } + + if ($level > $this->level) + { + return NULL; + } + else if ($this->level == $level) + { + return $this; + } + else if ($this->parent() instanceof Model_Page_Front) + { + return $this->parent()->parent($level); + } + + return NULL; + } + + /** + * + * @return Model_File_Layout + * @throws Kohana_Exception + */ + public function get_layout_object() + { + if ($this->_layout_object === NULL) + { + $layout_name = $this->layout(); + $this->_layout_object = new Model_File_Layout($layout_name); + } + + if (!$this->_layout_object->is_exists()) + { + if (($found_file = $this->_layout_object->find_file()) !== FALSE) + { + $this->_layout_object = new Model_File_Layout($found_file); + } + else + { + throw new HTTP_Exception_500('Layout file :file not found!', array( + ':file' => $layout_name)); + } + } + + return $this->_layout_object; + } + + /** + * + * @return View_Front + * @throws Kohana_Exception + */ + public function render_layout() + { + $layout = $this->get_layout_object(); + return View_Front::factory($layout->get_file()); + } + + /** + * Mime type + * @return string + */ + public function mime() + { + $mime = File::mime_by_ext(pathinfo($this->url(), PATHINFO_EXTENSION)); + return $mime === FALSE ? 'text/html' : $mime; + } + + /** + * find the layoutId of the page where the layout is set + */ + public function layout() + { + if (!empty($this->layout_file)) + { + return $this->layout_file; + } + else if ($this->parent() instanceof Model_Page_Front) + { + return $this->parent()->layout(); + } + + return NULL; + } + + /** + * Finds the "login needed" status for the page. + * + * @return int Integer corresponding to one of the LOGIN_* constants. + */ + public function needs_login() + { + if ($this->needs_login == Model_Page::LOGIN_INHERIT + AND $this->parent() instanceof Model_Page_Front) + { + return $this->parent()->needs_login(); + } + + return $this->needs_login; + } + + /** + * + * @param boolean $include_hidden + * @return array + */ + public static function get_statuses($include_hidden = FALSE) + { + $statuses = array(Model_Page::STATUS_PASSWORD_PROTECTED, Model_Page::STATUS_PUBLISHED); + + if ($include_hidden) + { + $statuses[] = Model_Page::STATUS_HIDDEN; + } + + return $statuses; + } + + /** + * + * @return \Model_Page_Front + */ + protected function _set_url() + { + $this->url = trim($this->parent()->url . '/' . $this->slug, '/'); + + return $this; + } + + /** + * + * @return string + */ + public function __toString() + { + return (string) $this->id(); + } + + /** + * + * @param string $key + * @return string + */ + public function parse_meta($key, $value = NULL) + { + if ($value === NULL) + { + $value = strtr($this->{$key}, array('\'' => '\\\'', '\\' => '\\\\')); + } + + $fields = array(); + + $found = preg_match_all( + '/(? $field) + { + $patterns[] = '/(?title(); + } + break; + case '..': // Parent page + if ($this->parent() instanceof Model_Page_Front) + { + $parts[] = $this->parent()->{$key}(); + } + break; + default: // Level + if ( + Valid::numeric($field) + AND + $this->level() != $field + AND + $this->parent($field) instanceof Model_Page_Front + ) + { + $parts[] = $this->parent($field)->{$key}(); + } + break; + } + + $param = NULL; + $meta_param = NULL; + $default = NULL; + + if (strpos($field, '|') !== FALSE) + { + list($field, $default) = explode('|', $field, 2); + } + + switch ($field{0}) + { + case '$': + $param = substr($field, 1); + break; + case ':': + $meta_param = substr($field, 1); + break; + } + + if ($param !== NULL) + { + if (strpos($param, 'site.') !== FALSE) + { + $parts[] = Config::get('site', substr($param, 5), $default); + } + else if (strpos($param, 'ctx.') !== FALSE) + { + $parts[] = Context::instance()->get(substr($param, 4)); + } + else if (strpos($param, 'parent.') !== FALSE AND $this->parent() instanceof Model_Page_Front AND method_exists($this->parent(), substr($param, 7))) + { + $parts[] = $this->parent()->{substr($param, 7)}(); + } + else if (method_exists($this, $param)) + { + $parts[] = $this->{$param}(); + } + } + + if ($meta_param !== NULL) + { + $parts[] = Arr::get($this->_meta_params, $meta_param, $default); + } + } + + $value = preg_replace($patterns, $parts, $value); + } + + return $value; + } + + /** + * + * @param integer $level + * @param Breadcrumbs $crumbs + */ + private function _recurse_breadcrumbs($level, &$crumbs) + { + if ($this->parent() instanceof Model_Page_Front + AND $this->level > $level) + { + $this->parent()->_recurse_breadcrumbs($level, $crumbs); + } + + $crumbs->add($this->breadcrumb(), $this->url, FALSE, NULL, array( + 'id' => $this->id + )); + } + + /** + * + * @param array|string $slug + * @param Model_Page_Front $parent + * @return string + */ + final protected static function _get_cache_id($slug, $parent) + { + if (is_array($slug)) + { + $slug = implode('::', $slug); + } + + if ($parent instanceof Model_Page_Front) + { + $parent = $parent->id; + } + else + { + $parent = 0; + } + + return $slug . $parent; + } +} \ No newline at end of file diff --git a/classes/KodiCMS/Model/Page/Role.php b/classes/KodiCMS/Model/Page/Role.php new file mode 100644 index 0000000..7368b03 --- /dev/null +++ b/classes/KodiCMS/Model/Page/Role.php @@ -0,0 +1,11 @@ + + * @link http://kodicms.ru + * @copyright (c) 2012-2014 butschster + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + */ +class KodiCMS_Model_Page_Role extends ORM {} \ No newline at end of file diff --git a/classes/KodiCMS/Model/Page/Sitemap.php b/classes/KodiCMS/Model/Page/Sitemap.php new file mode 100644 index 0000000..e5a0eab --- /dev/null +++ b/classes/KodiCMS/Model/Page/Sitemap.php @@ -0,0 +1,102 @@ + + * @link http://kodicms.ru + * @copyright (c) 2012-2014 butschster + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + */ +class KodiCMS_Model_Page_Sitemap { + + /** + * Хранение карт сайта с разынми параметрами + * @var array + */ + protected static $_sitemap = array(); + + /** + * Получение карты сайта + * + * @param boolean $include_hidden Включить скрытые страницы + * @return Model_Page_Sitemap + */ + public static function get($include_hidden = FALSE) + { + if (Kohana::$profiling) + { + $benchmark = Profiler::start('Sitemap', __METHOD__); + } + + $status = (bool) $include_hidden ? 1 : 0; + + if (!array_key_exists($status, Model_Page_Sitemap::$_sitemap)) + { + $pages = ORM::factory('page') + ->order_by('parent_id', 'asc') + ->order_by('position', 'asc'); + + if ((bool) $include_hidden === FALSE) + { + $pages->where('status_id', 'in', array(Model_Page::STATUS_PASSWORD_PROTECTED, Model_Page::STATUS_PUBLISHED)); + } + + $res_pages = $pages->find_all(); + + $_pages = array(); + foreach ($res_pages as $page) + { + $_pages[$page->id] = $page->as_array(); + $_pages[$page->id]['uri'] = ''; + $_pages[$page->id]['url'] = ''; + $_pages[$page->id]['slug'] = $page->slug; + $_pages[$page->id]['level'] = 0; + $_pages[$page->id]['is_active'] = TRUE; + } + + $pages = array(); + foreach ($_pages as & $page) + { + $pages[$page['parent_id']][] = & $page; + } + + foreach ($_pages as & $page) + { + if (isset($pages[$page['id']])) + { + foreach ($pages[$page['id']] as & $_page) + { + $_page['level'] = $page['level'] + 1; + $_page['parent'] = $page; + + $_page['uri'] = $page['uri'] . '/' . $_page['slug']; + $_page['url'] = URL::frontend($_page['uri']); + $_page['is_active'] = URL::match($_page['uri']); + + if (empty($_page['layout_file'])) + { + $_page['layout_file'] = $page['layout_file']; + } + + if ($_page['is_active']) + { + $page['is_active'] = TRUE; + } + } + + $page['childs'] = $pages[$page['id']]; + } + } + + Model_Page_Sitemap::$_sitemap[$status] = new Sitemap(reset($pages)); + } + + if (isset($benchmark)) + { + Profiler::stop($benchmark); + } + + return clone(Model_Page_Sitemap::$_sitemap[$status]); + } +} \ No newline at end of file diff --git a/classes/Model/API/Page.php b/classes/Model/API/Page.php new file mode 100644 index 0000000..fc4c279 --- /dev/null +++ b/classes/Model/API/Page.php @@ -0,0 +1,3 @@ + + * @link http://kodicms.ru + * @copyright (c) 2012-2014 butschster + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + */ +class Model_Page_Behavior extends Model_Page_Front { + + /** + * + * @param string $part + * @param boolean $inherit + * @param integer $cache_lifetime + */ + public function content($part = 'body', $inherit = FALSE, $cache_lifetime = NULL, array $tags = array()) + { + $method = 'content_' . URL::title($part, '_'); + if (method_exists($this, $method)) + { + return $this->{$method}($cache_lifetime, $tags); + } + + return parent::content($part, $inherit, $cache_lifetime, $tags); + } +} \ No newline at end of file diff --git a/classes/Model/Page/Behavior/Setting.php b/classes/Model/Page/Behavior/Setting.php new file mode 100644 index 0000000..1519706 --- /dev/null +++ b/classes/Model/Page/Behavior/Setting.php @@ -0,0 +1,66 @@ + + * @link http://kodicms.ru + * @copyright (c) 2012-2014 butschster + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + */ +class Model_Page_Behavior_Setting extends ORM { + + protected $_primary_key = 'page_id'; + + + public function filters() + { + return array( + 'data' => array( + array('serialize') + ) + ); + } + + public function set_page($page) + { + $this->_check_page($page); + + return $this + ->set('page_id', $page->id) + ->set('behavior_id', $page->behavior_id); + } + + public function find_by_page($page) + { + $this->_check_page($page); + + return $this->where('page_id', '=', $page->id) + ->find(); + } + + protected function _load_values(array $values) + { + if (!empty($values['data'])) + { + $values['data'] = Kohana::unserialize($values['data']); + } + + parent::_load_values($values); + } + + protected function _check_page($page) + { + if ($page instanceof Model_Page OR $page instanceof Model_Page_Front) + { + if ((int) $page->id == 0) + { + throw new Kohana_Exception('Page must be loaded'); + } + + return TRUE; + } + + throw new Kohana_Exception('Page must be instanced of Model_Page OR Model_Page_Front'); + } +} \ No newline at end of file diff --git a/classes/Model/Page/Front.php b/classes/Model/Page/Front.php new file mode 100644 index 0000000..fddbc6e --- /dev/null +++ b/classes/Model/Page/Front.php @@ -0,0 +1,3 @@ + Config::YES +); diff --git a/config/permissions.php b/config/permissions.php new file mode 100644 index 0000000..10d595e --- /dev/null +++ b/config/permissions.php @@ -0,0 +1,34 @@ + array( + array( + 'action' => 'index', + 'description' => 'View pages' + ), + array( + 'action' => 'add', + 'description' => 'Add pages' + ), + array( + 'action' => 'edit', + 'description' => 'Edit pages' + ), + array( + 'action' => 'sort', + 'description' => 'Sort pages' + ), + array( + 'action' => 'permissions', + 'description' => 'Set page permissions' + ), + array( + 'action' => 'parts', + 'description' => 'Manage parts' + ), + array( + 'action' => 'delete', + 'description' => 'Delete pages' + ), + ) +); \ No newline at end of file diff --git a/config/sitemap.php b/config/sitemap.php new file mode 100644 index 0000000..72ebd37 --- /dev/null +++ b/config/sitemap.php @@ -0,0 +1,11 @@ + 'Pages', + 'url' => Route::get('backend')->uri(array('controller' => 'page')), + 'permissions' => 'page.index', + 'priority' => 100, + 'icon' => 'sitemap' + ), +); diff --git a/i18n/ru.php b/i18n/ru.php new file mode 100644 index 0000000..0625dbe --- /dev/null +++ b/i18n/ru.php @@ -0,0 +1,57 @@ + 'Добавить страницу', + 'Author' => 'Автор', + 'Content' => 'Контент', + 'Slug' => 'Часть URL', + 'Edit page' => 'Редактировать страницу', + 'Find page' => 'Найти страницу', + 'Keywords' => 'Ключевые слова', + 'Last updated by :anchor on :date' + => 'Последнее обновление :anchor, :date', + 'Add child page' => 'Добавить страницу-потомка', + 'Draft' => 'Черновик', + 'Latest' => 'Последнее', + 'Published date' => 'Дата публикации', + 'Breadcrumb' => 'Хлебные крошки', + 'Page' => 'Страница', + 'Page has been deleted!' => 'Страница удалена!', + 'Page has been saved!' => 'Страница сохранена!', + 'Page has not been saved!' => 'Страница не сохранена!', + 'Page Meta information' => 'Мета-информация о странице', + 'Page not found!' => 'Страница не найдена!', + 'Page options' => 'Опции страницы', + 'Behavior routes' => 'Роуты страницы', + 'Page title' => 'Заголовок', + 'Meta title' => 'Мета заголовок', + 'Meta keywords' => 'Мета ключевые слова', + 'Meta description' => 'Мета описание', + 'Pages' => 'Страницы', + 'Published' => 'Опубликована', + 'Reorder' => 'Сортировать', + 'Password protected' => 'Защищена паролем', + 'Protected page' => 'Страница ввода пароля', + 'Page password' => 'Пароль', + 'Page status' => 'Статус', + 'Page permissions' => 'Права на доступ', + 'Page type' => 'Тип', + 'Parent page' => 'Родительская страница', + 'Users roles that can edit page' => 'Страница доступна для ролей', + 'View page' => 'Просмотреть страницу', + 'You do not have permission to access the requested page!' + => 'У вас нет прав для доступа к запрашиваемой странице!', + 'Current layout: :name' => 'Текущий шаблон :name', + 'Show field select' => 'Выбрать из списка', + 'Hide field select' => 'Скрыть список полей', + '--- inherit ( :layout ) ---' => 'Наследовать ( :layout )', + 'Metadata' => 'Метаданные', + '--- Not set ---' => '--- Не указан ---', + 'Use redirect' => 'Включить редирект', + 'Redirect URL' => 'URL для редиректа', + 'Redirect: :url' => 'Редирект: :url', + 'Robots' => 'Поисковые роботы', + 'WYSIWYG' => 'Редактор', +); \ No newline at end of file diff --git a/init.php b/init.php new file mode 100644 index 0000000..e58df39 --- /dev/null +++ b/init.php @@ -0,0 +1,60 @@ +set('url_segments', preg_split('/\//', $request_uri, -1, PREG_SPLIT_NO_EMPTY)); + + if (!empty($server_uri) AND strlen(URL_SUFFIX) > 0 AND Config::get('site', 'check_url_suffix') == Config::YES) + { + $request_uri = $request_uri . URL_SUFFIX; + + if ($server_uri !== $request_uri) + { + $ctx->throw_404(); + } + } +}); + +Observer::observe('frontpage_found', function($page) { + if ($page->is_password_protected()) + { + throw new HTTP_Exception_Front_401('Page protected'); + } +}); + +// Update page last-modified date +Observer::observe('widget_set_location', function($page_ids) { + ORM::factory('page')->set_update_date($page_ids); +}); + +Observer::observe( array('page_add_after_save', 'page_edit_after_save'), function($page) { + if (!empty($page->behavior_id)) + { + $data = Request::current()->post('behavior'); + ORM::factory('Page_Behavior_Setting') + ->find_by_page($page) + ->set_page($page) + ->set('data', $data) + ->save(); + } + else + { + $model = ORM::factory('Page_Behavior_Setting') + ->find_by_page($page); + + if ($model->loaded()) + { + $model->delete(); + } + } +}); + +Observer::observe('modules::after_load', function() { + Behavior::init(); +}); + +Observer::observe(array('controller_before_page_edit', 'controller_before_page_add'), function() { + Assets::js('controller.behavior', ADMIN_RESOURCES . 'js/controller/behavior.js', 'global'); +}); \ No newline at end of file diff --git a/install/dump.sql b/install/dump.sql new file mode 100644 index 0000000..9ee846a --- /dev/null +++ b/install/dump.sql @@ -0,0 +1,2 @@ +INSERT INTO `__TABLE_PREFIX__pages` (`id`, `title`, `slug`, `breadcrumb`, `meta_title`, `meta_keywords`, `meta_description`, `parent_id`, `layout_file`, `behavior_id`, `status_id`, `created_on`, `published_on`, `updated_on`, `created_by_id`, `updated_by_id`, `position`, `needs_login`) VALUES +(1, 'Home', '', 'Home', 'Home', '', '', 0, '', '', 100, '__DATE__', '__DATE__', '__DATE__', 1, 1, 0, 0); \ No newline at end of file diff --git a/install/schema.sql b/install/schema.sql new file mode 100644 index 0000000..aebb582 --- /dev/null +++ b/install/schema.sql @@ -0,0 +1,40 @@ +CREATE TABLE IF NOT EXISTS `__TABLE_PREFIX__page_behavior_settings` ( + `page_id` int(10) unsigned NOT NULL, + `behavior_id` varchar(50) NOT NULL, + `data` text NOT NULL, + PRIMARY KEY (`page_id`), + CONSTRAINT `__TABLE_PREFIX__page_behavior_settings_ibfk_1` FOREIGN KEY (`page_id`) REFERENCES `__TABLE_PREFIX__pages` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `__TABLE_PREFIX__pages` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `title` varchar(255) DEFAULT NULL, + `slug` varchar(100) DEFAULT NULL, + `breadcrumb` varchar(160) DEFAULT '', + `meta_title` varchar(255) DEFAULT '', + `meta_keywords` varchar(255) DEFAULT '', + `meta_description` text, + `robots` varchar(100) DEFAULT 'INDEX, FOLLOW', + `parent_id` int(11) unsigned DEFAULT NULL, + `layout_file` varchar(250) NOT NULL, + `behavior_id` varchar(25) NOT NULL, + `status_id` int(11) unsigned NOT NULL DEFAULT '100', + `created_on` datetime DEFAULT NULL, + `published_on` datetime DEFAULT NULL, + `updated_on` datetime DEFAULT NULL, + `created_by_id` int(11) unsigned DEFAULT NULL, + `updated_by_id` int(11) unsigned DEFAULT NULL, + `position` mediumint(6) unsigned DEFAULT NULL, + `needs_login` tinyint(1) NOT NULL DEFAULT '2', + `password` varchar(50) NOT NULL DEFAULT '', + `use_redirect` tinyint(1) unsigned NOT NULL DEFAULT '0', + `redirect_url` varchar(255) default '', + PRIMARY KEY (`id`), + KEY `parent_id` (`parent_id`), + KEY `created_by_id` (`created_by_id`), + KEY `updated_by_id` (`updated_by_id`), + KEY `slug` (`slug`), + KEY `status_id` (`status_id`), + CONSTRAINT `__TABLE_PREFIX__pages_ibfk_1` FOREIGN KEY (`created_by_id`) REFERENCES `__TABLE_PREFIX__users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `__TABLE_PREFIX__pages_ibfk_2` FOREIGN KEY (`updated_by_id`) REFERENCES `__TABLE_PREFIX__users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 ; \ No newline at end of file diff --git a/media/js/controller/behavior.js b/media/js/controller/behavior.js new file mode 100644 index 0000000..e33d531 --- /dev/null +++ b/media/js/controller/behavior.js @@ -0,0 +1,16 @@ +cms.init.add(['page_edit'], function () { + var loadBehaviorData = function(behaviorId) { + Api.get('behavior.settings', {id: behaviorId, page_id: PAGE_ID}, function(resp) { + $('#behavor_options').html(resp.response); + }); + }; + + var behaviorId = $('select[name="page[behavior_id]"]').change(function() { + var id = $('option:selected', this).val(); + + loadBehaviorData(id); + }).find('option:selected').val(); + + if(behaviorId.length > 0) + loadBehaviorData(behaviorId); +}); \ No newline at end of file diff --git a/media/js/controller/page.js b/media/js/controller/page.js new file mode 100644 index 0000000..20808b3 --- /dev/null +++ b/media/js/controller/page.js @@ -0,0 +1,247 @@ +cms.init.add('page_index', function() { + var cache_key = 'expanded_pages'; + + var expanded_pages = KodiCMS.getStoredValue(cache_key, true); + if(!expanded_pages) expanded_pages = []; + else expanded_pages = decodeURIComponent(expanded_pages).split(','); + + expanded_pages = _.map(expanded_pages, function(num) { return parseInt(num); }); + + var expandedPagesAdd = function(page_id) { + expanded_pages.push(page_id); + KodiCMS.storeValue(cache_key, _.uniq(expanded_pages).join(','), true); + }; + + + var expandedPagesRemove = function(page_id) { + expanded_pages = _.filter(expanded_pages, function(num) { + return num != page_id; + }); + KodiCMS.storeValue(cache_key, _.uniq(expanded_pages).join(','), true); + }; + + $('#page-tree-list').on('click', '.item-expander', function() { + var expander = $(this), + li = expander.closest('li'), + parent_id = li.data('id'); + + if ( ! li.hasClass('item-expanded')) { + var level = parseInt(li.parent().data('level')); + var success_handler = function(html) { + li.append(html); + expander + .addClass('item-expander-expand') + .removeClass('fa-plus') + .addClass('fa-minus'); + + li.addClass('item-expanded'); + + expandedPagesAdd(parent_id); + }; + + // When ajax error of updating information about page position + var error_handler = function(html) { + cms.messages.error('Ajax: Sub pages not loaded!'); + cms.loader.hide(); + }; + + // Sending information about page position to frog + $.ajax({ + url: SITE_URL + ADMIN_DIR_NAME + '/page/children/', + dataType: 'html', + data: { + parent_id: parent_id, + level: level + }, + success: success_handler, + error: error_handler + }); + } + else { + if (expander.hasClass('item-expander-expand')) { + expander + .removeClass('item-expander-expand') + .removeClass('fa-minus') + .addClass('fa-plus'); + + li.find('>ul').hide(); + + expandedPagesRemove(parent_id); + } + else { + expander + .addClass('item-expander-expand') + .removeClass('fa-plus') + .addClass('fa-minus'); + + li.find('>ul').show(); + + expandedPagesAdd(parent_id); + } + } + }); + + + // Reordering + $('#pageMapReorderButton').on('click', function() { + var self = $(this); + + if (self.hasClass('btn-inverse')) { + $('#page-search-list').empty().hide(); + $('#page-tree-header').show(); + self.removeClass('btn-inverse'); + + $.get(SITE_URL + ADMIN_DIR_NAME + '/page/children', {parent_id: 1, level: 0}, function(resp) { + $('#page-tree-list') + .find('ul') + .remove(); + + $('#page-tree-list') + .show() + .find('li') + .append(resp); + + cms.ui.init('icon'); + }, 'html'); + + } else { + self.addClass('btn-inverse'); + $('#page-tree-list').hide(); + $('#page-tree-header').hide(); + + Api.get('pages.sort', {}, function(response) { + $('#page-search-list') + .html(response.response) + .show(); + + $('#nestable').nestable({ + group: 1, + maxDepth: 10, + listNodeName: 'ul', + listClass: 'dd-list list-unstyled', + }).on('change', function(e, el) { + var list = e.length ? e : $(e.target); + var pages = list.nestable('serialize'); + if (!pages.length) + return false; + + Api.post('pages.sort', {'pages': pages}); + }); + }, self.parent()); + } + }); + + $('.form-search').on('submit', function(event) { + var form = $(this); + + if ($('#page-seacrh-input').val() !== '') { + $('#page-tree-list').hide(); + + Api.get('pages.search', form.serialize(), function(resp) { + $('#page-search-list').html(resp.response); + }); + + } else { + $('#page-tree-list').show(); + $('#page-search-list').hide(); + } + + return false; + }); + + var editable_status = { + type: 'select2', + title: __('Page status'), + send: 'always', + highlight: false, + ajaxOptions: { + dataType: 'json' + }, + params: function(params) { + params.page_id = $(this).closest('li').data('id'); + return params; + }, + url: '/api-pages.change_status', + source: PAGE_STATUSES, + select2: { + width: 200, + placeholder: __('Page status') + }, + success: function(response, newValue) { + if(response.response) { + $(this) + .replaceWith($(response.response).editable(editable_status)); + } + } + }; + + $('.editable-status').editable(editable_status); +}); + + +cms.init.add('page_add', function() { + $('body').on('keyup', 'input[name="page[title]"]', function() { + $('input[name="page[breadcrumb]"]') + .add('input[name="page[meta_title]"]') + .val($(this).val()); + }); + + $('.panel-toggler').click(); +}); + +cms.init.add(['page_add', 'page_edit'], function() { + $('body').on('change', 'select[name="page[status_id]"]', function() { + show_password_field($(this).val()); + }); + + $('#page-meta-panel').on('click', ':input', function() { + var $fields = {}; + var $array = ['breadcrumb', 'meta_title', 'meta_keywords', 'meta_description']; + for(i in $array) { + $fields[$array[i]] = $('#page_' + $array[i]).val(); + } + + Api.get('pages.parse_meta', { + page_id: PAGE_ID, + fields: $fields + }, function(response) { + if(response.response) { + for(field in response.response) { + $('#field-' + field + ' .help-block').text(response.response[field]); + } + } + }); + }); + + $('input[name="page[use_redirect]"]').on('change', function() { + show_redirect_field($(this)) + }); + + show_redirect_field($('input[name="page[use_redirect]"]')); + show_password_field($('select[name="page[status_id]"]').val()); + + function show_redirect_field(input) { + var cont = $('#redirect-to-container'), + meta_cont = $('#page-meta-panel-li'); + + if (input.is(':checked')) { + cont.show(); + meta_cont.hide(); + } else { + cont.hide(); + meta_cont.show(); + } + } + + function show_password_field(val) { + var select = $('select[name="page[status_id]"]'); + + if (val == 200) { + select.parent().addClass('well well-small').find('.password-container').removeClass('hidden'); + } else { + select.parent().removeClass('well well-small') + .find('.password-container').addClass('hidden') + .find('input').val(''); + } + } +}); \ No newline at end of file diff --git a/views/page/blocks/behavior.php b/views/page/blocks/behavior.php new file mode 100644 index 0000000..a2d9017 --- /dev/null +++ b/views/page/blocks/behavior.php @@ -0,0 +1,20 @@ +
+ router()->routes() as $route => $params): ?> + +
+
+ get_uri() . HTML::chars($route); ?> +
+
+ +
+ $regex): ?> +
+
+ +
+ +
+
+ +
\ No newline at end of file diff --git a/views/page/blocks/meta.php b/views/page/blocks/meta.php new file mode 100644 index 0000000..364a27e --- /dev/null +++ b/views/page/blocks/meta.php @@ -0,0 +1,26 @@ +
+ +
+ label($field, array('class' => 'control-label col-md-3')); ?> +
+ field($field, array( + 'class' => 'form-control', + 'prefix' => 'page' + )); ?> + +
+
+ + +
+ +
+ +
+ robots); ?> +
+
+
+ + \ No newline at end of file diff --git a/views/page/blocks/search.php b/views/page/blocks/search.php new file mode 100644 index 0000000..3a79e95 --- /dev/null +++ b/views/page/blocks/search.php @@ -0,0 +1,19 @@ + 'form-inline form-search')); + echo Form::hidden('token', Security::token()); +?> +
+ 'page-seacrh-input', + 'class' => 'form-control no-margin-b', + 'placeholder' => __('Find page') + )); ?> + +
+ UI::icon('search'), + 'class' => 'btn-default' + )); ?> +
+
+ \ No newline at end of file diff --git a/views/page/blocks/settings.php b/views/page/blocks/settings.php new file mode 100644 index 0000000..468d722 --- /dev/null +++ b/views/page/blocks/settings.php @@ -0,0 +1,119 @@ + + +layout(); +$layout_link = ''; + +if ((ACL::check('layout.edit') OR ACL::check('layout.view')) AND ! empty($layout_name)) +{ + $layout_link = HTML::anchor(Route::get('backend')->uri(array( + 'controller' => 'layout', + 'action' => 'edit', + 'id' => $layout_name + )), $layout_name, array( + 'class' => 'popup fancybox.iframe' + )); +} +?> + +
+ id != 1): ?> +
+ label('parent_id', array('class' => 'control-label col-md-3')); ?> +
+ field('parent_id', array( + 'prefix' => 'page' + )); ?> +
+
+ + +
+ label('layout_file', array('class' => 'control-label col-md-3')); ?> + +
+ field('layout_file', array( + 'prefix' => 'page' + )); ?> +
+ +
+ + $layout_link))); ?> + + + +
+ +
+ +
+ +
+ label('behavior_id', array('class' => 'control-label col-md-3')); ?> +
+ field('behavior_id', array( + 'prefix' => 'page' + )); ?> +
+
+
+ +
+ + id != 1): ?> +
+ label('status_id', array('class' => 'control-label col-md-3')); ?> +
+ field('status_id', array( + 'prefix' => 'page' + )); ?> + + +
+
+ +
+ + + id != 1): ?> +
+ label('published_on', array('class' => 'control-label col-md-3')); ?> +
+ field('published_on', array( + 'prefix' => 'page', + 'class' => 'form-control datetimepicker' + )); ?> +
+
+
+ + + +
+ label('needs_login', array('class' => 'control-label col-md-3')); ?> +
+ field('needs_login', array( + 'prefix' => 'page' + )); ?> +
+
+ +
+ + +
+ label('page_permissions', array('class' => 'panel-title')); ?> +
+
+ +
+ + \ No newline at end of file diff --git a/views/page/children.php b/views/page/children.php new file mode 100644 index 0000000..23597f6 --- /dev/null +++ b/views/page/children.php @@ -0,0 +1,87 @@ + + + \ No newline at end of file diff --git a/views/page/edit.php b/views/page/edit.php new file mode 100644 index 0000000..0a2f90b --- /dev/null +++ b/views/page/edit.php @@ -0,0 +1,133 @@ +
+ uri(array('controller' => 'page', 'action' => $action, 'id' => $action == 'add' ? $parent_id : $page->id)), array( + 'method' => Request::POST + )); ?> + + + + + + +
+ +
+
+
+
+ label('title', array('class' => 'control-label col-md-2')); ?> +
+ field('title', array( + 'class' => 'form-control slug-generator', + 'prefix' => 'page' + )); ?> +
+
+ + id != 1): ?> +
+
+ label('slug', array('class' => 'control-label col-md-2')); ?> +
+ field('slug', array( + 'class' => 'form-control slug', + 'prefix' => 'page' + )); ?> +
+
+
+
+
+ use_redirect, array( + 'id' => 'page_use_redirect' + )); ?> + label('use_redirect'); ?> +
+
+
+ +
+ label('redirect_url', array('class' => 'control-label col-md-2')); ?> +
+ field('redirect_url', array( + 'class' => 'form-control', + 'prefix' => 'page' + )); ?> +
+
+ +
+
+
+
+ + + +
+ loaded()): ?> + + updated_on)): ?> + HTML::anchor(Route::get('backend')->uri(array( + 'controller' => 'users', + 'action' => 'edit', + 'id' => $page->updator->id + )), $page->updator->username), + ':date' => Date::format($page->updated_on, 'D, j F Y'))), 'important'); ?> + + + get_frontend_url(), UI::label(UI::icon('globe') . ' ' . __('View page')), array( + 'class' => 'item-preview', 'target' => '_blank' + )); ?> + +
+
+ +
+ $page, + 'action' => $action + )); ?> +
+ + loaded() AND ($page->behavior() instanceof Behavior_Abstract)): ?> +
+ $page, + 'behavior' => $page->behavior() + )); ?> +
+ + +
+ $page, + 'permissions' => $permissions, + 'action' => $action, + 'page_permissions' => $page_permissions + )); ?> +
+
+ + +
+ +
\ No newline at end of file diff --git a/views/page/index.php b/views/page/index.php new file mode 100644 index 0000000..f389ae7 --- /dev/null +++ b/views/page/index.php @@ -0,0 +1,66 @@ +
+
+ + 'btn-default', + 'href' => Route::get('backend')->uri(array('controller' => 'page', 'action' => 'add')), + 'icon' => UI::icon('plus'), + 'data-hotkeys' => 'ctrl+a' + )); ?> + + + + 'pageMapReorderButton', + 'class' => 'btn-primary btn-sm', + 'icon' => UI::icon('sort'), + 'data-hotkeys' => 'ctrl+s' + )); ?> + + + +
+ + + + + + + + + + +
+
    +
  • +
    +
    + get_permissions())): ?> + + title; ?> + + get_url(), $page->title, array('data-icon' => 'home fa-lg fa-fw')); ?> + + + get_public_anchor(); ?> +
    +
    + + UI::icon('plus'), + 'href' => Route::get('backend')->uri(array('controller' => 'page', 'action' => 'add')), + 'class' => 'btn-default btn-xs')); ?> + +
    +
    +
    + +
  • +
+ +
    + +
    +
    \ No newline at end of file diff --git a/views/page/sort.php b/views/page/sort.php new file mode 100644 index 0000000..64d5a89 --- /dev/null +++ b/views/page/sort.php @@ -0,0 +1,38 @@ +
    + +
      +
    • +
      + + +
      +
    • +
    +
    + +
    + +
    +'; + foreach ($childs as $page) + { + $data .= (string) View::factory('page/sortitem', array( + 'page' => $page, + 'childs' => !empty($page['childs']) ? recurse_sort_pages($page['childs']) : '' + )); + } + + $data .= ''; + + return $data; +} + +?> \ No newline at end of file diff --git a/views/page/sortitem.php b/views/page/sortitem.php new file mode 100644 index 0000000..446469e --- /dev/null +++ b/views/page/sortitem.php @@ -0,0 +1,12 @@ +
  • +
    + + + + +    + +
    + + +
  • \ No newline at end of file