xref: /plugin/dw2pdf/src/NamespaceCollector.php (revision 4c5691130dbf9035b960f10f5b3cb4b319a026d8)
1<?php
2
3namespace dokuwiki\plugin\dw2pdf\src;
4
5use dokuwiki\Extension\Event;
6
7class NamespaceCollector extends AbstractCollector
8{
9    protected string $namespace;
10    protected $sortorder;
11    protected int $depth;
12    protected array $excludePages;
13    protected array $excludeNamespaces;
14
15
16    /**
17     * Initialize variables from global input
18     *
19     * @return void
20     * @throws ExportException When the requested namespace does not exist
21     */
22    protected function initVars(): void
23    {
24        $config = $this->getConfig();
25
26        $this->namespace = $config->getBookNamespace();
27        $this->sortorder = $config->getBookSortOrder();
28        $this->depth = $config->getBookNamespaceDepth();
29        if ($this->depth < 0) $this->depth = 0;
30        $this->excludePages = $config->getBookExcludedPages();
31        $this->excludeNamespaces = $config->getBookExcludedNamespaces();
32
33        // check namespace exists
34        $nsdir = dirname(wikiFN($this->namespace . ':dummy'));
35        if (!@is_dir($nsdir)) throw new ExportException('needns');
36    }
37
38    /**
39     * @inheritdoc
40     * @triggers DW2PDF_NAMESPACEEXPORT_SORT
41     * @throws ExportException When the requested namespace does not exist
42     * @todo currently we do not support the 'at' parameter. We would need to search pages in the attic for this.
43     */
44    protected function collect(): array
45    {
46        global $conf;
47
48        $this->initVars();
49
50        //page search
51        $result = [];
52        $opts = ['depth' => $this->depth]; //recursive all levels
53        $dir = utf8_encodeFN(str_replace(':', '/', $this->namespace));
54        search($result, $conf['datadir'], 'search_allpages', $opts, $dir);
55
56        // remove excluded pages and namespaces
57        $result = $this->excludePages($result);
58
59
60
61        // Sort pages, let plugins modify sorting
62        $eventData = ['pages' => &$result, 'sort' => $this->sortorder];
63        $event = new Event('DW2PDF_NAMESPACEEXPORT_SORT', $eventData);
64        if ($event->advise_before()) {
65            $result = $this->sortPages($result);
66        }
67        $event->advise_after();
68
69        // extract page ids
70        $pages = array_column($result, 'id');
71
72        // if a there is a namespace start page outside the namespace, add it at the beginning
73        if ($this->namespace !== '') {
74            if (!in_array($this->namespace . ':' . $conf['start'], $pages, true)) {
75                if (file_exists(wikiFN(rtrim($this->namespace, ':')))) {
76                    array_unshift($pages, rtrim($this->namespace, ':'));
77                }
78            }
79        }
80
81        return $pages;
82    }
83
84    /**
85     * Remove excluded pages and namespaces from the given list of pages
86     *
87     * @param array $pages The list of pages as returned by search()
88     * @return array The filtered list of pages
89     */
90    protected function excludePages(array $pages)
91    {
92        $pages = array_filter($pages, fn($page) => !in_array($page['id'], $this->excludePages));
93        $pages = array_filter($pages, function ($page) {
94            foreach ($this->excludeNamespaces as $ns) {
95                if (str_starts_with($page['id'], $ns . ':')) {
96                    return false;
97                }
98            }
99            return true;
100        });
101        return $pages;
102    }
103
104    /**
105     * Sort the given list of pages according to the selected sort order
106     *
107     * @param array $pages The list of pages as returned by search()
108     * @return array The sorted list of pages
109     */
110    protected function sortPages(array $pages): array
111    {
112        $sortoptions = ['pagename', 'date', 'natural'];
113        if (!in_array($this->sortorder, $sortoptions)) {
114            $this->sortorder = 'natural';
115        }
116
117        if ($this->sortorder == 'date') {
118            usort($pages, [$this, 'cbDateSort']);
119        } else {
120            usort($pages, [$this, 'cbPagenameSort']);
121        }
122
123        return $pages;
124    }
125
126    /**
127     * usort callback to sort by file lastmodified time
128     *
129     * @param array $a
130     * @param array $b
131     * @return int
132     */
133    public function cbDateSort($a, $b)
134    {
135        if ($b['rev'] < $a['rev']) {
136            return -1;
137        }
138        if ($b['rev'] > $a['rev']) {
139            return 1;
140        }
141        return strcmp($b['id'], $a['id']);
142    }
143
144    /**
145     * usort callback to sort by page id
146     * @param array $a
147     * @param array $b
148     * @return int
149     */
150    public function cbPagenameSort($a, $b)
151    {
152        global $conf;
153
154        $partsA = explode(':', $a['id']);
155        $countA = count($partsA);
156        $partsB = explode(':', $b['id']);
157        $countB = count($partsB);
158        $max = max($countA, $countB);
159
160
161        // compare namepsace by namespace
162        for ($i = 0; $i < $max; $i++) {
163            $partA = $partsA[$i] ?: null;
164            $partB = $partsB[$i] ?: null;
165
166            // have we reached the page level?
167            if ($i === ($countA - 1) || $i === ($countB - 1)) {
168                // start page first
169                if ($partA == $conf['start']) {
170                    return -1;
171                }
172                if ($partB == $conf['start']) {
173                    return 1;
174                }
175            }
176
177            // prefer page over namespace
178            if ($partA === $partB) {
179                if (!isset($partsA[$i + 1])) {
180                    return -1;
181                }
182                if (!isset($partsB[$i + 1])) {
183                    return 1;
184                }
185                continue;
186            }
187
188
189            // simply compare
190            return strnatcmp($partA, $partB);
191        }
192
193        return strnatcmp($a['id'], $b['id']);
194    }
195}
196