1<?php 2 3namespace dokuwiki\Feed; 4 5use dokuwiki\Extension\Event; 6 7class FeedCreator 8{ 9 10 /** @var \UniversalFeedCreator */ 11 protected $feed; 12 13 /** @var FeedCreatorOptions */ 14 protected $options; 15 16 /** 17 * @param FeedCreatorOptions $options 18 */ 19 public function __construct(FeedCreatorOptions $options) 20 { 21 $this->options = $options; 22 23 $this->feed = new \UniversalFeedCreator(); 24 $this->feed->title = $this->options->get('title'); 25 $this->feed->description = $this->options->get('subtitle'); 26 $this->feed->link = DOKU_URL; 27 $this->feed->syndicationURL = DOKU_URL . 'feed.php'; 28 $this->feed->cssStyleSheet = DOKU_URL . 'lib/exe/css.php?s=feed'; 29 30 $this->initLogo(); 31 } 32 33 /** 34 * Build the feed 35 * 36 * @return string The raw XML for the feed 37 */ 38 public function build() 39 { 40 switch ($this->options->get('feed_mode')) { 41 case 'list': 42 $items = $this->fetchItemsFromNamespace(); 43 break; 44 case 'search': 45 $items = $this->fetchItemsFromSearch(); 46 break; 47 case 'recent': 48 $items = $this->fetchItemsFromRecentChanges(); 49 break; 50 default: 51 $items = $this->fetchItemsFromPlugin(); 52 } 53 54 foreach ($items as $item) { 55 $this->createAndAddItem($item); 56 } 57 58 return $this->feed->createFeed($this->options->get('type')); 59 } 60 61 /** 62 * Process the raw data, create feed item and add it to the feed 63 * 64 * @param array|string $data raw item data 65 * @return \FeedItem 66 * @triggers FEED_ITEM_ADD 67 */ 68 protected function createAndAddItem($data) 69 { 70 if (is_string($data)) { 71 $data = ['id' => $data]; 72 } 73 74 if (($data['mode'] ?? '') == 'media' || isset($data['media'])) { 75 $data['id'] = $data['media'] ?? $data['id']; 76 $proc = new FeedMediaProcessor($data); 77 } else { 78 $proc = new FeedPageProcessor($data); 79 } 80 81 $item = new \FeedItem(); 82 $item->title = $proc->getTitle(); 83 if ($this->options->get('show_summary') && $proc->getSummary()) { 84 $item->title .= ' - ' . $proc->getSummary(); 85 } 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 'images/favicon.ico' 218 ], true); 219 } 220} 221