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
8require_once 'lib/wiki/pluginslib.php';
9
10class WikiPluginPluginManager extends PluginsLib
11{
12	var $expanded_params = ['info'];
13	function getDefaultArguments()
14	{
15		return [
16					'info' => 'description|parameters|paraminfo',
17					'plugin' => '',
18					'module' => '',
19					'singletitle' => 'none',
20					'titletag' => 'h3',
21					'start' => '',
22					'limit' => '',
23					'paramtype' => '',
24					'showparamtype' => 'n',
25					'showtopinfo' => 'y'
26				];
27	}
28	function getName()
29	{
30		return 'PluginManager';
31	}
32	function getVersion()
33	{
34		return preg_replace("/[Revision: $]/", '', "\$Revision: 1.11 $");
35	}
36	function getDescription()
37	{
38		return wikiplugin_pluginmanager_help();
39	}
40	function run($data, $params)
41	{
42		global $helpurl;
43		$wikilib = TikiLib::lib('wiki');
44		$tikilib = TikiLib::lib('tiki');
45		if (! is_dir(PLUGINS_DIR)) {
46			return $this->error('No plugin directory defined');
47		}
48		if (empty($helpurl)) {
49			$helpurl = 'http://doc.tiki.org/';
50		}
51
52		$params = $this->getParams($params);
53		extract($params, EXTR_SKIP);
54
55		if (! empty($module) && ! empty($plugin)) {
56			return $this->error(tra('Either the module or plugin parameter must be set, but not both.'));
57		} elseif (! empty($module)) {
58			$aPrincipalField = ['field' => 'plugin', 'name' => 'Module'];
59			$helppath = $helpurl . $aPrincipalField['name'] . ' ';
60			$filepath = 'mod-func-';
61
62			$modlib = TikiLib::lib('mod');
63			$aPlugins = $modlib->list_module_files();
64			$mod = true;
65			$type = ' module';
66			$plugin = $module;
67		} else {
68			$aPrincipalField = ['field' => 'plugin', 'name' => 'Plugin'];
69			$helppath = $helpurl . $aPrincipalField['name'];
70			$filepath = 'wikiplugin_';
71			$aPlugins = $wikilib->list_plugins();
72			$mod = false;
73			$type = ' plugin';
74		}
75		$all = $aPlugins;
76		//if the user set $module, that setting has now been moved to $plugin so that one code set is used
77		//$aPlugins and $all now has the complete list of plugin or module file names - the code below modifies $aPlugins
78		//if necessary based on user settings
79		if (! empty($plugin)) {
80			if (strpos($plugin, '|') !== false) {
81				$aPlugins = [];
82				$userlist = explode('|', $plugin);
83				foreach ($userlist as $useritem) {
84					$file = $filepath . $useritem . '.php';
85					$confirm = in_array($file, $all);
86					if ($confirm === false) {
87						return '^' . tr('Plugin Manager error: %0%1 not found', $useritem, $type) . '^';
88					} else {
89						$aPlugins[] = $file;
90					}
91				}
92			} elseif (strpos($plugin, '-') !== false) {
93				$userrange = explode('-', $plugin);
94				$begin = array_search($filepath . $userrange[0] . '.php', $aPlugins);
95				$end = array_search($filepath . $userrange[1] . '.php', $aPlugins);
96				$beginerror = '';
97				$enderror = '';
98				$type2 = $type;
99				if ($begin === false || $end === false) {
100					if ($begin === false) {
101						$beginerror = $userrange[0];
102					}
103					if ($end === false) {
104						$enderror = $userrange[1];
105						if (! empty($beginerror)) {
106							$and = ' and ';
107						} else {
108							$and = '';
109							$type = '';
110						}
111					}
112					return '^' . tr('Plugin Manager error: %0%1%2%3%4 not found', $beginerror, $type, $and, $enderror, $type2) . '^';
113				} elseif ($end > $begin) {
114					$aPlugins = array_slice($aPlugins, $begin, $end - $begin + 1);
115				} else {
116					$aPlugins = array_slice($aPlugins, $end, $begin - $end + 1);
117				}
118			} elseif (! empty($limit)) {
119				$begin = array_search($filepath . $plugin . '.php', $aPlugins);
120				if ($begin === false) {
121					return '^' . tr('Plugin Manager error: %0%1 not found', $begin, $type) . '^';
122				} else {
123					$aPlugins = array_slice($aPlugins, $begin, $limit);
124				}
125			} elseif ($plugin != 'all') {
126				$file = $filepath . $plugin . '.php';
127				$confirm = in_array($file, $aPlugins);
128				if ($confirm === false) {
129					return '^' . tr('Plugin Manager error:  %0%1 not found', $plugin, $type) . '^';
130				} else {
131					$aPlugins = [];
132					$aPlugins[] = $file;
133				}
134			}
135		} else {
136			if (! empty($start) || ! empty($limit)) {
137				if (! empty($start) && ! empty($limit)) {
138					$aPlugins = array_slice($aPlugins, $start - 1, $limit);
139				} elseif (! empty($start)) {
140					$aPlugins = array_slice($aPlugins, $start - 1);
141				} else {
142					$aPlugins = array_slice($aPlugins, 0, $limit);
143				}
144			}
145		}
146		//Set all data variables needed for separate code used to generate the display table
147		$aData = [];
148		if ($singletitle == 'table' || count($aPlugins) > 1) {
149			foreach ($aPlugins as $sPluginFile) {
150				global $sPlugin, $numparams;
151				if ($mod) {
152					$infoPlugin = get_module_params($sPluginFile);
153					$namepath = $sPlugin;
154				} else {
155					$infoPlugin = get_plugin_info($sPluginFile);
156					$namepath = ucfirst($sPlugin);
157				}
158				if (in_array('description', $info)) {
159					if (isset($infoPlugin['description'])) {
160						if ($numparams > 1) {
161							$aData[$sPlugin]['description']['onekey'] = $infoPlugin['description'];
162						} else {
163							$aData[$sPlugin]['description'] = $infoPlugin['description'];
164						}
165					} else {
166						$aData[$sPlugin]['description'] = ' --- ';
167					}
168				}
169				if (in_array('parameters', $info)) {
170					if ($numparams > 0) {
171						if ($aPrincipalField['field'] == 'plugin' && ! in_array('options', $info) && $numparams > 1) {
172							$aData[$sPlugin][$aPrincipalField['field']]['rowspan'] = $numparams;
173							if (in_array('description', $info)) {
174								$aData[$sPlugin]['description']['rowspan'] = $numparams;
175							}
176						}
177						foreach ($infoPlugin['params'] as $paramname => $param) {
178							if (isset($infoPlugin['params'][$paramname]['description'])) {
179								$paramblock = '~np~' . $infoPlugin['params'][$paramname]['description'] . '~/np~';
180							}
181							if (isset($param['options']) && is_array($param['options'])) {
182								$paramblock .= '<br /><em>' . tra('Options:') . '</em> ';
183								$i = 0;
184								foreach ($param['options'] as $oplist => $opitem) {
185									if (isset($opitem['value'])) {
186										$paramblock .= $opitem['value'];
187									} else {
188										$paramblock .= $opitem['text'];
189									}
190									$paramblock .= ' | ';
191									$i++;
192								}
193								$paramblock = substr($paramblock, 0, -3);
194							}
195							if (isset($infoPlugin['params'][$paramname]['required']) && $infoPlugin['params'][$paramname]['required'] == true) {
196								$aData[$sPlugin]['parameters']['<b><code>' . $paramname . '</code></b>'] = $paramblock;
197							} else {
198								$aData[$sPlugin]['parameters']['<code>' . $paramname . '</code>'] = $paramblock;
199							}
200						}
201					} else {
202						$aData[$sPlugin]['parameters']['<em>no parameters</em>'] = '<em>' . tra('n/a') . '</em>';
203					}
204				}
205				$aData[$sPlugin]['plugin']['plugin'] = '[' . $helppath . $namepath . '|' . ucfirst($sPlugin) . ']';
206			} // Plugins Loop
207			return PluginsLibUtil::createTable($aData, $info, $aPrincipalField);
208		} else {
209			//Replicates a documentation table for parameters for a single plugin or module
210			//Not using plugin lib table to avoid making custom modifications
211			global $sPlugin, $numparams;
212			if ($mod) {
213				$infoPlugin = get_module_params($aPlugins[0]);
214				$namepath = $sPlugin;
215			} else {
216				$infoPlugin = get_plugin_info($aPlugins[0]);
217				$namepath = ucfirst($sPlugin);
218			}
219			if ($singletitle == 'top') {
220				$title = '<' . $titletag . '>[' . $helppath . $namepath
221					. '|' . ucfirst($sPlugin) . ']</' . $titletag . '>';
222				$title .= $infoPlugin['description'] . '<br />';
223			} else {
224				$title = '';
225			}
226			$headbegin = "\n\t\t" . '<th class="heading">';
227			$cellbegin = "\n\t\t" . '<td>';
228			$header = "\n\t" . '<tr class="heading">' . $headbegin . 'Parameters</td>';
229			$rows = '';
230			if (isset($numparams) && $numparams > 0) {
231				$header .= $headbegin . tra('Accepted Values') . '</th>';
232					$header .= $headbegin . tra('Description') . '</th>';
233				$rowCounter = 1;
234				//sort required params first
235				$reqarray = array_column($infoPlugin['params'], 'required');
236				$keysarray = array_keys($infoPlugin['params']);
237				$reqarray = array_combine($keysarray, $reqarray);
238				if (count($reqarray) == count($infoPlugin['params'])) {
239					array_multisort($reqarray, SORT_DESC, $infoPlugin['params']);
240				}
241				//add body instructions to the parameter array
242				if (! empty($infoPlugin['body'])) {
243					$body = ['(body of plugin)' => ['description' => $infoPlugin['body']]];
244					$infoPlugin['params'] = array_merge($body, $infoPlugin['params']);
245				}
246				foreach ($infoPlugin['params'] as $paramname => $paraminfo) {
247					unset($sep, $septext);
248					//check is paramtype filter is set
249					if (empty($params['paramtype'])
250						|| ((empty($paraminfo['doctype']) && ! empty($params['paramtype']) && $params['paramtype'] === 'none')
251						|| (! empty($paraminfo['doctype']) && $params['paramtype'] == $paraminfo['doctype']))
252					) {
253						$filteredparams[] = $paraminfo;
254						$rows .= "\n\t" . '<tr>' . $cellbegin;
255						//Parameters column
256						if (isset($paraminfo['required']) && $paraminfo['required'] == true) {
257							$rows .= '<strong><code>' . $paramname . '</code></strong>';
258						} elseif ($paramname == '(body of plugin)') {
259							$rows .= tra('(body of plugin)');
260						} else {
261							$rows .= '<code>' . $paramname . '</code>' ;
262						}
263						if (isset($params['showparamtype']) && $params['showparamtype'] === 'y'
264							&& ! empty($paraminfo['doctype'])) {
265							$rows .= '<br /><small>(' . $paraminfo['doctype'] . ')</small>';
266						}
267						$rows .= '</td>';
268						//Accepted Values column
269						$rows .= $cellbegin;
270						if (isset($paraminfo['separator'])) {
271							$sep = $paraminfo['separator'];
272							$septext = tr('%0separator:%1 ', '<em>', '</em>') . '<code>' . $paraminfo['separator'] .
273								'</code>';
274						} else {
275							$sep = '| ';
276						}
277						if (isset($paraminfo['accepted'])) {
278							$rows .= $paraminfo['accepted'];
279							if (isset($septext)) {
280								$rows .= '<br />' . $septext;
281							}
282							$rows .= '</td>';
283						} elseif (isset($paraminfo['options'])) {
284							$optcounter = 1;
285							$numoptions = count($paraminfo['options']);
286							foreach ($paraminfo['options'] as $oplist => $opitem) {
287								$rows .= strlen($opitem['value']) == 0 ? tra('(blank)') : $opitem['value'];
288								if ($optcounter < $numoptions) {
289									if ($numoptions > 10) {
290										$rows .= $sep;
291									} else {
292										$rows .= '<br />';
293									}
294								}
295								$optcounter++;
296							}
297							if (isset($septext)) {
298								$rows .= '<br />' . $septext;
299							}
300							$rows .= '</td>';
301						} elseif (isset($paraminfo['filter'])) {
302							if ($paraminfo['filter'] == 'striptags') {
303								$rows .= tra('any string except for HTML and PHP tags');
304							} else {
305								$rows .= $paraminfo['filter'];
306							}
307							if (isset($septext)) {
308								$rows .= '<br />' . $septext;
309							}
310							$rows .= '</td>';
311						} else {
312							if (isset($septext)) {
313								$rows .= '<br />' . $septext;
314							}
315							$rows .= '</td>';
316						}
317						//Description column
318						$rows .= $cellbegin . $paraminfo['description'] . '</td>';
319						//Default column
320						if ($rowCounter == 1) {
321							$header .= $headbegin . tra('Default') . '</th>';
322						}
323						if (! isset($paraminfo['default'])) {
324							$paraminfo['default'] = '';
325						}
326						$rows .= $cellbegin . $paraminfo['default'] . '</td>';
327						//Since column
328						if ($rowCounter == 1) {
329							$header .= $headbegin . tra('Since') . '</th>';
330						}
331						$since = ! empty($paraminfo['since']) ? $paraminfo['since'] : '';
332						$rows .= $cellbegin . $since . '</td>';
333						$rows .= "\n\t" . '</tr>';
334						$rowCounter++;
335					}
336				}
337				if (! empty($infoPlugin['additional']) && (empty($params['paramtype']) || $params['paramtype'] === 'none')) {
338					$rows .= '<tr><td colspan="5">' . $infoPlugin['additional'] . '</td></tr>';
339				}
340			} else {
341				if (! empty($infoPlugin['body'])) {
342					$rows .= "\n\t" . '<tr>' . $cellbegin . '<em>' . tra('(body of plugin)') . ' - </em>'
343						. $infoPlugin['body'] . '</td>';
344				}
345				$rows .= "\n\t" . '<tr>' . $cellbegin . '<em>' . tra('no parameters') . '</em></td>';
346			}
347			$header .= "\n\t" . '</tr>';
348			$pluginprefs = ! empty($infoPlugin['prefs']) && $params['showtopinfo'] !== 'n' ? '<em>'
349				. tra('Preferences required:') . '</em> ' . implode(', ', $infoPlugin['prefs']) . '<br/>' : '';
350			$title .= isset($infoPlugin['introduced']) && $params['showtopinfo'] !== 'n' ? '<em>' .
351				tr('Introduced in %0', 'Tiki ' . $infoPlugin['introduced']) . '.</em>' : '';
352			$required = ! empty($filteredparams) ? array_column($filteredparams, 'required') : false;
353			$bold = in_array(true, $required) > 0 ? '<em> ' . tr(
354				'Required parameters are in%0 %1bold%2',
355				'</em>',
356				'<strong><code>',
357				'</code></strong>.'
358			) : '';
359			$sOutput = $title . $bold . '<br>' . $pluginprefs . '<div class="table-responsive">' .
360				'<table class="table table-striped table-hover">' . $header . $rows . '</table></div>' . "\n";
361			return $sOutput;
362		}
363	}
364	function processDescription($sDescription)
365	{
366		$sDescription = str_replace(',', ', ', $sDescription);
367		$sDescription = str_replace('|', '| ', $sDescription);
368		$sDescription = strip_tags(wordwrap($sDescription, 35));
369		return $sDescription;
370	}
371}
372
373function wikiplugin_pluginmanager_info()
374{
375	return [
376		'name' => tra('Plugin Manager'),
377		'documentation' => 'PluginPluginManager',
378		'description' => tra('List wiki plugin or module information for the site'),
379		'prefs' => [ 'wikiplugin_pluginmanager' ],
380		'introduced' => 1,
381		'iconname' => 'plugin',
382		'params' => [
383			'info' => [
384				'required' => false,
385				'name' => tra('Information'),
386				'description' => tr('Determines what information is shown. Values separated with %0|%1.
387					Ignored when %0singletitle%1 is set to %0top%1 or %0none%1.', '<code>', '</code>'),
388				   'filter' => 'text',
389				'accepted' => tra('One or more of: description | parameters | paraminfo'),
390				'default' => 'description | parameters | paraminfo ',
391				'since' => '1',
392				'options' => [
393					['text' => '', 'value' => ''],
394					['text' => tra('Description'), 'value' => 'description'],
395					['text' => tra('Description and Parameters'), 'value' => 'description|parameters'],
396					['text' => tra('Description & Parameter Info'), 'value' => 'description|paraminfo'],
397					['text' => tra('Parameters & Parameter Info'), 'value' => 'parameters|paraminfo'],
398					['text' => tra('All'), 'value' => 'description|parameters|paraminfo']
399				]
400			],
401			'plugin' => [
402				'required' => false,
403				'name' => tra('Plugin'),
404				'description' => tr('Name of a plugin (e.g., backlinks), or list separated by %0|%1, or range separated
405					 by %0-%1. Single plugin can be used with %0limit%1 parameter.', '<code>', '</code>'),
406				'filter' => 'text',
407				'default' => '',
408				'since' => '5.0',
409			],
410			'module' => [
411				'required' => false,
412				'name' => tra('Module'),
413				'description' => tr('Name of a module (e.g., calendar_new), or list separated by %0|%1, or range separated
414					by %0-%1. Single module can be used with %0limit%1 parameter.', '<code>', '</code>'),
415				'filter' => 'text',
416				'default' => '',
417				'since' => '6.1',
418			],
419			'singletitle' => [
420				'required' => false,
421				'name' => tra('Single Title'),
422				'description' => tr('Set placement of plugin name and description when displaying information for only one plugin'),
423				'filter' => 'alpha',
424				'default' => 'none',
425				'since' => '5.0',
426				'options' => [
427					['text' => tra(''), 'value' => ''],
428					['text' => tra('Top'), 'value' => 'top'],
429					['text' => tra('Table'), 'value' => 'table'],
430				],
431			],
432			'titletag' => [
433				'required' => false,
434				'name' => tra('Title Heading'),
435				'description' => tr('Sets the heading size for the title, e.g., %0h2%1.', '<code>', '</code>'),
436				'filter' => 'alnum',
437				'default' => 'h3',
438				'since' => '5.0',
439				'advanced' => true,
440			],
441			'start' => [
442				'required' => false,
443				'name' => tra('Start'),
444				'description' => tra('Start with this plugin record number (must be an integer 1 or greater).'),
445				'filter' => 'digits',
446				'default' => '',
447				'since' => '5.0',
448			],
449			'limit' => [
450				'required' => false,
451				'name' => tra('Limit'),
452				'description' => tra('Number of plugins to show. Can be used either with start or plugin as the starting
453					point. Must be an integer 1 or greater.'),
454				'filter' => 'digits',
455				'default' => '',
456				'since' => '5.0',
457			],
458			'paramtype' => [
459				'required' => false,
460				'name' => tra('Parameter Type'),
461				'description' => tr('Only list parameters with this %0doctype%1 setting. Set to %0none%1 to show only
462					parameters without a type setting and the body instructions.', '<code>', '</code>'),
463				'since' => '15.0',
464				'filter' => 'alpha',
465				'default' => '',
466				'advanced' => true,
467			],
468			'showparamtype' => [
469				'required' => false,
470				'name' => tra('Show Parameter Type'),
471				'description' => tr('Show the parameter %0doctype%1 value.', '<code>', '</code>'),
472				'since' => '15.0',
473				'filter' => 'alpha',
474				'default' => '',
475				'advanced' => true,
476				'options' => [
477					['text' => '', 'value' => ''],
478					['text' => tra('Yes'), 'value' => 'y'],
479					['text' => tra('No'), 'value' => 'n']
480				]
481			],
482			'showtopinfo' => [
483				'required' => false,
484				'name' => tra('Show Top Info'),
485				'description' => tr('Show information above the table regarding preferences required and the first
486					version when the plugin became available. Shown by default.'),
487				'since' => '15.0',
488				'filter' => 'alpha',
489				'default' => '',
490				'advanced' => true,
491				'options' => [
492					['text' => '', 'value' => ''],
493					['text' => tra('Yes'), 'value' => 'y'],
494					['text' => tra('No'), 'value' => 'n']
495				]
496			],
497		],
498	];
499}
500
501function get_plugin_info($sPluginFile)
502{
503	preg_match("/wikiplugin_(.*)\.php/i", $sPluginFile, $match);
504	global $sPlugin, $numparams;
505	$sPlugin = $match[1];
506	include_once(PLUGINS_DIR . '/' . $sPluginFile);
507	global $tikilib;
508	$parserlib = TikiLib::lib('parser');
509
510	$infoPlugin = $parserlib->plugin_info($sPlugin);
511	$numparams = isset($infoPlugin['params']) ? count($infoPlugin['params']) : 0;
512	return $infoPlugin;
513}
514
515function get_module_params($sPluginFile)
516{
517	preg_match("/mod-func-(.*)\.php/i", $sPluginFile, $match);
518	global $sPlugin, $numparams;
519	$sPlugin = $match[1];
520	include_once('modules/' . $sPluginFile);
521	$info_func = "module_{$sPlugin}_info";
522	$infoPlugin = $info_func();
523	$numparams = isset($infoPlugin['params']) ? count($infoPlugin['params']) : 0;
524	return $infoPlugin;
525}
526
527function wikiplugin_pluginmanager($data, $params)
528{
529	$plugin = new WikiPluginPluginManager();
530	return $plugin->run($data, $params);
531}
532