PageRenderTime 83ms CodeModel.GetById 21ms app.highlight 29ms RepoModel.GetById 25ms app.codeStats 0ms

/extensions/fieldtypes/ff_matrix/ft.ff_matrix.php

https://github.com/peteralewis/bk.fieldframe.ee_addon
PHP | 1064 lines | 770 code | 175 blank | 119 comment | 93 complexity | 64f8215978bbba24d6774b004bb181cc MD5 | raw file
   1<?php
   2
   3if ( ! defined('EXT')) exit('Invalid file request');
   4
   5
   6/**
   7 * FF Matrix Class
   8 *
   9 * @package   FieldFrame
  10 * @author    Brandon Kelly <me@brandon-kelly.com>
  11 * @copyright Copyright (c) 2009 Brandon Kelly
  12 * @license   http://creativecommons.org/licenses/by-sa/3.0/ Attribution-Share Alike 3.0 Unported
  13 */
  14class Ff_matrix extends Fieldframe_Fieldtype {
  15
  16	var $info = array(
  17		'name'     => 'FF Matrix',
  18		'version'  => '1.3.5',
  19		'desc'     => 'A customizable, expandable, and sortable table',
  20		'docs_url' => 'http://brandon-kelly.com/fieldframe/docs/ff-matrix'
  21	);
  22
  23	var $default_field_settings = array(
  24		'max_rows' => '',
  25		'cols' => array(
  26			'1' => array('name' => 'cell_1', 'label' => 'Cell 1', 'type' => 'ff_matrix_text', 'new' => 'y'),
  27			'2' => array('name' => 'cell_2', 'label' => 'Cell 2', 'type' => 'ff_matrix_textarea', 'new' => 'y')
  28		)
  29	);
  30
  31	var $default_tag_params = array(
  32		'cellspacing' => '1',
  33		'cellpadding' => '10',
  34		'orderby'     => '',
  35		'sort'        => 'asc',
  36		'offset'      => '',
  37		'limit'       => '',
  38		'backspace'   => ''
  39	);
  40
  41	var $postpone_saves = TRUE;
  42
  43	/**
  44	 * FF Matrix class constructor
  45	 */
  46	function __construct()
  47	{
  48		global $FFM;
  49		$FFM = $this;
  50	}
  51
  52	/**
  53	 * Update Fieldtype
  54	 *
  55	 * @param string  $from  The currently installed version
  56	 */
  57	function update($from)
  58	{
  59		global $DB, $FF;
  60
  61		if ($from AND version_compare($from, '1.3.0', '<'))
  62		{
  63			// convert any Select columns to FF Select
  64			$enable_ff_select = FALSE;
  65
  66			$fields = $DB->query('SELECT field_id, ff_settings FROM exp_weblog_fields WHERE field_type = "ftype_id_'.$this->_fieldtype_id.'"');
  67			foreach ($fields as $field)
  68			{
  69				$update = FALSE;
  70				$settings = $FF->_unserialize($field['ff_settings']);
  71				foreach ($settings['cols'] as &$col)
  72				{
  73					if ($col['type'] == 'ff_matrix_select')
  74					{
  75						$col['type'] = 'ff_select';
  76						$update = TRUE;
  77					}
  78				}
  79				if ($update)
  80				{
  81					$DB->query($DB->update_string('exp_weblog_fields', array('ff_settings' => $FF->_serialize($settings)), 'field_id = "'.$field['field_id'].'"'));
  82					$enable_ff_select = TRUE;
  83				}
  84			}
  85
  86			if ($enable_ff_select AND (($ff_select = $FF->_init_ftype('ff_select')) !== FALSE))
  87			{
  88				$DB->query($DB->insert_string('exp_ff_fieldtypes', array(
  89					'class'   => 'ff_select',
  90					'version' => $ff_select->info['version']
  91				)));
  92			}
  93		}
  94	}
  95
  96	/**
  97	 * Display Site Settings
  98	 */
  99	function display_site_settings()
 100	{
 101		global $DB, $DSP;
 102
 103		$fields_q = $DB->query('SELECT f.field_id, f.field_label, g.group_name
 104		                          FROM exp_weblog_fields AS f, exp_field_groups AS g
 105		                          WHERE f.field_type = "data_matrix"
 106		                            AND f.group_id = g.group_id
 107		                          ORDER BY g.group_name, f.field_order, f.field_label');
 108		if ($fields_q->num_rows)
 109		{
 110			$SD = new Fieldframe_SettingsDisplay();
 111
 112			$r = $SD->block();
 113
 114			$convert_r = '';
 115			$last_group_name = '';
 116			foreach($fields_q->result as $row)
 117			{
 118				if ($row['group_name'] != $last_group_name)
 119				{
 120					$convert_r .= $DSP->qdiv('defaultBold', $row['group_name']);
 121					$last_group_name = $row['group_name'];
 122				}
 123				$convert_r .= '<label>'
 124				            . $DSP->input_checkbox('convert[]', $row['field_id'])
 125				            . $row['field_label']
 126				            . '</label>'
 127				            . '<br>';
 128			}
 129			$r .= $SD->row(array(
 130				$SD->label('convert_label', 'convert_desc'),
 131				$convert_r
 132			));
 133
 134			$r .= $SD->block_c();
 135			return $r;
 136		}
 137
 138		return FALSE;
 139	}
 140
 141	/**
 142	 * Save Site Settings
 143	 *
 144	 * @param  array  $site_settings  The site settings post data
 145	 * @return array  The modified $site_settings
 146	 */
 147	function save_site_settings($site_settings)
 148	{
 149		global $DB, $FF, $LANG, $REGX;
 150
 151		if (isset($site_settings['convert']))
 152		{
 153			$setting_name_maps = array(
 154				'short_name' => 'name',
 155				'title'      => 'label'
 156			);
 157			$cell_type_maps = array(
 158				'text'     => 'ff_matrix_text',
 159				'textarea' => 'ff_matrix_textarea',
 160				'select'   => 'ff_matrix_select',
 161				'date'     => 'ff_matrix_date',
 162				'checkbox' => 'ff_checkbox'
 163			);
 164
 165			$fields_q = $DB->query('SELECT * FROM exp_weblog_fields
 166			                          WHERE field_id IN ('.implode(',', $site_settings['convert']).')');
 167
 168			$sql = array();
 169
 170			foreach($fields_q->result as $field)
 171			{
 172				$field_data = array('field_type' => 'ftype_id_'.$this->_fieldtype_id);
 173
 174				// get the conf string
 175				if (($old_conf = @unserialize($field['lg_field_conf'])) !== FALSE)
 176				{
 177					$conf = (is_array($old_conf) AND isset($old_conf['string']))
 178					  ?  $old_conf['string']  :  '';
 179				}
 180				else
 181				{
 182					$conf = $field['lg_field_conf'];
 183				}
 184
 185				// parse the conf string
 186
 187				$field_settings = array('cols' => array());
 188				$col_maps = array();
 189				foreach(preg_split('/[\r\n]{2,}/', trim($conf)) as $col_id => $col)
 190				{
 191					// default col settings
 192					$col_settings = array(
 193						'name'  => $LANG->line('cell').' '.($col_id+1),
 194						'label' => strtolower($LANG->line('cell')).'_'.($col_id+1),
 195						'type'  => 'text'
 196					);
 197
 198					foreach (preg_split('/[\r\n]/', $col) as $line)
 199					{
 200						$parts = explode('=', $line);
 201						$setting_name = trim($parts[0]);
 202						$setting_value = trim($parts[1]);
 203
 204						if (isset($setting_name_maps[$setting_name]))
 205						{
 206							$col_settings[$setting_name_maps[$setting_name]] = $setting_value;
 207						}
 208						else if ($setting_name == 'type')
 209						{
 210							$col_settings['type'] = isset($cell_type_maps[$setting_value])
 211							  ?  $cell_type_maps[$setting_value]
 212							  :  'ff_matrix_text';
 213						}
 214					}
 215					$col_maps[$col_settings['name']] = $col_id;
 216
 217					$field_settings['cols'][$col_id] = $col_settings;
 218				}
 219
 220				$field_data['ff_settings'] = $FF->_serialize($field_settings);
 221				$field_data['lg_field_conf'] = '';
 222				$sql[] = $DB->update_string('exp_weblog_fields', $field_data, 'field_id = '.$field['field_id']);
 223
 224				// update the weblog data
 225
 226				$data_q = $DB->query('SELECT entry_id, field_id_'.$field['field_id'].' data
 227				                        FROM exp_weblog_data
 228				                        WHERE field_id_'.$field['field_id'].' != ""');
 229
 230				foreach($data_q->result as $entry)
 231				{
 232					$entry_rows = array();
 233
 234					if (($data = @unserialize($entry['data'])) !== FALSE)
 235					{
 236						foreach($REGX->array_stripslashes($data) as $row_count => $row)
 237						{
 238							$entry_row = array();
 239							$include_row = FALSE;
 240							foreach($row as $name => $val)
 241							{
 242								if (isset($col_maps[$name]))
 243								{
 244									$entry_row[$col_maps[$name]] = $val;
 245									if ( ! $include_row AND $val) $include_row = TRUE;
 246								}
 247							}
 248							if ($include_row) $entry_rows[] = $entry_row;
 249						}
 250					}
 251
 252					$entry_data = array('field_id_'.$field['field_id'].'' => $FF->_serialize($entry_rows));
 253					$sql[] = $DB->update_string('exp_weblog_data', $entry_data, 'entry_id = '.$entry['entry_id']);
 254				}
 255			}
 256
 257			foreach($sql as $query)
 258			{
 259				$DB->query($query);
 260			}
 261		}
 262	}
 263
 264	/**
 265	 * Get Fieldtypes
 266	 *
 267	 * @access private
 268	 */
 269	function _get_ftypes()
 270	{
 271		global $FF;
 272
 273		if ( ! isset($this->ftypes))
 274		{
 275			// Add the included celltypes
 276			$this->ftypes = array(
 277				'ff_matrix_text' => new Ff_matrix_text(),
 278				'ff_matrix_textarea' => new Ff_matrix_textarea(),
 279				'ff_matrix_date' => new Ff_matrix_date()
 280			);
 281
 282			// Get the FF fieldtyes with display_cell
 283			$ftypes = array();
 284			if ( ! isset($FF->ftypes)) $FF->_get_ftypes();
 285			foreach($FF->ftypes as $class_name => $ftype)
 286			{
 287				if (method_exists($ftype, 'display_cell'))
 288				{
 289					$ftypes[$class_name] = $ftype;
 290				}
 291			}
 292			$FF->_sort_ftypes($ftypes);
 293
 294			// Combine with the included celltypes
 295			$this->ftypes = array_merge($this->ftypes, $ftypes);
 296		}
 297
 298		return $this->ftypes;
 299	}
 300
 301	/**
 302	 * Display Field Settings
 303	 * 
 304	 * @param  array  $field_settings  The field's settings
 305	 * @return array  Settings HTML (cell1, cell2, rows)
 306	 */
 307	function display_field_settings($field_settings)
 308	{
 309		global $DSP, $LANG;
 310
 311		$cell1 = $DSP->div('itemWrapper')
 312		       . '<label>'
 313		       . NBS.NBS.'<input type="text" name="max_rows" value="'. ($field_settings['max_rows'] ? $field_settings['max_rows'] : '∞').'" maxlength="3"'
 314		         . ' style="width:30px;'.($field_settings['max_rows'] ? '' : ' color:#999;').'"'
 315		         . ' onfocus="if (this.value == \'∞\'){ this.value = \'\'; this.style.color = \'#000\'; }"'
 316		         . ' onblur="if (!parseInt(this.value)){ this.value = \'∞\'; this.style.color = \'#999\'; }"/>'
 317		       . NBS.NBS.$LANG->line('max_rows_label')
 318		       . '</label>'
 319		       . $DSP->div_c();
 320
 321		$this->include_css('styles/ff_matrix.css');
 322		$this->include_js('scripts/jquery.sorttable.js');
 323		$this->include_js('scripts/jquery.ff_matrix_conf.js');
 324
 325		$ftypes = $this->_get_ftypes();
 326		$preview_name = 'ftype[ftype_id_'.$this->_fieldtype_id.'][preview]';
 327
 328		$cell_types = array();
 329		foreach($ftypes as $class_name => $ftype)
 330		{
 331			$cell_settings = isset($ftype->default_cell_settings) ? $ftype->default_cell_settings : array();
 332
 333			if (method_exists($ftype, 'display_cell_settings'))
 334			{
 335				if ( ! $ftype->info['no_lang']) $LANG->fetch_language_file($class_name);
 336				$settings_display = $ftype->display_cell_settings($cell_settings);
 337			}
 338			else
 339			{
 340				$settings_display = '';
 341			}
 342
 343			$cell_types[$class_name] = array(
 344				'name' => $ftype->info['name'],
 345				'preview' => $ftype->display_cell($preview_name, '', $cell_settings),
 346				'settings' => $settings_display
 347			);
 348		}
 349
 350		$cols = array();
 351		if ( ! is_array($field_settings['cols']))
 352		{
 353			$field_settings['cols'] = array();
 354		}
 355		foreach($field_settings['cols'] as $this->col_id => $this->col)
 356		{
 357			// Get the fieldtype. If it doesn't exist, use a text input in an attempt to preserve the data
 358			$ftype = isset($ftypes[$this->col['type']]) ? $ftypes[$this->col['type']] : $ftypes['ff_matrix_text'];
 359
 360			$cell_settings = array_merge(
 361				(isset($ftype->default_cell_settings) ? $ftype->default_cell_settings : array()),
 362				(isset($this->col['settings']) ? $this->col['settings'] : array())
 363			);
 364
 365			$cols[$this->col_id] = array(
 366				'name' => $this->col['name'],
 367				'label' => $this->col['label'],
 368				'type' => $this->col['type'],
 369				'preview' => $ftype->display_cell($preview_name.'['.rand().']', '', $cell_settings),
 370				'settings' => (method_exists($ftype, 'display_cell_settings') ? $ftype->display_cell_settings($cell_settings) : ''),
 371				'isNew' => isset($this->col['new'])
 372			);
 373		}
 374
 375		if (isset($this->col_id)) unset($this->col_id);
 376		if (isset($this->col)) unset($this->col);
 377
 378		// add json lib if < PHP 5.2
 379		include_once 'includes/jsonwrapper/jsonwrapper.php';
 380
 381		$js = 'jQuery(window).bind("load", function() {' . NL
 382		    . '  jQuery.fn.ffMatrixConf.lang.colName = "'.$LANG->line('col_name').'";' . NL
 383		    . '  jQuery.fn.ffMatrixConf.lang.colLabel = "'.$LANG->line('col_label').'";' . NL
 384		    . '  jQuery.fn.ffMatrixConf.lang.cellType = "'.$LANG->line('cell_type').'";' . NL
 385		    . '  jQuery.fn.ffMatrixConf.lang.cell = "'.$LANG->line('cell').'";' . NL
 386		    . '  jQuery.fn.ffMatrixConf.lang.deleteColumn = "'.$LANG->line('delete_column').'";' . NL
 387		    . '  jQuery.fn.ffMatrixConf.lang.confirmDeleteColumn = "'.$LANG->line('confirm_delete_column').'";' . NL
 388		    . NL
 389		    . '  jQuery.fn.ffMatrixConf.cellTypes = '.json_encode($cell_types).';' . NL
 390		    . NL
 391		    . '  jQuery(".ff_matrix_conf").ffMatrixConf('.$this->_fieldtype_id.', '.json_encode($cols).');' . NL
 392		    . '});';
 393
 394		$this->insert_js($js);
 395
 396		// display the config skeleton
 397		$conf = $DSP->qdiv('defaultBold', $LANG->line('conf_label'))
 398              . $DSP->qdiv('itemWrapper', $LANG->line('conf_subtext'))
 399		      . $DSP->div('ff_matrix ff_matrix_conf')
 400		      .   '<a class="button add" title="'.$LANG->line('add_column').'"></a>'
 401		      .   '<table cellspacing="0" cellpadding="0">'
 402		      .     '<tr class="tableHeading"></tr>'
 403		      .     '<tr class="preview"></tr>'
 404		      .     '<tr class="conf col"></tr>'
 405		      .     '<tr class="conf celltype"></tr>'
 406		      .     '<tr class="conf cellsettings"></tr>'
 407		      .     '<tr class="delete"></tr>'
 408		      .   '</table>'
 409		      . $DSP->div_c();
 410
 411		return array(
 412			'cell1' => $cell1,
 413			'rows'  => array(array($conf))
 414		);
 415	}
 416
 417	/**
 418	 * Save Field Settings
 419	 *
 420	 * Turn the options textarea value into an array of option names and labels
 421	 * 
 422	 * @param  array  $settings  The user-submitted settings, pulled from $_POST
 423	 * @return array  Modified $settings
 424	 */
 425	function save_field_settings($field_settings)
 426	{
 427		$field_settings['max_rows'] = is_numeric($field_settings['max_rows']) ? $field_settings['max_rows'] : '';
 428
 429		$ftypes = $this->_get_ftypes();
 430
 431		foreach($field_settings['cols'] as $this->col_id => &$this->col)
 432		{
 433			$ftype = isset($ftypes[$this->col['type']]) ? $ftypes[$this->col['type']] : $ftypes['ff_matrix_text'];
 434			if (method_exists($ftype, 'save_cell_settings'))
 435			{
 436				$this->col['settings'] = $ftype->save_cell_settings($this->col['settings']);
 437			}
 438		}
 439
 440		if (isset($this->col_id)) unset($this->col_id);
 441		if (isset($this->col)) unset($this->col);
 442
 443		if (isset($field_settings['preview'])) unset($field_settings['preview']);
 444
 445		return $field_settings;
 446	}
 447
 448	/**
 449	 * Display Field
 450	 * 
 451	 * @param  string  $field_name      The field's name
 452	 * @param  mixed   $field_data      The field's current value
 453	 * @param  array   $field_settings  The field's settings
 454	 * @return string  The field's HTML
 455	 */
 456	function display_field($field_name, $field_data, $field_settings)
 457	{
 458		global $DSP, $REGX, $FF, $LANG;
 459
 460		$ftypes = $this->_get_ftypes();
 461
 462		$this->include_css('styles/ff_matrix.css');
 463		$this->include_js('scripts/jquery.ff_matrix.js');
 464
 465		$cell_defaults = array();
 466		$r = '<div class="ff_matrix" id="'.$field_name.'">'
 467		   .   '<table cellspacing="0" cellpadding="0">'
 468		   .     '<tr class="head">'
 469		   .       '<td class="gutter"></td>';
 470
 471		// get the first and last col IDs
 472		$col_ids = array_keys($field_settings['cols']);
 473		$first_col_id = $col_ids[0];
 474		$last_col_id = $col_ids[count($col_ids)-1];
 475
 476		$this->row_count = -1;
 477
 478		foreach($field_settings['cols'] as $this->col_id => $this->col)
 479		{
 480			// add the header
 481			$class = '';
 482			if ($this->col_id == $first_col_id) $class .= ' first';
 483			if ($this->col_id == $last_col_id) $class .= ' last';
 484			$r .=  '<th class="tableHeading th'.$class.'">'.$this->col['label'].'</th>';
 485
 486			// get the default state
 487			if ( ! isset($ftypes[$this->col['type']]))
 488			{
 489				$this->col['type'] = 'ff_matrix_text';
 490				$this->col['settings'] = array('rows' => 1);
 491			}
 492			$ftype = $ftypes[$this->col['type']];
 493			$cell_settings = array_merge(
 494				(isset($ftype->default_cell_settings) ? $ftype->default_cell_settings : array()),
 495				(isset($this->col['settings']) ? $this->col['settings'] : array())
 496			);
 497			$cell_defaults[] = array(
 498				'type' => $this->col['type'],
 499				'cell' => $ftype->display_cell($field_name.'[0]['.$this->col_id.']', '', $cell_settings)
 500			);
 501		}
 502		$r .=      '<td class="gutter"></td>'
 503		    .    '</tr>';
 504
 505		if ( ! $field_data)
 506		{
 507			$field_data = array(array());
 508		}
 509
 510		$num_cols = count($field_settings['cols']);
 511		foreach($field_data as $this->row_count => $row)
 512		{
 513			$r .= '<tr>'
 514			    .   '<td class="gutter tableDnD-sort"></td>';
 515			$col_count = 0;
 516			foreach($field_settings['cols'] as $this->col_id => $this->col)
 517			{
 518				if ( ! isset($ftypes[$this->col['type']]))
 519				{
 520					$this->col['type'] = 'ff_matrix_text';
 521					$this->col['settings'] = array('rows' => 1);
 522					if (isset($row[$this->col_id]) AND is_array($row[$this->col_id]))
 523					{
 524						$row[$this->col_id] = serialize($row[$this->col_id]);
 525					}
 526				}
 527				$ftype = $ftypes[$this->col['type']];
 528				$cell_name = $field_name.'['.$this->row_count.']['.$this->col_id.']';
 529				$cell_settings = array_merge(
 530					(isset($ftype->default_cell_settings) ? $ftype->default_cell_settings : array()),
 531					(isset($this->col['settings']) ? $this->col['settings'] : array())
 532				);
 533
 534				$class = '';
 535				if ($this->col_id == $first_col_id) $class .= ' first';
 536				if ($this->col_id == $last_col_id) $class .= ' last';
 537
 538				$cell_data = isset($row[$this->col_id]) ? $row[$this->col_id] : '';
 539				$r .= '<td class="'.($this->row_count % 2 ? 'tableCellTwo' : 'tableCellOne').' '.$this->col['type'].' td'.$class.'">'
 540				    .   $ftype->display_cell($cell_name, $cell_data, $cell_settings)
 541				    . '</td>';
 542				$col_count++;
 543			}
 544			$r .=   '<td class="gutter"></td>'
 545			    . '</tr>';
 546		}
 547
 548		if (isset($this->row_count)) unset($this->row_count);
 549		if (isset($this->col_id)) unset($this->col_id);
 550		if (isset($this->col)) unset($this->col);
 551
 552		$r .=   '</table>'
 553		    . '</div>';
 554
 555		// add localized strings
 556		$LANG->fetch_language_file('ff_matrix');
 557		$this->insert_js('jQuery.fn.ffMatrix.lang.addRow = "'.$LANG->line('add_row').'";' . NL
 558		               . 'jQuery.fn.ffMatrix.lang.deleteRow = "'.$LANG->line('delete_row').'";' . NL
 559		               . 'jQuery.fn.ffMatrix.lang.confirmDeleteRow = "'.$LANG->line('confirm_delete_row').'";' . NL
 560		               . 'jQuery.fn.ffMatrix.lang.sortRow = "'.$LANG->line('sort_row').'";');
 561
 562		$this->insert('body', '<!--[if lte IE 7]>' . NL
 563		                    . '<script type="text/javascript" src="'.FT_URL.$this->_class_name.'/scripts/jquery.tablednd.js" charset="utf-8"></script>' . NL
 564		                    . '<script type="text/javascript" charset="utf-8">jQuery.fn.ffMatrix.useTableDnD = true;</script>' . NL
 565		                    . '<![endif]-->');
 566
 567		// add json lib if < PHP 5.2
 568		include_once 'includes/jsonwrapper/jsonwrapper.php';
 569
 570		$max_rows = $field_settings['max_rows'] ? $field_settings['max_rows'] : '0';
 571		$this->insert_js('jQuery(window).bind("load", function() {' . NL
 572		               . '  jQuery("#'.$field_name.'").ffMatrix("'.$field_name.'", '.json_encode($cell_defaults).', '.$max_rows.');' . NL
 573		               . '});');
 574
 575		return $r;
 576	}
 577
 578	/**
 579	 * Save Field
 580	 * 
 581	 * @param  mixed   $field_data      The field's current value
 582	 * @param  array   $field_settings  The field's settings
 583	 * @param  string  $entry_id        The entry ID
 584	 * @return array   Modified $field_settings
 585	 */
 586	function save_field($field_data, $field_settings, $entry_id)
 587	{
 588		$ftypes = $this->_get_ftypes();
 589
 590		$r = array();
 591
 592		foreach($field_data as $this->row_count => $row)
 593		{
 594			$include_row = FALSE;
 595
 596			foreach($row as $this->col_id => &$cell_data)
 597			{
 598				$this->col = $field_settings['cols'][$this->col_id];
 599				$ftype = isset($ftypes[$this->col['type']]) ? $ftypes[$this->col['type']] : $ftypes['ff_matrix_text'];
 600				if (method_exists($ftype, 'save_cell'))
 601				{
 602					$cell_settings = array_merge(
 603						(isset($ftype->default_cell_settings) ? $ftype->default_cell_settings : array()),
 604						(isset($this->col['settings']) ? $this->col['settings'] : array())
 605					);
 606					$cell_data = $ftype->save_cell($cell_data, $cell_settings, $entry_id);
 607				}
 608
 609				if ( ! $include_row AND $cell_data) $include_row = TRUE;
 610			}
 611
 612			if ($include_row) $r[] = $row;
 613		}
 614
 615		if (isset($this->row_count)) unset($this->row_count);
 616		if (isset($this->col_id)) unset($this->col_id);
 617		if (isset($this->col)) unset($this->col);
 618
 619		return $r;
 620	}
 621
 622	/**
 623	 * Sort Field Data
 624	 * @access private
 625	 */
 626	function _sort_field_data(&$row1, &$row2, $orderby_index=0)
 627	{
 628		$orderby = $this->orderby[$orderby_index][0];
 629		$sort = $this->orderby[$orderby_index][1];
 630
 631		$a = isset($row1[$orderby]) ? $row1[$orderby] : '';
 632		$b = isset($row2[$orderby]) ? $row2[$orderby] : '';
 633
 634		if ($a == $b)
 635		{
 636			$next_orderby_index = $orderby_index + 1;
 637			return ($next_orderby_index < count($this->orderby))
 638			  ?  $this->_sort_field_data($row1, $row2, $next_orderby_index)
 639			  :  0;
 640		}
 641
 642		return $sort * ($a < $b ? -1 : 1);
 643	}
 644
 645	function filter_field_data(&$field_data)
 646	{
 647		foreach($this->field_settings['cols'] as $col_id => $col)
 648		{
 649			// filtering by this col?
 650			if (isset($this->params['search:'.$col['name']]))
 651			{
 652				$val = $this->params['search:'.$col['name']];
 653
 654				preg_match('/(=)?(not )?(.*)/', $val, $matches);
 655				$exact_match = !! $matches[1];
 656				$negate = !! $matches[2];
 657				$val = $matches[3];
 658
 659				if (strpos($val, '&&') !== FALSE)
 660				{
 661					$delimiter = '&&';
 662					$find_all = TRUE;
 663				}
 664				else
 665				{
 666					$delimiter = '|';
 667					$find_all = FALSE;
 668				}
 669
 670				$terms = explode($delimiter, $val);
 671				$num_terms = count($terms);
 672				$exclude_rows = array();
 673
 674				foreach($field_data as $row_num => $row)
 675				{
 676					if ( ! isset($row[$col_id]))
 677					{
 678						$row[$col_id] = '';
 679					}
 680
 681					$cell = $row[$col_id];
 682
 683					// find the matches
 684					$num_matches = 0;
 685					foreach($terms as $term)
 686					{
 687						if ($term == 'IS_EMPTY') $term = '';
 688
 689						if ( ! $term OR $exact_match)
 690						{
 691							if ($cell == $term) $num_matches++;
 692						}
 693						else if (preg_match('/^([<>]=?)(.+)$/', $term, $matches) AND isset($matches[1]) AND isset($matches[2]))
 694						{
 695							eval('if ("'.$cell.'"'.$matches[1].'"'.$matches[2].'") $num_matches++;');
 696						}
 697						else if (strpos($cell, $term) !== FALSE) $num_matches++;
 698					}
 699
 700					$include = FALSE;
 701
 702					if ($num_matches)
 703					{
 704						if ($find_all)
 705						{
 706							if ($num_matches == $num_terms) $include = TRUE;
 707						}
 708						else
 709						{
 710							$include = TRUE;
 711						}
 712					}
 713
 714					if ($negate)
 715					{
 716						$include = !$include;
 717					}
 718
 719					if ( ! $include)
 720					{
 721						$exclude_rows[] = $row_num;
 722					}
 723				}
 724
 725				// remove excluded rows
 726				foreach(array_reverse($exclude_rows) as $row_num)
 727				{
 728					array_splice($field_data, $row_num, 1);
 729				}
 730			}
 731		}
 732	}
 733
 734	/**
 735	 * Display Tag
 736	 *
 737	 * @param  array   $params          Name/value pairs from the opening tag
 738	 * @param  string  $tagdata         Chunk of tagdata between field tag pairs
 739	 * @param  string  $field_data      Currently saved field value
 740	 * @param  array   $field_settings  The field's settings
 741	 * @return string  Modified $tagdata
 742	 */
 743	function display_tag($params, $tagdata, $field_data, $field_settings, $call_hook=TRUE)
 744	{
 745		global $FF, $TMPL;
 746
 747		// return table if single tag
 748		if ( ! $tagdata)
 749		{
 750			return $this->table($params, $tagdata, $field_data, $field_settings);
 751		}
 752
 753		$this->params = $params;
 754		$this->tagdata = $tagdata;
 755		$this->field_settings = $field_settings;
 756
 757		$r = '';
 758
 759		if ($this->field_settings['cols'] AND $field_data AND is_array($field_data))
 760		{
 761			// get the col names
 762			$col_ids_by_name = array();
 763			foreach($this->field_settings['cols'] as $col_id => $col)
 764			{
 765				$col_ids_by_name[$col['name']] = $col_id;
 766			}
 767
 768			// search: params
 769			$this->filter_field_data($field_data);
 770
 771			if ($call_hook AND $tmp_field_data = $FF->forward_hook('ff_matrix_tag_field_data', 10, array('field_data' => $field_data,
 772			                                                                              'field_settings' => $this->field_settings)))
 773			{
 774				$field_data = $tmp_field_data;
 775				unset($tmp_field_data);
 776			}
 777
 778			if ($this->params['orderby'])
 779			{
 780
 781				$this->orderby = array();
 782				$orderbys = explode('|', $this->params['orderby']);
 783				$sorts = explode('|', $this->params['sort']);
 784				foreach($orderbys as $i => $col_name)
 785				{
 786					// does this column exist?
 787					if (isset($col_ids_by_name[$col_name]))
 788					{
 789						$sort = (isset($sorts[$i]) AND strtolower($sorts[$i]) == 'desc') ? -1 : 1;
 790						$this->orderby[] = array($col_ids_by_name[$col_name], $sort);
 791					}
 792				}
 793
 794				usort($field_data, array(&$this, '_sort_field_data'));
 795				unset($this->orderby);
 796			}
 797
 798			else if ($this->params['sort'] == 'desc')
 799			{
 800				$field_data = array_reverse($field_data);
 801			}
 802
 803			else if ($this->params['sort'] == 'random')
 804			{
 805				shuffle($field_data);
 806			}
 807
 808			if ($this->params['offset'] OR $this->params['limit'])
 809			{
 810				if ($this->params['offset'] === '') $this->params['offset'] = '0';
 811				if ($this->params['limit'] === '') $this->params['limit'] = '0';
 812				$limit = $this->params['limit'] ? $this->params['limit'] : count($field_data);
 813				$field_data = array_splice($field_data, $this->params['offset'], $limit);
 814			}
 815
 816			$ftypes = $this->_get_ftypes();
 817			$total_rows = count($field_data);
 818
 819			// prepare for {switch} and {row_count} tags
 820			$this->prep_iterators($this->tagdata);
 821			$this->_count_tag = 'row_count';
 822
 823			foreach($field_data as $row_count => $row)
 824			{
 825				$row_tagdata = $this->tagdata;
 826
 827				if ($this->field_settings['cols'])
 828				{
 829					$cols = array();
 830					foreach($this->field_settings['cols'] as $col_id => $col)
 831					{
 832						$ftype = isset($ftypes[$col['type']]) ? $ftypes[$col['type']] : $ftypes['ff_matrix_text'];
 833						$cols[$col['name']] = array(
 834							'data'     => (isset($row[$col_id]) ? $row[$col_id] : ''),
 835							'settings' => array_merge(
 836							                  (isset($ftype->default_cell_settings) ? $ftype->default_cell_settings : array()),
 837							                  (isset($col['settings']) ? $col['settings'] : array())
 838							              ),
 839							'ftype'    => $ftype,
 840							'helpers'  => array()
 841						);
 842					}
 843
 844					$FF->_parse_tagdata($row_tagdata, $cols);
 845				}
 846
 847				// var swaps
 848				$row_tagdata = $TMPL->swap_var_single('total_rows', $total_rows, $row_tagdata);
 849
 850				// parse {switch} and {row_count} tags
 851				$this->parse_iterators($row_tagdata);
 852
 853				$r .= $row_tagdata;
 854			}
 855
 856			if ($this->params['backspace'])
 857			{
 858				$r = substr($r, 0, -$this->params['backspace']);
 859			}
 860		}
 861
 862		unset($this->params);
 863		unset($this->tagdata);
 864		unset($this->field_settings);
 865
 866		return $r;
 867	}
 868
 869	/**
 870	 * Table
 871	 *
 872	 * @param  array   $params          Name/value pairs from the opening tag
 873	 * @param  string  $tagdata         Chunk of tagdata between field tag pairs
 874	 * @param  string  $field_data      Currently saved field value
 875	 * @param  array   $field_settings  The field's settings
 876	 * @return string  Table
 877	 */
 878	function table($params, $tagdata, $field_data, $field_settings)
 879	{
 880		$thead = '';
 881		$tagdata = '    <tr>' . "\n";
 882
 883		foreach($field_settings['cols'] as $col_id => $col)
 884		{
 885			$thead .= '      <th scope="col">'.$col['label'].'</th>' . "\n";
 886			$tagdata .= '      <td>'.LD.$col['name'].RD.'</td>' . "\n";
 887		}
 888
 889		$tagdata .= '    </tr>' . "\n";
 890
 891		return '<table cellspacing="'.$params['cellspacing'].'" cellpadding="'.$params['cellpadding'].'">' . "\n"
 892		     . '  <thead>' . "\n"
 893		     . '    <tr>' . "\n"
 894		     .        $thead
 895		     . '    </tr>' . "\n"
 896		     . '  </thead>' . "\n"
 897		     . '  <tbody>' . "\n"
 898		     .      $this->display_tag($params, $tagdata, $field_data, $field_settings)
 899		     . '  </tbody>' . "\n"
 900		     . '</table>';
 901	}
 902
 903	/**
 904	 * Total Rows
 905	 *
 906	 * @param  array   $params          Name/value pairs from the opening tag
 907	 * @param  string  $tagdata         Chunk of tagdata between field tag pairs
 908	 * @param  string  $field_data      Currently saved field value
 909	 * @param  array   $field_settings  The field's settings
 910	 * @return string  Number of total rows
 911	 */
 912	function total_rows($params, $tagdata, $field_data, $field_settings)
 913	{
 914		// apparently count('') will return 1
 915		if ( ! $field_data) return 0;
 916
 917		$this->params = $params;
 918		$this->tagdata = $tagdata;
 919		$this->field_settings = $field_settings;
 920
 921		// search: params
 922		$this->filter_field_data($field_data);
 923
 924		unset($this->params);
 925		unset($this->tagdata);
 926		unset($this->field_settings);
 927
 928		return count($field_data);
 929	}
 930
 931}
 932
 933
 934class Ff_matrix_text extends Fieldframe_Fieldtype {
 935
 936	var $_class_name = 'ff_matrix_text';
 937
 938	var $info = array(
 939		'name' => 'Text',
 940		'no_lang' => TRUE
 941	);
 942
 943	var $default_cell_settings = array(
 944		'maxl' => '128',
 945		'size' => ''
 946	);
 947
 948	function display_cell_settings($cell_settings)
 949	{
 950		global $DSP, $LANG;
 951
 952		$r = '<label class="itemWrapper">'
 953		   . $DSP->input_text('maxl', $cell_settings['maxl'], '3', '5', 'input', '30px') . NBS
 954		   . $LANG->line('field_max_length')
 955		   . '</label>'
 956		   . '<label class="itemWrapper">'
 957		   . $DSP->input_text('size', $cell_settings['size'], '3', '5', 'input', '30px') . NBS
 958		   . $LANG->line('size')
 959		   . '</label>';
 960
 961		return $r;
 962	}
 963
 964	function display_cell($cell_name, $cell_data, $cell_settings)
 965	{
 966		global $DSP;
 967		$size = $cell_settings['size'] ? $cell_settings['size'] : '95%';
 968		if (is_numeric($size)) $size .= 'px';
 969		return $DSP->input_text($cell_name, $cell_data, '', $cell_settings['maxl'], '', $size);
 970	}
 971
 972}
 973
 974
 975class Ff_matrix_textarea extends Fieldframe_Fieldtype {
 976
 977	var $_class_name = 'ff_matrix_textarea';
 978
 979	var $info = array(
 980		'name' => 'Textarea',
 981		'no_lang' => TRUE
 982	);
 983
 984	var $default_cell_settings = array(
 985		'rows' => '2',
 986		'size' => ''
 987	);
 988
 989	function display_cell_settings($cell_settings)
 990	{
 991		global $DSP, $LANG;
 992
 993		$r = '<label class="itemWrapper">'
 994		   . $DSP->input_text('rows', $cell_settings['rows'], '3', '5', 'input', '30px') . NBS
 995		   . $LANG->line('textarea_rows')
 996		   . '</label>'
 997		   . '<label class="itemWrapper">'
 998		   . $DSP->input_text('size', $cell_settings['size'], '3', '5', 'input', '30px') . NBS
 999		   . $LANG->line('size')
1000		   . '</label>';
1001
1002		return $r;
1003	}
1004
1005	function display_cell($cell_name, $cell_data, $cell_settings)
1006	{
1007		global $DSP;
1008		$size = $cell_settings['size'] ? $cell_settings['size'] : '95%';
1009		if (is_numeric($size)) $size .= 'px';
1010		return $DSP->input_textarea($cell_name, $cell_data, $cell_settings['rows'], '', $size);
1011	}
1012
1013}
1014
1015
1016class Ff_matrix_date extends Fieldframe_Fieldtype {
1017
1018	var $_class_name = 'ff_matrix_date';
1019
1020	var $info = array(
1021		'name' => 'Date',
1022		'no_lang' => TRUE
1023	);
1024
1025	var $default_tag_params = array(
1026		'format' => '%F %d %Y'
1027	);
1028
1029	function display_cell($cell_name, $cell_data, $cell_settings)
1030	{
1031		global $DSP, $LOC, $LANG;
1032
1033		$LANG->fetch_language_file('search');
1034
1035		$cell_data = $cell_data
1036			? (is_numeric($cell_data)
1037				? $LOC->set_human_time($cell_data)
1038				: $LOC->set_human_time( strtotime($cell_data), FALSE))
1039			: '';
1040
1041		$r = $DSP->input_text($cell_name, $cell_data, '', '23', '', '140px') . NBS
1042		   . '<a style="cursor:pointer;" onclick="jQuery(this).prev().val(\''.$LOC->set_human_time($LOC->now).'\');" >'.$LANG->line('today').'</a>';
1043
1044		return $r;
1045	}
1046
1047	function save_cell($cell_data, $cell_settings)
1048	{
1049		global $LOC;
1050		return $cell_data ? strval($LOC->convert_human_date_to_gmt($cell_data)) : '';
1051	}
1052
1053	function display_tag($params, $tagdata, $field_data, $field_settings)
1054	{
1055		global $LOC;
1056		if ($params['format'])
1057		{
1058			$field_data = $LOC->decode_date($params['format'], $field_data);
1059		}
1060
1061		return $field_data;
1062	}
1063
1064}