PageRenderTime 80ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 1ms

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