1<?php
2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
3//
4// All Rights Reserved. See copyright.txt for details and a complete list of authors.
5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
6// $Id$
7
8//this script may only be included - so its better to die if called directly.
9if (strpos($_SERVER['SCRIPT_NAME'], basename(__FILE__)) !== false) {
10	header('location: index.php');
11	exit;
12}
13
14/**
15 * Class Table_Settings_Abstract
16 *
17 * Abstract class to produce default settings for applying the jQuery Tablesorter plugin to a table
18 *
19 * @package Tiki
20 * @subpackage Table
21 */
22abstract class Table_Settings_Abstract
23{
24	/**
25	 * Generic default settings - portions commented out are to illustrate options
26	 * Values here are overriden by table-specific defaults and any user settings
27	 * @var array
28	 */
29	protected $default = [
30		//this is the id of the table
31		'id' => 'tsTable',
32//		'selflinks' => true,				//if smarty self_links need to be removed
33		//overall sort settings for the table - individual column settings are under columns below
34		'sorts' => [
35			'type' => 'reset',				//choices: boolean true, boolean false, save, reset, savereset.
36			'group' => true,				//overall switch to allow or disallow group headings
37//			'multisort' => false,			//multisort on by default - set to false to disable
38//			'sortlist' => [					//to set initial sort icons in standard tables since sortList doesn't use selectors yet
39//				'col' => 0,					//id of the th element that the table is sorted by on server side
40//				'dir' => 'asc'				//direction of the default initial server-side sort
41//			]
42		],
43		//overall filter settings for the table or external filters - individual column settings are under columns below
44		'filters' => [
45			'type' => 'reset',						//choices: boolean true, boolean false, reset
46/*			'external' => false,
47			'hide' => false,					//to hide filters. choices: true, false (default)
48			//for filters external to the table
49			'external' => array(
50				0 => array(
51					'type' => 'dropdown',
52					'options' => array(
53						//label => url parameter value
54						'Email not confirmed' => 'filterEmailNotConfirmed=on',
55						'User not validated' => 'filterNotValidated=on',
56						'Never logged in' => 'filterNeverLoggedIn=on',
57					),
58				),
59			),
60*/
61		],
62/*
63		//to add pagination controls
64		'pager' => array(
65			'type' => true,					//choices: boolean true, boolean false
66			'max' => 25,
67			'expand' => array(50, 100, 250, 500),
68		),
69		//set whether filtering and sorting will be done server-side or client side
70		'ajax' => array(
71			'type' => false,
72			'url' => 'tiki-adminusers.php?{sort:sort}&{filter:filter}',
73			'offset' => 'offset'
74			//url sort and filter params manipulated on the server side if set to false
75			'custom' => false,
76		),
77		//set whether column, page or subtotals will be added
78		'math' => array(
79			'format' => '$(#,###.00)'       //see choices at http://mottie.github.io/tablesorter/docs/example-widget-math.html#mask_examples
80			//add a grand total or amount related to all numbers in the table on the page
81			'page' => 'sum'                 //see choices at http://mottie.github.io/tablesorter/docs/example-widget-math.html#attribute_settings
82			'pagelabel' => 'Grand total'    //custom label
83			'columnlabel' => 'Column total' //custon label for column totals
84		),
85		//determine whether the code uses columns selectors (e.g., th id) or indexes. With selectors the logic
86		//for which columns are shown doesn't need to be recreated for tables with smarty templates where some
87		//columns aren't shown based on logic. For plugins, indexes will generally need to be used since users set
88		// the columns
89*/
90		'usecolselector' => false,
91		'colselect' => [
92			'type' => false,
93		],
94/*
95		//Set individual sort and filter settings for each column
96		//No need to set if overall sorts and filters settings for the table are set to false above
97		'columns' => array(				//zero-based column index or th selector, used only if column-specific settings
98			0 => array(
99				//sort settings for the 1st column
100				'sort' => array(
101					'type' => true,			//choices: boolean true, boolean false, text, digit, currency, percent,
102											//usLongDate, shortDate, isoDate, dateFormat-ddmmyyyy, ipAddress, url, time
103											//also string-min (sort strings in a numeric column as large negative number)
104											//empty-top (sorts empty cells to the top)
105					'dir' => 'asc',			//asc for ascending and desc for descending
106					'ajax' =>'email',		//parameter name that is used when querying the database for this field value
107											//when ajax is used
108					'group' => 'letter'		//choices: letter (first letter), word (first word), number, date, date-year,
109											//date-month, date-day, date-week, date-time. letter and word can be
110											//extended, e.g., word-2 shows first 2 words. number-10 will group rows
111											//in blocks of ten.
112				),
113				//filter settings for the 1st column
114				'filter' => array(
115					//for a text input box where user can type to filter
116					'type' => 'text',							//choices: text, dropdown, date, range, non
117					'placeholder' => 'Enter valid email...',	//override default placeholder text
118					'ajax' => 'filterEmail',
119				),
120				//math settings for the 1st column
121				'math' => 'col-sum'         //choices: see http://mottie.github.io/tablesorter/docs/example-widget-math.html#attribute_settings
122			),
123			1 => array(
124				//sort settings for the 2nd column
125				'sort' => array(
126					'type' => false,
127				),
128				//filter settings for the 2nd column
129				'filter' => array(
130					//for a dropdown list for filtering
131					'type' => 'dropdown',
132					//options are optional -  automatically generated from column values if not set
133					//but automatic values will only reflect rows returned from server if ajax is used
134					'options' => array(
135						'first filter',
136						'second filter'
137					)
138				),
139			),
140			2 => array(
141				//sort settings for the 3rd column
142				'sort' => array(
143					'type' => false,
144				),
145				//filter settings for the 3rd column
146				'filter' => array(
147					//a sliding range filter for numeric fields
148					'type' => 'range',
149					'from' => 10,
150					'to' => 100,
151					'style' => 'popup'				//choices: popup or inline
152				),
153			),
154			3 => array(
155				//sort settings for the 4th column
156				'sort' => array(
157					'type' => false,
158				),
159				//filter settings for the 4th column
160				'filter' => array(
161					//produces from and to date fields for filtering
162					'type' => 'date',
163					'from' => '2013-12-15',
164					'to' => '2013-12-16',
165					'format' => 'yy-mm-dd'
166				),
167			),
168		),
169*/
170	];
171
172	/**
173	 * Default placeholder text for the different types of filters
174	 * @var array
175	 */
176	protected $defaultFilters = [
177		'text' => [
178			'type' => 'text',
179			'placeholder' => ''
180		],
181		//tra('Select a value')
182		'dropdown' => [
183			'type' => 'dropdown',
184			'placeholder' => ''
185		],
186		'date'	=> [
187			'type' => 'date',
188			'format' => 'yy-mm-dd',
189			'from' => '',
190			'to'	=> '',
191		],
192		'range'	=> [
193			'type' => 'range',
194			'style' => 'inline',		//other option is popup. from and to values can also be set
195		],
196	];
197
198	/**
199	 * Strings used to create button and pager control ids and default text
200	 * @var array
201	 */
202	protected $ids = [
203		'sorts' => [
204			'reset' => [
205				'id' => '-sortreset',
206				//tra('Unsort')
207				'text' => 'Unsort',
208			],
209		],
210		'filters' => [
211			'reset' => [
212				'id' => '-filterreset',
213				//tra('Clear Filters')
214				'text' => 'Clear Filters',
215			],
216			'external' => [
217				'id' => '-ext',
218			],
219		],
220		'pager' => [
221			'disable' => [
222				'id' => '-pagerbutton',
223				'text' => [
224					//tra('Enable Pager')
225					'enable' => 'Enable Pager',
226					//tra('Disable Pager')
227					'disable' => 'Disable Pager',
228				],
229			],
230			'controls' => [
231				'id' => '-pager',
232			],
233		],
234		'colselect' => [
235			'button' => [
236				'id' => '-colselectbtn',
237				//tra('Show/hide columns')
238				'text' => 'Show/hide columns',
239			],
240			'div' => [
241				'id' => '-colselectdiv',
242			]
243		],
244	];
245
246	/**
247	 * Used by a second level of abstract classes extending this class to set different
248	 * defaults for plugins vs standard tables
249	 * @var null
250	 */
251	protected $default2 = null;
252	/**
253	 * Used by table classes extending this class for table-specific default settings
254	 * @var null
255	 */
256	protected $ts = null;
257	/**
258	 * Used by table classes extending this class to manipulate user-specific settings
259	 * @var null
260	 */
261	protected $us = null;
262	/**
263	 * Final code settings
264	 * @var
265	 */
266	public $s;
267
268	/**
269	 * Constructs settings for the table
270	 *
271	 * @param array $settings		user-defined settings array
272	 */
273	public function __construct(array $settings)
274	{
275		//translate default text
276		$this->translateDefault();
277
278		$this->setUserSettings($settings);
279
280		//override any table settings with user settings
281		$this->ts = $this->overrideSettings($this->ts, $this->us);
282
283		//override second level of default settings
284		$this->ts = $this->overrideSettings($this->default2, $this->ts);
285
286		//get table-specific settings
287		$ts = $this->getTableSettings();
288
289		//override generic defaults with any table-specific defaults
290		$this->s = $this->overrideSettings($this->default, $ts);
291
292		//set placeholders for filters
293		$this->setPlaceholders();
294
295		//create id's for any buttons based on table id
296		$this->setIds();
297
298		//set pager row display levels
299		$this->setMax();
300
301		//create Ajax arrays for filtering and sorting
302		$this->setAjax();
303	}
304
305	/**
306	 * Translate and filter text
307	 */
308	private function translateDefault()
309	{
310		foreach ($this->ids as $type => $elements) {
311			foreach ($elements as $element => $info) {
312				if (isset($elements[$element]['text'])) {
313					if (is_array($elements[$element]['text'])) {
314						foreach ($elements[$element]['text'] as $each => $text) {
315							$this->default[$type][$element]['text'][$each] = htmlspecialchars(tra($text));
316						}
317					} else {
318						$this->default[$type][$element]['text'] = htmlspecialchars(tra($elements[$element]['text']));
319					}
320				}
321			}
322		}
323
324		foreach ($this->defaultFilters as $type => $settings) {
325			foreach ($settings as $each => $text) {
326				if ($each == 'placeholder' || ($type == 'date' && ($each == 'from' || $each == 'to'))) {
327					$this->defaultFilters[$type][$each] = htmlspecialchars(tra($text));
328				}
329			}
330		}
331	}
332
333	/**
334	 * Get table-specific settings
335	 *
336	 * @return null
337	 */
338	protected function getTableSettings()
339	{
340		return $this->ts;
341	}
342
343	/**
344	 * Get user-specific settings
345	 *
346	 * @return null
347	 */
348	protected function setUserSettings($settings)
349	{
350		$this->us = $settings;
351	}
352
353	/**
354	 * Used to override generic default settings with table-specific settings
355	 * and to override that result with user settings
356	 *
357	 * @param $default
358	 * @param $settings
359	 *
360	 * @return array
361	 */
362	protected function overrideSettings($default, $settings)
363	{
364		if (is_array($default) && is_array($settings)) {
365			$ret = array_replace_recursive($default, $settings);
366		} elseif (is_array($settings)) {
367			$ret = $settings;
368		} elseif (is_array($default)) {
369			$ret = $default;
370		}
371		return $ret;
372	}
373
374	/**
375	 * Set placeholders for filters
376	 */
377	private function setPlaceholders()
378	{
379		//TODO try array_column here
380		if (isset($this->s['columns'])) {
381			foreach ($this->s['columns'] as $col => $colinfo) {
382				if (isset($colinfo['filter'])) {
383					if (isset($colinfo['filter']['type'])) {
384						$ft = $colinfo['filter']['type'];
385					}
386					//add default placeholder text
387					if (isset($ft) && isset($this->defaultFilters[$ft])) {
388						$this->s['columns'][$col]['filter'] =
389							array_replace_recursive($this->defaultFilters[$ft], $colinfo['filter']);
390					}
391				}
392			}
393		}
394	}
395
396	/**
397	 * Automatically set the HTML ids for buttons and pager controls based on the overall table id
398	 */
399	private function setIds()
400	{
401		if (isset($this->s['id']) && isset($this->default['id']) && $this->s['id'] == $this->default['id']) {
402			static $i = 0;
403			++$i;
404			$this->s['id'] .= $i;
405		}
406		foreach ($this->ids as $type => $elements) {
407			if (isset($this->s[$type]['type'])
408				&& $this->s[$type]['type'] !== false
409				&& $this->s[$type]['type'] !== 'save') {
410				foreach ($elements as $element => $info) {
411					//for multiple elements
412					if (isset($this->s[$type][$element][0])) {
413						foreach ($this->s[$type][$element] as $key => $info) {
414							if (! isset($this->s[$type][$element][$key]['id'])) {
415								$this->s[$type][$element][$key]['id'] = $this->s['id']
416									. htmlspecialchars($elements[$element]['id'] . $key);
417							}
418						}
419					} elseif ((! isset($this->s[$type][$element]) || (isset($this->s[$type][$element])
420						&& $this->s[$type][$element] !== false)) && ! isset($this->s[$type][$element]['id'])) {
421						$this->s[$type][$element]['id'] = $this->s['id'] . htmlspecialchars($elements[$element]['id']);
422					}
423				}
424			}
425		}
426	}
427
428	/**
429	 * Set levels for pager dropdown that allows for displaying various numbers of rows
430	 */
431	private function setMax()
432	{
433		if (isset($this->s['pager']['type']) && $this->s['pager']['type'] !== false) {
434			if (isset($GLOBALS['maxRecords']) && ! isset($this->s['pager']['max'])) {
435				$this->s['pager']['max'] = $GLOBALS['maxRecords'];
436			} elseif (! isset($this->s['pager']['max'])) {
437				$this->s['pager']['max'] = 25;
438			}
439			if (! isset($this->s['pager']['expand']) && isset($this->s['pager']['max'])) {
440				$this->s['pager']['expand'] = [
441					$this->s['pager']['max'],
442					2 * $this->s['pager']['max'],
443					4 * $this->s['pager']['max'],
444					12 * $this->s['pager']['max'],
445				];
446			}
447		}
448	}
449
450	/**
451	 * Correlate Tablesorter sort and filter url parameters to those used in Tiki for database calls
452	 * This information will be passed to the jQuery code so that the url parameters generated by
453	 * Tablesorter can be changed to their Tiki equivalents for the specific table
454	 */
455	private function setAjax()
456	{
457		if (! empty($this->s['ajax'])) {
458			//sort and filter url parameters
459			if (isset($this->s['columns']) && is_array($this->s['columns'])) {
460				foreach ($this->s['columns'] as $col => $colinfo) {
461					$colpointer = $this->s['usecolselector'] ? substr($col, 1) : $col;
462					if (isset($colinfo['sort']['ajax'])) {
463						//tablesorter url param pattern is sort[0]=0 for ascending sort of first column
464						$this->s['ajax']['sort']['sort-' . $colpointer] = $colinfo['sort']['ajax'];
465					}
466					if (isset($colinfo['filter']['ajax'])) {
467						//tablesorter url param pattern is filter[0]=text for filter on first column
468						$this->s['ajax']['colfilters']['filter-' . $colpointer] = $colinfo['filter']['ajax'];
469					} elseif (isset($colinfo['filter']['options'])) {
470						foreach ($colinfo['filter']['options'] as $label => $value) {
471							$label = rawurlencode($label);
472							$this->s['ajax']['colfilters']['filter-' . $colpointer][$label] = $value;
473						}
474					}
475				}
476			}
477			//external filter params
478			if (isset($this->s['filters']['external']) && is_array($this->s['filters']['external'])) {
479				foreach ($this->s['filters']['external'] as $key => $info) {
480					if (isset($info['options']) && is_array($info['options'])) {
481						foreach ($info['options'] as $opt => $value) {
482							$this->s['ajax']['extfilters'][] = rawurlencode($value);
483						}
484					}
485				}
486			}
487		}
488	}
489}
490