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_trackerfilter_info()
9{
10	require_once 'lib/wiki-plugins/wikiplugin_trackerlist.php';
11	$list = wikiplugin_trackerlist_info();
12	$params = array_merge(
13		[
14			'filters' => [
15				'required' => true,
16				'name' => tra('Filters'),
17				'description' => tr(
18					'The list of fields that can be used as filters along with their formats.
19					The field number and format are separated by a %0/%1 and multiple fields are separated by %0:%1.',
20					'<code>',
21					'</code>'
22				)
23					. tr('Format choices are:') . '<br /><code>d</code> - ' . tr('dropdown')
24					. '<br /><code>r</code> - ' . tr('radio buttons')
25					. '<br /><code>m</code> - ' . tr('multiple choice dropdown')
26					. '<br /><code>c</code> - ' . tr('checkbox')
27					. '<br /><code>t</code> - ' . tr('text with wild characters')
28					. '<br /><code>T</code> - ' . tr('exact text match')
29					. '<br /><code>i</code> - ' . tr('initials')
30					. '<br /><code>sqlsearch</code> - ' . tr('advanced search')
31					. '<br /><code>range</code> - ' . tr('range search (from/to)')
32					. '<br /><code>></code>, <code>><</code>, <code>>>=</code>, <code>><=</code> - ' . tr('greater
33						than, less than, greater than or equal, less than or equal.') . '<br />'
34					. tr('Example:') . ' <code>2/d:4/r:5:(6:7)/sqlsearch</code>',
35				'since' => '1',
36				'doctype' => 'filter',
37				'default' => '',
38				'profile_reference' => 'tracker_field_string',
39			],
40			'action' => [
41				'required' => false,
42				'name' => tra('Action'),
43				'description' => tr('Label on the submit button. Default: %0Filter%1. Use a space character to omit the
44					button (for use in datachannels etc)', '<code>', '</code>'),
45				'since' => '2.0',
46				'doctype' => 'show',
47				'default' => 'Filter'
48			],
49			'displayList' => [
50				'required' => false,
51				'name' => tra('Display List'),
52				'description' => tra('Show the full list (before filtering) initially (filtered list shown by default)'),
53				'since' => '2.0',
54				'doctype' => 'show',
55				'filter' => 'alpha',
56				'default' => 'n',
57				'options' => [
58					['text' => '', 'value' => ''],
59					['text' => tra('Yes'), 'value' => 'y'],
60					['text' => tra('No'), 'value' => 'n']
61				]
62			],
63			'line' => [
64				'required' => false,
65				'name' => tra('Line'),
66				'description' => tra('Displays all the filters on the same line (not shown on same line by default)'),
67				'since' => '2.0',
68				'doctype' => 'show',
69				'filter' => 'alpha',
70				'default' => 'n',
71				'options' => [
72					['text' => '', 'value' => ''],
73					['text' => tra('Yes'), 'value' => 'y'],
74					['text' => tra('Yes with field label in dropdown'), 'value' => 'in'],
75					['text' => tra('No'), 'value' => 'n']
76				]
77			],
78			'noflipflop' => [
79				'required' => false,
80				'name' => tra('No Toggle'),
81				'description' => tr('The toggle button to show/hide filters will not be shown if set to Yes (%0y%1).
82					Default is not to show the toggle (default changed from "n" to "y" in Tiki 20.0).', '<code>', '</code>'),
83				'since' => '6.0',
84				'doctype' => 'show',
85				'filter' => 'alpha',
86				'default' => 'y',
87				'options' => [
88					['text' => '', 'value' => ''],
89					['text' => tra('Yes'), 'value' => 'y'],
90					['text' => tra('No'), 'value' => 'n']
91				]
92			],
93			'export_action' => [
94				'required' => false,
95				'name' => tra('Export CSV.'),
96				'description' => tra('Label for an export button. Leave blank to show the usual "Filter" button instead.'),
97				'since' => '6.0',
98				'doctype' => 'export',
99				'default' => '',
100				'advanced' => true,
101			],
102			'export_status' => [
103				'required' => false,
104				'name' => tra('Export Status Field'),
105				'description' => tra('Export the status field if the Export CSV option is used'),
106				'since' => '11.1',
107				'advanced' => true,
108				'filter' => 'alpha',
109				'doctype' => 'export',
110				'default' => 'n',
111				'options' => [
112					['text' => '', 'value' => ''],
113					['text' => tra('Yes'), 'value' => 'y'],
114					['text' => tra('No'), 'value' => 'n']
115				]
116			],
117			'export_created' => [
118				'required' => false,
119				'name' => tra('Export Created Date Field'),
120				'description' => tra('Export the created date field if the Export CSV option is used'),
121				'since' => '11.1',
122				'advanced' => true,
123				'filter' => 'alpha',
124				'doctype' => 'export',
125				'default' => 'n',
126				'options' => [
127					['text' => '', 'value' => ''],
128					['text' => tra('Yes'), 'value' => 'y'],
129					['text' => tra('No'), 'value' => 'n']
130				]
131			],
132			'export_modif' => [
133				'required' => false,
134				'name' => tra('Export Modified Date Field'),
135				'description' => tra('Export the modified date field if the Export CSV option is used'),
136				'since' => '11.1',
137				'advanced' => true,
138				'filter' => 'alpha',
139				'doctype' => 'export',
140				'default' => 'n',
141				'options' => [
142					['text' => '', 'value' => ''],
143					['text' => tra('Yes'), 'value' => 'y'],
144					['text' => tra('No'), 'value' => 'n']
145				]
146			],
147			'export_charset' => [
148				'required' => false,
149				'name' => tra('Export Character Set'),
150				'description' => tra('Character set to be used if the Export CSV option is used'),
151				'since' => '11.1',
152				'doctype' => 'export',
153				'default' => 'UTF-8',
154				'advanced' => true,
155			],
156			'mapButtons' => [
157				'required' => false,
158				'name' => tra('Map View Buttons'),
159				'description' => tra('Display Mapview and Listview buttons'),
160				'since' => '6.0' . tr(' - was %0 until 12.0', '<code>googlemapButtons</code>'),
161				'filter' => 'alpha',
162				'doctype' => 'show',
163				'default' => '',
164				'options' => [
165					['text' => '', 'value' => ''],
166					['text' => tra('Yes'), 'value' => 'y'],
167					['text' => tra('No'), 'value' => 'n']
168				]
169			],
170		],
171		$list['params']
172	);
173
174	return [
175		'name' => tra('Tracker Filter'),
176		'documentation' => 'PluginTrackerFilter',
177		'description' => tra('Create a form to filter tracker fields'),
178		'prefs' => [ 'feature_trackers', 'wikiplugin_trackerfilter' ],
179		'body' => tra('notice'),
180		'iconname' => 'filter',
181		'introduced' => 1,
182		'params' => $params,
183		'format' => 'html',
184		'extraparams' => true,
185	];
186}
187
188function wikiplugin_trackerfilter($data, $params)
189{
190	global $prefs;
191	$trklib = TikiLib::lib('trk');
192	$smarty = TikiLib::lib('smarty');
193	static $iTrackerFilter = 0;
194	if ($prefs['feature_trackers'] != 'y') {
195		return $smarty->fetch("wiki-plugins/error_tracker.tpl");
196	}
197	$iTrackerFilter++;
198	$default = ['noflipflop' => 'y', 'action' => 'Filter', 'line' => 'n', 'displayList' => 'n', 'export_action' => '',
199					 'export_itemid' => 'y', 'export_status' => 'n', 'export_created' => 'n', 'export_modif' => 'n', 'export_charset' => 'UTF-8', 'status' => 'opc'];
200
201	if (isset($_REQUEST['reset_filter'])) {
202		wikiplugin_trackerFilter_reset_filters($iTrackerFilter);
203	} elseif (! isset($_REQUEST['filter']) && isset($_REQUEST['session_filters']) && $_REQUEST['session_filters'] == 'y') {
204		$params = array_merge($params, wikiplugin_trackerFilter_get_session_filters($iTrackerFilter));
205	}
206	if (isset($_REQUEST["mapview"]) && $_REQUEST["mapview"] == 'y' && ! isset($_REQUEST["searchmap"]) && ! isset($_REQUEST["searchlist"]) || isset($_REQUEST["searchmap"]) && ! isset($_REQUEST["searchlist"])) {
207		$params["showmap"] = 'y';
208		$smarty->assign('mapview', true);
209	}
210	if (isset($_REQUEST["mapview"]) && $_REQUEST["mapview"] == 'n' && ! isset($_REQUEST["searchmap"]) && ! isset($_REQUEST["searchlist"]) || isset($_REQUEST["searchlist"]) && ! isset($_REQUEST["searchmap"])) {
211		$params["showmap"] = 'n';
212		$smarty->assign('mapview', false);
213	}
214	$params = array_merge($default, $params);
215	if ($params['noflipflop'] !== 'n') {
216		$params['noflipflop'] = 'y';
217	}
218	extract($params, EXTR_SKIP);
219	$dataRes = '';
220
221	if (isset($_REQUEST['msgTrackerFilter'])) {
222		$smarty->assign('msgTrackerFilter', $_REQUEST['msgTrackerFilter']);
223	}
224
225	$headerlib = TikiLib::lib('header');
226
227	/**
228	 * adding spinner when clicking on the filter button
229	 *added by Axel.mwenze on  Monday, august 05, 2019
230	 */
231	$headerlib->add_jq_onready(
232		'$("#form-filter").submit(function(r) {
233				$(".trackerfilter_loader").show();
234				return true;
235		})'
236	);
237
238	$headerlib->add_jq_onready(
239		'/* Maintain state of other trackerfilter plugin forms */
240					$(".trackerfilter form").submit( function () {
241						var current_tracker = this;
242						$(current_tracker).append("<input type=\"hidden\" name=\"tracker_filters[]\" value=\"" + $(current_tracker).serialize() + "\" />")
243						$(".trackerfilter form").each( function() {
244							if (current_tracker !== this && $("input[name=count_item]", this).val() > 0) {
245								$(current_tracker).append("<input type=\"hidden\" name=\"tracker_filters[]\" value=\"" + $(this).serialize() + "\" />")
246							}
247						});
248						return true;
249					});'
250	);
251	if ($prefs['jquery_ui_chosen'] === 'y') {
252		$headerlib->add_css('@media (min-width: 768px) { .tiki #col1 .trackerfilter form .table-responsive { overflow-x: visible; overflow-y: visible; }} /* jquery_ui_chosen specific: edit this in wikiplugin_trackerfilter.php */');
253	} // TODO: move the CSS to less and add class html attribute in wikiplugin_trackerfilter.tpl instead
254
255	if (! empty($_REQUEST['tracker_filters']) && count($_REQUEST['tracker_filters']) > 0) {
256		foreach ($_REQUEST['tracker_filters'] as $tf_vals) {
257			parse_str(urldecode($tf_vals), $vals);
258			foreach ($vals as $k => $v) {
259				// if it's me and i had some items
260				if ($k == 'iTrackerFilter' && $v == $iTrackerFilter && isset($vals['count_item']) && $vals['count_item'] > 0) {
261					// unset request params for all the plugins (my one will be array_merged below)
262					foreach ($_REQUEST['tracker_filters'] as $tf_vals2) {
263						parse_str(urldecode($tf_vals2), $vals2);
264						foreach ($vals2 as $k2 => $v2) {
265							unset($GLOBALS['_REQUEST'][$k2]);
266						}
267					}
268					 $_REQUEST = array_merge($_REQUEST, $vals);
269				}
270			}
271		}
272	}
273	if (! empty($_REQUEST['filter']) || ! empty($_REQUEST['reset_filter'])) {  // If we set a new filter, reset pagination for this plugin
274		unset($GLOBALS['_REQUEST']["tr_offset$iTrackerFilter"]);
275	}
276
277	if (! isset($filters)) {
278		if (empty($export_action)) {
279			return tra('missing parameters') . ' filters';
280		} else {
281			$listfields = [];
282			$filters = [];
283			$formats = [];
284		}
285	} else {
286		$listfields = wikiplugin_trackerFilter_split_filters($filters);
287		foreach ($listfields as $i => $f) {
288			if (strchr($f, '/')) {
289				list($fieldId, $format) = explode('/', $f);
290				$listfields[$i] = $fieldId;
291				$formats[$fieldId] = $format;
292			} else {
293				$formats[$f] = '';
294			}
295		}
296	}
297	if (empty($trackerId) && ! empty($_REQUEST['trackerId'])) {
298		 $trackerId = $_REQUEST['trackerId'];
299	}
300
301	$tracker_definition = Tracker_Definition::get($trackerId);
302
303	if (empty($_REQUEST['filter']) && empty($export_action)) { // look if not coming from an initial and not exporting
304		foreach ($_REQUEST as $key => $val) {
305			if (substr($key, 0, 2) == 'f_') {
306				$_REQUEST['filter'] = 'y';
307				break;
308			}
309		}
310	}
311	if (! isset($sortchoice)) {
312		$sortchoice = '';
313	} else {
314		unset($params['sortchoice']);
315		if (isset($_REQUEST["tr_sort_mode$iTrackerFilter"])) {
316			$params['sort_mode'] = $_REQUEST["tr_sort_mode$iTrackerFilter"];
317		}
318		foreach ($sortchoice as $i => $sc) {
319			$sc = explode('|', $sc);
320			$sortchoice[$i] = ['value' => $sc[0], 'label' => empty($sc[1]) ? $sc[0] : $sc[1]];
321		}
322	}
323	if (empty($trackerId) || ! ($tracker = $trklib->get_tracker($trackerId))) {
324		return $smarty->fetch("wiki-plugins/error_tracker.tpl");
325	}
326	$filters = wikiplugin_trackerFilter_get_filters($trackerId, $listfields, $formats, $status);
327	if (empty($export_action)) {
328		if (! is_array($filters)) {
329			return $filters;
330		}
331	}
332	if (($displayList == 'y' || isset($_REQUEST['filter']) || isset($_REQUEST["tr_offset$iTrackerFilter"]) || isset($_REQUEST['tr_sort_mode'])) &&
333				(! isset($_REQUEST['iTrackerFilter']) || $_REQUEST['iTrackerFilter'] == $iTrackerFilter)) {
334		$ffs = [];
335		$values = [];
336		$exactValues = [];
337		wikiplugin_trackerfilter_build_trackerlist_filter($_REQUEST, $formats, $ffs, $values, $exactValues, $tracker_definition);
338		// echo '<pre>BUILD_FILTER'; print_r($ffs); print_r($exactValues); echo '</pre>';
339
340		$params['fields'] = isset($fields) ? $fields : [];
341		if (empty($params['trackerId'])) {
342			$params['trackerId'] = $trackerId;
343		}
344		if (! empty($ffs)) {
345			if (empty($params['filterfield'])) {
346				$params['filterfield'] = $ffs;
347				$params['exactvalue'] = $exactValues;
348				$params['filtervalue'] = $values;
349			} else {
350				if (! isset($params['exactvalue']) && ! isset($params['filtervalue'])) {
351					Feedback::error(tr('TrackerFilter: Wrong parameter specified - filterfield exists but exactvalue or filtervalue not set.'));
352				}
353				$c = count($params['filterfield']);
354				$params['filterfield'] = array_merge($params['filterfield'], $ffs);
355				for ($i = 0; $i < $c; ++$i) {
356					$params['exactvalue'][$i] = empty($params['exactvalue'][$i]) ? '' : $params['exactvalue'][$i];
357					$params['filtervalue'][$i] = empty($params['filtervalue'][$i]) ? '' : $params['filtervalue'][$i];
358				}
359				$params['exactvalue'] = array_merge($params['exactvalue'], $exactValues);
360				$params['filtervalue'] = array_merge($params['filtervalue'], $values);
361			}
362		}
363		if (empty($params['max'])) {
364			$params['max'] = $prefs['maxRecords'];
365		}
366		if (! empty($_REQUEST['f_status'])) {
367			$params['status'] = $_REQUEST['f_status'];
368		}
369		wikiplugin_trackerFilter_save_session_filters($params, $iTrackerFilter);
370		$smarty->assign('urlquery', wikiplugin_trackerFilter_build_urlquery($params));
371		include_once('lib/wiki-plugins/wikiplugin_trackerlist.php');
372		$dataRes .= wikiplugin_trackerlist($data, $params);
373	} else {
374		$data = '';
375	}
376
377	$smarty->assign_by_ref('sortchoice', $sortchoice);
378	$smarty->assign_by_ref('filters', $filters);
379	//echo '<pre>';print_r($filters); echo '</pre>';
380	$smarty->assign_by_ref('trackerId', $trackerId);
381	$smarty->assign('line', ($line == 'y' || $line == 'in') ? 'y' : 'n');
382	$smarty->assign('indrop', $line == 'in' ? 'y' : 'n');
383	$smarty->assign('iTrackerFilter', $iTrackerFilter);
384	if (! empty($export_action)) {
385		$smarty->assign('export_action', $export_action);
386		$smarty->assign('export_fields', implode(':', $fields));
387		$smarty->assign('export_itemid', $export_itemid == 'y' ? 'on' : '');
388		$smarty->assign('export_status', $export_status == 'y' ? 'on' : '');
389		$smarty->assign('export_created', $export_created == 'y' ? 'on' : '');
390		$smarty->assign('export_modif', $export_modif == 'y' ? 'on' : '');
391		$smarty->assign('export_charset', $export_charset);
392		if (! empty($_REQUEST['itemId']) && (empty($ignoreRequestItemId) || $ignoreRequestItemId != 'y')) {
393			$smarty->assign('export_itemId', $_REQUEST['itemId']);
394		}
395
396
397		if (empty($params['filters'])) {
398			if (! empty($filterfield)) { 	// convert param filters to export params
399				$f_fields = [];
400				for ($i = 0, $cfilterfield = count($filterfield); $i < $cfilterfield; $i++) {
401					if (! empty($exactvalue[$i])) {
402						$f_fields['f_' . $filterfield[$i]] = $exactvalue[$i];
403					} elseif (! empty($filtervalue[$i])) {
404						$f_fields['f_' . $filterfield[$i]] = $filtervalue[$i];
405						$f_fields['x_' . $filterfield[$i]] = 't';	// x_ is for not exact?
406					}
407				}
408				$smarty->assign_by_ref('f_fields', $f_fields);
409			}
410			$filters = [];	// clear out filters set up earlier which default to all fields if not exporting
411		} else {
412			$f_fields = [];
413			foreach ($formats as $fid => $fformat) {
414				$f_fields['x_' . $fid] = $fformat;  // x_ is for not exact
415			}
416			$smarty->assign_by_ref('f_fields', $f_fields);
417		}
418	}
419	if ($displayList == 'n' || ! empty($_REQUEST['filter']) || $noflipflop !== 'n' || $prefs['javascript_enabled'] != 'y' || (isset($_SESSION['tiki_cookie_jar']["show_trackerFilter$iTrackerFilter"]) && $_SESSION['tiki_cookie_jar']["show_trackerFilter$iTrackerFilter"] == 'y')) {
420		$open = 'y';
421		$_SESSION['tiki_cookie_jar']["show_trackerFilter$iTrackerFilter"] = 'y';
422	} else {
423		$open = 'n';
424	}
425	$smarty->assign_by_ref('open', $open);
426	$smarty->assign_by_ref('action', $action);
427	$smarty->assign_by_ref('noflipflop', $noflipflop);
428	$smarty->assign_by_ref('dataRes', $dataRes);
429
430	if (isset($mapButtons)) {
431		$smarty->assign('mapButtons', $mapButtons);
432	}
433
434	$dataF = $smarty->fetch('wiki-plugins/wikiplugin_trackerfilter.tpl');
435
436	static $first = true;
437
438	if ($first) {
439		$first = false;
440		$headerlib->add_jq_onready(
441			'$("a.prevnext", "#trackerFilter' . $iTrackerFilter . ' + .trackerfilter-result").click( function( e ) {
442				e.preventDefault();
443				$("#trackerFilter' . $iTrackerFilter . ' form")
444				.attr("action", $(this).attr("href"))
445				.submit();
446			} );'
447		);
448	}
449
450	return $data . $dataF;
451}
452
453function wikiplugin_trackerfilter_build_trackerlist_filter($input, $formats, &$ffs, &$values, &$exactValues, Tracker_Definition $tracker_definition)
454{
455	$trklib = TikiLib::lib('trk');
456
457	foreach ($input as $key => $val) {
458		if (substr($key, 0, 2) == 'f_' && ! empty($val) && (! is_array($val) || ! empty($val[0]))) {
459			if (! is_array($val)) {
460				$val = urldecode($val);
461			}
462			if ($val === '-Blank (no data)-') {
463				$val = '';
464			}
465			$fieldId = substr($key, 2);
466			$field = $tracker_definition->getField((int)$fieldId);
467
468			if ($fieldId == 'status') {
469				continue;
470			}
471			if (preg_match('/([0-9]+)(Month|Day|Year|Hour|Minute|Second)/', $fieldId, $matches)) { // a date
472				if (! in_array($matches[1], $ffs)) {
473					$fieldId = $matches[1];
474					$ffs[] = $matches[1];
475					// TO do optimize get options of the field
476					$date = $trklib->build_date($_REQUEST, $trklib->get_tracker_field($fieldId), 'f_' . $fieldId);
477					if (empty($formats[$fieldId])) { // = date
478						$exactValues[] = $date;
479					} else { // > or < data
480						$exactValues[] = [$formats[$fieldId] => $date];
481					}
482				}
483			} elseif ($field['type'] == 'F') {
484				// if field type is freetag force the use of $values instead of $exactValues
485				$ffs[] = $fieldId;
486
487				if (is_array($val)) {
488					$val = implode('%', $val);
489				}
490
491				$values[] = "%$val%";
492			} else {
493				if (preg_match("/\d+_(from|to)(Month|Day|Year|Hour|Minute|Second)?/", $fieldId, $m)) { // range filter
494					$fieldId = (int)$fieldId;
495					$formats[$fieldId] = ( $m[1] == 'from' ? '>=' : '<=' );
496
497					if (! empty($m[2])) {
498						if ($m[2] != 'Year') {
499							continue;
500						} else {
501							$val = $trklib->build_date($_REQUEST, $trklib->get_tracker_field($fieldId), 'f_' . $fieldId . '_' . $m[1]);
502						}
503					} else {
504						$handler = $trklib->get_field_handler($field);
505						$input['ins_' . $fieldId] = $val;
506						$data = $handler->getFieldData($input);
507						$val = $data['value'];
508					}
509				}
510				if (! is_numeric($fieldId)) { // composite filter
511					$ffs[] = ['sqlsearch' => explode(':', str_replace(['(', ')'], '', $fieldId))];
512				} else {
513					$ffs[] = $fieldId;
514				}
515				if (isset($formats[$fieldId]) && ($formats[$fieldId] == 't' || $formats[$fieldId] == 'm' || $formats[$fieldId] == 'i')) {
516					$exactValues[] = '';
517					$values[] = ($formats[$fieldId] == 'i') ? "$val%" : $val;
518				} else {
519					if (! empty($formats[$fieldId]) && preg_match('/[\>\<]+/', $formats[$fieldId])) {
520						$exactValues[] = [$formats[$fieldId] => $val];
521					} else {
522						$exactValues[] = $val;
523					}
524					$values[] = '';
525				}
526			}
527		}
528	}
529}
530
531function wikiplugin_trackerFilter_reset_filters($iTrackerFilter = 0)
532{
533	unset($_SESSION[wikiplugin_trackerFilter_get_session_filters_key($iTrackerFilter)]);
534	unset($_REQUEST['tracker_filters']);
535
536	foreach ($_REQUEST as $key => $val) {
537		if (substr($key, 0, 2) == 'f_') {
538			unset($_REQUEST[$key]);
539		}
540	}
541}
542
543function wikiplugin_trackerFilter_get_session_filters_key($iTrackerFilter = 0)
544{
545	$trackerId = isset($_REQUEST['trackerId']) ? $_REQUEST['trackerId'] : 0;
546	if (! empty($_REQUEST['page'])) {
547		return 'f_' . $_REQUEST['page'] . '_' . $iTrackerFilter;
548	}
549	return '';
550}
551
552function wikiplugin_trackerFilter_save_session_filters($filters, $iTrackerFilter = 0)
553{
554	$_SESSION[wikiplugin_trackerFilter_get_session_filters_key($iTrackerFilter)] = $filters;
555}
556
557function wikiplugin_trackerFilter_get_session_filters($iTrackerFilter = 0)
558{
559	$key = wikiplugin_trackerFilter_get_session_filters_key($iTrackerFilter);
560
561	if (! isset($_SESSION[$key])) {
562		return [];
563	}
564
565	if (isset($_SESSION[$key]['filterfield'])) {
566		foreach ($_SESSION[$key]['filterfield'] as $idx => $field) {
567			$_REQUEST['f_' . $field] = $_SESSION[$key]['filtervalue'][$idx];
568		}
569	}
570
571	return $_SESSION[$key];
572}
573
574function wikiplugin_trackerFilter_split_filters($filters)
575{
576	if (empty($filters)) {
577		return [];
578	}
579	$in = false;
580	for ($i = 0, $max = strlen($filters); $i < $max; ++$i) {
581		if ($filters[$i] == '(') {
582			$in = true;
583		} elseif ($filters[$i] == ')') {
584			$in = false;
585		} elseif ($in && $filters[$i] == ':') {
586			$filters[$i] = ',';
587		}
588	}
589	$list = explode(':', $filters);
590	foreach ($list as $i => $filter) {
591		$list[$i] = str_replace(',', ':', $filter);
592	}
593	return $list;
594}
595
596function wikiplugin_trackerFilter_get_filters($trackerId = 0, array $listfields = [], &$formats, $status = 'opc')
597{
598	global $tiki_p_admin_trackers;
599	$trklib = TikiLib::lib('trk');
600	$filters = [];
601	if (empty($trackerId) && ! empty($listfields[0])) {
602		$field = $trklib->get_tracker_field($listfields[0]);
603		$trackerId = $field['trackerId'];
604	}
605
606	if ($listfields) {
607		$newListFields = [];
608		foreach ($listfields as $id => $field) {
609			/** @var TrackerLib $trklib */
610			$info = $trklib->get_field_info($field);
611			if (! is_numeric($field) || ($info && $info['trackerId'] == $trackerId)) {
612				$newListFields[] = $field;
613			}
614		}
615		if (count($listfields) != count($newListFields)) {
616			$listfields = $newListFields;
617			if ($tiki_p_admin_trackers == "y") {
618				Feedback::error(tr('TrackerFields: Unknown tracker field used in the parameter "filters"'));
619			}
620		}
621	}
622
623	$fields = $trklib->list_tracker_fields($trackerId, 0, -1, 'position_asc', '', true, empty($listfields) ? '' : ['fieldId' => $listfields]);
624	if (empty($listfields)) {
625		foreach ($fields['data'] as $field) {
626			$listfields[] = $field['fieldId'];
627		}
628	}
629
630	$iField = 0;
631	foreach ($listfields as $fieldId) {
632		if ($fieldId == 'status' || $fieldId == 'Status') {
633			$filter = ['name' => $fieldId, 'fieldId' => 'status', 'format' => 'd', 'opts' => [['id' => 'o', 'name' => 'open', 'selected' => (! empty($_REQUEST['f_status'])&& $_REQUEST['f_status'] == 'o') ? 'y' : 'n'], ['id' => 'p', 'name' => 'pending', 'selected' => (! empty($_REQUEST['f_status'])&& $_REQUEST['f_status'] == 'p') ? 'y' : 'n'], ['id' => 'c', 'name' => 'closed', 'selected' => (! empty($_REQUEST['f_status'])&& $_REQUEST['f_status'] == 'c') ? 'y' : 'n']]];
634			$filters[] = $filter;
635			continue;
636		}
637		if (! is_numeric($fieldId)) { // composite field
638			$filter = ['name' => 'Text', 'fieldId' => $fieldId, 'format' => 'sqlsearch'];
639			if (! empty($_REQUEST['f_' . $fieldId])) {
640				$filter['selected'] = $_REQUEST['f_' . $fieldId];
641			}
642			$filters[] = $filter;
643			continue;
644		}
645		foreach ($fields['data'] as $iField => $field) {
646			if ($field['fieldId'] == $fieldId) {
647				break;
648			}
649		}
650		if (($field['isHidden'] == 'y' || $field['isHidden'] == 'c') && $tiki_p_admin_trackers != 'y') {
651			continue;
652		}
653		if ($field['type'] == 'i' || $field['type'] == 'h' || $field['type'] == 'G' || $field['type'] == 'x') {
654			continue;
655		}
656		$fieldId = $field['fieldId'];
657		$res = [];
658		if (empty($formats)) {
659			$formats = [];
660		}
661		if (empty($formats[$fieldId])) { // default format depends on field type
662			switch ($field['type']) {
663				case 'e':// category
664					$res = wikiplugin_trackerfilter_get_categories($field);
665					$formats[$fieldId] = (count($res) >= 6) ? 'd' : 'r';
666					break;
667				case 'd': // drop down list
668				case 'y': // country
669				case 'g': // group selector
670				case 'M': // Multiple Values
671					$formats[$fieldId] = 'd';
672					break;
673				case 'REL':
674					$formats[$fieldId] = 'REL';
675					break;
676				case 'R': // radio
677					$formats[$fieldId] = 'r';
678					break;
679				case '*': //rating
680					$formats[$fieldId] = '*';
681					break;
682				case 'f':
683				case 'j':
684					$formats[$fieldId] = $field['type'];
685					break;
686				default:
687					$formats[$fieldId] = 't';
688					break;
689			}
690		}
691		if ($field['type'] == 'e' && ($formats[$fieldId] == 't' || $formats[$fieldId] == 'T' || $formats[$fieldId] == 'i')) { // do not accept a format text for a categ for the moment
692			if (empty($res)) {
693				$res = wikiplugin_trackerfilter_get_categories($field);
694			}
695			$formats[$fieldId] = (count($res) >= 6) ? 'd' : 'r';
696		}
697		$opts = [];
698		if ($formats[$fieldId] == 't' || $formats[$fieldId] == 'T' || $formats[$fieldId] == 'i') {
699			$selected = empty($_REQUEST['f_' . $fieldId]) ? '' : $_REQUEST['f_' . $fieldId];
700		} elseif ($formats[$fieldId] == 'range') {
701			// map f_ID_from/to request vars to ins_ ones for tracker fields to parse them
702			$from_input = $_REQUEST;
703			$to_input = $_REQUEST;
704			foreach (['', 'Month', 'Day', 'Year', 'Hour', 'Minute'] as $suffix) {
705				if (isset($from_input['f_' . $fieldId . '_from' . $suffix])) {
706					$from_input['ins_' . $fieldId . $suffix] = $from_input['f_' . $fieldId . '_from' . $suffix];
707				}
708				if (isset($to_input['f_' . $fieldId . '_to' . $suffix])) {
709					$to_input['ins_' . $fieldId . $suffix] = $to_input['f_' . $fieldId . '_to' . $suffix];
710				}
711			}
712			$handler = $trklib->get_field_handler($field);
713			$data = $handler->getFieldData($from_input);
714			$field['ins_id'] = 'f_' . $field['fieldId'] . '_from';
715			$field['value'] = $data['value'];
716			$opts['from'] = $field;
717			$data = $handler->getFieldData($to_input);
718			$field['ins_id'] = 'f_' . $field['fieldId'] . '_to';
719			$field['value'] = $data['value'];
720			$opts['to'] = $field;
721		} else {
722			$selected = false;
723			switch ($field['type']) {
724				case 'e': // category
725					if (empty($res)) {
726						$res = wikiplugin_trackerfilter_get_categories($field);
727					}
728					foreach ($res as $opt) {
729						$opt['id'] = $opt['categId'];
730						if (! empty($_REQUEST['f_' . $fieldId]) && ((is_array($_REQUEST['f_' . $fieldId]) && in_array($opt['id'], $_REQUEST['f_' . $fieldId])) || (! is_array($_REQUEST['f_' . $fieldId]) && $opt['id'] == $_REQUEST['f_' . $fieldId]))) {
731							$opt['selected'] = 'y';
732							$selected = true;
733						} else {
734							$opt['selected'] = 'n';
735						}
736						$opts[] = $opt;
737					}
738					$opts[] = wikiplugin_trackerFilter_add_empty_option($fieldId);
739					break;
740				case 'd': // drop down list
741				case 'R': // radio buttons
742				case '*': // stars
743				case 'M': // Multiple Values
744					$cumul = '';
745					foreach ($field['options_array'] as $val) {
746						// check if exists custom label, the format is 'value=label'
747						$delimiterPos = stripos($val, '=');
748						if ($delimiterPos !== false) {
749							$optval = substr($val, 0, $delimiterPos);
750							$sval = substr($val, $delimiterPos + 1);
751						} else {
752							$optval = $val;
753							$sval = $val;
754						}
755						$sval = strip_tags(TikiLib::lib('parser')->parse_data($sval, ['parsetoc' => false]));
756						$opt['id'] = $optval;
757						if ($field['type'] == '*') {
758							$cumul = $opt['name'] = "$cumul*";
759						} else {
760							$opt['name'] = $sval;
761						}
762						if (! empty($_REQUEST['f_' . $fieldId]) && ((! is_array($_REQUEST['f_' . $fieldId]) && $_REQUEST['f_' . $fieldId] == $optval) || (is_array($_REQUEST['f_' . $fieldId]) && in_array($optval, $_REQUEST['f_' . $fieldId])))) {
763							$opt['selected'] = 'y';
764							$selected = true;
765						} else {
766							$opt['selected'] = 'n';
767						}
768						$opts[] = $opt;
769					}
770					$opts[] = wikiplugin_trackerFilter_add_empty_option($fieldId);
771					break;
772				case 'c': // checkbox
773					$opt['id'] = 'y';
774					$opt['name'] = 'Yes';
775					if (! empty($_REQUEST['f_' . $fieldId]) && $_REQUEST['f_' . $fieldId] == 'y') {
776						$opt['selected'] = 'y';
777						$selected = true;
778					} else {
779						$opt['selected'] = 'n';
780					}
781					$opts[] = $opt;
782					$opt['id'] = 'n';
783					$opt['name'] = 'No';
784					if (! empty($_REQUEST['f_' . $fieldId]) && $_REQUEST['f_' . $fieldId] == 'n') {
785						$opt['selected'] = 'y';
786						$selected = true;
787					} else {
788						$opt['selected'] = 'n';
789					}
790					$opts[] = $opt;
791					$formats[$fieldId] = 'r';
792					break;
793				case 'n': // numeric
794				case 'D': // drop down + other
795				case 't': // text
796				case 'i': // text with initial
797				case 'a': // textarea
798				case 'm': // email
799				case 'y': // country
800				case 'k': //page selector
801				case 'u': // user
802				case 'g': // group
803				case 'q': // auto increment
804					if (isset($status)) {
805						$res = $trklib->list_tracker_field_values($trackerId, $fieldId, $status);
806					} else {
807						$res = $trklib->list_tracker_field_values($trackerId, $fieldId);
808					}
809					if ($field['type'] == 'u') {
810						$res = array_unique(
811							call_user_func_array(
812								'array_merge',
813								array_map(
814									function ($users) use ($trklib) {
815										return $trklib->parse_user_field($users);
816									},
817									$res
818								)
819							)
820						);
821						sort($res, SORT_NATURAL);
822					}
823					foreach ($res as $val) {
824						$sval = strip_tags(TikiLib::lib('parser')->parse_data($val, ['parsetoc' => false]));
825						$opt['id'] = $val;
826						$opt['name'] = $sval;
827						if ($field['type'] == 'y') { // country
828							$opt['name'] = str_replace('_', ' ', $opt['name']);
829						}
830						if (! empty($_REQUEST['f_' . $fieldId]) && ((! is_array($_REQUEST['f_' . $fieldId]) && urldecode($_REQUEST['f_' . $fieldId]) == $val) || (is_array($_REQUEST['f_' . $fieldId]) && in_array($val, $_REQUEST['f_' . $fieldId])))) {
831							$opt['selected'] = 'y';
832							$selected = true;
833						} else {
834							$opt['selected'] = 'n';
835						}
836						$opts[] = $opt;
837					}
838					$opts[] = wikiplugin_trackerFilter_add_empty_option($fieldId);
839					break;
840				case 'REL':
841					if (! empty($field['options_map']['filter'])) {
842						parse_str($field['options_map']['filter'], $filter);
843					} else {
844						$filter = [];
845					}
846					$format = '{title}';
847
848					$opts = [];
849					$opts['field_filter'] = $filter;
850					$opts['field_selection'] = isset($_REQUEST['f_'.$fieldId]) ? $_REQUEST['f_'.$fieldId] : '';
851					$opts['field_format'] = $format;
852					$opts['other_options'][] = wikiplugin_trackerFilter_add_empty_option($fieldId);
853
854					break;
855				case 'w': //dynamic item lists
856				case 'r': // item link
857					$opts = [];
858					$handler = $trklib->get_field_handler($field);
859					if ($handler) {
860						$list1 = $handler->getItemList();
861						foreach ($list1 as $id => $option) {
862							$opt['id'] = $id;
863							$opt['name'] = html_entity_decode($option); // this will be escaped by smarty but already escaped from ItemLink
864							if (! empty($_REQUEST['f_' . $fieldId]) &&
865								((! is_array($_REQUEST['f_' . $fieldId]) &&
866										urldecode($_REQUEST['f_' . $fieldId]) == $id) ||
867									(is_array($_REQUEST['f_' . $fieldId]) &&
868										in_array($id, $_REQUEST['f_' . $fieldId]))
869								)) {
870								$opt['selected'] = 'y';
871								$selected = true;
872							} else {
873								$opt['selected'] = 'n';
874							}
875							$opts[] = $opt;
876						}
877					}
878					$opts[] = wikiplugin_trackerFilter_add_empty_option($fieldId);
879					break;
880
881				case 'f':
882				case 'j':
883					$field['ins_id'] = 'f_' . $field['fieldId'];
884					break;
885				case 'F': // freetags
886					$freetaglib = TikiLib::lib('freetag');
887					$opts = [];
888					$tags = [];
889					$items = $trklib->list_items($field['trackerId'], 0, -1, '', [$field]);
890
891					foreach ($items['data'] as $item) {
892						$tags = array_merge($tags, $item['field_values'][0]['freetags']);
893					}
894
895					$tags = array_unique($tags);
896					sort($tags);
897
898					foreach ($tags as $tag) {
899						$selected = false;
900
901						if (isset($_REQUEST['f_' . $fieldId])) {
902							$selection = $_REQUEST['f_' . $fieldId];
903
904							if ((is_array($selection) && in_array($tag, $selection)) || $selection == $tag) {
905								$selected = true;
906							}
907						}
908
909						$opts[] = [
910						'id' => $tag,
911						'name' => $tag,
912						'selected' => $selected,
913						];
914					}
915
916					break;
917				default:
918					return tra('tracker field type not processed yet') . ' ' . $field['type'];
919			}
920		}
921		$filters[] = ['name' => $field['name'], 'fieldId' => $fieldId, 'format' => $formats[$fieldId], 'opts' => $opts, 'selected' => $selected, 'field' => $field];
922	}
923	return $filters;
924}
925
926/** get get categories for field
927 *
928 * @param array $field
929 * @return array of category arrays
930 * @throws Exception
931 */
932function wikiplugin_trackerfilter_get_categories($field)
933{
934	$handler = TikiLib::lib('trk')->get_field_handler($field);
935
936	if ($handler) {
937		$res = $handler->getFieldData();
938		// handle full path setting here
939		if ($field['options_map']['descendants'] == 2) {
940			foreach ($res['list'] as & $cat) {
941				$cat['name'] = $cat['categpath'];
942			}
943		}
944		return $res['list'];
945	} else {
946		return [];
947	}
948}
949
950function wikiplugin_trackerFilter_build_urlquery($params)
951{
952	if (empty($params['filterfield'])) {
953		return [];
954	}
955	$urlquery = [];
956	foreach ($params['filterfield'] as $key => $filter) {
957		$filterfield[] = $filter;
958		if (! empty($params['exactvalue'][$key]) && empty($params['filtervalue'][$key])) {
959			$filtervalue[] = '';
960			$exactvalue[] = $params['exactvalue'][$key];
961		} else {
962			$filtervalue[] = $params['filtervalue'][$key];
963			$exactvalue[] = '';
964		}
965	}
966	if (! empty($filterfield)) {
967		$urlquery['filterfield'] = implode(':', $filterfield);
968		$urlquery['filtervalue'] = implode(':', $filtervalue);
969		$urlquery['exactvalue'] = implode(':', array_map(
970			function ($ev) {
971				return is_array($ev) ?
972					key($ev) . reset($ev)
973					: $ev;
974			},
975			$exactvalue
976		));
977	}
978	if (! empty($params['sort_mode'])) {
979		$urlquery['sort_mode'] = $params['sort_mode'];
980	}
981	return $urlquery;
982}
983
984function wikiplugin_trackerFilter_add_empty_option($fieldId)
985{
986	$empty = '-Blank (no data)-';
987	return [
988		'id' => $empty,
989		'name' => $empty,
990		'selected' => (! empty($_REQUEST['f_' . $fieldId]) && ((! is_array($_REQUEST['f_' . $fieldId]) && $_REQUEST['f_' . $fieldId] === $empty) || (is_array($_REQUEST['f_' . $fieldId]) && in_array($empty, $_REQUEST['f_' . $fieldId])))) ? 'y' : 'n'
991	];
992}
993