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 119 $data = []; 120 $search_opts = [ 121 'depth' => 1, 122 'pagesonly' => true, 123 'listfiles' => true 124 ]; 125 search( 126 $data, 127 $conf['datadir'], 128 'search_universal', 129 $search_opts, 130 $ns, 131 $lvl = 1, 132 $this->options->get('sort') 133 ); 134 135 return $data; 136 } 137 138 /** 139 * Add the result of a full text search to the feed object 140 * 141 * @return array 142 */ 143 protected function fetchItemsFromSearch() 144 { 145 if (!actionOK('search')) throw new \RuntimeException('search is disabled'); 146 if (!$this->options->get('search_query')) return []; 147 148 $data = ft_pageSearch($this->options->get('search_query'), $poswords); 149 return array_keys($data); 150 } 151 152 /** 153 * Add recent changed pages to the feed object 154 * 155 * @return array 156 */ 157 protected function fetchItemsFromRecentChanges() 158 { 159 global $conf; 160 $flags = 0; 161 if (!$this->options->get('show_deleted')) $flags += RECENTS_SKIP_DELETED; 162 if (!$this->options->get('show_minor')) $flags += RECENTS_SKIP_MINORS; 163 if ($this->options->get('only_new')) $flags += RECENTS_ONLY_CREATION; 164 if ($this->options->get('content_type') == 'media' && $conf['mediarevisions']) { 165 $flags += RECENTS_MEDIA_CHANGES; 166 } 167 if ($this->options->get('content_type') == 'both' && $conf['mediarevisions']) { 168 $flags += RECENTS_MEDIA_PAGES_MIXED; 169 } 170 171 return getRecents(0, $this->options->get('items'), $this->options->get('namespace'), $flags); 172 } 173 174 /** 175 * Add items from a plugin to the feed object 176 * 177 * @triggers FEED_MODE_UNKNOWN 178 * @return array 179 */ 180 protected function fetchItemsFromPlugin() 181 { 182 $eventData = [ 183 'opt' => $this->options->options, 184 'data' => [], 185 ]; 186 $event = new Event('FEED_MODE_UNKNOWN', $eventData); 187 if ($event->advise_before(true)) { 188 throw new \RuntimeException('unknown feed mode'); 189 } 190 $event->advise_after(); 191 192 return $eventData['data']; 193 } 194 195 /** 196 * Add a logo to the feed 197 * 198 * Looks at different possible candidates for a logo and adds the first one 199 * 200 * @return void 201 */ 202 protected function initLogo() 203 { 204 global $conf; 205 206 $this->feed->image = new \FeedImage(); 207 $this->feed->image->title = $conf['title']; 208 $this->feed->image->link = DOKU_URL; 209 $this->feed->image->url = tpl_getMediaFile([ 210 ':wiki:logo.svg', 211 ':logo.svg', 212 ':wiki:logo.png', 213 ':logo.png', 214 ':wiki:logo.jpg', 215 ':logo.jpg', 216 ':wiki:favicon.ico', 217 ':favicon.ico', 218 ':wiki:dokuwiki.svg', 219 ':wiki:dokuwiki-128.png', 220 'images/favicon.ico' 221 ], true); 222 } 223} 224