diff --git a/_extensions/s2_blog/_include/Page/Day.php b/_extensions/s2_blog/_include/Page/Day.php index 60aa0ed9..2d888e60 100644 --- a/_extensions/s2_blog/_include/Page/Day.php +++ b/_extensions/s2_blog/_include/Page/Day.php @@ -19,7 +19,7 @@ public function body (Request $request): ?Response { $params = $request->attributes->all(); - if ($this->inTemplate('')) { + if ($this->hasPlaceholder('')) { $this->page['s2_blog_calendar'] = Lib::calendar($params['year'], $params['month'], $params['day']); } diff --git a/_extensions/s2_blog/_include/Page/Favorite.php b/_extensions/s2_blog/_include/Page/Favorite.php index 66e24923..005c9bdc 100644 --- a/_extensions/s2_blog/_include/Page/Favorite.php +++ b/_extensions/s2_blog/_include/Page/Favorite.php @@ -25,7 +25,7 @@ public function body (Request $request): ?Response $this->ensureTemplateIsLoaded(); - if ($this->inTemplate('')) + if ($this->hasPlaceholder('')) $this->page['s2_blog_calendar'] = Lib::calendar(date('Y'), date('m'), '0'); $this->favorite_posts(); diff --git a/_extensions/s2_blog/_include/Page/HTML.php b/_extensions/s2_blog/_include/Page/HTML.php index edd6550d..44cad437 100644 --- a/_extensions/s2_blog/_include/Page/HTML.php +++ b/_extensions/s2_blog/_include/Page/HTML.php @@ -49,7 +49,7 @@ public function handle(Request $request): ?Response $this->page['head_title'] = empty($this->page['head_title']) ? S2_BLOG_TITLE : $this->page['head_title'] . ' - ' . S2_BLOG_TITLE; - if (!isset($this->page['s2_blog_navigation']) && $this->inTemplate('')) { + if (!isset($this->page['s2_blog_navigation']) && $this->hasPlaceholder('')) { $this->page['s2_blog_navigation'] = $this->blog_navigation(); } diff --git a/_extensions/s2_blog/_include/Page/Main.php b/_extensions/s2_blog/_include/Page/Main.php index 1e554fdf..9c3923bc 100644 --- a/_extensions/s2_blog/_include/Page/Main.php +++ b/_extensions/s2_blog/_include/Page/Main.php @@ -28,7 +28,7 @@ public function body (Request $request): ?Response $this->template_id = $s2_blog_skip ? 'blog.php' : 'blog_main.php'; - if ($this->inTemplate('')) + if ($this->hasPlaceholder('')) $this->page['s2_blog_calendar'] = Lib::calendar(date('Y'), date('m'), '0'); $this->last_posts($s2_blog_skip); diff --git a/_extensions/s2_blog/_include/Page/Month.php b/_extensions/s2_blog/_include/Page/Month.php index 4cd0fc94..91e16146 100644 --- a/_extensions/s2_blog/_include/Page/Month.php +++ b/_extensions/s2_blog/_include/Page/Month.php @@ -20,7 +20,7 @@ public function body (Request $request): ?Response { $params = $request->attributes->all(); - if ($this->inTemplate('')) + if ($this->hasPlaceholder('')) $this->page['s2_blog_calendar'] = Lib::calendar($params['year'], $params['month'], 0); $this->page['title'] = ''; diff --git a/_extensions/s2_blog/_include/Page/Post.php b/_extensions/s2_blog/_include/Page/Post.php index 8a34694a..521bcd8f 100644 --- a/_extensions/s2_blog/_include/Page/Post.php +++ b/_extensions/s2_blog/_include/Page/Post.php @@ -22,7 +22,7 @@ public function body (Request $request): ?Response { $params = $request->attributes->all(); - if ($this->inTemplate('')) + if ($this->hasPlaceholder('')) $this->page['s2_blog_calendar'] = Lib::calendar($params['year'], $params['month'], $params['day'], $params['url']); $this->page['title'] = ''; @@ -95,7 +95,7 @@ private function get_post($year, $month, $day, $url) $this->page['canonical_path'] = S2_BLOG_PATH . date('Y/m/d/', $row['create_time']) . $row['url']; - $is_back_forward = $this->inTemplate(''); + $is_back_forward = $this->hasPlaceholder(''); $queries = []; if ($label) { @@ -180,7 +180,7 @@ private function get_post($year, $month, $day, $url) ]; $this->page['commented'] = $row['commented']; - if ($row['commented'] && S2_SHOW_COMMENTS && $this->inTemplate('')) + if ($row['commented'] && S2_SHOW_COMMENTS && $this->hasPlaceholder('')) $this->page['comments'] = $this->get_comments($post_id); $row['time'] = s2_date_time($row['create_time']); @@ -191,7 +191,7 @@ private function get_post($year, $month, $day, $url) $this->page['text'] = $this->renderPartial('post', $row); - if ($this->inTemplate('')) { + if ($this->hasPlaceholder('')) { /** @var RecommendationProvider $recommendationProvider */ $recommendationProvider = \Container::get(RecommendationProvider::class); global $request_uri; diff --git a/_extensions/s2_blog/_include/Page/Tag.php b/_extensions/s2_blog/_include/Page/Tag.php index 490e85b7..04f852eb 100644 --- a/_extensions/s2_blog/_include/Page/Tag.php +++ b/_extensions/s2_blog/_include/Page/Tag.php @@ -23,7 +23,7 @@ public function body (Request $request): ?Response { $params = $request->attributes->all(); - if ($this->inTemplate('')) { + if ($this->hasPlaceholder('')) { $this->page['s2_blog_calendar'] = Lib::calendar(date('Y'), date('m'), '0'); } diff --git a/_extensions/s2_blog/_include/Page/Tags.php b/_extensions/s2_blog/_include/Page/Tags.php index a138d196..4092db64 100644 --- a/_extensions/s2_blog/_include/Page/Tags.php +++ b/_extensions/s2_blog/_include/Page/Tags.php @@ -33,7 +33,7 @@ public function body (Request $request): ?Response return new RedirectResponse(s2_link($request->getPathInfo() . '/'), Response::HTTP_MOVED_PERMANENTLY); } - if ($this->inTemplate('')) + if ($this->hasPlaceholder('')) $this->page['s2_blog_calendar'] = Lib::calendar(date('Y'), date('m'), '0'); // The list of tags diff --git a/_extensions/s2_blog/_include/Page/Year.php b/_extensions/s2_blog/_include/Page/Year.php index cfca2907..22bfd513 100644 --- a/_extensions/s2_blog/_include/Page/Year.php +++ b/_extensions/s2_blog/_include/Page/Year.php @@ -20,7 +20,7 @@ public function body (Request $request): ?Response { $params = $request->attributes->all(); - if ($this->inTemplate('')) + if ($this->hasPlaceholder('')) $this->page['s2_blog_calendar'] = Lib::calendar($params['year'], '', 0); $this->page['title'] = ''; diff --git a/_extensions/s2_blog/hooks/fn_s2_parse_page_url_end.php b/_extensions/s2_blog/hooks/fn_s2_parse_page_url_end.php index 9ed4b79a..893413e3 100644 --- a/_extensions/s2_blog/hooks/fn_s2_parse_page_url_end.php +++ b/_extensions/s2_blog/hooks/fn_s2_parse_page_url_end.php @@ -2,29 +2,32 @@ /** * Hook fn_s2_parse_page_url_end * - * @copyright (C) 2023 Roman Parpalak - * @license http://www.gnu.org/licenses/gpl.html GPL version 2 or higher + * @copyright 2023-2024 Roman Parpalak + * @license MIT * @package s2_blog + * + * @var int $articleId + * @var \S2\Cms\Controller\PageCommon $this + * @var \S2\Cms\Template\HtmlTemplate $template */ - if (!defined('S2_ROOT')) { - die; +if (!defined('S2_ROOT')) { + die; } -if ($this->inTemplate('')) -{ - Lang::load('s2_blog', function () - { - if (file_exists(S2_ROOT.'/_extensions/s2_blog'.'/lang/'.S2_LANGUAGE.'.php')) - return require S2_ROOT.'/_extensions/s2_blog'.'/lang/'.S2_LANGUAGE.'.php'; - else - return require S2_ROOT.'/_extensions/s2_blog'.'/lang/English.php'; - }); +if ($template->hasPlaceholder('')) { + Lang::load('s2_blog', static function () { + if (file_exists(S2_ROOT . '/_extensions/s2_blog' . '/lang/' . S2_LANGUAGE . '.php')) { + return require S2_ROOT . '/_extensions/s2_blog' . '/lang/' . S2_LANGUAGE . '.php'; + } + + return require S2_ROOT . '/_extensions/s2_blog' . '/lang/English.php'; + }); - $s2_blog_tags = s2_extensions\s2_blog\Placeholder::blog_tags($id); - $page['s2_blog_tags'] = empty($s2_blog_tags) ? '' : $this->renderPartial('menu_block', array( - 'title' => Lang::get('See in blog', 's2_blog'), - 'menu' => $s2_blog_tags, - 'class' => 's2_blog_tags', - )); + $s2_blog_tags = s2_extensions\s2_blog\Placeholder::blog_tags($articleId); + $template->putInPlaceholder('s2_blog_tags', empty($s2_blog_tags) ? '' : $this->viewer->render('menu_block', [ + 'title' => Lang::get('See in blog', 's2_blog'), + 'menu' => $s2_blog_tags, + 'class' => 's2_blog_tags', + ])); } diff --git a/_extensions/s2_blog/hooks/idx_pre_get_queries.php b/_extensions/s2_blog/hooks/idx_pre_get_queries.php index f7910662..e614ddc5 100644 --- a/_extensions/s2_blog/hooks/idx_pre_get_queries.php +++ b/_extensions/s2_blog/hooks/idx_pre_get_queries.php @@ -14,7 +14,7 @@ $s2_blog_placehoders = array(); foreach (array('s2_blog_last_comments', 's2_blog_last_discussions', 's2_blog_last_post') as $s2_blog_placehoder) - if ($this->inTemplate('')) + if ($this->hasPlaceholder('')) $s2_blog_placehoders[$s2_blog_placehoder] = 1; if (!empty($s2_blog_placehoders)) @@ -29,7 +29,7 @@ if (isset($s2_blog_placehoders['s2_blog_last_comments'])) { $s2_blog_recent_comments = s2_extensions\s2_blog\Placeholder::recent_comments(); - $replace[''] = empty($s2_blog_recent_comments) ? '' : $this->renderPartial('menu_comments', array( + $replace[''] = empty($s2_blog_recent_comments) ? '' : $this->viewer->render('menu_comments', array( 'title' => Lang::get('Last comments', 's2_blog'), 'menu' => $s2_blog_recent_comments, )); @@ -37,7 +37,7 @@ if (isset($s2_blog_placehoders['s2_blog_last_discussions'])) { $s2_blog_last_discussions = s2_extensions\s2_blog\Placeholder::recent_discussions(); - $replace[''] = empty($s2_blog_last_discussions) ? '' : $this->renderPartial('menu_block', array( + $replace[''] = empty($s2_blog_last_discussions) ? '' : $this->viewer->render('menu_block', array( 'title' => Lang::get('Last discussions', 's2_blog'), 'menu' => $s2_blog_last_discussions, 'class' => 's2_blog_last_discussions', @@ -52,7 +52,7 @@ unset($s2_blog_post); $replace[''] = implode('', $s2_blog_data); } -$replace[''] = $page['s2_blog_tags'] ?? ''; -$replace[''] = $page['s2_blog_calendar'] ?? ''; -$replace[''] = $page['s2_blog_navigation'] ?? ''; -$replace[''] = $page['s2_blog_back_forward'] ?? ''; +$replace[''] = $this->page['s2_blog_tags'] ?? ''; +$replace[''] = $this->page['s2_blog_calendar'] ?? ''; +$replace[''] = $this->page['s2_blog_navigation'] ?? ''; +$replace[''] = $this->page['s2_blog_back_forward'] ?? ''; diff --git a/_include/Page/Abstract.php b/_include/Page/Abstract.php index 6594ef84..6f706268 100644 --- a/_include/Page/Abstract.php +++ b/_include/Page/Abstract.php @@ -51,7 +51,7 @@ public function ensureTemplateIsLoaded(): void } } - public function inTemplate($placeholder): bool + public function hasPlaceholder($placeholder): bool { $this->ensureTemplateIsLoaded(); diff --git a/_include/Page/Common.php b/_include/Page/Common.php deleted file mode 100644 index 37ee0764..00000000 --- a/_include/Page/Common.php +++ /dev/null @@ -1,596 +0,0 @@ -parse_page_url($request->getPathInfo()); - return $result ?? parent::handle($request); - } - - private function tagged_articles ($id) - { - /** @var DbLayer $s2_db */ - $s2_db = \Container::get(DbLayer::class); - - $query = array( - 'SELECT' => 't.tag_id as tag_id, name, t.url as url', - 'FROM' => 'tags AS t', - 'JOINS' => array( - array( - 'INNER JOIN' => 'article_tag AS atg', - 'ON' => 'atg.tag_id = t.tag_id' - ) - ), - 'WHERE' => 'atg.article_id = '.$id - ); - ($hook = s2_hook('fn_tagged_articles_pre_get_tags_qr')) ? eval($hook) : null; - $result = $s2_db->buildAndQuery($query); - - $tag_names = $tag_urls = array(); - while ($row = $s2_db->fetchAssoc($result)) - { - ($hook = s2_hook('fn_tagged_articles_loop_get_tags')) ? eval($hook) : null; - - $tag_names[$row['tag_id']] = $row['name']; - $tag_urls[$row['tag_id']] = $row['url']; - } - - if (empty($tag_urls)) - return ''; - - $subquery = array( - 'SELECT' => '1', - 'FROM' => 'articles AS a1', - 'WHERE' => 'a1.parent_id = atg.article_id AND a1.published = 1', - 'LIMIT' => '1' - ); - $raw_query1 = $s2_db->build($subquery); - - $query = array( - 'SELECT' => 'title, tag_id, parent_id, url, a.id AS id, ('.$raw_query1.') IS NOT NULL AS children_exist', - 'FROM' => 'articles AS a', - 'JOINS' => array( - array( - 'INNER JOIN' => 'article_tag AS atg', - 'ON' => 'a.id = atg.article_id' - ), - ), - 'WHERE' => 'atg.tag_id IN ('.implode(', ', array_keys($tag_names)).') AND a.published = 1' -// 'ORDER BY' => 'create_time' // no temp table is created but order by ID is almost the same - ); - ($hook = s2_hook('fn_tagged_articles_pre_get_articles_qr')) ? eval($hook) : null; - $result = $s2_db->buildAndQuery($query); - - // Build article lists that have the same tags as our article - - $create_tag_list = false; - - $titles = $parent_ids = $urls = $tag_ids = $original_ids = array(); - while ($row = $s2_db->fetchAssoc($result)) - { - ($hook = s2_hook('fn_tagged_articles_get_articles_loop')) ? eval($hook) : null; - - if ($id <> $row['id']) - $create_tag_list = true; - $titles[] = $row['title']; - $parent_ids[] = $row['parent_id']; - $urls[] = urlencode($row['url']).(S2_USE_HIERARCHY && $row['children_exist'] ? '/' : ''); - $tag_ids[] = $row['tag_id']; - $original_ids[] = $row['id']; - } - - if (empty($urls)) - return ''; - - if ($create_tag_list) - $urls = Model::get_group_url($parent_ids, $urls); - - // Sorting all obtained article links into groups by each tag - $art_by_tags = array(); - - foreach ($urls as $k => $url) - $art_by_tags[$tag_ids[$k]][] = array( - 'title' => $titles[$k], - 'link' => $url, - 'is_current' => $original_ids[$k] == $id, - ); - - ($hook = s2_hook('fn_tagged_articles_pre_art_by_tags_merge')) ? eval($hook) : null; - - // Remove tags that have only one article - foreach ($art_by_tags as $tag_id => $title_array) - if (count($title_array) <= 1) - unset($art_by_tags[$tag_id]); - - $output = array(); - ($hook = s2_hook('fn_tagged_articles_pre_menu_merge')) ? eval($hook) : null; - foreach ($art_by_tags as $tag_id => $articles) - $output[] = $this->renderPartial('menu_block', array( - 'title' => sprintf(Lang::get('With this tag'), ''.$tag_names[$tag_id].''), - 'menu' => $articles, - 'class' => 'article_tags', - )); - - ($hook = s2_hook('fn_tagged_articles_end')) ? eval($hook) : null; - return !empty($output) ? implode("\n", $output) : ''; - } - - private function get_tags ($id) - { - /** @var DbLayer $s2_db */ - $s2_db = \Container::get(DbLayer::class); - - $query = array( - 'SELECT' => 'name, url', - 'FROM' => 'tags AS t', - 'JOINS' => array( - array( - 'INNER JOIN' => 'article_tag AS at', - 'ON' => 'at.tag_id = t.tag_id' - ) - ), - 'WHERE' => 'at.article_id = '.$id - ); - ($hook = s2_hook('fn_tags_list_pre_get_tags_qr')) ? eval($hook) : null; - $result = $s2_db->buildAndQuery($query); - - $tags = array(); - while ($row = $s2_db->fetchAssoc($result)) - $tags[] = array( - 'title' => $row['name'], - 'link' => s2_link('/'.S2_TAGS_URL.'/'.urlencode($row['url']).'/'), - ); - - if (empty($tags)) - return ''; - - return $this->renderPartial('tags', array( - 'title' => Lang::get('Tags'), - 'tags' => $tags, - )); - } - - - // Processes site pages - private function parse_page_url ($request_uri): ?Response - { - /** @var DbLayer $s2_db */ - $s2_db = \Container::get(DbLayer::class); - - $page = &$this->page; - - $request_array = explode('/', $request_uri); // []/[dir1]/[dir2]/[dir3]/[file1] - - // Correcting trailing slash and the rest of URL - if (!S2_USE_HIERARCHY && count($request_array) > 2) { - return new RedirectResponse(s2_link('/'.$request_array[1]), Response::HTTP_MOVED_PERMANENTLY); - } - - $was_end_slash = str_ends_with($request_uri, '/'); - - $bread_crumbs = array(); - - $parent_path = ''; - $parent_id = Model::ROOT_ID; - $parent_num = count($request_array) - 1 - (int) $was_end_slash; - - $this->template_id = ''; - - ($hook = s2_hook('fn_s2_parse_page_url_start')) ? eval($hook) : null; - - if (S2_USE_HIERARCHY) - { - $urls = array_unique($request_array); - $urls = array_map(array($s2_db, 'escape'), $urls); - - $query = array( - 'SELECT' => 'id, parent_id, title, template', - 'FROM' => 'articles', - 'WHERE' => 'url IN (\''.implode('\', \'', $urls).'\') AND published=1' - ); - ($hook = s2_hook('fn_s2_parse_page_url_loop_pre_get_parents_query')) ? eval($hook) : null; - $result = $s2_db->buildAndQuery($query); - - $nodes = $s2_db->fetchAssocAll($result); - - // Walking through the page parents - // 1. We ensure all of them are published - // 2. We build "bread crumbs" - // 3. We determine the template of the page - for ($i = 0; $i < $parent_num; $i++) - { - $parent_path .= urlencode($request_array[$i]).'/'; - - $cur_node = array(); - $found_node_num = 0; - foreach ($nodes as $node) - { - if ($node['parent_id'] == $parent_id) - { - $cur_node = $node; - $found_node_num++; - } - } - - if ($found_node_num === 0) { - throw new NotFoundException(); - } - if ($found_node_num > 1) - error(Lang::get('DB repeat items') . (defined('S2_DEBUG') ? ' (parent_id='.$parent_id.', url="'.s2_htmlencode($request_array[$i]).'")' : '')); - - ($hook = s2_hook('fn_s2_parse_page_url_loop_pre_build_stuff')) ? eval($hook) : null; - - $parent_id = $cur_node['id']; - if ($cur_node['template'] != '') - $this->template_id = $cur_node['template']; - - $bread_crumbs[] = array( - 'link' => s2_link($parent_path), - 'title' => $cur_node['title'] - ); - } - } - else - { - $parent_path = '/'; - $i = 1; - } - // Path to the requested page (without trailing slash) - $current_path = $parent_path.urlencode($request_array[$i]); - - $subquery = array( - 'SELECT' => '1', - 'FROM' => 'articles AS a1', - 'WHERE' => 'a1.parent_id = a.id AND a1.published = 1', - 'LIMIT' => '1' - ); - $raw_query_children = $s2_db->build($subquery); - - $subquery = array( - 'SELECT' => 'u.name', - 'FROM' => 'users AS u', - 'WHERE' => 'u.id = a.user_id' - ); - $raw_query_author = $s2_db->build($subquery); - - $query = array( - 'SELECT' => 'a.id, a.title, a.meta_keys as meta_keywords, a.meta_desc as meta_description, a.excerpt as excerpt, a.pagetext as text, a.create_time as date, favorite, commented, template, ('.$raw_query_children.') IS NOT NULL AS children_exist, ('.$raw_query_author.') AS author', - 'FROM' => 'articles AS a', - 'WHERE' => 'url=\''.$s2_db->escape($request_array[$i]).'\''.(S2_USE_HIERARCHY ? ' AND parent_id='.$parent_id : '').' AND published=1' - ); - ($hook = s2_hook('fn_s2_parse_page_url_pre_get_page')) ? eval($hook) : null; - $result = $s2_db->buildAndQuery($query); - - $page = $s2_db->fetchAssoc($result); - - // Error handling - if (!$page) { - throw new NotFoundException(); - } - - if ($s2_db->fetchAssoc($result)) - error(Lang::get('DB repeat items') . (defined('S2_DEBUG') ? ' (parent_id='.$parent_id.', url="'.$request_array[$i].'")' : '')); - - if ($page['template']) - $this->template_id = $page['template']; - - if (!$this->template_id) - { - if (S2_USE_HIERARCHY) - { - $bread_crumbs[] = array( - 'link' => s2_link($parent_path), - 'title' => $page['title'], - ); - - error(sprintf(Lang::get('Error no template'), implode('
', array_map(function ($a) - { - return ''.s2_htmlencode($a['title']).''; - }, $bread_crumbs)))); - } - else - error(Lang::get('Error no template flat')); - } - - if (S2_USE_HIERARCHY && $parent_num && $page['children_exist'] != $was_end_slash) { - return new RedirectResponse(s2_link($current_path . (!$was_end_slash ? '/' : '')), Response::HTTP_MOVED_PERMANENTLY); - } - - $page['canonical_path'] = $current_path.($was_end_slash ? '/' : ''); - - $id = $page['id']; - $bread_crumbs[] = array( - 'title' => $page['title'] - ); - $page['title'] = s2_htmlencode($page['title']); - - if (!empty($page['author'])) - $page['author'] = s2_htmlencode($page['author']); - - if (S2_USE_HIERARCHY) - { - $page['path'] = $bread_crumbs; - - $page['link_navigation']['top'] = s2_link('/'); - if (count($bread_crumbs) > 1) - { - $page['link_navigation']['up'] = s2_link($parent_path); - $page['section_link'] = ''.$bread_crumbs[count($bread_crumbs) - 2]['title'].''; - } - } - - ($hook = s2_hook('fn_s2_parse_page_url_pre_get_tpl')) ? eval($hook) : null; - - // Dealing with sections, subsections, neighbours - if (S2_USE_HIERARCHY && $page['children_exist'] && ($this->inTemplate('') || $this->inTemplate('')|| $this->inTemplate('') || $this->inTemplate(''))) - { - // It's a section. We have to fetch subsections and articles. - - // Fetching children - $subquery = array( - 'SELECT' => 'a1.id', - 'FROM' => 'articles AS a1', - 'WHERE' => 'a1.parent_id = a.id AND a1.published = 1', - 'LIMIT' => '1' - ); - $raw_query1 = $s2_db->build($subquery); - - $sort_order = SORT_DESC; - $query = array( - 'SELECT' => 'title, url, ('.$raw_query1.') IS NOT NULL AS children_exist, id, excerpt, favorite, create_time, parent_id', - 'FROM' => 'articles AS a', - 'WHERE' => 'parent_id = '.$id.' AND published = 1', - 'ORDER BY' => 'priority' - ); - ($hook = s2_hook('fn_s2_parse_page_url_pre_get_children_qr')) ? eval($hook) : null; - $result = $s2_db->buildAndQuery($query); - - $subarticles = $subsections = $sort_array = array(); - while ($row = $s2_db->fetchAssoc($result)) - { - if ($row['children_exist']) - { - // The child is a subsection - $item = array( - 'id' => $row['id'], - 'title' => $row['title'], - 'link' => s2_link($current_path . '/' . urlencode($row['url']) . '/'), - 'date' => s2_date($row['create_time']), - 'excerpt' => $row['excerpt'], - 'favorite' => $row['favorite'], - ); - - ($hook = s2_hook('pc_parse_page_url_add_subsection')) ? eval($hook) : null; - - $subsections[] = $item; - } - else - { - // The child is an article - $item = array( - 'id' => $row['id'], - 'title' => $row['title'], - 'link' => s2_link($current_path . '/' . urlencode($row['url'])), - 'date' => s2_date($row['create_time']), - 'excerpt' => $row['excerpt'], - 'favorite' => $row['favorite'], - ); - $sort_field = $row['create_time']; - - ($hook = s2_hook('pc_parse_page_url_add_subarticle')) ? eval($hook) : null; - - $subarticles[] = $item; - $sort_array[] = $sort_field; - } - } - - $sections_text = ''; - if (!empty($subsections)) - { - // There are subsections in the section - $page['menu_subsections'] = $this->renderPartial('menu_block', array( - 'title' => Lang::get('Subsections'), - 'menu' => $subsections, - 'class' => 'menu_subsections', - )); - - foreach ($subsections as $item) - $sections_text .= $this->renderPartial('subarticles_item', $item); - } - - $articles_text = ''; - if (!empty($subarticles)) - { - // There are articles in the section - $page['menu_children'] = $this->renderPartial('menu_block', array( - 'title' => Lang::get('In this section'), - 'menu' => $subarticles, - 'class' => 'menu_children', - )); - - ($sort_order == SORT_DESC) ? arsort($sort_array) : asort($sort_array); - - if (S2_MAX_ITEMS) - { - // Paging navigation - $page_num = isset($_GET['p']) ? intval($_GET['p']) - 1 : 0; - if ($page_num < 0) - $page_num = 0; - - $start = $page_num * S2_MAX_ITEMS; - if ($start >= count($subarticles)) - $page_num = $start = 0; - - $total_pages = ceil(1.0 * count($subarticles) / S2_MAX_ITEMS); - - $link_nav = array(); - $paging = s2_paging($page_num + 1, $total_pages, s2_link(str_replace('%', '%%', $current_path.'/'), array('p=%d')), $link_nav)."\n"; - foreach ($link_nav as $rel => $href) - $page['link_navigation'][$rel] = $href; - - $i = 0; - foreach ($sort_array as $index => $value) - { - if ($i < $start || $i >= $start + S2_MAX_ITEMS) - unset($sort_array[$index]); - $i++; - } - } - - foreach ($sort_array as $index => $value) - $articles_text .= $this->renderPartial('subarticles_item', $subarticles[$index]); - - if (S2_MAX_ITEMS) - $articles_text .= $paging; - } - - $page['subcontent'] = $this->renderPartial('subarticles', array( - 'articles' => $articles_text, - 'sections' => $sections_text, - )); - } - - if (S2_USE_HIERARCHY && !$page['children_exist'] && ($this->inTemplate('') || $this->inTemplate('') || $this->inTemplate(''))) - { - // It's an article. We have to fetch other articles in the parent section - - // Fetching "siblings" - $subquery = array( - 'SELECT' => '1', - 'FROM' => 'articles AS a2', - 'WHERE' => 'a2.parent_id = a.id AND a2.published = 1', - 'LIMIT' => '1' - ); - $raw_query_child_num = $s2_db->build($subquery); - - $query = array( - 'SELECT' => 'title, url, id, excerpt, create_time, parent_id', - 'FROM' => 'articles AS a', - 'WHERE' => 'parent_id = '.$parent_id.' AND published=1 AND ('.$raw_query_child_num.') IS NULL', - 'ORDER BY' => 'priority' - ); - ($hook = s2_hook('fn_s2_parse_page_url_pre_get_neighbours_qr')) ? eval($hook) : null; - $result = $s2_db->buildAndQuery($query); - - $neighbour_urls = $menu_articles = array(); - $i = 0; - $curr_item = -1; - while ($row = $s2_db->fetchAssoc($result)) - { - // A neighbour - $url = s2_link($parent_path.urlencode($row['url'])); - - $menu_articles[] = array( - 'title' => $row['title'], - 'link' => $url, - 'is_current' => $id == $row['id'], - ); - - if ($id == $row['id']) - $curr_item = $i; - - $neighbour_urls[] = $url; - - ($hook = s2_hook('fn_s2_parse_page_url_add_neighbour')) ? eval($hook) : null; - - $i++; - } - - if (count($bread_crumbs) > 1) - $page['menu_siblings'] = $this->renderPartial('menu_block', array( - 'title' => sprintf(Lang::get('More in this section'), ''.$bread_crumbs[count($bread_crumbs) - 2]['title'].''), - 'menu' => $menu_articles, - 'class' => 'menu_siblings', - )); - - if ($curr_item != -1) - { - if (isset($neighbour_urls[$curr_item - 1])) - $page['link_navigation']['prev'] = $neighbour_urls[$curr_item - 1]; - if (isset($neighbour_urls[$curr_item + 1])) - $page['link_navigation']['next'] = $neighbour_urls[$curr_item + 1]; - - $page['back_forward'] = array( - 'up' => count($bread_crumbs) <= 1 ? null : array( - 'title' => $bread_crumbs[count($bread_crumbs) - 2]['title'], - 'link' => s2_link($parent_path), - ), - 'back' => empty($menu_articles[$curr_item - 1]) ? null : array( - 'title' => $menu_articles[$curr_item - 1]['title'], - 'link' => $menu_articles[$curr_item - 1]['link'], - ), - 'forward' => empty($menu_articles[$curr_item + 1]) ? null : array( - 'title' => $menu_articles[$curr_item + 1]['title'], - 'link' => $menu_articles[$curr_item + 1]['link'], - ), - ); - } - } - - // Tags - if ($this->inTemplate('')) - $page['article_tags'] = $this->tagged_articles($id); - - if ($this->inTemplate('')) - $page['tags'] = $this->get_tags($id); - - // Recommendations - if ($this->inTemplate('')) { - /** @var RecommendationProvider $recommendationProvider */ - $recommendationProvider = \Container::get(RecommendationProvider::class); - global $request_uri; // ??? - - [$recommendations, $log, $rawRecommendations] = $recommendationProvider->getRecommendations($request_uri, new ExternalId($id)); - $this->page['recommendations'] = $this->renderPartial('recommendations', [ - 'raw' => $rawRecommendations, - 'content' => $recommendations, - 'log' => $log, - ]); - } - - // Comments - if ($page['commented'] && S2_SHOW_COMMENTS && $this->inTemplate('')) - { - $query = array( - 'SELECT' => 'nick, time, email, show_email, good, text', - 'FROM' => 'art_comments', - 'WHERE' => 'article_id = '.$id.' AND shown = 1', - 'ORDER BY' => 'time' - ); - ($hook = s2_hook('fn_s2_parse_page_url_pre_get_comm_qr')) ? eval($hook) : null; - $result = $s2_db->buildAndQuery($query); - - $comments = ''; - - for ($i = 1; $row = $s2_db->fetchAssoc($result); $i++) - { - $row['i'] = $i; - $comments .= $this->renderPartial('comment', $row); - } - - if ($comments) - $page['comments'] = $this->renderPartial('comments', array('comments' => $comments)); - } - - ($hook = s2_hook('fn_s2_parse_page_url_end')) ? eval($hook) : null; - - return null; - } -} diff --git a/_include/src/Application.php b/_include/src/Application.php index 5a9bcdf3..16279d38 100644 --- a/_include/src/Application.php +++ b/_include/src/Application.php @@ -13,6 +13,7 @@ use Psr\Log\LogLevel; use S2\Cms\Config\DynamicConfigProvider; use S2\Cms\Controller\NotFoundController; +use S2\Cms\Controller\PageCommon; use S2\Cms\Controller\PageFavorite; use S2\Cms\Controller\PageTag; use S2\Cms\Controller\PageTags; @@ -229,6 +230,22 @@ private function buildContainer(): void $provider->get('S2_TAGS_URL'), ); }); + + $this->container->set(PageCommon::class, function (Container $container) { + /** @var DynamicConfigProvider $provider */ + $provider = $container->get(DynamicConfigProvider::class); + return new PageCommon( + $container->get(DbLayer::class), + $container->get(HtmlTemplateProvider::class), + $container->get(RecommendationProvider::class), + $container->get(Viewer::class), + $provider->get('S2_USE_HIERARCHY') === '1', + $provider->get('S2_SHOW_COMMENTS') === '1', + $provider->get('S2_TAGS_URL'), + (int)$provider->get('S2_MAX_ITEMS'), + $container->getParameter('debug'), + ); + }); } private function addRoutes(): void @@ -246,7 +263,7 @@ private function addRoutes(): void $routes->add('favorite', new Route('/' . $favoriteUrl . '{slash}', ['_controller' => PageFavorite::class])); $routes->add('tags', new Route('/' . $tagsUrl . '{slash}', ['_controller' => PageTags::class])); $routes->add('tag', new Route('/' . $tagsUrl . '/{name}{slash}', ['_controller' => PageTag::class])); - $routes->add('common', new Route('/{path<.*>}', ['_controller' => \Page_Common::class])); + $routes->add('common', new Route('/{path<.*>}', ['_controller' => PageCommon::class])); $this->routes = $routes; } @@ -275,6 +292,7 @@ private function loadParameters(): array 'cache_dir' => S2_CACHE_DIR, 'log_dir' => (defined('S2_LOG_DIR') ? S2_LOG_DIR : S2_CACHE_DIR), 'base_url' => defined('S2_BASE_URL') ? S2_BASE_URL : null, + 'debug' => defined('S2_DEBUG'), 'debug_view' => defined('S2_DEBUG_VIEW'), 'redirect_map' => $GLOBALS['s2_redirect'] ?? [], ]; diff --git a/_include/src/Controller/PageCommon.php b/_include/src/Controller/PageCommon.php new file mode 100644 index 00000000..474f403f --- /dev/null +++ b/_include/src/Controller/PageCommon.php @@ -0,0 +1,597 @@ +getPathInfo(); + + $request_array = explode('/', $request_uri); // []/[dir1]/[dir2]/[dir3]/[file1] + + // Correcting trailing slash and the rest of URL + if (!$this->useHierarchy && \count($request_array) > 2) { + return new RedirectResponse(s2_link('/' . $request_array[1]), Response::HTTP_MOVED_PERMANENTLY); + } + + $was_end_slash = str_ends_with($request_uri, '/'); + + $bread_crumbs = []; + + $parent_path = ''; + $parent_id = \Model::ROOT_ID; + $parent_num = \count($request_array) - 1 - (int)$was_end_slash; + + $template_id = ''; + + if ($this->useHierarchy) { + $urls = array_unique($request_array); + $urls = array_map([$this->dbLayer, 'escape'], $urls); + + $query = [ + 'SELECT' => 'id, parent_id, title, template', + 'FROM' => 'articles', + 'WHERE' => 'url IN (\'' . implode('\', \'', $urls) . '\') AND published=1' + ]; + $result = $this->dbLayer->buildAndQuery($query); + + $nodes = $this->dbLayer->fetchAssocAll($result); + + /** + * Walking through the page parents + * 1. We ensure all of them are published + * 2. We build "bread crumbs" + * 3. We determine the template of the page + */ + for ($i = 0; $i < $parent_num; $i++) { + $parent_path .= urlencode($request_array[$i]) . '/'; + + $cur_node = []; + $found_node_num = 0; + foreach ($nodes as $node) { + if ($node['parent_id'] == $parent_id) { + $cur_node = $node; + $found_node_num++; + } + } + + if ($found_node_num === 0) { + throw new NotFoundException(); + } + if ($found_node_num > 1) { + error(\Lang::get('DB repeat items') . ($this->debug ? ' (parent_id=' . $parent_id . ', url="' . s2_htmlencode($request_array[$i]) . '")' : '')); + } + + $parent_id = $cur_node['id']; + if ($cur_node['template'] != '') { + $template_id = $cur_node['template']; + } + + $bread_crumbs[] = [ + 'link' => s2_link($parent_path), + 'title' => $cur_node['title'] + ]; + } + } else { + $parent_path = '/'; + $i = 1; + } + // Path to the requested page (without trailing slash) + $current_path = $parent_path . urlencode($request_array[$i]); + + $subquery = [ + 'SELECT' => '1', + 'FROM' => 'articles AS a1', + 'WHERE' => 'a1.parent_id = a.id AND a1.published = 1', + 'LIMIT' => '1' + ]; + $raw_query_children = $this->dbLayer->build($subquery); + + $subquery = [ + 'SELECT' => 'u.name', + 'FROM' => 'users AS u', + 'WHERE' => 'u.id = a.user_id' + ]; + $raw_query_author = $this->dbLayer->build($subquery); + + $query = [ + 'SELECT' => 'a.id, a.title, a.meta_keys as meta_keywords, a.meta_desc as meta_description, a.excerpt as excerpt, a.pagetext as text, a.create_time as date, favorite, commented, template, (' . $raw_query_children . ') IS NOT NULL AS children_exist, (' . $raw_query_author . ') AS author', + 'FROM' => 'articles AS a', + 'WHERE' => 'url=\'' . $this->dbLayer->escape($request_array[$i]) . '\'' . ($this->useHierarchy ? ' AND parent_id=' . $parent_id : '') . ' AND published=1' + ]; + $result = $this->dbLayer->buildAndQuery($query); + + $page = $this->dbLayer->fetchAssoc($result); + + // Error handling + if (!$page) { + throw new NotFoundException(); + } + + if ($this->dbLayer->fetchAssoc($result)) { + error(\Lang::get('DB repeat items') . ($this->debug ? ' (parent_id=' . $parent_id . ', url="' . $request_array[$i] . '")' : '')); + } + + if ($page['template']) { + $template_id = $page['template']; + } + + if ($template_id === '') { + if ($this->useHierarchy) { + $bread_crumbs[] = [ + 'link' => s2_link($parent_path), + 'title' => $page['title'], + ]; + + error(sprintf(\Lang::get('Error no template'), implode('
', array_map(static function ($a) { + return '' . s2_htmlencode($a['title']) . ''; + }, $bread_crumbs)))); + } else { + error(\Lang::get('Error no template flat')); + } + } + + if ($this->useHierarchy && $parent_num && $page['children_exist'] != $was_end_slash) { + return new RedirectResponse(s2_link($current_path . (!$was_end_slash ? '/' : '')), Response::HTTP_MOVED_PERMANENTLY); + } + + $articleId = (int)$page['id']; + $template = $this->htmlTemplateProvider->getTemplate($template_id); + $template + ->putInPlaceholder('id', $articleId) // for comments form + ->putInPlaceholder('meta_keywords', $page['meta_keywords']) + ->putInPlaceholder('meta_description', $page['meta_description']) + ->putInPlaceholder('excerpt', $page['excerpt']) + ->putInPlaceholder('text', $page['text']) + ->putInPlaceholder('date', $page['date']) + ->putInPlaceholder('commented', $page['commented']) + ->putInPlaceholder('author', $page['author']) + ->putInPlaceholder('canonical_path', $current_path . ($was_end_slash ? '/' : '')) + ; + + $bread_crumbs[] = [ + 'title' => $page['title'] + ]; + $template->putInPlaceholder('title', s2_htmlencode($page['title'])); + + if (!empty($page['author'])) { + $template->putInPlaceholder('author', s2_htmlencode($page['author'])); + } + + if ($this->useHierarchy) { + foreach ($bread_crumbs as $crumb) { + $template->addBreadCrumb($crumb['title'], $crumb['link'] ?? null); + } + $template->setLink('top', s2_link('/')); + + if (\count($bread_crumbs) > 1) { + $template->setLink('up', s2_link($parent_path)); + $template->putInPlaceholder( + 'section_link', + '' . $bread_crumbs[\count($bread_crumbs) - 2]['title'] . '' + ); + } + } + + // Dealing with sections, subsections, neighbours + if ( + $this->useHierarchy + && $page['children_exist'] + && ( + $template->hasPlaceholder('') + || $template->hasPlaceholder('') + || $template->hasPlaceholder('') + || $template->hasPlaceholder('') + ) + ) { + // It's a section. We have to fetch subsections and articles. + + // Fetching children + $subquery = [ + 'SELECT' => 'a1.id', + 'FROM' => 'articles AS a1', + 'WHERE' => 'a1.parent_id = a.id AND a1.published = 1', + 'LIMIT' => '1' + ]; + $raw_query1 = $this->dbLayer->build($subquery); + + $sort_order = SORT_DESC; + $query = [ + 'SELECT' => 'title, url, (' . $raw_query1 . ') IS NOT NULL AS children_exist, id, excerpt, favorite, create_time, parent_id', + 'FROM' => 'articles AS a', + 'WHERE' => 'parent_id = ' . $articleId . ' AND published = 1', + 'ORDER BY' => 'priority' + ]; + $result = $this->dbLayer->buildAndQuery($query); + + $subarticles = $subsections = $sort_array = []; + while ($row = $this->dbLayer->fetchAssoc($result)) { + if ($row['children_exist']) { + // The child is a subsection + $item = [ + 'id' => $row['id'], + 'title' => $row['title'], + 'link' => s2_link($current_path . '/' . urlencode($row['url']) . '/'), + 'date' => s2_date($row['create_time']), + 'excerpt' => $row['excerpt'], + 'favorite' => $row['favorite'], + ]; + + $subsections[] = $item; + } else { + // The child is an article + $item = array( + 'id' => $row['id'], + 'title' => $row['title'], + 'link' => s2_link($current_path . '/' . urlencode($row['url'])), + 'date' => s2_date($row['create_time']), + 'excerpt' => $row['excerpt'], + 'favorite' => $row['favorite'], + ); + $sort_field = $row['create_time']; + + $subarticles[] = $item; + $sort_array[] = $sort_field; + } + } + + $sections_text = ''; + if (\count($subsections) > 0) { + // There are subsections in the section + $template->putInPlaceholder('menu_subsections', $this->viewer->render('menu_block', [ + 'title' => \Lang::get('Subsections'), + 'menu' => $subsections, + 'class' => 'menu_subsections', + ])); + + foreach ($subsections as $item) { + $sections_text .= $this->viewer->render('subarticles_item', $item); + } + } + + $articles_text = ''; + if (\count($subarticles) > 0) { + // There are articles in the section + $template->putInPlaceholder('menu_children', $this->viewer->render('menu_block', [ + 'title' => \Lang::get('In this section'), + 'menu' => $subarticles, + 'class' => 'menu_children', + ])); + + ($sort_order == SORT_DESC) ? arsort($sort_array) : asort($sort_array); + + if ($this->maxItems > 0) { + // Paging navigation + $page_num = $request->query->get('p', 1) - 1; + if ($page_num < 0) { + $page_num = 0; + } + + $start = $page_num * $this->maxItems; + if ($start >= \count($subarticles)) { + $page_num = $start = 0; + } + + $total_pages = ceil(1.0 * \count($subarticles) / $this->maxItems); + + $link_nav = []; + $paging = s2_paging($page_num + 1, $total_pages, s2_link(str_replace('%', '%%', $current_path . '/'), ['p=%d']), $link_nav) . "\n"; + foreach ($link_nav as $rel => $href) { + $template->setLink($rel, $href); + } + + $sort_array = \array_slice($sort_array, $start, $this->maxItems, true); + } + + foreach ($sort_array as $index => $value) { + $articles_text .= $this->viewer->render('subarticles_item', $subarticles[$index]); + } + + if ($this->maxItems) { + $articles_text .= $paging; + } + } + + $template->putInPlaceholder('subcontent', $this->viewer->render('subarticles', [ + 'articles' => $articles_text, + 'sections' => $sections_text, + ])); + } + + if ( + $this->useHierarchy + && !$page['children_exist'] + && ( + $template->hasPlaceholder('') + || $template->hasPlaceholder('') + || $template->hasPlaceholder('') + ) + ) { + // It's an article. We have to fetch other articles in the parent section + + // Fetching "siblings" + $subquery = [ + 'SELECT' => '1', + 'FROM' => 'articles AS a2', + 'WHERE' => 'a2.parent_id = a.id AND a2.published = 1', + 'LIMIT' => '1' + ]; + $raw_query_child_num = $this->dbLayer->build($subquery); + + $query = [ + 'SELECT' => 'title, url, id, excerpt, create_time, parent_id', + 'FROM' => 'articles AS a', + 'WHERE' => 'parent_id = ' . $parent_id . ' AND published=1 AND (' . $raw_query_child_num . ') IS NULL', + 'ORDER BY' => 'priority' + ]; + $result = $this->dbLayer->buildAndQuery($query); + + $neighbour_urls = $menu_articles = []; + + $i = 0; + $curr_item = -1; + while ($row = $this->dbLayer->fetchAssoc($result)) { + // A neighbour + $url = s2_link($parent_path . urlencode($row['url'])); + + $menu_articles[] = [ + 'title' => $row['title'], + 'link' => $url, + 'is_current' => $articleId == $row['id'], + ]; + + if ($articleId == $row['id']) + $curr_item = $i; + + $neighbour_urls[] = $url; + + $i++; + } + + if (\count($bread_crumbs) > 1) { + $template->putInPlaceholder('menu_siblings', $this->viewer->render('menu_block', [ + 'title' => sprintf(\Lang::get('More in this section'), '' . $bread_crumbs[\count($bread_crumbs) - 2]['title'] . ''), + 'menu' => $menu_articles, + 'class' => 'menu_siblings', + ])); + } + + if ($curr_item !== -1) { + if (isset($neighbour_urls[$curr_item - 1])) { + $template->setLink('prev', $neighbour_urls[$curr_item - 1]); + } + if (isset($neighbour_urls[$curr_item + 1])) { + $template->setLink('next', $neighbour_urls[$curr_item + 1]); + } + + $template->putInPlaceholder('back_forward', [ + 'up' => \count($bread_crumbs) <= 1 ? null : [ + 'title' => $bread_crumbs[\count($bread_crumbs) - 2]['title'], + 'link' => s2_link($parent_path), + ], + 'back' => empty($menu_articles[$curr_item - 1]) ? null : [ + 'title' => $menu_articles[$curr_item - 1]['title'], + 'link' => $menu_articles[$curr_item - 1]['link'], + ], + 'forward' => empty($menu_articles[$curr_item + 1]) ? null : [ + 'title' => $menu_articles[$curr_item + 1]['title'], + 'link' => $menu_articles[$curr_item + 1]['link'], + ], + ]); + } + } + + // Tags + if ($template->hasPlaceholder('')) { + $template->putInPlaceholder('article_tags', $this->tagged_articles($articleId)); + } + + if ($template->hasPlaceholder('')) { + $template->putInPlaceholder('tags', $this->get_tags($articleId)); + } + + // Recommendations + if ($template->hasPlaceholder('')) { + [$recommendations, $log, $rawRecommendations] = $this->recommendationProvider->getRecommendations($request_uri, new ExternalId($articleId)); + $template->putInPlaceholder('recommendations', $this->viewer->render('recommendations', [ + 'raw' => $rawRecommendations, + 'content' => $recommendations, + 'log' => $log, + ])); + } + + // Comments + if ($page['commented'] && $this->showComments && $template->hasPlaceholder('')) { + $query = [ + 'SELECT' => 'nick, time, email, show_email, good, text', + 'FROM' => 'art_comments', + 'WHERE' => 'article_id = ' . $articleId . ' AND shown = 1', + 'ORDER BY' => 'time' + ]; + $result = $this->dbLayer->buildAndQuery($query); + + $comments = ''; + for ($i = 1; $row = $this->dbLayer->fetchAssoc($result); $i++) { + $row['i'] = $i; + $comments .= $this->viewer->render('comment', $row); + } + + if ($comments !== '') { + $template->putInPlaceholder('comments', $this->viewer->render('comments', ['comments' => $comments])); + } + } + + ($hook = s2_hook('fn_s2_parse_page_url_end')) ? eval($hook) : null; + + return $template->toHttpResponse(); + } + + private function tagged_articles(int $articleId): string + { + $query = [ + 'SELECT' => 't.tag_id as tag_id, name, t.url as url', + 'FROM' => 'tags AS t', + 'JOINS' => [ + [ + 'INNER JOIN' => 'article_tag AS atg', + 'ON' => 'atg.tag_id = t.tag_id' + ] + ], + 'WHERE' => 'atg.article_id = ' . $articleId + ]; + $result = $this->dbLayer->buildAndQuery($query); + + $tag_names = $tag_urls = []; + while ($row = $this->dbLayer->fetchAssoc($result)) { + $tag_names[$row['tag_id']] = $row['name']; + $tag_urls[$row['tag_id']] = $row['url']; + } + + if (\count($tag_urls) === 0) { + return ''; + } + + $subquery = [ + 'SELECT' => '1', + 'FROM' => 'articles AS a1', + 'WHERE' => 'a1.parent_id = atg.article_id AND a1.published = 1', + 'LIMIT' => '1' + ]; + $raw_query1 = $this->dbLayer->build($subquery); + + $query = [ + 'SELECT' => 'title, tag_id, parent_id, url, a.id AS id, (' . $raw_query1 . ') IS NOT NULL AS children_exist', + 'FROM' => 'articles AS a', + 'JOINS' => [ + [ + 'INNER JOIN' => 'article_tag AS atg', + 'ON' => 'a.id = atg.article_id' + ], + ], + 'WHERE' => 'atg.tag_id IN (' . implode(', ', array_keys($tag_names)) . ') AND a.published = 1' +// 'ORDER BY' => 'create_time' // no temp table is created but order by ID is almost the same + ]; + $result = $this->dbLayer->buildAndQuery($query); + + // Build article lists that have the same tags as our article + + $hasArticlesInList = false; + + $titles = $parent_ids = $urls = $tag_ids = $original_ids = []; + while ($row = $this->dbLayer->fetchAssoc($result)) { + if ($articleId <> $row['id']) { + $hasArticlesInList = true; + } + $titles[] = $row['title']; + $parent_ids[] = $row['parent_id']; + $urls[] = urlencode($row['url']) . (S2_USE_HIERARCHY && $row['children_exist'] ? '/' : ''); + $tag_ids[] = $row['tag_id']; + $original_ids[] = $row['id']; + } + + if (\count($urls) === 0) { + return ''; + } + + if ($hasArticlesInList) { + $urls = \Model::get_group_url($parent_ids, $urls); + } + + // Sorting all obtained article links into groups by each tag + $art_by_tags = []; + + foreach ($urls as $k => $url) { + $art_by_tags[$tag_ids[$k]][] = [ + 'title' => $titles[$k], + 'link' => $url, + 'is_current' => $original_ids[$k] == $articleId, + ]; + } + + // Remove tags that have only one article + foreach ($art_by_tags as $tag_id => $title_array) { + if (\count($title_array) <= 1) { + unset($art_by_tags[$tag_id]); + } + } + + $output = []; + foreach ($art_by_tags as $tag_id => $articles) { + $output[] = $this->viewer->render('menu_block', array( + 'title' => sprintf(\Lang::get('With this tag'), '' . $tag_names[$tag_id] . ''), + 'menu' => $articles, + 'class' => 'article_tags', + )); + } + + return implode("\n", $output); + } + + + private function get_tags(int $articleId): string + { + $query = [ + 'SELECT' => 'name, url', + 'FROM' => 'tags AS t', + 'JOINS' => [ + [ + 'INNER JOIN' => 'article_tag AS at', + 'ON' => 'at.tag_id = t.tag_id' + ] + ], + 'WHERE' => 'at.article_id = ' . $articleId + ]; + $result = $this->dbLayer->buildAndQuery($query); + + $tags = []; + while ($row = $this->dbLayer->fetchAssoc($result)) { + $tags[] = array( + 'title' => $row['name'], + 'link' => s2_link('/' . $this->tagsUrl . '/' . urlencode($row['url']) . '/'), + ); + } + + if (\count($tags) === 0) { + return ''; + } + + return $this->viewer->render('tags', [ + 'title' => \Lang::get('Tags'), + 'tags' => $tags, + ]); + } +} diff --git a/_include/src/Template/HtmlTemplate.php b/_include/src/Template/HtmlTemplate.php index b7cdbb8b..eb1c66b9 100644 --- a/_include/src/Template/HtmlTemplate.php +++ b/_include/src/Template/HtmlTemplate.php @@ -15,6 +15,7 @@ class HtmlTemplate { protected array $page = []; protected array $breadCrumbs = []; + private array $navLinks = []; public function __construct( protected string $template, @@ -71,16 +72,14 @@ public function toHttpResponse(): Response // Content $replace[''] = S2_SITE_NAME; - $replace[''] = ''; - if (isset($this->page['link_navigation'])) { - $link_navigation = []; - foreach ($this->page['link_navigation'] as $link_rel => $link_href) { - $link_navigation[] = ''; - } - $replace[''] = implode("\n", $link_navigation); + $link_navigation = []; + foreach ($this->navLinks as $link_rel => $link_href) { + $link_navigation[] = ''; } + $replace[''] = implode("\n", $link_navigation); + $replace[''] = !empty($this->page['author']) ? '
' . $this->page['author'] . '
' : ''; $replace[''] = !empty($this->page['title']) ? $this->viewer->render('title', array_intersect_key($this->page, ['title' => 1, 'favorite' => 1])) : ''; $replace[''] = !empty($this->page['date']) ? '
' . s2_date($this->page['date']) . '
' : ''; @@ -200,8 +199,15 @@ protected function simplePlaceholders(): array ]; } - public function inTemplate(string $placeholder): bool + public function hasPlaceholder(string $placeholder): bool { return str_contains($this->template, $placeholder); } + + public function setLink(string $rel, string $link): static + { + $this->navLinks[$rel] = $link; + + return $this; + } } diff --git a/_tests/acceptance/InstallCest.php b/_tests/acceptance/InstallCest.php index f7939636..06afff97 100644 --- a/_tests/acceptance/InstallCest.php +++ b/_tests/acceptance/InstallCest.php @@ -59,6 +59,7 @@ protected function tryToTest(AcceptanceTester $I, Example $example): void $this->testBlogExtension($I); $this->testBlogRssAndSitemap($I); $this->testSearchExtension($I); + $this->testAdminAddArticles($I); $this->testAdminTagListAndEdit($I); $this->testAdminCommentManagement($I); } @@ -176,6 +177,85 @@ private function testAdminEditAndTagsAdded(AcceptanceTester $I): void $I->see('another tag'); } + private function testAdminAddArticles(AcceptanceTester $I): void + { + foreach ([4, 5] as $newId) { + $I->sendAjaxGetRequest('/_admin/site_ajax.php?action=create&id=2&title=New+page+' . $newId); + $data = json_decode($I->grabPageSource(), true, 512, JSON_THROW_ON_ERROR); + $I->assertArrayHasKey('status', $data); + $I->assertEquals(1, $data['status']); + $I->assertArrayHasKey('id', $data); + $I->assertEquals($newId, $data['id']); + + $dataProvider = static function (string $id) { + return [ + 'page' => [ + 'title' => 'New Page ' . $id, + 'meta_keys' => 'New Meta Keywords', + 'meta_desc' => 'New Meta Description', + 'excerpt' => 'New Excerpt', + 'tags' => 'tag1, another tag', + 'create_time' => [ + 'hour' => '11', + 'min' => '32', + 'day' => '10', + 'mon' => '08', + 'year' => '2023', + ], + 'modify_time' => [ + 'hour' => '12', + 'min' => '15', + 'day' => '11', + 'mon' => '08', + 'year' => '2023', + ], + 'text' => '

Some new page text

', + 'id' => $id, + 'revision' => '1', + 'user_id' => '0', + 'template' => 'site.php', + 'url' => 'new_page' . $id, + ], + 'flags' => [ + 'favorite' => '1', + 'published' => '1', + 'commented' => '1', + ], + ]; + }; + + $I->sendAjaxPostRequest('/_admin/site_ajax.php?action=save', $dataProvider((string)$newId)); + $I->seeResponseCodeIsSuccessful(); + $I->dontSee('Warning! An error occurred during page saving. Copy the content to a text editor and save into a file out of caution.'); + $I->see('{"revision":2,"status":"ok","url_status":"ok"}'); + } + + // Links to related pages in section and by tags + $I->amOnPage('/section1/new_page4'); + $I->see('New Page 4', 'h1'); + $I->see('Some new page text', '#content'); + + $I->see('More in the section “Section 1”', '.header.menu_siblings'); + $I->see('New Page Title', '.menu_siblings a'); + $I->see('New Page 4', '.menu_siblings span'); + + $I->see('On the subject “tag1”', '.header.article_tags'); + $I->see('New Page Title', '.article_tags a'); + $I->see('New Page 4', '.article_tags span'); + + $I->see('See in blog', '.header.s2_blog_tags'); + $I->see('tag1', '.s2_blog_tags a'); + + // Links to sub-pages + $I->amOnPage('/section1/'); + $I->see('In this section', '.header.menu_children'); + $I->see('New Page Title', '.menu_children a'); + $I->see('New Page 4', '.menu_children a'); + + $I->see('New Page Title', 'h3.subsection'); + $I->see('New Excerpt', 'p.subsection'); + } + private function testTagsPage(AcceptanceTester $I): void { $I->stopFollowingRedirects();