1<?php
2/**
3 * CodeIgniter
4 *
5 * An open source application development framework for PHP
6 *
7 * This content is released under the MIT License (MIT)
8 *
9 * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
10 *
11 * Permission is hereby granted, free of charge, to any person obtaining a copy
12 * of this software and associated documentation files (the "Software"), to deal
13 * in the Software without restriction, including without limitation the rights
14 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 * copies of the Software, and to permit persons to whom the Software is
16 * furnished to do so, subject to the following conditions:
17 *
18 * The above copyright notice and this permission notice shall be included in
19 * all copies or substantial portions of the Software.
20 *
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 * THE SOFTWARE.
28 *
29 * @package	CodeIgniter
30 * @author	EllisLab Dev Team
31 * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
32 * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
33 * @license	http://opensource.org/licenses/MIT	MIT License
34 * @link	https://codeigniter.com
35 * @since	Version 1.3.1
36 * @filesource
37 */
38defined('BASEPATH') OR exit('No direct script access allowed');
39
40/**
41 * HTML Table Generating Class
42 *
43 * Lets you create tables manually or from database result objects, or arrays.
44 *
45 * @package		CodeIgniter
46 * @subpackage	Libraries
47 * @category	HTML Tables
48 * @author		EllisLab Dev Team
49 * @link		https://codeigniter.com/user_guide/libraries/table.html
50 */
51class CI_Table {
52
53	/**
54	 * Data for table rows
55	 *
56	 * @var array
57	 */
58	public $rows		= array();
59
60	/**
61	 * Data for table heading
62	 *
63	 * @var array
64	 */
65	public $heading		= array();
66
67	/**
68	 * Whether or not to automatically create the table header
69	 *
70	 * @var bool
71	 */
72	public $auto_heading	= TRUE;
73
74	/**
75	 * Table caption
76	 *
77	 * @var string
78	 */
79	public $caption		= NULL;
80
81	/**
82	 * Table layout template
83	 *
84	 * @var array
85	 */
86	public $template	= NULL;
87
88	/**
89	 * Newline setting
90	 *
91	 * @var string
92	 */
93	public $newline		= "\n";
94
95	/**
96	 * Contents of empty cells
97	 *
98	 * @var string
99	 */
100	public $empty_cells	= '';
101
102	/**
103	 * Callback for custom table layout
104	 *
105	 * @var function
106	 */
107	public $function	= NULL;
108
109	/**
110	 * Set the template from the table config file if it exists
111	 *
112	 * @param	array	$config	(default: array())
113	 * @return	void
114	 */
115	public function __construct($config = array())
116	{
117		// initialize config
118		foreach ($config as $key => $val)
119		{
120			$this->template[$key] = $val;
121		}
122
123		log_message('info', 'Table Class Initialized');
124	}
125
126	// --------------------------------------------------------------------
127
128	/**
129	 * Set the template
130	 *
131	 * @param	array	$template
132	 * @return	bool
133	 */
134	public function set_template($template)
135	{
136		if ( ! is_array($template))
137		{
138			return FALSE;
139		}
140
141		$this->template = $template;
142		return TRUE;
143	}
144
145	// --------------------------------------------------------------------
146
147	/**
148	 * Set the table heading
149	 *
150	 * Can be passed as an array or discreet params
151	 *
152	 * @param	mixed
153	 * @return	CI_Table
154	 */
155	public function set_heading($args = array())
156	{
157		$this->heading = $this->_prep_args(func_get_args());
158		return $this;
159	}
160
161	// --------------------------------------------------------------------
162
163	/**
164	 * Set columns. Takes a one-dimensional array as input and creates
165	 * a multi-dimensional array with a depth equal to the number of
166	 * columns. This allows a single array with many elements to be
167	 * displayed in a table that has a fixed column count.
168	 *
169	 * @param	array	$array
170	 * @param	int	$col_limit
171	 * @return	array
172	 */
173	public function make_columns($array = array(), $col_limit = 0)
174	{
175		if ( ! is_array($array) OR count($array) === 0 OR ! is_int($col_limit))
176		{
177			return FALSE;
178		}
179
180		// Turn off the auto-heading feature since it's doubtful we
181		// will want headings from a one-dimensional array
182		$this->auto_heading = FALSE;
183
184		if ($col_limit === 0)
185		{
186			return $array;
187		}
188
189		$new = array();
190		do
191		{
192			$temp = array_splice($array, 0, $col_limit);
193
194			if (count($temp) < $col_limit)
195			{
196				for ($i = count($temp); $i < $col_limit; $i++)
197				{
198					$temp[] = '&nbsp;';
199				}
200			}
201
202			$new[] = $temp;
203		}
204		while (count($array) > 0);
205
206		return $new;
207	}
208
209	// --------------------------------------------------------------------
210
211	/**
212	 * Set "empty" cells
213	 *
214	 * Can be passed as an array or discreet params
215	 *
216	 * @param	mixed	$value
217	 * @return	CI_Table
218	 */
219	public function set_empty($value)
220	{
221		$this->empty_cells = $value;
222		return $this;
223	}
224
225	// --------------------------------------------------------------------
226
227	/**
228	 * Add a table row
229	 *
230	 * Can be passed as an array or discreet params
231	 *
232	 * @param	mixed
233	 * @return	CI_Table
234	 */
235	public function add_row($args = array())
236	{
237		$this->rows[] = $this->_prep_args(func_get_args());
238		return $this;
239	}
240
241	// --------------------------------------------------------------------
242
243	/**
244	 * Prep Args
245	 *
246	 * Ensures a standard associative array format for all cell data
247	 *
248	 * @param	array
249	 * @return	array
250	 */
251	protected function _prep_args($args)
252	{
253		// If there is no $args[0], skip this and treat as an associative array
254		// This can happen if there is only a single key, for example this is passed to table->generate
255		// array(array('foo'=>'bar'))
256		if (isset($args[0]) && count($args) === 1 && is_array($args[0]) && ! isset($args[0]['data']))
257		{
258			$args = $args[0];
259		}
260
261		foreach ($args as $key => $val)
262		{
263			is_array($val) OR $args[$key] = array('data' => $val);
264		}
265
266		return $args;
267	}
268
269	// --------------------------------------------------------------------
270
271	/**
272	 * Add a table caption
273	 *
274	 * @param	string	$caption
275	 * @return	CI_Table
276	 */
277	public function set_caption($caption)
278	{
279		$this->caption = $caption;
280		return $this;
281	}
282
283	// --------------------------------------------------------------------
284
285	/**
286	 * Generate the table
287	 *
288	 * @param	mixed	$table_data
289	 * @return	string
290	 */
291	public function generate($table_data = NULL)
292	{
293		// The table data can optionally be passed to this function
294		// either as a database result object or an array
295		if ( ! empty($table_data))
296		{
297			if ($table_data instanceof CI_DB_result)
298			{
299				$this->_set_from_db_result($table_data);
300			}
301			elseif (is_array($table_data))
302			{
303				$this->_set_from_array($table_data);
304			}
305		}
306
307		// Is there anything to display? No? Smite them!
308		if (empty($this->heading) && empty($this->rows))
309		{
310			return 'Undefined table data';
311		}
312
313		// Compile and validate the template date
314		$this->_compile_template();
315
316		// Validate a possibly existing custom cell manipulation function
317		if (isset($this->function) && ! is_callable($this->function))
318		{
319			$this->function = NULL;
320		}
321
322		// Build the table!
323
324		$out = $this->template['table_open'].$this->newline;
325
326		// Add any caption here
327		if ($this->caption)
328		{
329			$out .= '<caption>'.$this->caption.'</caption>'.$this->newline;
330		}
331
332		// Is there a table heading to display?
333		if ( ! empty($this->heading))
334		{
335			$out .= $this->template['thead_open'].$this->newline.$this->template['heading_row_start'].$this->newline;
336
337			foreach ($this->heading as $heading)
338			{
339				$temp = $this->template['heading_cell_start'];
340
341				foreach ($heading as $key => $val)
342				{
343					if ($key !== 'data')
344					{
345						$temp = str_replace('<th', '<th '.$key.'="'.$val.'"', $temp);
346					}
347				}
348
349				$out .= $temp.(isset($heading['data']) ? $heading['data'] : '').$this->template['heading_cell_end'];
350			}
351
352			$out .= $this->template['heading_row_end'].$this->newline.$this->template['thead_close'].$this->newline;
353		}
354
355		// Build the table rows
356		if ( ! empty($this->rows))
357		{
358			$out .= $this->template['tbody_open'].$this->newline;
359
360			$i = 1;
361			foreach ($this->rows as $row)
362			{
363				if ( ! is_array($row))
364				{
365					break;
366				}
367
368				// We use modulus to alternate the row colors
369				$name = fmod($i++, 2) ? '' : 'alt_';
370
371				$out .= $this->template['row_'.$name.'start'].$this->newline;
372
373				foreach ($row as $cell)
374				{
375					$temp = $this->template['cell_'.$name.'start'];
376
377					foreach ($cell as $key => $val)
378					{
379						if ($key !== 'data')
380						{
381							$temp = str_replace('<td', '<td '.$key.'="'.$val.'"', $temp);
382						}
383					}
384
385					$cell = isset($cell['data']) ? $cell['data'] : '';
386					$out .= $temp;
387
388					if ($cell === '' OR $cell === NULL)
389					{
390						$out .= $this->empty_cells;
391					}
392					elseif (isset($this->function))
393					{
394						$out .= call_user_func($this->function, $cell);
395					}
396					else
397					{
398						$out .= $cell;
399					}
400
401					$out .= $this->template['cell_'.$name.'end'];
402				}
403
404				$out .= $this->template['row_'.$name.'end'].$this->newline;
405			}
406
407			$out .= $this->template['tbody_close'].$this->newline;
408		}
409
410		$out .= $this->template['table_close'];
411
412		// Clear table class properties before generating the table
413		$this->clear();
414
415		return $out;
416	}
417
418	// --------------------------------------------------------------------
419
420	/**
421	 * Clears the table arrays.  Useful if multiple tables are being generated
422	 *
423	 * @return	CI_Table
424	 */
425	public function clear()
426	{
427		$this->rows = array();
428		$this->heading = array();
429		$this->auto_heading = TRUE;
430		return $this;
431	}
432
433	// --------------------------------------------------------------------
434
435	/**
436	 * Set table data from a database result object
437	 *
438	 * @param	CI_DB_result	$object	Database result object
439	 * @return	void
440	 */
441	protected function _set_from_db_result($object)
442	{
443		// First generate the headings from the table column names
444		if ($this->auto_heading === TRUE && empty($this->heading))
445		{
446			$this->heading = $this->_prep_args($object->list_fields());
447		}
448
449		foreach ($object->result_array() as $row)
450		{
451			$this->rows[] = $this->_prep_args($row);
452		}
453	}
454
455	// --------------------------------------------------------------------
456
457	/**
458	 * Set table data from an array
459	 *
460	 * @param	array	$data
461	 * @return	void
462	 */
463	protected function _set_from_array($data)
464	{
465		if ($this->auto_heading === TRUE && empty($this->heading))
466		{
467			$this->heading = $this->_prep_args(array_shift($data));
468		}
469
470		foreach ($data as &$row)
471		{
472			$this->rows[] = $this->_prep_args($row);
473		}
474	}
475
476	// --------------------------------------------------------------------
477
478	/**
479	 * Compile Template
480	 *
481	 * @return	void
482	 */
483	protected function _compile_template()
484	{
485		if ($this->template === NULL)
486		{
487			$this->template = $this->_default_template();
488			return;
489		}
490
491		$this->temp = $this->_default_template();
492		foreach (array('table_open', 'thead_open', 'thead_close', 'heading_row_start', 'heading_row_end', 'heading_cell_start', 'heading_cell_end', 'tbody_open', 'tbody_close', 'row_start', 'row_end', 'cell_start', 'cell_end', 'row_alt_start', 'row_alt_end', 'cell_alt_start', 'cell_alt_end', 'table_close') as $val)
493		{
494			if ( ! isset($this->template[$val]))
495			{
496				$this->template[$val] = $this->temp[$val];
497			}
498		}
499	}
500
501	// --------------------------------------------------------------------
502
503	/**
504	 * Default Template
505	 *
506	 * @return	array
507	 */
508	protected function _default_template()
509	{
510		return array(
511			'table_open'		=> '<table border="0" cellpadding="4" cellspacing="0">',
512
513			'thead_open'		=> '<thead>',
514			'thead_close'		=> '</thead>',
515
516			'heading_row_start'	=> '<tr>',
517			'heading_row_end'	=> '</tr>',
518			'heading_cell_start'	=> '<th>',
519			'heading_cell_end'	=> '</th>',
520
521			'tbody_open'		=> '<tbody>',
522			'tbody_close'		=> '</tbody>',
523
524			'row_start'		=> '<tr>',
525			'row_end'		=> '</tr>',
526			'cell_start'		=> '<td>',
527			'cell_end'		=> '</td>',
528
529			'row_alt_start'		=> '<tr>',
530			'row_alt_end'		=> '</tr>',
531			'cell_alt_start'	=> '<td>',
532			'cell_alt_end'		=> '</td>',
533
534			'table_close'		=> '</table>'
535		);
536	}
537
538}
539