1<?php
2
3/*
4 * This file is part of the TYPO3 CMS project.
5 *
6 * It is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License, either version 2
8 * of the License, or any later version.
9 *
10 * For the full copyright and license information, please read the
11 * LICENSE.txt file that was distributed with this source code.
12 *
13 * The TYPO3 project - inspiring people to share!
14 */
15
16namespace TYPO3\CMS\Frontend\ContentObject\Menu;
17
18use TYPO3\CMS\Core\Collection\AbstractRecordCollection;
19use TYPO3\CMS\Core\Utility\GeneralUtility;
20use TYPO3\CMS\Frontend\Category\Collection\CategoryCollection;
21
22/**
23 * Utility class for menus based on category collections of pages.
24 *
25 * Returns all the relevant pages for rendering with a menu content object.
26 * @internal this is only used for internal purposes and solely used for EXT:frontend and not part of TYPO3's Core API.
27 */
28class CategoryMenuUtility
29{
30    /**
31     * @var string Name of the field used for sorting the pages
32     */
33    protected static $sortingField;
34
35    /**
36     * Collects all pages for the selected categories, sorted according to configuration.
37     *
38     * @param string $selectedCategories Comma-separated list of system categories primary keys
39     * @param array $configuration TypoScript configuration for the "special." keyword
40     * @param \TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject $parentObject Back-reference to the calling object
41     * @return array List of selected pages
42     */
43    public function collectPages($selectedCategories, $configuration, $parentObject)
44    {
45        $selectedPages = [];
46        $categoriesPerPage = [];
47        // Determine the name of the relation field
48        $relationField = '';
49        if (isset($configuration['relation.'])) {
50            $relationField = $parentObject->parent_cObj->stdWrap(
51                $configuration['relation'],
52                $configuration['relation.']
53            );
54        } elseif (isset($configuration['relation'])) {
55            $relationField = $configuration['relation'];
56        }
57        // Get the pages for each selected category
58        $selectedCategories = GeneralUtility::intExplode(',', $selectedCategories, true);
59        foreach ($selectedCategories as $aCategory) {
60            $collection = CategoryCollection::load(
61                $aCategory,
62                true,
63                'pages',
64                $relationField
65            );
66            $categoryUid = 0;
67            if ($collection instanceof AbstractRecordCollection) {
68                $categoryUid = $collection->getUid();
69            }
70            // Loop on the results, overlay each page record found
71            foreach ($collection as $pageItem) {
72                $parentObject->getSysPage()->versionOL('pages', $pageItem, true);
73                if (is_array($pageItem)) {
74                    $selectedPages[$pageItem['uid']] = $parentObject->getSysPage()->getPageOverlay($pageItem);
75                    // Keep a list of the categories each page belongs to
76                    if (!isset($categoriesPerPage[$pageItem['uid']])) {
77                        $categoriesPerPage[$pageItem['uid']] = [];
78                    }
79                    $categoriesPerPage[$pageItem['uid']][] = $categoryUid;
80                }
81            }
82        }
83        // Loop on the selected pages to add the categories they belong to, as comma-separated list of category uid's)
84        // (this makes them available for rendering, if needed)
85        foreach ($selectedPages as $uid => $pageRecord) {
86            $selectedPages[$uid]['_categories'] = implode(',', $categoriesPerPage[$uid]);
87        }
88
89        // Sort the pages according to the sorting property
90        self::$sortingField = isset($configuration['sorting.']) ? $parentObject->getParentContentObject()->stdWrap($configuration['sorting'], $configuration['sorting.']) : $configuration['sorting'];
91        $order = isset($configuration['order.']) ? $parentObject->getParentContentObject()->stdWrap($configuration['order'], $configuration['order.']) : $configuration['order'];
92        $selectedPages = $this->sortPages($selectedPages, $order);
93
94        return $selectedPages;
95    }
96
97    /**
98     * Sorts the selected pages
99     *
100     * If the sorting field is not defined or does not corresponding to an existing field
101     * of the "pages" tables, the list of pages will remain unchanged.
102     *
103     * @param array $pages List of selected pages
104     * @param string $order Order for sorting (should "asc" or "desc")
105     * @return array Sorted list of pages
106     */
107    protected function sortPages($pages, $order)
108    {
109        // Perform the sorting only if a criterion was actually defined
110        if (!empty(self::$sortingField)) {
111            // Check that the sorting field exists (checking the first record is enough)
112            $firstPage = current($pages);
113            if (isset($firstPage[self::$sortingField])) {
114                // Make sure the order property is either "asc" or "desc" (default is "asc")
115                if (!empty($order)) {
116                    $order = strtolower($order);
117                    if ($order !== 'desc') {
118                        $order = 'asc';
119                    }
120                }
121                uasort(
122                    $pages,
123                    [
124                        self::class,
125                        'sortPagesUtility'
126                    ]
127                );
128                // If the sort order is descending, reverse the sorted array
129                if ($order === 'desc') {
130                    $pages = array_reverse($pages, true);
131                }
132            }
133        }
134        return $pages;
135    }
136
137    /**
138     * Static utility for sorting pages according to the selected criterion
139     *
140     * @param array $pageA Record for first page to be compared
141     * @param array $pageB Record for second page to be compared
142     * @return int -1 if first argument is smaller than second argument, 1 if first is greater than second and 0 if both are equal
143     */
144    public static function sortPagesUtility($pageA, $pageB)
145    {
146        return strnatcasecmp($pageA[self::$sortingField], $pageB[self::$sortingField]);
147    }
148}
149