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