1<?php
2require_once(SERVERPATH . '/' . ZENFOLDER . '/template-functions.php');
3
4/**
5 *
6 * Base feed class from which all others descend.
7 *
8 * Plugins will set the <var>feedtype</var> property to the feed desired
9 * <ul>
10 * 	<li>gallery</li>
11 * 	<li>news</li>
12 * 	<li>pages</li>
13 * 	<li>comments</li>
14 * </ul>
15 *
16 * Feed details are determined by the <var>option</var> property.
17 * Elements of this array and their meaning follow:
18 * <ul>
19 * 	<li>lang
20 * 		<ul>
21 * 			<li><i>locale</i></li>
22 * 		</ul>
23 * 	</li>
24 * 	<li>sortdir
25 * 		<ul>
26 * 			<li>desc (default) for descending order</li>
27 * 			<li>asc for ascending order</li>
28 * 		</ul>
29 * 	</li>
30 * 	<li>sortorder</li>
31 * 		<ul>
32 * 			<li><i>latest</i> (default) for the latest uploaded by id (discovery order)</li>
33 * 			<li><i>latest-date</i> for the latest fetched by date</li>
34 * 			<li><i>latest-mtime</i> for the latest fetched by mtime</li>
35 * 			<li><i>latest-publishdate</i> for the latest fetched by publishdate</li>
36 * 			<li><i>popular</i> for the most popular albums</li>
37 * 			<li><i>topratedv for the best voted</li>
38 * 			<li><i>mostrated</i> for the most voted</li>
39 * 			<li><i>random</i> for random order</li>
40 * 			<li><i>id</i> internal <var>id</var> order</li>
41 * 		</ul>
42 * 	</li>
43 * 	<li>albumname</li>
44 * 	<li>albumsmode</li>
45 * 	<li>folder</li>
46 * 	<li>size</li>
47 * 	<li>category</li>
48 * 	<il>id</li>
49 * 	<li>itemnumber</li>
50 * 	<li>type (for comments feed)
51 * 		<ul>
52 * 			<li>albums</li>
53 * 			<li>images</li>
54 * 			<li>pages</li>
55 * 			<li>news</li>
56 * 		</ul>
57 * 	</li>
58 * </ul>
59 *
60 *
61 * @package core
62 * @subpackage classes\objects
63 */
64class feed {
65
66	protected $feed = 'feed'; //	feed type
67	protected $mode; //	feed mode
68	protected $options; // This array will store the options for the feed.
69	//general feed type gallery, news or comments
70	protected $feedtype = NULL;
71	protected $itemnumber = NULL;
72	protected $locale = NULL; // standard locale for lang parameter
73	protected $locale_xml = NULL; // xml locale within feed
74	protected $host = NULL;
75	protected $sortorder = NULL;
76	protected $sortdirection = NULL;
77	//gallery feed specific vars
78	protected $albumfolder = NULL;
79	protected $collection = NULL;
80	protected $albumpath = NULL;
81	protected $imagepath = NULL;
82	protected $imagesize = NULL;
83	protected $modrewritesuffix = NULL;
84	// Zenpage news feed specific
85	protected $catlink = NULL;
86	protected $cattitle = NULL;
87	protected $newsoption = NULL;
88	protected $titleappendix = NULL;
89	//comment feed specific
90	protected $id = NULL;
91	protected $commentfeedtype = NULL;
92	protected $itemobj = NULL; // if comments for an item its object
93	//channel vars
94	protected $channel_title = NULL;
95	protected $feeditem = array();
96
97	/**
98	 * Creates a file name from the options array
99	 *
100	 * @return string
101	 */
102	protected function getCacheFilename() {
103		$filename = array();
104		foreach ($this->options as $key => $value) {
105			if (empty($value) || $key == 'albumsmode') { // supposed to be empty always
106				$filename[] = $key;
107			} else {
108				$filename[] = $value;
109			}
110		}
111		$filename = seoFriendly(implode('_', $filename));
112		return $filename . ".xml";
113	}
114
115	/**
116	 * Starts static caching
117	 *
118	 */
119	protected function startCache() {
120		$caching = getOption($this->feed . "_cache") && !zp_loggedin();
121		if ($caching) {
122			$cachefilepath = SERVERPATH . '/' . STATIC_CACHE_FOLDER . '/' . strtolower($this->feed) . '/' . internalToFilesystem($this->getCacheFilename());
123			if (file_exists($cachefilepath) AND time() - filemtime($cachefilepath) < getOption($this->feed . "_cache_expire")) {
124				echo file_get_contents($cachefilepath);
125				exitZP();
126			} else {
127				if (file_exists($cachefilepath)) {
128					@chmod($cachefilepath, 0777);
129					@unlink($cachefilepath);
130				}
131				ob_start();
132			}
133		}
134	}
135
136	/**
137	 * Ends the static caching.
138	 *
139	 */
140	protected function endCache() {
141		$caching = getOption($this->feed . "_cache") && !zp_loggedin();
142		if ($caching) {
143			$cachefilepath = internalToFilesystem($this->getCacheFilename());
144			if (!empty($cachefilepath)) {
145				$cachefilepath = SERVERPATH . '/' . STATIC_CACHE_FOLDER . '/' . strtolower($this->feed) . '/' . $cachefilepath;
146				mkdir_recursive(SERVERPATH . '/' . STATIC_CACHE_FOLDER . '/' . strtolower($this->feed) . '/', FOLDER_MOD);
147				$pagecontent = ob_get_contents();
148				ob_end_clean();
149				if ($fh = @fopen($cachefilepath, "w")) {
150					fputs($fh, $pagecontent);
151					fclose($fh);
152					clearstatcache();
153				}
154				echo $pagecontent;
155			}
156		}
157	}
158
159	/**
160	 * Cleans out the cache folder
161	 *
162	 * @param string $cachefolder the sub-folder to clean
163	 */
164	function clearCache($cachefolder = NULL) {
165		removeDir(SERVERPATH . '/' . STATIC_CACHE_FOLDER . '/' . strtolower($this->feed) . '/' . $cachefolder, true);
166	}
167
168	function __construct($options) {
169		$this->options = $options;
170		$invalid_options = array();
171		$this->locale = $this->getLang();
172		$this->locale_xml = strtr($this->locale, '_', '-');
173		$this->sortdirection = $this->getSortdir();
174		$this->sortorder = $this->getSortorder();
175		switch ($this->feedtype) {
176			case 'comments':
177				$this->commentfeedtype = $this->getCommentFeedType();
178				$this->id = $this->getId();
179				$invalid_options = array('albumsmode', 'folder', 'albumname', 'category', 'size');
180				break;
181			case 'gallery':
182				if (isset($this->options['albumsmode'])) {
183					$this->mode = 'albums';
184				}
185				if (isset($this->options['folder'])) {
186					$this->albumfolder = $this->getAlbum('folder');
187					$this->collection = true;
188				} else if (isset($this->options['albumname'])) {
189					$this->albumfolder = $this->getAlbum('albumname');
190					$this->collection = false;
191				} else {
192					$this->collection = false;
193				}
194				if (is_null($this->sortorder)) {
195					if ($this->mode == "albums") {
196						$this->sortorder = getOption($this->feed . "_sortorder_albums");
197					} else {
198						$this->sortorder = getOption($this->feed . "_sortorder");
199					}
200				}
201				$this->imagesize = $this->getImageSize();
202				$invalid_options = array('id', 'type', 'category');
203				break;
204			case 'news':
205				if ($this->sortorder == 'latest') {
206					$this->sortorder = NULL;
207				}
208				$this->catlink = $this->getCategory();
209				if (!empty($this->catlink)) {
210					$catobj = new ZenpageCategory($this->catlink);
211					$this->cattitle = $catobj->getTitle();
212					$this->newsoption = 'category';
213				} else {
214					$this->catlink = '';
215					$this->cattitle = '';
216					$this->newsoption = 'news';
217				}
218				$invalid_options = array('folder', 'albumname', 'albumsmode', 'type', 'id', 'size');
219				break;
220			case 'pages':
221				$invalid_options = array('folder', 'albumname', 'albumsmode', 'type', 'id', 'category', 'size');
222				break;
223			case 'null': //we just want the class instantiated
224				return;
225		}
226		$this->unsetOptions($invalid_options); // unset invalid options that this feed type does not support
227		if (isset($this->options['itemnumber'])) {
228			$this->itemnumber = (int) $this->options['itemnumber'];
229		} else {
230			$this->itemnumber = getOption($this->feed . '_items');
231		}
232	}
233
234	/**
235	 * Validates and gets the "lang" parameter option value
236	 *
237	 * @global array $_zp_active_languages
238	 * @return string
239	 */
240	protected function getLang() {
241		if (isset($this->options['lang'])) {
242			$langs = generateLanguageList();
243			$valid = array_values($langs);
244			if (in_array($this->options['lang'], $valid)) {
245				return $this->options['lang'];
246			}
247		}
248		return getOption('locale');
249	}
250
251	/**
252	 * Validates and gets the "sortdir" parameter option value
253	 *
254	 * @return bool
255	 */
256	protected function getSortdir() {
257		$valid = array('desc', 'asc');
258		if (isset($this->options['sortdir']) && in_array($this->options['sortdir'], $valid)) {
259			return strtolower($this->options['sortdir']) != 'asc';
260		}
261		$this->options['sortdir'] = 'desc'; // make sure this is a valid default name
262		return true;
263	}
264
265	/**
266	 * Validates and gets the "sortorder" parameter option value
267	 *
268	 * @return string
269	 */
270	protected function getSortorder() {
271		if (isset($this->options['sortorder'])) {
272			$valid = array('latest', 'latest-date', 'latest-mtime', 'latest-publishdate', 'popular', 'toprated', 'mostrated', 'random', 'id');
273			if (in_array($this->options['sortorder'], $valid)) {
274				$this->options['sortdir'] = $this->options['sortorder']; // make sure this is a valid default name
275				return $this->options['sortorder'];
276			} else {
277				$this->unsetOptions(array('sortorder'));
278			}
279		}
280		return null;
281	}
282
283	/**
284	 * Validates and gets the "type" parameter option value for comment feeds
285	 *
286	 * @return string
287	 */
288	protected function getCommentFeedType() {
289		$valid = false;
290		if (isset($this->options['type'])) {
291			$valid = array( 'all', 'album' , 'image', 'news', 'page');
292			if (in_array($this->options['type'], $valid)) {
293				return $this->options['type'];
294			}
295		}
296		return 'all';
297	}
298
299	/**
300	 * Validates and gets the "id" parameter option value for comments feeds of a specific item
301	 *
302	 * @return int
303	 */
304	protected function getID() {
305		if (isset($this->options['id'])) {
306			$type = $this->getCommentFeedType();
307			$table = '';
308			if ($type != 'all') {
309				switch ($this->commentfeedtype) {
310					case 'album':
311						$table = 'albums';
312						break;
313					case 'image':
314						$table = 'images';
315						break;
316					case 'news':
317						$table = 'news';
318						break;
319					case 'page':
320						$table = 'pages';
321						break;
322				}
323				if ($table) {
324					$id = (int) $this->options['id'];
325					$result = query_single_row('SELECT `id` FROM ' . prefix($table) . ' WHERE id =' . $id);
326					if ($result) {
327						return $id;
328					}
329				}
330			}
331		}
332		$this->unsetOptions(array('id'));
333		return '';
334	}
335
336	/**
337	 * Validates and gets the "folder" or 'albumname" parameter option value
338	 * @param string $option "folder" or "albumname"
339	 * @return int
340	 */
341	protected function getAlbum($option) {
342		if (in_array($option, array('folder', 'albumname')) && isset($this->options[$option])) {
343			$albumobj = newAlbum($this->options[$option], true, true);
344			if ($albumobj->exists) {
345				return $this->options[$option];
346			}
347		}
348		$this->unsetOptions(array($option));
349		return '';
350	}
351
352	/**
353	 * Validates and gets the "category" parameter option value
354	 *
355	 * @return int
356	 */
357	protected function getCategory() {
358		if (isset($this->options['category']) && class_exists('ZenpageCategory')) {
359			$catobj = new ZenpageCategory($this->options['category']);
360			if ($catobj->exists) {
361				return $this->options['category'];
362			}
363		}
364		$this->unsetOptions(array('category'));
365		return '';
366	}
367
368	/**
369	 * Helper function that gets the images size of the "size" get parameter
370	 *
371	 * @return string
372	 */
373	protected function getImageSize() {
374		if (isset($this->options['size'])) {
375			$imagesize = (int) $this->options['size'];
376		} else {
377			$imagesize = NULL;
378		}
379		if ($this->mode == 'albums') {
380			if (is_null($imagesize) || $imagesize > getOption($this->feed . '_imagesize_albums')) {
381				$imagesize = getOption($this->feed . '_imagesize_albums'); // un-cropped image size
382			}
383		} else {
384			if (is_null($imagesize) || $imagesize > getOption($this->feed . '_imagesize')) {
385				$imagesize = getOption($this->feed . '_imagesize'); // un-cropped image size
386			}
387		}
388		return $imagesize;
389	}
390
391	/**
392	 * Unsets certain option name indices from the $options property.
393	 * @param array $options Array of option (parameter) names to be unset
394	 */
395	protected function unsetOptions($options = null) {
396		if (!empty($options)) {
397			foreach ($options as $option) {
398				unset($this->options[$option]);
399			}
400		}
401	}
402
403	protected function getChannelTitleExtra() {
404		switch ($this->sortorder) {
405			default:
406			case 'latest':
407			case 'latest-date':
408			case 'latest-mtime':
409			case 'latest-publishdate':
410				if ($this->mode == 'albums') {
411					$albumextra = ' (' . gettext('Latest albums') . ')'; //easier to understand for translators as if I would treat "images"/"albums" in one place separately
412				} else {
413					$albumextra = ' (' . gettext('Latest images') . ')';
414				}
415				break;
416			case 'latestupdated':
417				$albumextra = ' (' . gettext('latest updated albums') . ')';
418				break;
419			case 'popular':
420				if ($this->mode == 'albums') {
421					$albumextra = ' (' . gettext('Most popular albums') . ')';
422				} else {
423					$albumextra = ' (' . gettext('Most popular images') . ')';
424				}
425				break;
426			case 'toprated':
427				if ($this->mode == 'albums') {
428					$albumextra = ' (' . gettext('Top rated albums') . ')';
429				} else {
430					$albumextra = ' (' . gettext('Top rated images') . ')';
431				}
432				break;
433			case 'random':
434				if ($this->mode == 'albums') {
435					$albumextra = ' (' . gettext('Random albums') . ')';
436				} else {
437					$albumextra = ' (' . gettext('Random images') . ')';
438				}
439				break;
440		}
441		return $albumextra;
442	}
443
444	/**
445	 * Gets the feed items
446	 *
447	 * @return array
448	 */
449	public function getitems() {
450		global $_zp_zenpage;
451		switch ($this->feedtype) {
452			case 'gallery':
453				if ($this->mode == "albums") {
454					$items = getAlbumStatistic($this->itemnumber, $this->sortorder, $this->albumfolder, $this->sortdirection);
455				} else {
456					$items = getImageStatistic($this->itemnumber, $this->sortorder, $this->albumfolder, $this->collection, 0, $this->sortdirection);
457				}
458				break;
459			case 'news':
460				switch ($this->newsoption) {
461					case "category":
462						if ($this->sortorder) {
463							$items = getZenpageStatistic($this->itemnumber, 'categories', $this->sortorder, $this->sortdirection);
464						} else {
465							$items = getLatestNews($this->itemnumber, $this->catlink, false, $this->sortdirection);
466						}
467						break;
468					default:
469					case "news":
470						if ($this->sortorder) {
471							$items = getZenpageStatistic($this->itemnumber, 'news', $this->sortorder, $this->sortdirection);
472						} else {
473							// Needed baceause type variable "news" is used by the feed item method and not set by the class method getArticles!
474							$items = getLatestNews($this->itemnumber, '', false, $this->sortdirection);
475						}
476						break;
477				}
478				break;
479			case "pages":
480				if ($this->sortorder) {
481					$items = getZenpageStatistic($this->itemnumber, 'pages', $this->sortorder, $this->sortdirection);
482				} else {
483					$items = $_zp_zenpage->getPages(NULL, false, $this->itemnumber);
484				}
485				break;
486			case 'comments':
487				switch ($this->commentfeedtype) {
488					case 'album':
489						$items = getLatestComments($this->itemnumber, 'album', $this->id);
490						break;
491					case 'image':
492						$items = getLatestComments($this->itemnumber, 'image', $this->id);
493						break;
494					case 'news':
495					case 'page':
496						if (function_exists('getLatestZenpageComments')) {
497							$items = getLatestZenpageComments($this->itemnumber, $this->commentfeedtype, $this->id);
498						}
499						break;
500					case 'gallery':
501					case 'allcomments':
502					case 'all':
503						$items = getLatestComments($this->itemnumber, 'all');
504						$items_zenpage = array();
505						if (function_exists('getLatestZenpageComments')) {
506							$items_zenpage = getLatestZenpageComments($this->itemnumber, 'all', $this->id);
507							$items = array_merge($items, $items_zenpage);
508							$items = sortMultiArray($items, 'date', true);
509							$items = array_slice($items, 0, $this->itemnumber);
510						}
511						break;
512				}
513				break;
514		}
515		if (isset($items)) {
516			return $items;
517		}
518		if (TEST_RELEASE) {
519			trigger_error(gettext('Bad ' . $this->feed . ' feed respectively no items available:' . $this->feedtype), E_USER_WARNING);
520		}
521		return NULL;
522	}
523
524	/**
525	 * Gets the feed item data in a Zenpage news feed
526	 *
527	 * @param array $item Titlelink a Zenpage article or filename of an image if a combined feed
528	 * @return array
529	 */
530	protected function getitemPages($item, $len) {
531		$obj = new ZenpagePage($item['titlelink']);
532		$feeditem['title'] = $feeditem['title'] = get_language_string($obj->getTitle('all'), $this->locale);
533		$feeditem['link'] = $obj->getLink();
534		$desc = $obj->getContent($this->locale);
535		$desc = str_replace('//<![CDATA[', '', $desc);
536		$desc = str_replace('//]]>', '', $desc);
537		$feeditem['desc'] = shortenContent($desc, $len, '...');
538		$feeditem['enclosure'] = '';
539		$feeditem['category'] = '';
540		$feeditem['media_content'] = '';
541		$feeditem['media_thumbnail'] = '';
542		$feeditem['pubdate'] = date("r", strtotime($obj->getDatetime()));
543		return zp_apply_filter('feed_page', $feeditem, $obj);
544	}
545
546	/**
547	 * Gets the feed item data in a comments feed
548	 *
549	 * @param array $item Array of a comment
550	 * @return array
551	 */
552	protected function getitemComments($item) {
553		if ($item['anon']) {
554			$author = "";
555		} else {
556			$author = " " . gettext("by") . " " . $item['name'];
557		}
558		$commentpath = $imagetag = $title = '';
559		$obj = null;
560		switch ($item['type']) {
561			case 'images':
562				$title = get_language_string($item['title']);
563				$obj = newImage(NULL, array('folder' => $item['folder'], 'filename' => $item['filename']));
564				$link = $obj->getlink();
565				$feeditem['pubdate'] = date("r", strtotime($item['date']));
566				$category = get_language_string($item['albumtitle']);
567				$website = $item['website'];
568				$title = $category . ": " . $title;
569				$commentpath = PROTOCOL . '://' . $this->host . $link . "#zp_comment_id_" . $item['id'];
570				break;
571			case 'albums':
572				$obj = newAlbum($item['folder']);
573				$link = rtrim($obj->getLink(), '/');
574				$feeditem['pubdate'] = date("r", strtotime($item['date']));
575				$title = get_language_string($item['albumtitle']);
576				$website = $item['website'];
577				$commentpath = PROTOCOL . '://' . $this->host . $link . "#zp_comment_id_" . $item['id'];
578				break;
579			case 'news':
580			case 'pages':
581				if (extensionEnabled('zenpage')) {
582					$album = '';
583					$feeditem['pubdate'] = date("r", strtotime($item['date']));
584					$category = '';
585					$title = get_language_string($item['title']);
586					$titlelink = $item['titlelink'];
587					$website = $item['website'];
588					if ($item['type'] == 'news') {
589						$obj = new ZenpageNews($titlelink);
590					} else {
591						$obj = new ZenpagePage($titlelink);
592					}
593					$commentpath = PROTOCOL . '://' . $this->host . html_encode($obj->getLink()) . "#zp_comment_id_" . $item['id'];
594				} else {
595					$commentpath = '';
596				}
597
598				break;
599		}
600		$feeditem['title'] = getBare($title . $author);
601		$feeditem['link'] = $commentpath;
602		$feeditem['desc'] = $item['comment'];
603		return zp_apply_filter('feed_comment', $feeditem, $item, $obj);
604	}
605
606	static protected function feed404() {
607		header("HTTP/1.0 404 Not Found");
608		header("Status: 404 Not Found");
609		include(SERVERPATH . '/' . ZENFOLDER . '/404.php');
610		exitZP();
611	}
612
613}