diff --git a/core/components/pdotools/docs/changelog.txt b/core/components/pdotools/docs/changelog.txt index 0f4bff4..8420d4e 100644 --- a/core/components/pdotools/docs/changelog.txt +++ b/core/components/pdotools/docs/changelog.txt @@ -9,6 +9,7 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html) - PHPUnit 9.6 harness (Unit + Integration) and GitHub Actions on PHP 8.1, 8.2, 8.3, 8.4. - Codecov PR comments in informational mode, same as MODX Revolution. +- [pdoTools3#11] [pdoMenu/pdoPage/pdoCrumbs] Template rows expose isFirst, isLast, isActive, hasChildren/hasChilds (menu also isHere, isStart, isCategory, isInner; page isSkip; crumbs isHome). Specialized tpl* chunks still work. - [#306] [pdoMenu] Added the "children" placeholder to tplInner. - [#355] [Fenom] Added type cast modifiers: boolval, doubleval, floatval, intval, strval. - [#356] [Fenom] Enabled the array_merge modifier by default. diff --git a/core/components/pdotools/elements/snippets/snippet.pdocrumbs.php b/core/components/pdotools/elements/snippets/snippet.pdocrumbs.php index 4fdf909..57ae7b4 100644 --- a/core/components/pdotools/elements/snippets/snippet.pdocrumbs.php +++ b/core/components/pdotools/elements/snippets/snippet.pdocrumbs.php @@ -1,6 +1,7 @@ id && empty($showCurrent)) { continue; - } elseif ($row['id'] == $resource->id && !empty($tplCurrent)) { + } + $prepared[] = $row; + } + + $total = count($prepared); + foreach ($prepared as $index => $row) { + $row = array_merge( + $row, + TemplateFlags::toPlaceholders([ + 'isFirst' => $index === 0, + 'isLast' => $total > 0 && $index === $total - 1, + 'isActive' => (int)$row['id'] === (int)$resource->id, + 'isHome' => (int)$row['id'] === (int)$siteStart, + ]) + ); + if (isset($return) && $return === 'data') { + $output[] = $row; + continue; + } + if ($row['id'] == $resource->id && !empty($tplCurrent)) { $tpl = $tplCurrent; } elseif ($row['id'] == $siteStart && !empty($tplHome)) { $tpl = $tplHome; diff --git a/core/components/pdotools/elements/snippets/snippet.pdopage.php b/core/components/pdotools/elements/snippets/snippet.pdopage.php index f4bd8f9..73acf13 100644 --- a/core/components/pdotools/elements/snippets/snippet.pdopage.php +++ b/core/components/pdotools/elements/snippets/snippet.pdopage.php @@ -160,19 +160,19 @@ if (!empty($pageCount) && $pageCount > 1) { $pagination = [ 'first' => $page > 1 && !empty($tplPageFirst) - ? $paginator->makePageLink($url, 1, $tplPageFirst) + ? $paginator->makePageLink($url, 1, $tplPageFirst, $page, $pageCount) : '', 'prev' => $page > 1 && !empty($tplPagePrev) - ? $paginator->makePageLink($url, $page - 1, $tplPagePrev) + ? $paginator->makePageLink($url, $page - 1, $tplPagePrev, $page, $pageCount) : '', 'pages' => $pageLimit >= 7 && empty($disableModernPagination) ? $paginator->buildModernPagination($page, $pageCount, $url) : $paginator->buildClassicPagination($page, $pageCount, $url), 'next' => $page < $pageCount && !empty($tplPageNext) - ? $paginator->makePageLink($url, $page + 1, $tplPageNext) + ? $paginator->makePageLink($url, $page + 1, $tplPageNext, $page, $pageCount) : '', 'last' => $page < $pageCount && !empty($tplPageLast) - ? $paginator->makePageLink($url, $pageCount, $tplPageLast) + ? $paginator->makePageLink($url, $pageCount, $tplPageLast, $page, $pageCount) : '', ]; diff --git a/core/components/pdotools/src/Support/MenuBuilder.php b/core/components/pdotools/src/Support/MenuBuilder.php index f2c3ab0..6c5fe78 100644 --- a/core/components/pdotools/src/Support/MenuBuilder.php +++ b/core/components/pdotools/src/Support/MenuBuilder.php @@ -180,7 +180,8 @@ public function templateBranch($row = []) $row['menutitle'] = $row['pagetitle']; } - $classes = $this->getClasses($row); + $state = $this->itemState($row); + $classes = $state->classes(); if (!empty($classes)) { $row['classNames'] = $row['classnames'] = $classes; $row['classes'] = ' class="' . $classes . '"'; @@ -201,7 +202,8 @@ public function templateBranch($row = []) ? $row[$this->pdoTools->config('titleOfLinks')] : $row['pagetitle']; - $tpl = $this->getTpl($row); + $row = array_merge($row, $state->placeholders()); + $tpl = $this->tplFromState($state, $row); $row = $this->addWayFinderPlaceholders($row); return $this->pdoTools->getChunk($tpl, $row, $this->pdoTools->config('fastMode')); @@ -221,6 +223,18 @@ public function isHere($id = 0) } + /** + * @param array $row + * @return MenuItemState + */ + protected function itemState(array $row = []) + { + $config = $this->pdoTools->config(); + $rowId = MenuItemState::resolveRowId($row, $config); + + return MenuItemState::fromRow($row, $config, $this->isHere($rowId)); + } + /** * Determine style class for current item being processed * @@ -230,41 +244,12 @@ public function isHere($id = 0) */ public function getClasses($row = []) { - $classes = []; - - if (!empty($this->pdoTools->config('rowClass'))) { - $classes[] = $this->pdoTools->config('rowClass'); - } - if ($row['idx'] == 1 && !empty($this->pdoTools->config('firstClass'))) { - $classes[] = $this->pdoTools->config('firstClass'); - } elseif (!empty($row['last']) && !empty($this->pdoTools->config('lastClass'))) { - $classes[] = $this->pdoTools->config('lastClass'); - } - if (!empty($this->pdoTools->config('levelClass'))) { - $classes[] = $this->pdoTools->config('levelClass') . $row['level']; - } - if ($row['children'] && !empty($this->pdoTools->config('parentClass')) && ($row['level'] < $this->pdoTools->config('level') || empty($this->pdoTools->config('level')))) { - $classes[] = $this->pdoTools->config('parentClass'); - } - $row_id = !empty($this->pdoTools->config('useWeblinkUrl')) && !empty($row['content']) && !empty($row['class_key']) && is_numeric(trim($row['content'], '[]~ ')) && $row['class_key'] == modWebLink::class - ? (int)trim($row['content'], '[]~ ') - : $row['id']; - if ($this->isHere($row_id) && !empty($this->pdoTools->config('hereClass'))) { - $classes[] = $this->pdoTools->config('hereClass'); - } - if ($row_id == $this->pdoTools->config('hereId') && !empty($this->pdoTools->config('selfClass'))) { - $classes[] = $this->pdoTools->config('selfClass'); - } - if (!empty($row['class_key']) && $row['class_key'] === modWebLink::class && !empty($this->pdoTools->config('webLinkClass'))) { - $classes[] = $this->pdoTools->config('webLinkClass'); - } - - return implode(' ', $classes); + return $this->itemState($row)->classes(); } /** - * Determine style class for current item being processed + * Chunk name for the current menu row. * * @param array $row * @@ -272,34 +257,22 @@ public function getClasses($row = []) */ public function getTpl($row = []) { - $row_id = !empty($this->pdoTools->config('useWeblinkUrl')) && !empty($row['class_key']) && !empty($row['content']) && $row['class_key'] === modWebLink::class && is_numeric(trim($row['content'], '[]~ ')) - ? (int)trim($row['content'], '[]~ ') - : $row['id']; - if ($row['level'] === 1 && !empty($this->pdoTools->config('tplStart')) && !empty($this->pdoTools->config('displayStart'))) { - $tpl = 'tplStart'; - } elseif ($row['children'] && $row_id == $this->pdoTools->config('hereId') && !empty($this->pdoTools->config('tplParentRowHere'))) { - $tpl = 'tplParentRowHere'; - } elseif ($row['level'] > 1 && $row_id == $this->pdoTools->config('hereId') && !empty($this->pdoTools->config('tplInnerHere'))) { - $tpl = 'tplInnerHere'; - } elseif ($row_id == $this->pdoTools->config('hereId') && !empty($this->pdoTools->config('tplHere'))) { - $tpl = 'tplHere'; - } elseif ($row['children'] && $this->isHere($row_id) && !empty($this->pdoTools->config('tplParentRowActive'))) { - $tpl = 'tplParentRowActive'; - } elseif ($row['children'] && (empty($row['template']) || strpos($row['link_attributes'], 'category') != false) && !empty($this->pdoTools->config('tplCategoryFolder'))) { - $tpl = 'tplCategoryFolder'; - } // It's a typo, but it is left for backward compatibility - elseif ($row['children'] && (empty($row['template']) || strpos($row['link_attributes'], 'category') != false) && !empty($this->pdoTools->config('tplCategoryFolders'))) { - $tpl = 'tplCategoryFolders'; - } // --- - elseif ($row['children'] && !empty($this->pdoTools->config('tplParentRow'))) { - $tpl = 'tplParentRow'; - } elseif ($row['level'] > 1 && !empty($this->pdoTools->config('tplInnerRow'))) { - $tpl = 'tplInnerRow'; - } else { + return $this->tplFromState($this->itemState($row), $row); + } + + /** + * @param MenuItemState $state + * @param array $row + * @return mixed + */ + protected function tplFromState(MenuItemState $state, array $row) + { + $key = $state->tplKey(); + if ($key === null) { return $this->pdoTools->defineChunk($row); } - return $this->pdoTools->config($tpl); + return $this->pdoTools->config($key); } diff --git a/core/components/pdotools/src/Support/MenuItemState.php b/core/components/pdotools/src/Support/MenuItemState.php new file mode 100644 index 0000000..2b77c1b --- /dev/null +++ b/core/components/pdotools/src/Support/MenuItemState.php @@ -0,0 +1,184 @@ +config = $config; + $state->level = (int)($row['level'] ?? 1); + $state->rowId = self::resolveRowId($row, $config); + $state->isFirst = isset($row['idx']) && (int)$row['idx'] === 1; + $state->isLast = !empty($row['last']); + $state->hasChildren = !empty($row['children']); + $state->isActive = $state->rowId == ($config['hereId'] ?? 0); + $state->isHere = (bool)$isHere; + $state->isStart = $state->level === 1 && !empty($config['displayStart']); + $state->isInner = $state->level > 1; + $state->isWebLink = !empty($row['class_key']) && $row['class_key'] === modWebLink::class; + $attrs = isset($row['link_attributes']) ? (string)$row['link_attributes'] : ''; + $state->isCategory = $state->hasChildren && ( + empty($row['template']) + || ($attrs !== '' && strpos($attrs, 'category') !== false) + ); + + return $state; + } + + /** + * @param array $row + * @param array $config + * @return int + */ + public static function resolveRowId(array $row, array $config) + { + if ( + !empty($config['useWeblinkUrl']) + && !empty($row['class_key']) + && !empty($row['content']) + && $row['class_key'] === modWebLink::class + && is_numeric(trim($row['content'], '[]~ ')) + ) { + return (int)trim($row['content'], '[]~ '); + } + + return (int)($row['id'] ?? 0); + } + + /** + * @return array + */ + public function placeholders() + { + $out = TemplateFlags::toPlaceholders([ + 'isFirst' => $this->isFirst, + 'isLast' => $this->isLast, + 'isActive' => $this->isActive, + 'hasChildren' => $this->hasChildren, + 'isHere' => $this->isHere, + 'isStart' => $this->isStart, + 'isCategory' => $this->isCategory, + 'isInner' => $this->isInner, + ]); + $out['hasChilds'] = $out['hasChildren']; + + return $out; + } + + /** + * @return string + */ + public function classes() + { + $config = $this->config; + $classes = []; + + if (!empty($config['rowClass'])) { + $classes[] = $config['rowClass']; + } + if ($this->isFirst && !empty($config['firstClass'])) { + $classes[] = $config['firstClass']; + } elseif ($this->isLast && !empty($config['lastClass'])) { + $classes[] = $config['lastClass']; + } + if (!empty($config['levelClass'])) { + $classes[] = $config['levelClass'] . $this->level; + } + if ( + $this->hasChildren + && !empty($config['parentClass']) + && ($this->level < ($config['level'] ?? 0) || empty($config['level'])) + ) { + $classes[] = $config['parentClass']; + } + if ($this->isHere && !empty($config['hereClass'])) { + $classes[] = $config['hereClass']; + } + if ($this->isActive && !empty($config['selfClass'])) { + $classes[] = $config['selfClass']; + } + if ($this->isWebLink && !empty($config['webLinkClass'])) { + $classes[] = $config['webLinkClass']; + } + + return implode(' ', $classes); + } + + /** + * Config key for a specialized tpl*, or null to fall back to defineChunk(). + * + * @return string|null + */ + public function tplKey() + { + $config = $this->config; + if ($this->isStart && !empty($config['tplStart'])) { + return 'tplStart'; + } + if ($this->hasChildren && $this->isActive && !empty($config['tplParentRowHere'])) { + return 'tplParentRowHere'; + } + if ($this->isInner && $this->isActive && !empty($config['tplInnerHere'])) { + return 'tplInnerHere'; + } + if ($this->isActive && !empty($config['tplHere'])) { + return 'tplHere'; + } + if ($this->hasChildren && $this->isHere && !empty($config['tplParentRowActive'])) { + return 'tplParentRowActive'; + } + if ($this->isCategory && !empty($config['tplCategoryFolder'])) { + return 'tplCategoryFolder'; + } + // Typo kept for backward compatibility + if ($this->isCategory && !empty($config['tplCategoryFolders'])) { + return 'tplCategoryFolders'; + } + if ($this->hasChildren && !empty($config['tplParentRow'])) { + return 'tplParentRow'; + } + if ($this->isInner && !empty($config['tplInnerRow'])) { + return 'tplInnerRow'; + } + + return null; + } +} diff --git a/core/components/pdotools/src/Support/PageItemState.php b/core/components/pdotools/src/Support/PageItemState.php new file mode 100644 index 0000000..db472ce --- /dev/null +++ b/core/components/pdotools/src/Support/PageItemState.php @@ -0,0 +1,55 @@ + + */ + public static function placeholders($page, $current, $pages, $skip = false) + { + $page = (int)$page; + $current = (int)$current; + $pages = (int)$pages; + + return TemplateFlags::toPlaceholders([ + 'isFirst' => $page === 1, + 'isLast' => $pages > 0 && $page === $pages, + 'isActive' => !$skip && $page === $current, + 'isSkip' => (bool)$skip, + ]); + } + + /** + * Pick tplPageActive / tplPage / tplPageSkip from config. + * Skip slots only return tplPageSkip (or empty), never fall back to page tpls. + * + * @param array $config + * @param int $page + * @param int $current + * @param bool $skip + * @return string + */ + public static function tpl(array $config, $page, $current, $skip = false) + { + if ($skip) { + return !empty($config['tplPageSkip']) ? (string)$config['tplPageSkip'] : ''; + } + if ((int)$page === (int)$current && !empty($config['tplPageActive'])) { + return (string)$config['tplPageActive']; + } + if (!empty($config['tplPage'])) { + return (string)$config['tplPage']; + } + + return ''; + } +} diff --git a/core/components/pdotools/src/Support/Paginator.php b/core/components/pdotools/src/Support/Paginator.php index 00ee90c..716c7a8 100644 --- a/core/components/pdotools/src/Support/Paginator.php +++ b/core/components/pdotools/src/Support/Paginator.php @@ -187,10 +187,13 @@ public function getBaseUrl() * @param string $url * @param int $page * @param string $tpl + * @param int|null $current Current page (for isActive / isFirst / isLast) + * @param int|null $pages Total pages + * @param bool $skip Skip/ellipsis slot * * @return string $href */ - public function makePageLink($url = '', $page = 1, $tpl = '') + public function makePageLink($url = '', $page = 1, $tpl = '', $current = null, $pages = null, $skip = false) { if (empty($url)) { $url = $this->getBaseUrl(); @@ -244,12 +247,55 @@ public function makePageLink($url = '', $page = 1, $tpl = '') 'pageNo' => $page, 'href' => $href, ]; + if ($current !== null && $pages !== null) { + $data = array_merge( + $data, + PageItemState::placeholders($page, $current, $pages, $skip) + ); + } return !empty($tpl) ? $this->pdoTools->getChunk($tpl, $data) : $href; } + /** + * @param string $url + * @param int $page Slot number + * @param int $current Current page + * @param int $pages Total pages + * @param bool $skip + * @return string + */ + protected function renderPageItem($url, $page, $current, $pages, $skip = false) + { + $tpl = PageItemState::tpl($this->pdoTools->config(), $page, $current, $skip); + if (empty($tpl)) { + return ''; + } + + return $this->makePageLink($url, $page, $tpl, $current, $pages, $skip); + } + + /** + * Skip chunk without a page href (modern pagination separators). + * + * @param int $pages + * @return string + */ + protected function renderPageSkip($pages = 0) + { + $tpl = $this->pdoTools->config('tplPageSkip'); + if (empty($tpl)) { + return ''; + } + + return $this->pdoTools->getChunk( + $tpl, + PageItemState::placeholders(0, 0, $pages, true) + ); + } + /** * Creates the correct URL of the page, including folder subdomains. * @see https://github.com/modx-pro/pdoTools/issues/318 @@ -283,7 +329,7 @@ public function buildClassicPagination($page = 1, $pages = 5, $url = '') $pageLimit = 0; } else { // -1 because we need to show current page - $tmp = (integer)floor(($pageLimit - 1) / 2); + $tmp = (int)floor(($pageLimit - 1) / 2); $left = $tmp; // Pages from left $right = $pageLimit - $left - 1; // Pages from right @@ -316,16 +362,7 @@ public function buildClassicPagination($page = 1, $pages = 5, $url = '') break; } - if ($page == $i && !empty($this->pdoTools->config('tplPageActive'))) { - $tpl = $this->pdoTools->config('tplPageActive'); - } elseif (!empty($this->pdoTools->config('tplPage'))) { - $tpl = $this->pdoTools->config('tplPage'); - } - - $pagination .= !empty($tpl) - ? $this->makePageLink($url, $i, $tpl) - : ''; - + $pagination .= $this->renderPageItem($url, $i, $page, $pages); $i++; } @@ -349,7 +386,7 @@ public function buildModernPagination($page = 1, $pages = 5, $url = '') if ($pageLimit >= $pages || $pageLimit < 7) { return $this->buildClassicPagination($page, $pages, $url); } else { - $tmp = (integer)floor($pageLimit / 3); + $tmp = (int)floor($pageLimit / 3); $left = $right = $tmp; $center = $pageLimit - ($tmp * 2); } @@ -357,112 +394,64 @@ public function buildModernPagination($page = 1, $pages = 5, $url = '') $pagination = []; // Left for ($i = 1; $i <= $left; $i++) { - if ($page == $i && !empty($this->pdoTools->config('tplPageActive'))) { - $tpl = $this->pdoTools->config('tplPageActive'); - } elseif (!empty($this->pdoTools->config('tplPage'))) { - $tpl = $this->pdoTools->config('tplPage'); - } - $pagination[$i] = !empty($tpl) - ? $this->makePageLink($url, $i, $tpl) - : ''; + $pagination[$i] = $this->renderPageItem($url, $i, $page, $pages); } // Right for ($i = $pages - $right + 1; $i <= $pages; $i++) { - if ($page == $i && !empty($this->pdoTools->config('tplPageActive'))) { - $tpl = $this->pdoTools->config('tplPageActive'); - } elseif (!empty($this->pdoTools->config('tplPage'))) { - $tpl = $this->pdoTools->config('tplPage'); - } - $pagination[$i] = !empty($tpl) - ? $this->makePageLink($url, $i, $tpl) - : ''; + $pagination[$i] = $this->renderPageItem($url, $i, $page, $pages); } // Center if ($page <= $left) { $i = $left + 1; while ($i <= $center + $left) { - if ($i == $center + $left && !empty($this->pdoTools->config('tplPageSkip'))) { - $tpl = $this->pdoTools->config('tplPageSkip'); - } else { - $tpl = $this->pdoTools->config('tplPage'); - } - - $pagination[$i] = !empty($tpl) - ? $this->makePageLink($url, $i, $tpl) - : ''; + $skip = $i == $center + $left && !empty($this->pdoTools->config('tplPageSkip')); + $pagination[$i] = $this->renderPageItem($url, $i, $page, $pages, $skip); $i++; } } elseif ($page > $pages - $right) { $i = $pages - $right - $center + 1; while ($i <= $pages - $right) { - if ($i == $pages - $right - $center + 1 && !empty($this->pdoTools->config('tplPageSkip'))) { - $tpl = $this->pdoTools->config('tplPageSkip'); - } else { - $tpl = $this->pdoTools->config('tplPage'); - } - - $pagination[$i] = !empty($tpl) - ? $this->makePageLink($url, $i, $tpl) - : ''; + $skip = $i == $pages - $right - $center + 1 && !empty($this->pdoTools->config('tplPageSkip')); + $pagination[$i] = $this->renderPageItem($url, $i, $page, $pages, $skip); $i++; } } else { if ($page - $center < $left) { $i = $left + 1; while ($i <= $center + $left) { - if ($page == $i && !empty($this->pdoTools->config('tplPageActive'))) { - $tpl = $this->pdoTools->config('tplPageActive'); - } elseif (!empty($this->pdoTools->config('tplPage'))) { - $tpl = $this->pdoTools->config('tplPage'); - } - $pagination[$i] = !empty($tpl) - ? $this->makePageLink($url, $i, $tpl) - : ''; + $pagination[$i] = $this->renderPageItem($url, $i, $page, $pages); $i++; } if (!empty($this->pdoTools->config('tplPageSkip'))) { $key = ($page + 1 == $left + $center) ? $pages - $right + 1 : $left + $center; - $pagination[$key] = $this->pdoTools->getChunk($this->pdoTools->config('tplPageSkip')); + $pagination[$key] = $this->renderPageSkip($pages); } } elseif ($page + $center - 1 > $pages - $right) { $i = $pages - $right - $center + 1; while ($i <= $pages - $right) { - if ($page === $i && !empty($this->pdoTools->config('tplPageActive'))) { - $tpl = $this->pdoTools->config('tplPageActive'); - } elseif (!empty($this->pdoTools->config('tplPage'))) { - $tpl = $this->pdoTools->config('tplPage'); - } - $pagination[$i] = !empty($tpl) - ? $this->makePageLink($url, $i, $tpl) - : ''; + $pagination[$i] = $this->renderPageItem($url, $i, $page, $pages); $i++; } if (!empty($this->pdoTools->config('tplPageSkip'))) { $key = ($page - 1 == $pages - $right - $center + 1) ? $left : $pages - $right - $center + 1; - $pagination[$key] = $this->pdoTools->getChunk($this->pdoTools->config('tplPageSkip')); + $pagination[$key] = $this->renderPageSkip($pages); } } else { - $tmp = (integer)floor(($center - 1) / 2); + $tmp = (int)floor(($center - 1) / 2); $i = $page - $tmp; while ($i < $page - $tmp + $center) { - if ($page === $i && !empty($this->pdoTools->config('tplPageActive'))) { - $tpl = $this->pdoTools->config('tplPageActive'); - } elseif (!empty($this->pdoTools->config('tplPage'))) { - $tpl = $this->pdoTools->config('tplPage'); - } - $pagination[$i] = !empty($tpl) - ? $this->makePageLink($url, $i, $tpl) - : ''; + $pagination[$i] = $this->renderPageItem($url, $i, $page, $pages); $i++; } if (!empty($this->pdoTools->config('tplPageSkip'))) { - $pagination[$left] = $pagination[$pages - $right + 1] = $this->pdoTools->getChunk($this->pdoTools->config('tplPageSkip')); + $skip = $this->renderPageSkip($pages); + $pagination[$left] = $pagination[$pages - $right + 1] = $skip; } } } diff --git a/core/components/pdotools/src/Support/TemplateFlags.php b/core/components/pdotools/src/Support/TemplateFlags.php new file mode 100644 index 0000000..0da5726 --- /dev/null +++ b/core/components/pdotools/src/Support/TemplateFlags.php @@ -0,0 +1,23 @@ + $bools + * @return array + */ + public static function toPlaceholders(array $bools) + { + $out = []; + foreach ($bools as $key => $value) { + $out[$key] = $value ? 1 : 0; + } + + return $out; + } +} diff --git a/core/components/pdotools/tests/Support/CapturingTools.php b/core/components/pdotools/tests/Support/CapturingTools.php new file mode 100644 index 0000000..e0fc1b4 --- /dev/null +++ b/core/components/pdotools/tests/Support/CapturingTools.php @@ -0,0 +1,41 @@ + */ + public $lastChunkProperties = []; + + /** + * @param array $properties + */ + public function getChunk($name = '', array $properties = [], $fastMode = false) + { + $properties = $this->prepareRow($properties); + $this->lastChunkName = (string)$name; + $this->lastChunkProperties = $properties; + + return (string)$name; + } + + public function makeUrl($id, $options = [], $args = []) + { + return '/id/' . (int)$id; + } + + public function defineChunk(array $properties = []) + { + return !empty($this->config['tpl']) ? (string)$this->config['tpl'] : '@INLINE default'; + } +} diff --git a/core/components/pdotools/tests/Support/MenuBuilderHarness.php b/core/components/pdotools/tests/Support/MenuBuilderHarness.php new file mode 100644 index 0000000..e3b570d --- /dev/null +++ b/core/components/pdotools/tests/Support/MenuBuilderHarness.php @@ -0,0 +1,23 @@ + $parentTree + */ + public function __construct(modX $modx, CoreTools $pdoTools, array $parentTree = []) + { + $this->modx = $modx; + $this->pdoTools = $pdoTools; + $this->parentTree = $parentTree; + $this->level = 1; + } +} diff --git a/core/components/pdotools/tests/Support/PaginatorHarness.php b/core/components/pdotools/tests/Support/PaginatorHarness.php new file mode 100644 index 0000000..8bfc497 --- /dev/null +++ b/core/components/pdotools/tests/Support/PaginatorHarness.php @@ -0,0 +1,38 @@ +modx = $modx; + $this->pdoTools = $pdoTools; + } + + /** + * @param string $url + * @param int $page + * @param int $current + * @param int $pages + * @param bool $skip + */ + public function publicRenderPageItem($url, $page, $current, $pages, $skip = false): string + { + return $this->renderPageItem($url, $page, $current, $pages, $skip); + } + + /** + * @param int $pages + */ + public function publicRenderPageSkip($pages = 0): string + { + return $this->renderPageSkip($pages); + } +} diff --git a/core/components/pdotools/tests/Unit/Support/MenuBuilderFlagsTest.php b/core/components/pdotools/tests/Unit/Support/MenuBuilderFlagsTest.php new file mode 100644 index 0000000..1b413fe --- /dev/null +++ b/core/components/pdotools/tests/Unit/Support/MenuBuilderFlagsTest.php @@ -0,0 +1,81 @@ +modx, [ + 'hereId' => 5, + 'firstClass' => 'first', + 'selfClass' => 'self', + 'hereClass' => 'active', + 'tplHere' => '@INLINE here', + 'tpl' => '@INLINE row', + ]); + $menu = new MenuBuilderHarness($this->modx, $tools, [5 => 0]); + + $out = $menu->templateBranch([ + 'id' => 5, + 'idx' => 1, + 'last' => false, + 'pagetitle' => 'Home', + 'children' => [], + ]); + + $this->assertSame('@INLINE here', $out); + $this->assertSame('@INLINE here', $tools->lastChunkName); + $pls = $tools->lastChunkProperties; + $this->assertSame(1, $pls['isActive']); + $this->assertSame(1, $pls['isFirst']); + $this->assertSame(1, $pls['isHere']); + $this->assertSame(0, $pls['hasChildren']); + $this->assertSame(0, $pls['hasChilds']); + $this->assertSame('Home', $pls['menutitle']); + $this->assertSame('/id/5', $pls['link']); + $this->assertStringContainsString('first', $pls['classNames']); + $this->assertStringContainsString('self', $pls['classNames']); + $this->assertStringContainsString('active', $pls['classNames']); + } + + public function testGetTplPrefersSpecializedChunk(): void + { + $tools = new CapturingTools($this->modx, [ + 'hereId' => 9, + 'tplHere' => '@INLINE here', + 'tpl' => '@INLINE row', + ]); + $menu = new MenuBuilderHarness($this->modx, $tools, [9 => 0]); + + $this->assertSame( + '@INLINE here', + $menu->getTpl(['id' => 9, 'idx' => 2, 'level' => 1, 'children' => 0]) + ); + } + + public function testGetClassesUsesFrozenConfig(): void + { + $tools = new CapturingTools($this->modx, [ + 'hereId' => 1, + 'firstClass' => 'first', + 'lastClass' => 'last', + ]); + $menu = new MenuBuilderHarness($this->modx, $tools); + + $classes = $menu->getClasses([ + 'id' => 2, + 'idx' => 1, + 'last' => false, + 'level' => 1, + 'children' => 0, + ]); + $this->assertStringContainsString('first', $classes); + } +} diff --git a/core/components/pdotools/tests/Unit/Support/MenuItemStateTest.php b/core/components/pdotools/tests/Unit/Support/MenuItemStateTest.php new file mode 100644 index 0000000..e08b2c1 --- /dev/null +++ b/core/components/pdotools/tests/Unit/Support/MenuItemStateTest.php @@ -0,0 +1,158 @@ + 42, + 'idx' => 1, + 'last' => false, + 'level' => 1, + 'children' => 3, + 'template' => 1, + 'link_attributes' => '', + ], + ['hereId' => 42, 'displayStart' => true], + true + ); + + $this->assertTrue($state->isFirst); + $this->assertFalse($state->isLast); + $this->assertTrue($state->isActive); + $this->assertTrue($state->isHere); + $this->assertTrue($state->hasChildren); + $this->assertTrue($state->isStart); + $this->assertFalse($state->isInner); + $this->assertSame(1, $state->placeholders()['hasChildren']); + $this->assertSame(1, $state->placeholders()['hasChilds']); + } + + public function testTplKeyPrefersTplHereWhenConfigured(): void + { + $config = ['hereId' => 5, 'tplHere' => '@INLINE here']; + $state = MenuItemState::fromRow( + [ + 'id' => 5, + 'idx' => 2, + 'last' => true, + 'level' => 1, + 'children' => 0, + ], + $config, + true + ); + + $this->assertSame('tplHere', $state->tplKey()); + + $without = MenuItemState::fromRow( + [ + 'id' => 5, + 'idx' => 2, + 'last' => true, + 'level' => 1, + 'children' => 0, + ], + ['hereId' => 5], + true + ); + $this->assertNull($without->tplKey()); + } + + public function testCategoryAndParentTpl(): void + { + $state = MenuItemState::fromRow( + [ + 'id' => 9, + 'idx' => 2, + 'last' => false, + 'level' => 2, + 'children' => 2, + 'template' => 0, + 'link_attributes' => '', + ], + ['hereId' => 99, 'tplCategoryFolder' => '@INLINE cat'], + false + ); + + $this->assertTrue($state->isCategory); + $this->assertTrue($state->isInner); + $this->assertSame('tplCategoryFolder', $state->tplKey()); + + $parentOnly = MenuItemState::fromRow( + [ + 'id' => 9, + 'idx' => 2, + 'last' => false, + 'level' => 2, + 'children' => 2, + 'template' => 0, + 'link_attributes' => '', + ], + ['hereId' => 99, 'tplParentRow' => '@INLINE parent'], + false + ); + $this->assertSame('tplParentRow', $parentOnly->tplKey()); + } + + public function testCategoryFromLinkAttributesAtOffsetZero(): void + { + $state = MenuItemState::fromRow( + [ + 'id' => 9, + 'idx' => 1, + 'last' => false, + 'level' => 1, + 'children' => 2, + 'template' => 3, + 'link_attributes' => 'category', + ], + ['hereId' => 99], + false + ); + + $this->assertTrue($state->isCategory); + } + + public function testWeblinkRowIdAndClasses(): void + { + $state = MenuItemState::fromRow( + [ + 'id' => 10, + 'idx' => 1, + 'last' => false, + 'level' => 1, + 'children' => 0, + 'class_key' => modWebLink::class, + 'content' => '[[~20]]', + ], + [ + 'hereId' => 20, + 'useWeblinkUrl' => true, + 'firstClass' => 'first', + 'selfClass' => 'self', + 'hereClass' => 'active', + 'webLinkClass' => 'weblink', + ], + true + ); + + $this->assertSame(20, $state->rowId); + $this->assertTrue($state->isActive); + $this->assertTrue($state->isWebLink); + $classes = $state->classes(); + $this->assertStringContainsString('first', $classes); + $this->assertStringContainsString('self', $classes); + $this->assertStringContainsString('active', $classes); + $this->assertStringContainsString('weblink', $classes); + } +} diff --git a/core/components/pdotools/tests/Unit/Support/PageItemStateTest.php b/core/components/pdotools/tests/Unit/Support/PageItemStateTest.php new file mode 100644 index 0000000..2429587 --- /dev/null +++ b/core/components/pdotools/tests/Unit/Support/PageItemStateTest.php @@ -0,0 +1,46 @@ +assertSame(1, $flags['isActive']); + $this->assertSame(0, $flags['isFirst']); + $this->assertSame(0, $flags['isLast']); + $this->assertSame(0, $flags['isSkip']); + } + + public function testFirstAndLast(): void + { + $this->assertSame(1, PageItemState::placeholders(1, 5, 10)['isFirst']); + $this->assertSame(1, PageItemState::placeholders(10, 5, 10)['isLast']); + } + + public function testSkipClearsActive(): void + { + $flags = PageItemState::placeholders(5, 5, 10, true); + $this->assertSame(1, $flags['isSkip']); + $this->assertSame(0, $flags['isActive']); + } + + public function testTplSelection(): void + { + $config = [ + 'tplPage' => 'page', + 'tplPageActive' => 'active', + 'tplPageSkip' => 'skip', + ]; + $this->assertSame('active', PageItemState::tpl($config, 2, 2)); + $this->assertSame('page', PageItemState::tpl($config, 3, 2)); + $this->assertSame('skip', PageItemState::tpl($config, 4, 2, true)); + $this->assertSame('', PageItemState::tpl(['tplPage' => 'page'], 4, 2, true)); + } +} diff --git a/core/components/pdotools/tests/Unit/Support/PaginatorFlagsTest.php b/core/components/pdotools/tests/Unit/Support/PaginatorFlagsTest.php new file mode 100644 index 0000000..4937406 --- /dev/null +++ b/core/components/pdotools/tests/Unit/Support/PaginatorFlagsTest.php @@ -0,0 +1,94 @@ +tools = new CapturingTools($this->modx, [ + 'pageVarKey' => 'page', + 'tplPage' => '@INLINE page', + 'tplPageActive' => '@INLINE active', + 'tplPageSkip' => '@INLINE skip', + ]); + $this->paginator = new PaginatorHarness($this->modx, $this->tools); + } + + public function testMakePageLinkWithoutContextReturnsHrefOnly(): void + { + $href = $this->paginator->makePageLink('/list', 3); + $this->assertSame('/list?page=3', $href); + } + + public function testMakePageLinkMergesFlagsIntoChunk(): void + { + $this->paginator->makePageLink('/list', 3, '@INLINE page', 3, 10); + + $this->assertSame('@INLINE page', $this->tools->lastChunkName); + $pls = $this->tools->lastChunkProperties; + $this->assertSame(3, $pls['page']); + $this->assertSame('/list?page=3', $pls['href']); + $this->assertSame(1, $pls['isActive']); + $this->assertSame(0, $pls['isFirst']); + $this->assertSame(0, $pls['isLast']); + $this->assertSame(0, $pls['isSkip']); + } + + public function testMakePageLinkSkipFlag(): void + { + $this->paginator->makePageLink('/list', 5, '@INLINE skip', 3, 10, true); + $this->assertSame(1, $this->tools->lastChunkProperties['isSkip']); + $this->assertSame(0, $this->tools->lastChunkProperties['isActive']); + } + + public function testRenderPageItemPicksActiveTpl(): void + { + $out = $this->paginator->publicRenderPageItem('/list', 2, 2, 5); + $this->assertSame('@INLINE active', $out); + $this->assertSame(1, $this->tools->lastChunkProperties['isActive']); + } + + public function testRenderPageItemSkipWithoutTplReturnsEmpty(): void + { + $this->tools->setConfig([ + 'pageVarKey' => 'page', + 'tplPage' => '@INLINE page', + ]); + $this->assertSame('', $this->paginator->publicRenderPageItem('/list', 4, 2, 10, true)); + } + + public function testRenderPageItemSkipUsesSkipTpl(): void + { + $out = $this->paginator->publicRenderPageItem('/list', 4, 2, 10, true); + $this->assertSame('@INLINE skip', $out); + $this->assertSame(1, $this->tools->lastChunkProperties['isSkip']); + } + + public function testRenderPageSkipWithoutTplReturnsEmpty(): void + { + $this->tools->setConfig(['pageVarKey' => 'page']); + $this->assertSame('', $this->paginator->publicRenderPageSkip(10)); + } + + public function testRenderPageSkipPassesFlags(): void + { + $out = $this->paginator->publicRenderPageSkip(8); + $this->assertSame('@INLINE skip', $out); + $this->assertSame(1, $this->tools->lastChunkProperties['isSkip']); + $this->assertSame(0, $this->tools->lastChunkProperties['isActive']); + } +} diff --git a/core/components/pdotools/tests/Unit/Support/TemplateFlagsTest.php b/core/components/pdotools/tests/Unit/Support/TemplateFlagsTest.php new file mode 100644 index 0000000..29af368 --- /dev/null +++ b/core/components/pdotools/tests/Unit/Support/TemplateFlagsTest.php @@ -0,0 +1,19 @@ +assertSame( + ['isActive' => 1, 'isFirst' => 0], + TemplateFlags::toPlaceholders(['isActive' => true, 'isFirst' => false]) + ); + } +}