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