1<?php
2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
3//
4// All Rights Reserved. See copyright.txt for details and a complete list of authors.
5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
6// $Id$
7
8//this script may only be included - so its better to die if called directly.
9if (strpos($_SERVER["SCRIPT_NAME"], basename(__FILE__)) !== false) {
10	header("location: index.php");
11	exit;
12}
13
14class RSSLib extends TikiDb_Bridge
15{
16	private $items;
17	private $feeds;
18	private $modules;
19
20	function __construct()
21	{
22		$this->items = $this->table('tiki_rss_items');
23		$this->feeds = $this->table('tiki_rss_feeds');
24		$this->modules = $this->table('tiki_rss_modules');
25	}
26
27	// ------------------------------------
28	// functions for rss feeds we syndicate
29	// ------------------------------------
30
31	/**
32	 * Return the feed format name
33	 *
34	 * @return string feed format name
35	 */
36	function get_current_feed_format_name()
37	{
38		$ver = $this->get_current_feed_format();
39
40		if ($ver == '2') {
41			$name = 'rss';
42		} elseif ($ver == '5') {
43			$name = 'atom';
44		}
45
46		return $name;
47	}
48
49	/**
50	 * Return the feed format code (2 for rss and 5 for atom)
51	 * we currently use (user param or default value)
52	 *
53	 * @return int $ver
54	 */
55	function get_current_feed_format()
56	{
57		global $prefs;
58
59		if (isset($_REQUEST['ver'])) {
60			$ver = $_REQUEST['ver'];
61		} else {
62			$ver = $prefs['feed_default_version'];
63		}
64
65		return $ver;
66	}
67
68	/* check for cached rss feed data */
69	function get_from_cache($uniqueid, $rss_version = "9")
70	{
71		global $tikilib, $user, $prefs;
72
73		$rss_version = $this->get_current_feed_format();
74
75		$output = [];
76		$output["content-type"] = "application/xml";
77		$output["encoding"] = "UTF-8";
78
79		$output["data"] = "EMPTY";
80
81		// caching rss data for anonymous users only
82		if (isset($user) && $user <> "") {
83			return $output;
84		}
85
86		$res = $this->feeds->fetchFullRow(['name' => $uniqueid, 'rssVer' => $rss_version]);
87		if (! $res) {
88			// nothing found, then insert empty row for this feed+rss_ver
89			$this->feeds->insert(
90				[
91					'name' => $uniqueid,
92					'rssVer' => $rss_version,
93					'refresh' => (int) $prefs['feed_cache_time'],
94					'lastUpdated' => 1,
95					'cache' => '-',
96				]
97			);
98		} else {
99			// entry found in db:
100			$output["data"] = $res["cache"];
101			// $refresh = $res["refresh"]; // global cache time currently
102			$refresh = $prefs['feed_cache_time']; // global cache time currently
103			$lastUpdated = $res["lastUpdated"];
104			// up to date? if not, then set trigger to reload data:
105			if ($tikilib->now - $lastUpdated >= $refresh) {
106				$output["data"] = "EMPTY";
107			}
108		}
109		$output['content-type'] = 'application/xml';
110		return $output;
111	}
112
113	/* put to cache */
114	function put_to_cache($uniqueid, $rss_version = "9", $output)
115	{
116		global $user, $tikilib;
117		// caching rss data for anonymous users only
118		if (isset($user) && $user <> "") {
119			return;
120		}
121		if ($output == "" || $output == "EMPTY") {
122			return;
123		}
124
125		$rss_version = $this->get_current_feed_format();
126
127		// update cache with new generated data if data not empty
128
129		$this->feeds->update(
130			[
131				'cache' => $output,
132				'lastUpdated' => $tikilib->now,
133			],
134			[
135				'name' => $uniqueid,
136				'rssVer' => $rss_version,
137			]
138		);
139	}
140
141	/**
142	 * Generate a feed (ATOM 1.0 or RSS 2.0 using Zend\Feed\Writer\Feed
143	 *
144	 * @param string $section Tiki feature the feed is related to
145	 * @param string $uniqueid
146	 * @param string $feed_version DEPRECATED
147	 * @param array $changes the content that will be used to generate the feed
148	 * @param string $itemurl base url for items (e.g. "tiki-view_blog_post.php?postId=%s")
149	 * @param string $urlparam
150	 * @param string $id name of the id field used to identify each feed item (e.g. "postId")
151	 * @param string $title title for the feed
152	 * @param string $titleId name of the key in the $changes array with the title of each item
153	 * @param string $desc description for the feed
154	 * @param string $descId name of the key in the $changes array with the description of each item
155	 * @param string $dateId name of the key in the $changes array with the date of each item
156	 * @param string $authorId name of the key in the $changes array with the author of each item
157	 * @param bool $fromcache if true recover the feed from cache
158	 *
159	 * @return array the generated feed
160	 */
161	function generate_feed($section, $uniqueid, $feed_version, $changes, $itemurl, $urlparam, $id, $title, $titleId, $desc, $descId, $dateId, $authorId, $fromcache = false
162			)
163	{
164		global $tiki_p_admin, $prefs;
165		$userlib = TikiLib::lib('user');
166		$tikilib = TikiLib::lib('tiki');
167		$smarty = TikiLib::lib('smarty');
168
169		// both title and description fields cannot be null
170		if (empty($title) || empty($desc)) {
171			$msg = tra('The title and description must be entered, to generate a feed.');
172			if ($tiki_p_admin) {
173				$msg .= ' ' . tra('To fix this error go to Admin -> Feeds.');
174			} else {
175				$msg .= ' ' . tra('Please contact the site administrator and request this error to be corrected');
176			}
177			$smarty->assign('msg', $msg);
178			$smarty->display('error.tpl');
179			die;
180		}
181
182		$feed_format = $this->get_current_feed_format();
183		$feed_format_name = $this->get_current_feed_format_name();
184
185		if ($prefs['feed_cache_time'] < 1) {
186			$fromcache = false;
187		}
188
189		// only get cache data if rss cache is enabled
190		if ($fromcache) {
191			$output = $this->get_from_cache($uniqueid, $feed_format);
192			if ($output['data'] != 'EMPTY') {
193				return $output;
194			}
195		}
196
197		$urlarray = parse_url($_SERVER["REQUEST_URI"]);
198		$rawPath = str_replace('\\', '/', dirname($urlarray["path"]));
199		$URLPrefix = $tikilib->httpPrefix() . $rawPath;
200		if ($rawPath != "/") {
201			$URLPrefix .= "/"; // Append a slash unless Tiki is in the document root. dirname() removes a slash except in that case.
202		}
203
204		if (isset($prefs['feed_' . $section . '_index']) && $prefs['feed_' . $section . '_index'] != '') {
205			$feedLink = $prefs['feed_' . $section . '_index'];
206		} else {
207			$feedLink = htmlspecialchars($tikilib->httpPrefix() . $_SERVER["REQUEST_URI"]);
208		}
209
210		$img = htmlspecialchars($URLPrefix . $prefs['feed_img']);
211
212		$title = htmlspecialchars($title);
213		$desc = htmlspecialchars($desc);
214		$read = $URLPrefix . $itemurl;
215
216		$feed = new Zend\Feed\Writer\Feed();
217		$feed->setTitle($title);
218		$feed->setDescription($desc);
219
220		if (! empty($prefs['feed_language'])) {
221			$feed->setLanguage($prefs['feed_language']);
222		}
223
224		$feed->setLink($tikilib->tikiUrl(''));
225		$feed->setFeedLink($feedLink, $feed_format_name);
226		$feed->setDateModified($tikilib->now);
227
228		if ($feed_format_name == 'atom') {
229			$author = [];
230
231			if (! empty($prefs['feed_atom_author_name'])) {
232				$author['name'] = $prefs['feed_atom_author_name'];
233			}
234			if (! empty($prefs['feed_atom_author_email'])) {
235				$author['email'] = $prefs['feed_atom_author_email'];
236			}
237			if (! empty($prefs['feed_atom_author_url'])) {
238				$author['url'] = $prefs['feed_atom_author_url'];
239			}
240
241			if (! empty($author)) {
242				if (empty($author['name'])) {
243					$msg = tra('If you set feed author email or URL, you must set feed author name.');
244					$smarty->assign('msg', $msg);
245					$smarty->display('error.tpl');
246					die;
247				}
248				$feed->addAuthor($author);
249			}
250		} else {
251			$authors = [];
252
253			if (! empty($prefs['feed_rss_editor_email'])) {
254				$authors['name'] = $prefs['feed_rss_editor_email'];
255			}
256			if (! empty($prefs['feed_rss_webmaster_email'])) {
257				$authors['name'] = $prefs['feed_rss_webmaster_email'];
258			}
259
260			if (! empty($authors)) {
261				$feed->addAuthors([$authors]);
262			}
263		}
264
265		if (! empty($prefs['feed_img'])) {
266			$image = [];
267			$image['uri'] = $tikilib->tikiUrl($prefs['feed_img']);
268			$image['title'] = tra('Feed logo');
269			$image['link'] = $tikilib->tikiUrl('');
270			$feed->setImage($image);
271		}
272
273		foreach ($changes["data"] as $data) {
274			$item = $feed->createEntry();
275			$item->setTitle($data[$titleId]);
276
277			if (isset($data['sefurl'])) {
278				$item->setLink($URLPrefix . urlencode($data['sefurl']));
279			} elseif ($urlparam != '') {			// 2 parameters to replace
280				$item->setlink(sprintf($read, urlencode($data["$id"]), urlencode($data["$urlparam"])));
281			} else {
282				$item->setLink(sprintf($read, urlencode($data["$id"])));
283			}
284
285			if (isset($data[$descId]) && $data[$descId] != '') {
286				$item->setDescription($data[$descId]);
287			}
288
289			$item->setDateCreated((int) $data[$dateId]);
290			$item->setDateModified((int) $data[$dateId]);
291
292			if ($authorId != '' && $prefs['feed_' . $section . '_showAuthor'] == 'y') {
293				$author = $this->process_item_author($data[$authorId]);
294				$item->addAuthor($author);
295			}
296
297			$feed->addEntry($item);
298		}
299
300		$data = $feed->export($feed_format_name);
301		$this->put_to_cache($uniqueid, $feed_format, $data);
302
303		$output = [];
304		$output["data"] = $data;
305		$output["content-type"] = 'application/xml';
306
307		return $output;
308	}
309
310	/**
311	 * Return information about the user acording to its preferences
312	 *
313	 * @param string $login
314	 * @return array author data (can be the login name or the realName if set and email if public)
315	 */
316	function process_item_author($login)
317	{
318		$userlib = TikiLib::lib('user');
319		$tikilib = TikiLib::lib('tiki');
320
321		$author = [];
322
323		if ($userlib->user_exists($login) && $tikilib->get_user_preference($login, 'user_information', 'private') == 'public') {
324			// if realName is not set use $login
325			$author['name'] = $tikilib->get_user_preference($login, 'realName', $login);
326
327			if ($tikilib->get_user_preference($login, 'email is public', 'n') != 'n') {
328				$res = $userlib->get_user_info($login, false);
329				$author['email'] = TikiMail::scrambleEmail($res['email']);
330			}
331		} else {
332			$author['name'] = $login;
333		}
334
335		return $author;
336	}
337
338	// --------------------------------------------
339	// functions for rss feeds syndicated by others
340	// --------------------------------------------
341
342	/* get (a part of) the list of existing rss feeds from db */
343	function list_rss_modules($offset = 0, $maxRecords = null, $sort_mode = 'name_asc', $find = '')
344	{
345		global $prefs;
346
347		$conditions = [];
348		if ($maxRecords === null) {
349			$maxRecords = $prefs['maxRecords'];
350		}
351		if ($find) {
352			$conditions['search'] = $this->modules->expr('(`name` LIKE ? OR `description` LIKE ?)', ["%$find%", "%$find%"]);
353		}
354
355		$ret = $this->modules->fetchAll($this->modules->all(), $conditions, $maxRecords, $offset, $this->modules->sortMode($sort_mode));
356
357		foreach ($ret as & $res) {
358			$res["minutes"] = $res["refresh"] / 60;
359		}
360
361		return [
362			'data' => $ret,
363			'cant' => $this->modules->fetchCount($conditions),
364		];
365	}
366
367	/* replace rss feed in db */
368	function replace_rss_module($rssId, $name, $description, $url, $refresh, $showTitle, $showPubDate, $noUpdate = false)
369	{
370		//if ($this->rss_module_name_exists($name)) return false; // TODO: Check the name
371		$refresh = 60 * $refresh;
372
373		$data = [
374				'name' => $name,
375				'description' => $description,
376				'refresh' => $refresh,
377				'url' => $url,
378				'showTitle' => $showTitle,
379				'showPubDate' => $showPubDate,
380				];
381
382		if ($rssId) {
383			$this->modules->update($data, ['rssId' => (int) $rssId,]);
384		} else {
385			$data['lastUpdated'] = 1;
386			$rssId = $this->modules->insert($data);
387		}
388
389		if (! $noUpdate) {
390			// Updating is normally required, except for cases where we know it will be updated later (e.g. after article generation is set, so that articles are created immediately)
391			$this->refresh_rss_module($rssId);
392		}
393		return $rssId;
394	}
395
396	/* remove rss feed from db */
397	function remove_rss_module($rssId)
398	{
399		$this->modules->delete(['rssId' => $rssId,]);
400		$this->items->deleteMultiple(['rssId' => $rssId,]);
401
402		return true;
403	}
404
405	/* read rss feed data from db */
406	function get_rss_module($rssId)
407	{
408		return $this->modules->fetchFullRow(['rssId' => $rssId]);
409	}
410
411	function refresh_rss_module($rssId)
412	{
413		$this->update_feeds([ $rssId ], true);
414	}
415
416	function refresh_all_rss_modules()
417	{
418		$mods = $this->list_rss_modules(0, -1);
419		$feeds = [];
420		foreach ($mods['data'] as $mod) {
421			$feeds[] = $mod['rssId'];
422		}
423		$this->update_feeds($feeds, true);
424	}
425
426	/**
427	 * @param int $rssId       feed id
428	 * @param int $olderThan   publication date more than than this number of seconds ago
429	 */
430	function clear_rss_cache($rssId, $olderThan = 0)
431	{
432		$conditions = ['rssId' => (int)$rssId];
433
434		if ($olderThan) {
435			$conditions['publication_date'] = $this->items->lesserThan(time() - $olderThan);
436		}
437
438		$this->items->deleteMultiple($conditions);
439	}
440
441	/* check if an rss feed name already exists */
442	function rss_module_name_exists($name)
443	{
444		return $this->modules->fetchCount(['name' => $name]);
445	}
446
447	/* get rss feed id by name */
448	function get_rss_module_id($name)
449	{
450		return $this->modules->fetchOne('rssId', ['name' => $name]);
451	}
452
453	/* check if 'showTitle' for an rss feed is enabled */
454	function get_rss_showTitle($rssId)
455	{
456		return $this->modules->fetchOne('showTitle', ['rssId' => $rssId]);
457	}
458
459	/* check if 'showPubdate' for an rss feed is enabled */
460	function get_rss_showPubDate($rssId)
461	{
462		return $this->modules->fetchOne('showPubDate', ['rssId' => $rssId]);
463	}
464
465	function get_feed_items($feeds, $count = 10)
466	{
467		$feeds = (array) $feeds;
468
469		$this->update_feeds($feeds);
470
471		return $this->items->fetchAll(
472			$this->items->all(),
473			['rssId' => $this->items->in($feeds),],
474			$count,
475			0,
476			['publication_date' => 'DESC']
477		);
478	}
479
480	private function update_feeds($feeds, $force = false)
481	{
482		global $tikilib;
483
484		$conditions = ['rssId' => $this->modules->in($feeds),];
485
486		if (! $force) {
487			$conditions['date'] = $this->modules->expr('`lastUpdated` < ? - `refresh`', [$tikilib->now]);
488		}
489
490		$result = $this->modules->fetchAll(['rssId', 'url', 'actions'], $conditions);
491
492		foreach ($result as $row) {
493			$this->update_feed($row['rssId'], $row['url'], $row['actions']);
494		}
495	}
496
497	private function update_feed($rssId, $url, $actions)
498	{
499		global $tikilib;
500
501		$filter = new DeclFilter;
502		$filter->addStaticKeyFilters(
503			[
504				'url' => 'url',
505				'title' => 'striptags',
506				'author' => 'striptags',
507				'description' => 'striptags',
508				'content' => 'purifier',
509			]
510		);
511
512		$guidFilter = TikiFilter::get('url');
513
514		try {
515			$content = $tikilib->httprequest($url);
516			$feed = Zend\Feed\Reader\Reader::importString($content);
517		} catch (Zend\Feed\Exception\ExceptionInterface $e) {
518			$this->modules->update(
519				[
520					'lastUpdated' => $tikilib->now,
521					'sitetitle' => 'N/A',
522					'siteurl' => '#',
523					],
524				['rssId' => $rssId,]
525			);
526			return;
527		}
528		$siteTitle = TikiFilter::get('striptags')->filter($feed->getTitle());
529		$siteUrl = TikiFilter::get('url')->filter($feed->getLink());
530
531		$this->modules->update(
532			[
533				'lastUpdated' => $tikilib->now,
534				'sitetitle' => $siteTitle,
535				'siteurl' => $siteUrl,
536				],
537			['rssId' => $rssId,]
538		);
539
540		foreach ($feed as $entry) { // TODO: optimize. Atom entries have an 'updated' element which can be used to only update updated entries
541			$guid = $guidFilter->filter($entry->getId());
542
543			$authors = $entry->getAuthors();
544
545			$categories = $entry->getCategories();
546
547			$link = $entry->getLink();
548			if (! $link) {
549				$link = '';
550			}
551			$description = $entry->getDescription();
552			if (! $description) {
553				$description = '';
554			}
555			$data = $filter->filter(
556				[
557					'title' => $entry->getTitle(),
558					'url' => $link,
559					'description' => $description,
560					'content' => $entry->getContent(),
561					'author' => $authors ? implode(', ', $authors->getValues()) : '',
562					'categories' => $categories ? json_encode($categories->getValues()) : json_encode([]),
563				]
564			);
565
566			$data['guid'] = $guid;
567			if (method_exists($entry, 'getDateCreated') && $createdDate = $entry->getDateCreated()) {
568				$data['publication_date'] = $createdDate->getTimestamp();
569			} else {
570				global $tikilib;
571				$data['publication_date'] = $tikilib->now;
572			}
573
574			$count = $this->items->fetchCount(['rssId' => $rssId, 'guid' => $guid]);
575			if (0 == $count) {
576				$this->insert_item($rssId, $data, $actions);
577			} else {
578				$this->update_item($rssId, $data['guid'], $data);
579			}
580		}
581	}
582
583	function get_feed_source_categories($rssId)
584	{
585		$feeds = $this->items->fetchAll(['categories'], ['rssId' => $rssId]);
586		$categories = [];
587		foreach ($feeds as $feed) {
588			if (isset($feed['categories'])) {
589				foreach (json_decode($feed['categories']) as $sourcecat) {
590					 $categories[$sourcecat] = [];
591				}
592			}
593		}
594		$custom_info = $this->get_article_custom_info($rssId);
595		$categories = array_merge($categories, $custom_info);
596		ksort($categories, SORT_NATURAL);
597		return $categories;
598	}
599
600	function get_article_custom_info($rssId)
601	{
602		$result = $this->modules->fetchOne('actions', ['rssId' => $rssId]);
603		$actions = json_decode($result);
604		$categories = [];
605		foreach ($actions as $action) {
606			if (isset($action->custom_atype)) {
607				foreach ($action->custom_atype as $source_category => $atype) {
608					$categories[$source_category]['atype'] = $atype;
609				}
610			}
611			if (isset($action->custom_topic)) {
612				foreach ($action->custom_topic as $source_category => $topic) {
613					$categories[$source_category]['topic'] = $topic;
614				}
615			}
616			if (isset($action->custom_rating)) {
617				foreach ($action->custom_rating as $source_category => $rating) {
618					$categories[$source_category]['rating'] = $rating;
619				}
620			}
621			if (isset($action->custom_priority)) {
622				foreach ($action->custom_priority as $source_category => $priority) {
623					$categories[$source_category]['priority'] = $priority;
624				}
625			}
626		}
627		return $categories;
628	}
629
630	private function insert_item($rssId, $data, $actions)
631	{
632		$this->items->insert(
633			[
634				'rssId' => $rssId,
635				'guid' => $data['guid'],
636				'url' => $data['url'],
637				'publication_date' => $data['publication_date'],
638				'title' => $data['title'],
639				'author' => $data['author'],
640				'description' => $data['description'],
641				'content' => $data['content'],
642				'categories' => $data['categories'],
643			]
644		);
645
646		$actions = json_decode($actions, true);
647
648		if (! empty($actions)) {
649			$pagecontentlib = TikiLib::lib('pagecontent');
650			$data = $pagecontentlib->augmentInformation($data);
651
652			foreach ($actions as $action) {
653				$method = 'process_action_' . $action['type'];
654				unset($action['type']);
655
656				if ($action['active']) {
657					$this->$method($action, $data, $rssId);
658				}
659			}
660		}
661	}
662
663	private function update_item($rssId, $guid, $data)
664	{
665		// A feed may contain several entries with the same GUID... see http://framework.zend.com/issues/browse/ZF-10954. Assuming a single record would actually cause issues, see r37318.
666		$this->items->updateMultiple(
667			['rssId' => $rssId, 'guid' => $guid,],
668			[
669				'url' => $data['url'],
670				'publication_date' => $data['publication_date'],
671				'title' => $data['title'],
672				'author' => $data['author'],
673				'description' => $data['description'],
674				'content' => $data['content'],
675			]
676		);
677	}
678
679	private function process_action_article($configuration, $data, $rssId)
680	{
681		$tikilib = TikiLib::lib('tiki');
682		$artlib = TikiLib::lib('art');
683		$publication = $data['publication_date'];
684
685		// First override with custom settings for source categories if any
686		if (isset($data['categories'])) {
687			$source_categories = json_decode($data['categories'], true);
688		}
689		if (! empty($source_categories)) {
690			$custominfo = $this->get_article_custom_info($rssId);
691
692			$oldcats = array_keys($custominfo);
693
694			if ($newcats = array_diff($source_categories, $oldcats)) {
695				// send a notification if there are new categories
696				$nots = $tikilib->get_event_watches('article_submitted', '*');
697				if (count($nots)) {
698					$title = $this->modules->fetchOne('name', ['rssId' => $rssId]);
699					include_once('lib/notifications/notificationemaillib.php');
700					$smarty = TikiLib::lib('smarty');
701					$smarty->assign('mail_site', $_SERVER['SERVER_NAME']);
702					$smarty->assign('rssId', $rssId);
703					$smarty->assign('title', $title);
704					$smarty->assign('newcats', $newcats);
705					sendEmailNotification($nots, 'watch', 'rss_new_source_category_subject.tpl', $_SERVER['SERVER_NAME'], 'rss_new_source_category.tpl');
706				}
707			}
708
709			$current_priority = 0;
710			foreach ($custominfo as $source_category => $settings) {
711				if (in_array($source_category, $source_categories)) {
712					if (isset($settings['priority']) && $settings['priority'] < $current_priority) {
713						continue;
714					}
715					if (! empty($settings['atype'])) {
716						$configuration['atype'] = $settings['atype'];
717					}
718					if (isset($settings['topic']) && $settings['topic'] > '') {
719						// need to match 0
720						$configuration['topic'] = $settings['topic'];
721					}
722					if (isset($settings['rating']) && $settings['rating'] > '') {
723						// need to match 0
724						$configuration['rating'] = $settings['rating'];
725					}
726					$current_priority = isset($settings['priority']) ? $settings['priority'] : 0;
727				}
728			}
729		}
730
731		if ($configuration['future_publish'] > 0) {
732			$publication = $tikilib->now + $configuration['future_publish'] * 60;
733		}
734
735		$expire = $publication + 3600 * 24 * $configuration['expiry'];
736
737		if (strpos($data['content'], trim($data['description'])) === 0 && strlen($data['description']) < 1024) {
738			$data['content'] = substr($data['content'], strlen(trim($data['description'])));
739		}
740		$data['content'] = trim($data['content']) == '' ? $data['content'] : '~np~' . $data['content'] . '~/np~';
741
742		if ($configuration['submission'] == true) {
743			$subid = $this->table('tiki_submissions')->fetchOne('subId', [
744				'linkto' => $data['url'],
745				'topicId' => $configuration['topic'],
746			]);
747			if (! $subid) {
748				$subid = 0;
749			}
750			$subid = $artlib->replace_submission(
751				$data['title'],
752				$data['author'],
753				$configuration['topic'],
754				'n',
755				'',
756				0,
757				'',
758				'',
759				$data['description'],
760				$data['content'],
761				$publication,
762				$expire,
763				'admin',
764				$subid,
765				0,
766				0,
767				$configuration['atype'],
768				'',
769				'',
770				$data['url'],
771				'',
772				$configuration['a_lang'],
773				$configuration['rating']
774			);
775
776			if (count($configuration['categories'])) {
777				$categlib = TikiLib::lib('categ');
778				$objectId = $categlib->add_categorized_object('submission', $subid, $data['title'], $data['title'], 'tiki-edit_submission.php?subId=' . $subid);
779
780				foreach ($configuration['categories'] as $categId) {
781					$categlib->categorize($objectId, $categId);
782				}
783			}
784		} else {
785			$id = $this->table('tiki_articles')->fetchOne('articleId', [
786				'linkto' => $data['url'],
787				'topicId' => $configuration['topic'],
788			]);
789			if (! $id) {
790				$id = 0;
791			}
792			$id = $artlib->replace_article(
793				$data['title'],
794				$data['author'],
795				$configuration['topic'],
796				'n',
797				'',
798				0,
799				'',
800				'',
801				$data['description'],
802				$data['content'],
803				$publication,
804				$expire,
805				'admin',
806				$id,
807				0,
808				0,
809				$configuration['atype'],
810				'',
811				'',
812				$data['url'],
813				'',
814				$configuration['a_lang'],
815				$configuration['rating']
816			);
817
818			if (count($configuration['categories'])) {
819				$categlib = TikiLib::lib('categ');
820				$objectId = $categlib->add_categorized_object('article', $id, $data['title'], $data['title'], 'tiki-read_article.php?articleId=' . $id);
821
822				foreach ($configuration['categories'] as $categId) {
823					$categlib->categorize($objectId, $categId);
824				}
825			}
826
827			TikiLib::lib('relation')->add_relation('tiki.rss.source', 'article', $id, 'rss', $rssId);
828			require_once('lib/search/refresh-functions.php');
829			refresh_index('articles', $id);
830			$related_items = TikiLib::lib('relation')->get_relations_to('article', $id, 'tiki.article.attach');
831			foreach ($related_items as $item) {
832				refresh_index($item['type'], $item['itemId']);
833			}
834		}
835	}
836
837	function set_article_generator($rssId, $configuration)
838	{
839		$configuration['type'] = 'article';
840
841		$module = $this->get_rss_module($rssId);
842
843		if ($module['actions']) {
844			$actions = json_decode($module['actions'], true);
845		} else {
846			$actions = [];
847		}
848
849		$out = [];
850		foreach ($actions as $action) {
851			if ($action['type'] != 'article') {
852				$out[] = $action;
853			}
854		}
855
856		$out[] = $configuration;
857
858		$this->modules->update(
859			['actions' => json_encode($out),],
860			['rssId' => $rssId,]
861		);
862	}
863
864	function get_article_generator($rssId)
865	{
866		$module = $this->get_rss_module($rssId);
867
868		if ($module['actions']) {
869			$actions = json_decode($module['actions'], true);
870		} else {
871			$actions = [];
872		}
873
874		$default = [
875				'active' => false,
876				'expiry' => 365,
877				'atype' => 'Article',
878				'topic' => 0,
879				'future_publish' => -1,
880				'categories' => [],
881				'rating' => 5,
882				'feed_name' => $module['name'],
883				];
884
885		foreach ($actions as $action) {
886			if ($action['type'] == 'article') {
887				unset($action['type']);
888				return array_merge($default, $action);
889			}
890		}
891
892		return $default;
893	}
894
895	function generate_feed_from_data($data, $feed_descriptor)
896	{
897		require_once 'lib/smarty_tiki/modifier.sefurl.php';
898
899		$tikilib = TikiLib::lib('tiki');
900		$writer = new Zend\Feed\Writer\Feed();
901		$writer->setTitle($feed_descriptor['feedTitle']);
902		$writer->setDescription($feed_descriptor['feedDescription']);
903		$writer->setLink($tikilib->tikiUrl(''));
904		$writer->setDateModified(time());
905
906		foreach ($data as $row) {
907			$titleKey = $feed_descriptor['entryTitleKey'];
908			$url = $row[$feed_descriptor['entryUrlKey']];
909			$title = $row[$titleKey];
910
911			if (isset($feed_descriptor['entryObjectDescriptors'])) {
912				list($typeKey, $objectKey) = $feed_descriptor['entryObjectDescriptors'];
913				$object = $row[$objectKey];
914				$type = $row[$typeKey];
915
916				if (empty($url)) {
917					$url = smarty_modifier_sefurl($object, $type);
918				}
919
920				if (empty($title)) {
921					$title = TikiLib::lib('object')->get_title($type, $object);
922				}
923			}
924
925			$entry = $writer->createEntry();
926			$entry->setTitle($title ? $title : tra('Unspecified'));
927			$entry->setLink($tikilib->tikiUrl($url));
928
929
930			if (! empty($row[$feed_descriptor['entryModificationKey']])) {
931				$date = $row[$feed_descriptor['entryModificationKey']];
932				if (! is_int($date)) {
933					$date = strtotime($date);
934				}
935				$entry->setDateModified($date);
936			}
937
938			$writer->addEntry($entry);
939		}
940
941		return $writer;
942	}
943}
944