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
8function wikiplugin_articles_info()
9{
10	global $prefs;
11	return [
12		'name' => tra('Article List'),
13		'documentation' => 'PluginArticles',
14		'description' => tra('Display multiple articles'),
15		'prefs' => [ 'feature_articles', 'wikiplugin_articles' ],
16		'iconname' => 'articles',
17		'tags' => [ 'basic' ],
18		'introduced' => 1,
19		'params' => [
20			'usePagination' => [
21				'required' => false,
22				'name' => tra('Use Pagination'),
23				'description' => tr('Activate pagination when the articles list is long. Default is %0', '<code>n</code>'),
24				'filter' => 'alpha',
25				'default' => 'n',
26				'since' => '1',
27				'options' => [
28					['text' => '', 'value' => ''],
29					['text' => tra('Yes'), 'value' => 'y'],
30					['text' => tra('No'), 'value' => 'n']
31				],
32			],
33			'max' => [
34				'required' => false,
35				'name' => tra('Maximum Displayed'),
36				'description' => tr('The number of articles to display in the list (use %0 to show all)', '<code>-1</code>'),
37				'filter' => 'int',
38				'since' => '1',
39				'default' => $prefs['maxRecords'],
40			],
41			'topic' => [
42				'required' => false,
43				'name' => tra('Topic Name Filter'),
44				'description' => tra('Filter the list of articles by topic. Example: ') . '<code>[!]topic+topic+topic</code>',
45				'filter' => 'text',
46				'since' => '1',
47				'default' => '',
48			],
49			'topicId' => [
50				'required' => false,
51				'name' => tra('Topic ID Filter'),
52				'description' => tra('Filter the list of articles by topic ID. Example: ') . '<code>[!]topicId+topicId+topicId</code>',
53				'filter' => 'text',
54				'accepted' => tra('Valid topic IDs'),
55				'default' => '',
56				'profile_reference' => 'article_topic',
57				'since' => '2.0',
58			],
59			'type' => [
60				'required' => false,
61				'name' => tra('Type Filter'),
62				'description' => tra('Filter the list of articles by types. Example: ') . '<code>[!]type+type+type</code>',
63				'filter' => 'text',
64				'since' => '1',
65				'accepted' => tra('Valid article types'),
66				'default' => '',
67				'profile_reference' => 'article_type',
68			],
69			'categId' => [
70				'required' => false,
71				'name' => tra('Category ID'),
72				'description' => tra('List of category IDs, separated by "%0". Only articles in all these categories are
73					listed', '<code>|</code>'),
74				'filter' => 'digits',
75				'default' => '',
76				'profile_reference' => 'category',
77				'since' => '1',
78				'separator' => '|',
79			],
80			'lang' => [
81				'required' => false,
82				'name' => tra('Language'),
83				'description' => tra('List only articles in this language'),
84				'filter' => 'lang',
85				'since' => '1',
86				'default' => '',
87			],
88			'sort' => [
89				'required' => false,
90				'name' => tra('Sort order'),
91				'description' => tr('The column and order of the sort in %0columnName_asc%1 or %0columnName_desc%1 format.
92					Defaults to %0publishDate_desc%1 (other column examples are %0title%1, %0lang%1, %0articleId%1,
93					%0authorName%1 & %0topicName%1). Use "random" to have random items.', '<code>', '</code>'),
94				'filter' => 'word',
95				'default' => 'publishDate_desc',
96				'since' => '2.0',
97				'accepted' => tra('random or column names to add _asc _desc to: ')
98					. 'created, author, title, publishDate, expireDate, articleId, topline, subtitle, lang, linkto, authorName, topicId, topicName, state, size, heading, body, isfloat, useImage, image_name, image_caption, image_type, image_size, image_x, image_y, image_data, list_image_x, list_image_y, nbreads, votes, points, type, rating, ispublished'],
99			'order' => [
100				'required' => false,
101				'name' => tra('Specific order'),
102				'description' => tra('List of ArticleId that must appear in this order if present'),
103				'filter' => 'digits',
104				'separator' => '|',
105				'default' => '',
106				'since' => '9.0',
107			],
108			'articleId' => [
109				'required' => false,
110				'name' => tra('Only these articles'),
111				'description' => tr('List of article IDs to display, separated by "%0"', '<code>|</code>'),
112				'filter' => 'digits',
113				'separator' => '|',
114				'default' => '',
115				'profile_reference' => 'article',
116				'since' => '9.0',
117			],
118			'notArticleId' => [
119				'required' => false,
120				'name' => tra('Not these articles'),
121				'description' => tra('List of article IDs to not display, separated by "%0"', '<code>|</code>'),
122				'filter' => 'digits',
123				'separator' => '|',
124				'default' => '',
125				'profile_reference' => 'article',
126				'since' => '5.0',
127			],
128			'quiet' => [
129				'required' => false,
130				'name' => tra('Quiet'),
131				'description' => tra('Whether to not report when there are no articles (no reporting by default)'),
132				'filter' => 'alpha',
133				'default' => 'n',
134				'since' => '1',
135				'options' => [
136					['text' => '', 'value' => ''],
137					['text' => tra('Yes'), 'value' => 'y'],
138					['text' => tra('No'), 'value' => 'n'],
139				],
140			],
141			'titleonly' => [
142				'required' => false,
143				'name' => tra('Title Only'),
144				'description' => tra('Whether to only show the title of the articles (not set to title only by default)'),
145				'filter' => 'alpha',
146				'since' => '1',
147				'default' => '',
148				'options' => [
149					['text' => '', 'value' => ''],
150					['text' => tra('Yes'), 'value' => 'y'],
151					['text' => tra('No'), 'value' => 'n'],
152				],
153			],
154			'fullbody' => [
155				'required' => false,
156				'name' => tra('Show Article Body'),
157				'description' => tra('Whether to show the body of the articles instead of the heading (not set by default).'),
158				'filter' => 'alpha',
159				'default' => 'n',
160				'since' => '5',
161				'options' => [
162					['text' => '', 'value' => ''],
163					['text' => tra('Yes'), 'value' => 'y'],
164					['text' => tra('No'), 'value' => 'n'],
165				],
166			],
167			'start' => [
168				'required' => false,
169				'name' => tra('Starting Article'),
170				'description' => tra('The article number that the list should start with (starts with first article by
171					default)') . '. ' . tra('This will not work if Pagination is used.'),
172				'filter' => 'int',
173				'since' => '1',
174				'default' => 0,
175			],
176			'dateStart' => [
177				'required' => false,
178				'name' => tra('Start Date'),
179				'description' => tra('Earliest date to select articles from.') . tr(' (%0YYYY-MM-DD%1)', '<code>', '</code>'),
180				'filter' => 'date',
181				'default' => '',
182				'since' => '5.0',
183			],
184			'dateEnd' => [
185				'required' => false,
186				'name' => tra('End date'),
187				'description' => tra('Latest date to select articles from.') . tr(' (%0YYYY-MM-DD%1)', '<code>', '</code>'),
188				'filter' => 'date',
189				'default' => '',
190				'since' => '5.0',
191			],
192			'periodQuantity' => [
193				'required' => false,
194				'name' => tra('Period quantity'),
195				'description' => tr('Numeric value to display only last articles published within a user defined
196					time-frame. Used in conjunction with the next parameter "Period unit", this parameter indicates how
197					many of those units are to be considered to define the time frame. If this parameter is set,
198					"Start Date" and "End Date" are ignored.'),
199				'filter' => 'digits',
200				'since' => '1',
201				'default' => '',
202			],
203			'periodUnit' => [
204				'required' => false,
205				'name' => tra('Period unit'),
206				'description' => tr('Time unit used with "Period quantity"'),
207				'filter' => 'word',
208				'since' => '1',
209				'options' => [
210					['text' => '', 'value' => ''],
211					['text' => tr('Hour'), 'value' => 'hour'],
212					['text' => tr('Day'), 'value' => 'day'],
213					['text' => tr('Week'), 'value' => 'week'],
214					['text' => tr('Month'), 'value' => 'month'],
215				],
216			],
217			'overrideDates' => [
218				'required' => false,
219				'name' => tra('Override Dates'),
220				'description' => tra('Whether to comply with the article type\'s "show before publish" and "show after expiration" settings (not complied with by default)'),
221				'filter' => 'alpha',
222				'default' => 'n',
223				'since' => '1',
224				'options' => [
225					['text' => '', 'value' => ''],
226					['text' => tra('Yes'), 'value' => 'y'],
227					['text' => tra('No'), 'value' => 'n'],
228				],
229			],
230			'containerClass' => [
231				'required' => false,
232				'name' => tra('Containing class'),
233				'description' => tr(
234					'CSS class to add to the containing "div.article" (default: "%0")',
235					'<code>wikiplugin_articles</code>'
236				),
237				'filter' => 'text',
238				'since' => '1',
239				'accepted' => tra('Valid CSS class'),
240				'default' => 'wikiplugin_articles',
241			],
242			'largefirstimage' => [
243				'required' => false,
244				'name' => tra('Large First Image'),
245				'description' => tr('If set to %0 (Yes), the first image will be displayed with the dimension used to
246					view of the article', '<code>y</code>'),
247				'filter' => 'alpha',
248				'default' => 'n',
249				'since' => '6.0',
250				'options' => [
251					['text' => '', 'value' => ''],
252					['text' => tra('Yes'), 'value' => 'y'],
253					['text' => tra('No'), 'value' => 'n'],
254				],
255			],
256			'urlparam' => [
257				'required' => false,
258				'name' => tra('Additional URL parameter for the link to read the article'),
259				'filter' => 'text',
260				'default' => '',
261				'since' => '6.0',
262			],
263			'actions' => [
264				'required' => false,
265				'name' => tra('Show actions (buttons and links)'),
266				'description' => tra('Whether to show the buttons and links to do actions on each article (for the
267					actions you have permission to do'),
268				'filter' => 'alpha',
269				'default' => 'n',
270				'since' => '6.1',
271				'options' => [
272					['text' => '', 'value' => ''],
273					['text' => tra('Yes'), 'value' => 'y'],
274					['text' => tra('No'), 'value' => 'n'],
275				],
276			],
277			'translationOrphan' => [
278				'required' => false,
279				'name' => tra('No translation'),
280				'description' => tra('User- or pipe-separated list of two-letter language codes for additional languages
281					to display. List pages with no language or with a missing translation in one of the language'),
282				'filter' => 'alpha',
283				'separator' => '|',
284				'since' => '1',
285				'default' => '',
286			],
287			'useLinktoURL' => [
288				'required' => false,
289				'name' => tra('Use Source URL'),
290				'description' => tra('Use the external source URL as link for articles.'),
291				'filter' => 'alpha',
292				'since' => '1',
293				'default' => 'n',
294				'options' => [
295					['text' => '', 'value' => ''],
296					['text' => tra('Yes'), 'value' => 'y'],
297					['text' => tra('No'), 'value' => 'n'],
298				],
299			],
300		],
301	];
302}
303
304function wikiplugin_articles($data, $params)
305{
306	global $prefs, $tiki_p_read_article, $tiki_p_articles_read_heading, $pageLang;
307	$smarty = TikiLib::lib('smarty');
308	$tikilib = TikiLib::lib('tiki');
309	$artlib = TikiLib::lib('art');
310	$default = ['max' => $prefs['maxRecords'], 'start' => 0, 'usePagination' => 'n', 'topicId' => '', 'topic' => '', 'sort' => 'publishDate_desc', 'type' => '', 'lang' => '', 'quiet' => 'n', 'categId' => '', 'largefirstimage' => 'n', 'urlparam' => '', 'actions' => 'n', 'translationOrphan' => '', 'headerLinks' => 'n', 'showtable' => 'n', 'useLinktoURL' => 'n'];
311	$auto_args = ['lang', 'topicId', 'topic', 'sort', 'type', 'lang', 'categId'];
312	$params = array_merge($default, $params);
313
314	extract($params, EXTR_SKIP);
315	$filter = [];
316	if ($prefs['feature_articles'] != 'y') {
317		//	the feature is disabled or the user can't read articles, not even article headings
318		return("");
319	}
320
321	$urlnext = '';
322	if ($usePagination == 'y') {
323		//Set offset when pagniation is used
324		if (! isset($_REQUEST["offset"])) {
325			$start = 0;
326		} else {
327			$start = $_REQUEST["offset"];
328		}
329
330		foreach ($auto_args as $arg) {
331			if (! empty($$arg)) {
332				$paramsnext[$arg] = $$arg;
333			}
334		}
335		$paramsnext['_type'] = 'absolute_path';
336		$smarty->loadPlugin('smarty_function_query');
337		$urlnext = smarty_function_query($paramsnext, $smarty->getEmptyInternalTemplate());
338	}
339
340	$smarty->assign_by_ref('quiet', $quiet);
341	$smarty->assign_by_ref('urlparam', $urlparam);
342	$smarty->assign_by_ref('urlnext', $urlnext);
343	$smarty->assign_by_ref('useLinktoURL', $useLinktoURL);
344
345	if (! isset($containerClass)) {
346		$containerClass = 'wikiplugin_articles';
347	}
348	$smarty->assign('container_class', $containerClass);
349
350	$dateStartTS = 0;
351	$dateEndTS = 0;
352
353	// if a period of time is set, date start and end are ignored
354	if (isset($periodQuantity)) {
355		switch ($periodUnit) {
356			case 'hour':
357				$periodUnit = 3600;
358				break;
359			case 'day':
360				$periodUnit = 86400;
361				break;
362			case 'week':
363				$periodUnit = 604800;
364				break;
365			case 'month':
366				$periodUnit = 2628000;
367				break;
368			default:
369				break;
370		}
371
372		if (is_int($periodUnit)) {
373			$dateStartTS = $tikilib->now - ($periodQuantity * $periodUnit);
374			$dateEndTS = $tikilib->now;
375		}
376	} else {
377		if (isset($dateStart)) {
378			$dateStartTS = strtotime($dateStart);
379		}
380
381		if (isset($dateEnd)) {
382			$dateEndTS = strtotime($dateEnd);
383		}
384	}
385
386	if (isset($fullbody) && $fullbody == 'y') {
387		$smarty->assign('fullbody', 'y');
388	} else {
389		$smarty->assign('fullbody', 'n');
390		$fullbody = 'n';
391	}
392	$smarty->assign('largefirstimage', $largefirstimage);
393	if (! isset($overrideDates)) {
394		$overrideDates = 'n';
395	}
396
397	if (! empty($translationOrphan)) {
398		$filter['translationOrphan'] = $translationOrphan;
399	}
400	if (! empty($articleId)) {
401		$filter['articleId'] = $articleId;
402	}
403	if (! empty($notArticleId)) {
404		$filter['notArticleId'] = $notArticleId;
405	}
406
407	if (! is_array($categId) || count($categId) == 0) {
408		$categIds = '';
409	} elseif (is_array($categId) && count($categId) == 1) {
410		// For performance reasons, if there is only one value, the SQL query should not return IN () as it does with arrays
411		// So we send a single value instead of a single-value array
412		$categIds = $categId[0];
413	} else {
414		// We want the list of articles which are in all categories
415		$categIds = [ 'AND' => $categId];
416	}
417
418	$listpages = $artlib->list_articles($start, $max, $sort, '', $dateStartTS, $dateEndTS, 'admin', $type, $topicId, 'y', $topic, $categIds, '', '', $lang, '', '', ($overrideDates == 'y'), 'y', $filter);
419	if ($prefs['feature_multilingual'] == 'y' && empty($translationOrphan)) {
420		$multilinguallib = TikiLib::lib('multilingual');
421		$listpages['data'] = $multilinguallib->selectLangList('article', $listpages['data'], $pageLang);
422		foreach ($listpages['data'] as &$article) {
423			$article['translations'] = $multilinguallib->getTranslations('article', $article['articleId'], $article["title"], $article['lang']);
424		}
425	}
426
427	for ($i = 0, $icount_listpages = count($listpages["data"]); $i < $icount_listpages; $i++) {
428		$listpages["data"][$i]["parsed_heading"] = TikiLib::lib('parser')->parse_data(
429			$listpages["data"][$i]["heading"],
430			[
431				'min_one_paragraph' => true,
432				'is_html' => $artlib->is_html($listpages["data"][$i], true),
433			]
434		);
435		if ($fullbody == 'y') {
436			$listpages["data"][$i]["parsed_body"] = TikiLib::lib('parser')->parse_data(
437				$listpages["data"][$i]["body"],
438				[
439					'min_one_paragraph' => true,
440					'is_html' => $artlib->is_html($listpages["data"][$i]),
441				]
442			);
443		}
444		$comments_prefix_var = 'article:';
445		$comments_object_var = $listpages["data"][$i]["articleId"];
446		$comments_objectId = $comments_prefix_var . $comments_object_var;
447		$listpages["data"][$i]["comments_cant"] = TikiLib::lib('comments')->count_comments($comments_objectId);
448		//print_r($listpages["data"][$i]['title']);
449	}
450
451	$topics = $artlib->list_topics();
452	$smarty->assign_by_ref('topics', $topics);
453
454	if (empty($topicId)) {
455		$topicId = '';
456	}
457	if (empty($type)) {
458		$type = '';
459	}
460
461	if (! empty($topic) && ! strstr($topic, '!') && ! strstr($topic, '+')) {
462		$smarty->assign_by_ref('topic', $topic);
463	} elseif (! empty($topicId) &&  is_numeric($topicId)) {
464		$smarty->assign_by_ref('topicId', $topicId);
465		if (! empty($listpages['data'][0]['topicName'])) {
466			$smarty->assign_by_ref('topic', $listpages['data'][0]['topicName']);
467		} else {
468			$topic_info = $artlib->get_topic($topicId);
469			if (isset($topic_info['name'])) {
470				$smarty->assign_by_ref('topic', $topic_info['name']);
471			}
472		}
473	} elseif (empty($topicId)) {
474		$smarty->assign_by_ref('topicId', $topicId);
475	}
476	if (! empty($type) && ! strstr($type, '!') && ! strstr($type, '+')) {
477		$smarty->assign_by_ref('type', $type);
478	} elseif (empty($type)) {
479		$smarty->assign_by_ref('type', $type);
480	}
481
482	if ($usePagination == 'y') {
483		$smarty->assign('maxArticles', $max);
484		$smarty->assign_by_ref('offset', $start);
485		$smarty->assign_by_ref('cant', $listpages['cant']);
486	}
487	if (! empty($order)) {
488		foreach ($listpages['data'] as $i => $article) {
489			$memo[$article['articleId']] = $i;
490		}
491		foreach ($order as $articleId) {
492			if (isset($memo[$articleId])) {
493				$list[] = $listpages['data'][$memo[$articleId]];
494			}
495		}
496		foreach ($listpages['data'] as $i => $article) {
497			if (! in_array($article['articleId'], $order)) {
498				$list[] = $article;
499			}
500		}
501		$smarty->assign_by_ref('listpages', $list);
502	} else {
503		$smarty->assign_by_ref('listpages', $listpages["data"]);
504	}
505	$smarty->assign('usePagination', $usePagination);
506	$smarty->assign_by_ref('actions', $actions);
507	$smarty->assign('headerLinks', $headerLinks);
508
509	if (isset($titleonly) && $titleonly == 'y') {
510		return "~np~ " . $smarty->fetch('tiki-view_articles-titleonly.tpl') . " ~/np~";
511	} else {
512		return "~np~ " . $smarty->fetch('tiki-view_articles.tpl') . " ~/np~";
513	}
514	//return str_replace("\n","",$smarty->fetch('tiki-view_articles.tpl')); // this considers the hour in the header like a link
515}
516