1<?php 2 3namespace dokuwiki\Feed; 4 5use dokuwiki\Extension\Event; 6 7class FeedCreator 8{ 9 /** @var \UniversalFeedCreator */ 10 protected $feed; 11 12 /** @var FeedCreatorOptions */ 13 protected $options; 14 15 /** 16 * @param FeedCreatorOptions $options 17 */ 18 public function __construct(FeedCreatorOptions $options) 19 { 20 $this->options = $options; 21 22 $this->feed = new \UniversalFeedCreator(); 23 $this->feed->title = $this->options->get('title'); 24 $this->feed->description = $this->options->get('subtitle'); 25 $this->feed->link = DOKU_URL; 26 $this->feed->syndicationURL = DOKU_URL . 'feed.php'; 27 $this->feed->cssStyleSheet = DOKU_URL . 'lib/exe/css.php?s=feed'; 28 29 $this->initLogo(); 30 } 31 32 /** 33 * Build the feed 34 * 35 * @return string The raw XML for the feed 36 */ 37 public function build() 38 { 39 $items = match ($this->options->get('feed_mode')) { 40 'list' => $this->fetchItemsFromNamespace(), 41 'search' => $this->fetchItemsFromSearch(), 42 'recent' => $this->fetchItemsFromRecentChanges(), 43 default => $this->fetchItemsFromPlugin(), 44 }; 45 46 $eventData = [ 47 'rss' => $this->feed, 48 'data' => &$items, 49 'opt' => &$this->options->options, 50 ]; 51 $event = new Event('FEED_DATA_PROCESS', $eventData); 52 if ($event->advise_before(false)) { 53 foreach ($items as $item) { 54 $this->createAndAddItem($item); 55 } 56 } 57 $event->advise_after(); 58 59 return $this->feed->createFeed($this->options->getType()); 60 } 61 62 /** 63 * Process the raw data, create feed item and add it to the feed 64 * 65 * @param array|string $data raw item data 66 * @return \FeedItem 67 * @triggers FEED_ITEM_ADD 68 */ 69 protected function createAndAddItem($data) 70 { 71 if (is_string($data)) { 72 $data = ['id' => $data]; 73 } 74 75 if (($data['mode'] ?? '') == 'media' || isset($data['media'])) { 76 $data['id'] = $data['media'] ?? $data['id']; 77 $proc = new FeedMediaProcessor($data); 78 } else { 79 $proc = new FeedPageProcessor($data); 80 } 81 82 $item = new \FeedItem(); 83 $item->title = $proc->getTitle(); 84 if ($this->options->get('show_summary') && $proc->getSummary()) { 85 $item->title .= ' - ' . $proc->getSummary(); 86 } 87 $item->date = $proc->getRev(); 88 [$item->authorEmail, $item->author] = $proc->getAuthor(); 89 $item->link = $proc->getURL($this->options->get('link_to')); 90 $item->description = $proc->getBody($this->options->get('item_content')); 91 92 $evdata = [ 93 'item' => $item, 94 'opt' => &$this->options->options, 95 'ditem' => &$data, 96 'rss' => $this->feed, 97 ]; 98 99 $evt = new Event('FEED_ITEM_ADD', $evdata); 100 if ($evt->advise_before()) { 101 $this->feed->addItem($item); 102 } 103 $evt->advise_after(); 104 105 return $item; 106 } 107 108 /** 109 * Read all pages from a namespace 110 * 111 * @todo this currently does not honor the rss_media setting and only ever lists pages 112 * @return array 113 */ 114 protected function fetchItemsFromNamespace() 115 { 116 global $conf; 117 118 $ns = ':' . cleanID($this->options->get('namespace')); 119 $ns = utf8_encodeFN(str_replace(':', '/', $ns)); 120 121 $data = []; 122 $search_opts = [ 123 'depth' => 1, 124 'pagesonly' => true, 125 'listfiles' => true 126 ]; 127 search( 128 $data, 129 $conf['datadir'], 130 'search_universal', 131 $search_opts, 132 $ns, 133 $lvl = 1, 134 $this->options->get('sort') 135 ); 136 137 return $data; 138 } 139 140 /** 141 * Add the result of a full text search to the feed object 142 * 143 * @return array 144 */ 145 protected function fetchItemsFromSearch() 146 { 147 if (!actionOK('search')) throw new \RuntimeException('search is disabled'); 148 if (!$this->options->get('search_query')) return []; 149 150 $data = ft_pageSearch($this->options->get('search_query'), $poswords); 151 return array_keys($data); 152 } 153 154 /** 155 * Add recent changed pages to the feed object 156 * 157 * @return array 158 */ 159 protected function fetchItemsFromRecentChanges() 160 { 161 global $conf; 162 $flags = 0; 163 if (!$this->options->get('show_deleted')) $flags += RECENTS_SKIP_DELETED; 164 if (!$this->options->get('show_minor')) $flags += RECENTS_SKIP_MINORS; 165 if ($this->options->get('only_new')) $flags += RECENTS_ONLY_CREATION; 166 if ($this->options->get('content_type') == 'media' && $conf['mediarevisions']) { 167 $flags += RECENTS_MEDIA_CHANGES; 168 } 169 if ($this->options->get('content_type') == 'both' && $conf['mediarevisions']) { 170 $flags += RECENTS_MEDIA_PAGES_MIXED; 171 } 172 173 return getRecents(0, $this->options->get('items'), $this->options->get('namespace'), $flags); 174 } 175 176 /** 177 * Add items from a plugin to the feed object 178 * 179 * @triggers FEED_MODE_UNKNOWN 180 * @return array 181 */ 182 protected function fetchItemsFromPlugin() 183 { 184 $eventData = [ 185 'opt' => $this->options->options, 186 'data' => [], 187 ]; 188 $event = new Event('FEED_MODE_UNKNOWN', $eventData); 189 if ($event->advise_before(true)) { 190 throw new \RuntimeException('unknown feed mode'); 191 } 192 $event->advise_after(); 193 194 return $eventData['data']; 195 } 196 197 /** 198 * Add a logo to the feed 199 * 200 * Looks at different possible candidates for a logo and adds the first one 201 * 202 * @return void 203 */ 204 protected function initLogo() 205 { 206 global $conf; 207 208 $this->feed->image = new \FeedImage(); 209 $this->feed->image->title = $conf['title']; 210 $this->feed->image->link = DOKU_URL; 211 $this->feed->image->url = tpl_getMediaFile([ 212 ':wiki:logo.svg', 213 ':logo.svg', 214 ':wiki:logo.png', 215 ':logo.png', 216 ':wiki:logo.jpg', 217 ':logo.jpg', 218 ':wiki:favicon.ico', 219 ':favicon.ico', 220 ':wiki:dokuwiki.svg', 221 ':wiki:dokuwiki-128.png', 222 'images/favicon.ico' 223 ], true); 224 } 225} 226