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