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