PageRenderTime 78ms CodeModel.GetById 3ms RepoModel.GetById 0ms app.codeStats 0ms

/_backkup/Matrix2.2.4/ee1/fieldtypes/matrix/ft.matrix.php

https://bitbucket.org/sims/heartbeets
PHP | 2366 lines | 1453 code | 510 blank | 403 comment | 245 complexity | 9c24bc1a6bbb390067f8ac5d8f1cc2d2 MD5 | raw file
  1. <?php if (! defined('EXT')) exit('Invalid file request');
  2. /**
  3. * Matrix Fieldtype Class for EE1
  4. *
  5. * @package Matrix
  6. * @author Brandon Kelly <brandon@pixelandtonic.com>
  7. * @copyright Copyright (c) 2011 Pixel & Tonic, Inc
  8. */
  9. class Matrix_ft extends Fieldframe_Fieldtype {
  10. var $info = array(
  11. 'name' => 'Matrix',
  12. 'version' => '2.2.4',
  13. 'desc' => 'A customizable, expandable, and sortable table field',
  14. 'docs_url' => 'http://pixelandtonic.com/ffmatrix/docs',
  15. 'versions_xml_url' => 'http://pixelandtonic.com/ee/versions.xml'
  16. );
  17. var $requires = array(
  18. 'ff' => '1.4.3'
  19. );
  20. var $hooks = array(
  21. 'delete_entries_loop'
  22. );
  23. var $default_site_settings = array(
  24. 'license_key' => ''
  25. );
  26. var $default_field_settings = array(
  27. 'min_rows' => '0',
  28. 'max_rows' => '',
  29. 'col_ids' => array()
  30. );
  31. var $default_tag_params = array(
  32. 'dynamic_parameters' => '',
  33. 'row_id' => '',
  34. 'fixed_order' => '',
  35. 'orderby' => '',
  36. 'sort' => 'asc',
  37. 'offset' => '',
  38. 'limit' => '',
  39. 'backspace' => ''
  40. );
  41. var $postpone_saves = TRUE;
  42. /**
  43. * Fieldtype Constructor
  44. */
  45. function __construct()
  46. {
  47. global $FFM;
  48. $FFM = $this;
  49. }
  50. // --------------------------------------------------------------------
  51. /**
  52. * Theme URL
  53. */
  54. private function _theme_url()
  55. {
  56. if (! isset($this->_theme_url))
  57. {
  58. global $PREFS;
  59. $theme_folder_url = $PREFS->ini('theme_folder_url', 1);
  60. $this->_theme_url = $theme_folder_url.'third_party/matrix/';
  61. }
  62. return $this->_theme_url;
  63. }
  64. /**
  65. * Include Theme CSS
  66. */
  67. private function _include_theme_css($file)
  68. {
  69. $this->insert('head', '<link rel="stylesheet" type="text/css" href="'.$this->_theme_url().$file.'?'.$this->info['version'].'" />');
  70. }
  71. /**
  72. * Include Theme JS
  73. */
  74. private function _include_theme_js($file)
  75. {
  76. $this->insert('body', '<script type="text/javascript" src="'.$this->_theme_url().$file.'?'.$this->info['version'].'"></script>');
  77. }
  78. // --------------------------------------------------------------------
  79. /**
  80. * Add column
  81. */
  82. private function _add_col($data)
  83. {
  84. global $DB;
  85. // add the row to exp_matrix_cols
  86. $DB->query($DB->insert_string('exp_matrix_cols', $data));
  87. // get the col_id
  88. $col_id = $DB->insert_id;
  89. // add the column to exp_matrix_data
  90. $DB->query('ALTER TABLE exp_matrix_data ADD col_id_'.$col_id.' TEXT');
  91. return $col_id;
  92. }
  93. // --------------------------------------------------------------------
  94. /**
  95. * Update
  96. */
  97. function update($from)
  98. {
  99. global $DB, $FF;
  100. if ($from === FALSE)
  101. {
  102. // -------------------------------------------
  103. // Create the exp_matrix_cols table
  104. // -------------------------------------------
  105. if (! $DB->table_exists('exp_matrix_cols'))
  106. {
  107. $DB->query("CREATE TABLE exp_matrix_cols (
  108. `col_id` int(6) unsigned auto_increment,
  109. `site_id` int(4) unsigned default '1',
  110. `field_id` int(6) unsigned NULL,
  111. `col_name` varchar(32) NULL,
  112. `col_label` varchar(50) NULL,
  113. `col_instructions` text NULL,
  114. `col_type` varchar(50) default 'text',
  115. `col_required` char(1) default 'n',
  116. `col_search` char(1) default 'n',
  117. `col_order` int(3) unsigned NULL,
  118. `col_width` varchar(4) NULL,
  119. `col_settings` text NULL,
  120. PRIMARY KEY (`col_id`),
  121. KEY (`site_id`),
  122. KEY (`field_id`)
  123. )");
  124. }
  125. // -------------------------------------------
  126. // Create the exp_matrix_data table
  127. // -------------------------------------------
  128. if (! $DB->table_exists('exp_matrix_data'))
  129. {
  130. $DB->query("CREATE TABLE exp_matrix_data (
  131. `row_id` int(10) unsigned auto_increment,
  132. `site_id` int(4) unsigned default '1',
  133. `entry_id` int(10) unsigned NULL,
  134. `field_id` int(6) unsigned NULL,
  135. `row_order` int(4) unsigned NULL,
  136. PRIMARY KEY (`row_id`),
  137. KEY (`site_id`),
  138. KEY (`entry_id`),
  139. KEY (`row_order`)
  140. )");
  141. }
  142. // -------------------------------------------
  143. // FF Matrix Conversion
  144. // -------------------------------------------
  145. $ff_matrix = $DB->query('SELECT fieldtype_id, version FROM exp_ff_fieldtypes WHERE class = "ff_matrix"');
  146. if ($ff_matrix->num_rows)
  147. {
  148. // uninstall the old fieldtype
  149. $DB->query('DELETE FROM exp_ff_fieldtypes WHERE class = "ff_matrix"');
  150. // were there any FF Matrix fields?
  151. $fields = $DB->query('SELECT field_id, site_id, field_search, ff_settings FROM exp_weblog_fields WHERE field_type = "ftype_id_'.$ff_matrix->row['fieldtype_id'].'"');
  152. foreach ($fields->result as $field)
  153. {
  154. $settings = $FF->_unserialize($field['ff_settings']);
  155. $settings['col_ids'] = array();
  156. if (isset($settings['cols']))
  157. {
  158. if ($settings['cols'])
  159. {
  160. // -------------------------------------------
  161. // Add the rows to exp_matrix_cols
  162. // -------------------------------------------
  163. $col_ids_by_key = array();
  164. foreach ($settings['cols'] as $col_key => $col)
  165. {
  166. $col_type = $col['type'];
  167. $col_settings = isset($col['settings']) ? $col['settings'] : array();
  168. switch ($col_type)
  169. {
  170. case 'ff_matrix_select':
  171. $col_type = 'ff_select';
  172. break;
  173. case 'ff_matrix_text':
  174. case 'ff_matrix_textarea':
  175. $col_settings['multiline'] = ($col_type == 'ff_matrix_text' ? 'n' : 'y');
  176. $col_type = 'text';
  177. break;
  178. case 'ff_matrix_date':
  179. $col_type = 'date';
  180. break;
  181. }
  182. $col_id = $this->_add_col(array(
  183. 'site_id' => $field['site_id'],
  184. 'field_id' => $field['field_id'],
  185. 'col_name' => $col['name'],
  186. 'col_label' => $col['label'],
  187. 'col_type' => $col_type,
  188. 'col_search' => $field['field_search'],
  189. 'col_order' => $col_key,
  190. 'col_settings' => base64_encode(serialize($col_settings))
  191. ));
  192. $settings['col_ids'][] = $col_id;
  193. // map the col_id to the col_key for later
  194. $col_ids_by_key[$col_key] = $col_id;
  195. }
  196. // -------------------------------------------
  197. // Move the field data into exp_matrix_data
  198. // -------------------------------------------
  199. $field_id = 'field_id_'.$field['field_id'];
  200. $entries = $DB->query('SELECT entry_id, '.$field_id.' FROM exp_weblog_data WHERE '.$field_id.' != ""');
  201. foreach ($entries->result as $entry)
  202. {
  203. // unserialize the data
  204. $old_data = $FF->_unserialize($entry[$field_id]);
  205. foreach ($old_data as $row_count => $row)
  206. {
  207. $data = array(
  208. 'site_id' => $field['site_id'],
  209. 'entry_id' => $entry['entry_id'],
  210. 'field_id' => $field['field_id'],
  211. 'row_order' => $row_count+1
  212. );
  213. foreach ($row as $col_key => $cell_data)
  214. {
  215. // does this col exist?
  216. if (! isset($col_ids_by_key[$col_key])) continue;
  217. // get the col_id
  218. $col_id = $col_ids_by_key[$col_key];
  219. // serialize the cell data if necessary
  220. if (is_array($cell_data))
  221. {
  222. $cell_data = $FF->_serialize($cell_data);
  223. }
  224. // queue it up
  225. $data['col_id_'.$col_id] = $cell_data;
  226. }
  227. // add the row to exp_matrix_data
  228. $DB->query($DB->insert_string('exp_matrix_data', $data));
  229. }
  230. // clear out the old field data from exp_weblog_data
  231. $new_data = $this->_flatten_data($old_data);
  232. $DB->query($DB->update_string('exp_weblog_data', array($field_id => $new_data), 'entry_id = '.$entry['entry_id']));
  233. }
  234. }
  235. // remove 'cols' from field settings
  236. unset($settings['cols']);
  237. }
  238. // -------------------------------------------
  239. // Update the field
  240. // -------------------------------------------
  241. $settings = $FF->_serialize($settings);
  242. // save them back to the DB
  243. $DB->query($DB->update_string('exp_weblog_fields', array(
  244. 'field_type' => 'ftype_id_'.$this->_fieldtype_id,
  245. 'ff_settings' => $settings
  246. ), 'field_id = '.$field['field_id']));
  247. }
  248. }
  249. }
  250. }
  251. // --------------------------------------------------------------------
  252. /**
  253. * Display Site Settings
  254. */
  255. function display_site_settings()
  256. {
  257. global $DB, $DSP;
  258. $SD = new Fieldframe_SettingsDisplay();
  259. $r = $SD->block()
  260. . $SD->row(array(
  261. $SD->label('license_key'),
  262. $SD->text('license_key', $this->site_settings['license_key'])
  263. ));
  264. $fields_q = $DB->query('SELECT f.field_id, f.field_label, g.group_name
  265. FROM exp_weblog_fields AS f, exp_field_groups AS g
  266. WHERE f.field_type = "data_matrix"
  267. AND f.group_id = g.group_id
  268. ORDER BY g.group_name, f.field_order, f.field_label');
  269. if ($fields_q->num_rows)
  270. {
  271. $convert_r = '';
  272. $last_group_name = '';
  273. foreach ($fields_q->result as $row)
  274. {
  275. if ($row['group_name'] != $last_group_name)
  276. {
  277. $convert_r .= $DSP->qdiv('defaultBold', $row['group_name']);
  278. $last_group_name = $row['group_name'];
  279. }
  280. $convert_r .= '<label>'
  281. . $DSP->input_checkbox('convert[]', $row['field_id'])
  282. . $row['field_label']
  283. . '</label>'
  284. . '<br>';
  285. }
  286. $r .= $SD->row(array(
  287. $SD->label('convert_label', 'convert_desc'),
  288. $convert_r
  289. ));
  290. }
  291. $r .= $SD->block_c();
  292. return $r;
  293. }
  294. // --------------------------------------------------------------------
  295. /**
  296. * Save Site Settings
  297. */
  298. function save_site_settings($site_settings)
  299. {
  300. global $DB, $FF, $LANG, $REGX;
  301. if (isset($site_settings['convert']))
  302. {
  303. $setting_name_maps = array(
  304. 'short_name' => 'col_name',
  305. 'title' => 'col_label'
  306. );
  307. $cell_type_maps = array(
  308. 'text' => 'ff_matrix_text',
  309. 'textarea' => 'ff_matrix_textarea',
  310. 'select' => 'ff_matrix_select',
  311. 'date' => 'ff_matrix_date',
  312. 'checkbox' => 'ff_checkbox'
  313. );
  314. $fields = $DB->query('SELECT field_id, site_id, field_search, lg_field_conf FROM exp_weblog_fields WHERE field_id IN ('.implode(',', $site_settings['convert']).')');
  315. $sql = array();
  316. foreach ($fields->result as $field)
  317. {
  318. $field_data = array(
  319. 'field_type' => 'ftype_id_'.$this->_fieldtype_id
  320. );
  321. // get the conf string
  322. if (($old_conf = @unserialize($field['lg_field_conf'])) !== FALSE)
  323. {
  324. $conf = (is_array($old_conf) && isset($old_conf['string'])) ? $old_conf['string'] : '';
  325. }
  326. else
  327. {
  328. $conf = $field['lg_field_conf'];
  329. }
  330. // parse the conf string
  331. $field_settings = array(
  332. 'col_ids' => array()
  333. );
  334. $col_ids_by_name = array();
  335. foreach (preg_split('/[\r\n]{2,}/', trim($conf)) as $col_key => $col)
  336. {
  337. // default col settings
  338. $col_settings = array(
  339. 'col_name' => strtolower($LANG->line('cell')).'_'.($col_key+1),
  340. 'col_label' => $LANG->line('cell').' '.($col_key+1),
  341. 'col_type' => 'text'
  342. );
  343. $old = array();
  344. foreach (preg_split('/[\r\n]/', $col) as $line)
  345. {
  346. $parts = explode('=', $line);
  347. $old[$parts[0]] = $parts[1];
  348. }
  349. if (! $old['short_name']) continue;
  350. $col_type = 'text';
  351. $col_settings = array();
  352. if (isset($old['options']))
  353. {
  354. $options = explode('|', $old['options']);
  355. $col_settings['options'] = array();
  356. foreach ($options as $option)
  357. {
  358. $col_settings['options'][$option] = $option;
  359. }
  360. }
  361. if (isset($old['type']))
  362. {
  363. switch ($old['type'])
  364. {
  365. case 'text':
  366. $col_settings = array('multiline' => 'n');
  367. break;
  368. case 'textarea':
  369. $col_settings = array('multiline' => 'y');
  370. break;
  371. case 'select':
  372. $col_type = 'ff_select';
  373. break;
  374. case 'date':
  375. $col_type = 'date';
  376. break;
  377. case 'checkbox':
  378. $col_type = 'checkbox';
  379. break;
  380. }
  381. }
  382. $col_id = $this->_add_col(array(
  383. 'site_id' => $field['site_id'],
  384. 'field_id' => $field['field_id'],
  385. 'col_name' => $old['short_name'],
  386. 'col_label' => (isset($old['title']) ? $old['title'] : $LANG->line('cell').' '.($col_key+1)),
  387. 'col_type' => $col_type,
  388. 'col_search' => $field['field_search'],
  389. 'col_order' => $col_key,
  390. 'col_settings' => $FF->_serialize($col_settings)
  391. ));
  392. $field_settings['col_ids'][] = $col_id;
  393. // map the col_id to the short_name for later
  394. $col_ids_by_name[$old['short_name']] = $col_id;
  395. }
  396. // -------------------------------------------
  397. // Move the field data into exp_matrix_data
  398. // -------------------------------------------
  399. $field_id = 'field_id_'.$field['field_id'];
  400. $entries = $DB->query('SELECT entry_id, '.$field_id.' FROM exp_weblog_data WHERE '.$field_id.' != ""');
  401. foreach ($entries->result as $entry)
  402. {
  403. // unserialize the data
  404. $old_data = @unserialize($entry[$field_id]);
  405. if ($old_data !== FALSE)
  406. {
  407. foreach ($REGX->array_stripslashes($old_data) as $row_count => $row)
  408. {
  409. $data = array(
  410. 'site_id' => $field['site_id'],
  411. 'entry_id' => $entry['entry_id'],
  412. 'field_id' => $field['field_id'],
  413. 'row_order' => $row_count+1
  414. );
  415. $include_row = FALSE;
  416. foreach ($row as $name => $cell_data)
  417. {
  418. // does this col exist?
  419. if (! isset($col_ids_by_name[$name])) continue;
  420. // get the col_id
  421. $col_id = $col_ids_by_name[$name];
  422. // serialize the cell data if necessary
  423. if (is_array($cell_data))
  424. {
  425. $cell_data = $FF->_serialize($cell_data);
  426. }
  427. // queue it up
  428. $data['col_id_'.$col_id] = $cell_data;
  429. if ($cell_data) $include_row = TRUE;
  430. }
  431. if ($include_row)
  432. {
  433. // add the row to exp_matrix_data
  434. $DB->query($DB->insert_string('exp_matrix_data', $data));
  435. }
  436. if ($include_row) $entry_rows[] = $entry_row;
  437. }
  438. // clear out the old field data from exp_weblog_data
  439. $new_data = $this->_flatten_data($old_data);
  440. $DB->query($DB->update_string('exp_weblog_data', array($field_id => $new_data), 'entry_id = '.$entry['entry_id']));
  441. }
  442. }
  443. // -------------------------------------------
  444. // Update the field
  445. // -------------------------------------------
  446. $field_settings = $FF->_serialize($field_settings);
  447. // save them back to the DB
  448. $DB->query($DB->update_string('exp_weblog_fields', array(
  449. 'field_type' => 'ftype_id_'.$this->_fieldtype_id,
  450. 'ff_settings' => $settings
  451. ), 'field_id = '.$field['field_id']));
  452. }
  453. unset($site_settings['convert']);
  454. }
  455. return $site_settings;
  456. }
  457. // --------------------------------------------------------------------
  458. /**
  459. * Get Field Cols
  460. */
  461. private function _get_field_cols($col_ids)
  462. {
  463. global $PREFS, $DB;
  464. $col_ids = array_filter($col_ids);
  465. if (! $col_ids) return FALSE;
  466. // only look up the ones that aren't already cached
  467. $fetch_cols = array();
  468. foreach ($col_ids as $col_id)
  469. {
  470. if (! isset($this->field_cols[$col_id]))
  471. {
  472. $fetch_cols[] = $col_id;
  473. }
  474. }
  475. if ($fetch_cols)
  476. {
  477. $cols = $DB->query('SELECT col_id, col_type, col_label, col_name, col_instructions, col_width, col_required, col_search, col_settings
  478. FROM exp_matrix_cols
  479. WHERE col_id IN ('.implode(',', $fetch_cols).')
  480. ORDER BY col_order');
  481. // unserialize the settings and cache
  482. foreach ($cols->result as $col)
  483. {
  484. $col['col_settings'] = unserialize(base64_decode($col['col_settings']));
  485. if (! is_array($col['col_settings'])) $col['col_settings'] = array();
  486. $this->field_cols[$col['col_id']] = $col;
  487. }
  488. }
  489. // return the cached cols in the requested order
  490. $r = array();
  491. foreach ($col_ids as $col_id)
  492. {
  493. $r[] = $this->field_cols[$col_id];
  494. }
  495. return $r;
  496. }
  497. // --------------------------------------------------------------------
  498. /**
  499. * Get Fieldtypes
  500. *
  501. * @access private
  502. */
  503. function _get_celltypes()
  504. {
  505. global $FF;
  506. if (! isset($this->ftypes))
  507. {
  508. // Add the included celltypes
  509. require_once FT_PATH.'matrix/celltypes/text.php';
  510. require_once FT_PATH.'matrix/celltypes/date.php';
  511. $this->ftypes = array(
  512. 'text' => new Matrix_text(),
  513. 'date' => new Matrix_date()
  514. );
  515. // Get the FF fieldtyes with display_cell
  516. $ftypes = array();
  517. if (! isset($FF->ftypes)) $FF->_get_ftypes();
  518. foreach ($FF->ftypes as $class_name => $ftype)
  519. {
  520. if (method_exists($ftype, 'display_cell'))
  521. {
  522. $ftypes[$class_name] = $ftype;
  523. }
  524. }
  525. $FF->_sort_ftypes($ftypes);
  526. // Combine with the included celltypes
  527. $this->ftypes = array_merge($this->ftypes, $ftypes);
  528. }
  529. return $this->ftypes;
  530. }
  531. // --------------------------------------------------------------------
  532. /**
  533. * Namespace Settings
  534. */
  535. function _namespace_settings(&$settings, $namespace)
  536. {
  537. $settings = preg_replace('/(name=([\'\"]))([^\'"\[\]]+)([^\'"]*)(\2)/i', '$1'.$namespace.'[$3]$4$5', $settings);
  538. }
  539. // --------------------------------------------------------------------
  540. /**
  541. * Celltype Settings HTML
  542. */
  543. private function _celltype_settings_html($name, $namespace, $celltype, $cell_settings = array())
  544. {
  545. global $LANG;
  546. if (method_exists($celltype, 'display_cell_settings'))
  547. {
  548. if (! $celltype->info['no_lang']) $LANG->fetch_language_file($name);
  549. $cell_settings = array_merge((isset($celltype->default_cell_settings) ? $celltype->default_cell_settings : array()), $cell_settings);
  550. $returned = $celltype->display_cell_settings($cell_settings);
  551. // should we create the html for them?
  552. if (is_array($returned))
  553. {
  554. $r = '<table class="matrix-col-settings" cellspacing="0" cellpadding="0" border="0">';
  555. $total_cell_settings = count($returned);
  556. foreach ($returned as $cs_key => $cell_setting)
  557. {
  558. $tr_class = '';
  559. if ($cs_key == 0) $tr_class .= ' matrix-first';
  560. if ($cs_key == $total_cell_settings-1) $tr_class .= ' matrix-last';
  561. $r .= '<tr class="'.$tr_class.'">'
  562. . '<th class="matrix-first">'.$cell_setting[0].'</th>'
  563. . '<td class="matrix-last">'.$cell_setting[1].'</td>'
  564. . '</tr>';
  565. }
  566. $r .= '</table>';
  567. }
  568. else
  569. {
  570. $r = $returned;
  571. }
  572. $this->_namespace_settings($r, $namespace);
  573. }
  574. else
  575. {
  576. $r = '';
  577. }
  578. return $r;
  579. }
  580. // --------------------------------------------------------------------
  581. /**
  582. * Display Field Settings
  583. */
  584. function display_field_settings($field_settings)
  585. {
  586. global $FF, $DSP, $LANG;
  587. // include css and js
  588. $this->_include_theme_css('styles/matrix.css');
  589. $this->_include_theme_js('scripts/matrix.js');
  590. $this->_include_theme_js('scripts/matrix_text.js');
  591. $this->_include_theme_js('scripts/matrix_conf.js');
  592. // language
  593. $this->insert_js('MatrixConf.lang = { '
  594. . 'delete_col: "'.$LANG->line('delete_col').'" };');
  595. // -------------------------------------------
  596. // Get the celltypes
  597. // -------------------------------------------
  598. $celltypes = $this->_get_celltypes();
  599. $celltypes_select_options = array();
  600. $celltypes_js = array();
  601. foreach ($celltypes as $name => $celltype)
  602. {
  603. $celltypes_select_options[$name] = $celltype->info['name'];
  604. // default cell settings
  605. $celltypes_js[$name] = $this->_celltype_settings_html($name, 'ftype[ftype_id_'.$this->_fieldtype_id.'][cols][{COL_ID}][settings]', $celltype, array());
  606. }
  607. // -------------------------------------------
  608. // Get the columns
  609. // -------------------------------------------
  610. // is this an existing Matrix field?
  611. if ($FF->data['field_id'] && $FF->data['field_type'] == "ftype_id_{$this->_fieldtype_id}" && $field_settings['col_ids'])
  612. {
  613. $cols = $this->_get_field_cols($field_settings['col_ids']);
  614. $new = FALSE;
  615. }
  616. if (! isset($cols) || ! $cols)
  617. {
  618. $new = TRUE;
  619. // start off with a couple text cells
  620. $cols = array(
  621. array('col_id' => '0', 'col_label' => 'Cell 1', 'col_instructions' => '', 'col_name' => 'cell_1', 'col_type' => 'text', 'col_width' => '33%', 'col_required' => 'n', 'col_search' => 'n', 'col_settings' => array('maxl' => '', 'multiline' => 'n')),
  622. array('col_id' => '1', 'col_label' => 'Cell 2', 'col_instructions' => '', 'col_name' => 'cell_2', 'col_type' => 'text', 'col_width' => '', 'col_required' => 'n', 'col_search' => 'n', 'col_settings' => array('maxl' => '140', 'multiline' => 'y'))
  623. );
  624. }
  625. $cols_js = array();
  626. foreach ($cols as &$col)
  627. {
  628. $cols_js[] = array(
  629. 'id' => ($new ? 'col_new_' : 'col_id_') . $col['col_id'],
  630. 'type' => $col['col_type']
  631. );
  632. }
  633. $SD = new Fieldframe_SettingsDisplay();
  634. // -------------------------------------------
  635. // Minimum Rows
  636. // -------------------------------------------
  637. $r[] = array(
  638. $SD->label('min_rows'),
  639. $SD->text('min_rows', $field_settings['min_rows'], array('width' => '3em'))
  640. );
  641. // -------------------------------------------
  642. // Maximum Rows
  643. // -------------------------------------------
  644. $r[] = array(
  645. $SD->label('max_rows'),
  646. $SD->text('max_rows', $field_settings['max_rows'], array('width' => '3em'))
  647. );
  648. // -------------------------------------------
  649. // Matrix Configuration
  650. // -------------------------------------------
  651. $total_cols = count($cols);
  652. $table = '<div id="matrix-conf-container" style="margin-left: -6px;"><div id="matrix-conf">'
  653. . '<table class="matrix matrix-conf" cellspacing="0" cellpadding="0" border="0" style="background: #ecf1f4;">'
  654. . '<thead class="matrix">'
  655. . '<tr class="matrix matrix-first">'
  656. . '<td class="matrix-breakleft"></td>';
  657. // -------------------------------------------
  658. // Labels
  659. // -------------------------------------------
  660. foreach ($cols as $col_index => &$col)
  661. {
  662. $col_id = $new ? 'col_new_'.$col_index : 'col_id_'.$col['col_id'];
  663. $class = 'matrix';
  664. if ($col_index == 0) $class .= ' matrix-first';
  665. if ($col_index == $total_cols - 1) $class .= ' matrix-last';
  666. $table .= '<th class="'.$class.'" scope="col">'
  667. . '<input type="hidden" name="col_order[]" value="'.$col_id.'" />'
  668. . '<span>'.$col['col_label'].'</span>'
  669. . '</th>';
  670. }
  671. $table .= '</tr>'
  672. . '<tr class="matrix matrix-last">'
  673. . '<td class="matrix-breakleft"></td>';
  674. // -------------------------------------------
  675. // Instructions
  676. // -------------------------------------------
  677. foreach ($cols as $col_index => &$col)
  678. {
  679. $class = 'matrix';
  680. if ($col_index == 0) $class .= ' matrix-first';
  681. if ($col_index == $total_cols - 1) $class .= ' matrix-last';
  682. $table .= '<td class="'.$class.'">'.($col['col_instructions'] ? nl2br($col['col_instructions']) : '&nbsp;').'</td>';
  683. }
  684. $table .= '</tr>'
  685. . '</thead>'
  686. . '<tbody class="matrix">';
  687. // -------------------------------------------
  688. // Col Settings
  689. // -------------------------------------------
  690. $col_settings = array('type', 'label', 'name', 'instructions', 'width', 'search', 'settings');
  691. $total_settings = count($col_settings);
  692. foreach ($col_settings as $row_index => $col_setting)
  693. {
  694. $tr_class = 'matrix';
  695. if ($row_index == 0) $tr_class .= ' matrix-first';
  696. if ($row_index == $total_settings - 1) $tr_class .= ' matrix-last';
  697. $table .= '<tr class="'.$tr_class.'">'
  698. . '<th class="matrix-breakleft" scope="row">'.$LANG->line('col_'.$col_setting).'</th>';
  699. foreach ($cols as $col_index => &$col)
  700. {
  701. $col_id = $new ? 'col_new_'.$col_index : 'col_id_'.$col['col_id'];
  702. $setting_name = 'cols['.$col_id.']['.$col_setting.']';
  703. $td_class = 'matrix';
  704. if ($col_index == 0) $td_class .= ' matrix-first';
  705. if ($col_index == $total_cols - 1) $td_class .= ' matrix-last';
  706. switch ($col_setting)
  707. {
  708. case 'type':
  709. $shtml = $SD->select($setting_name, $col['col_'.$col_setting], $celltypes_select_options);
  710. break;
  711. case 'name':
  712. case 'width':
  713. $td_class .= ' matrix-text';
  714. $shtml = '<input type="text" class="matrix-textarea" name="'.$setting_name.'" value="'.$col['col_'.$col_setting].'" />';
  715. break;
  716. case 'required':
  717. case 'search':
  718. $shtml = '<input type="checkbox" name="'.$setting_name.'" value="y"'.($col['col_'.$col_setting] == 'y' ? ' checked="checked"' : '').' />';
  719. break;
  720. case 'settings':
  721. $cell_data = is_array($col['col_'.$col_setting]) ? $col['col_'.$col_setting] : array();
  722. if (! ($shtml = $this->_celltype_settings_html($col['col_type'], $setting_name, $celltypes[$col['col_type']], $cell_data)))
  723. {
  724. $td_class .= ' matrix-disabled';
  725. $shtml = '&nbsp;';
  726. }
  727. break;
  728. default:
  729. $td_class .= ' matrix-text';
  730. $shtml = '<textarea class="matrix-textarea" name="'.$setting_name.'" rows="1">'.$col['col_'.$col_setting].'</textarea>';
  731. }
  732. $table .= '<td class="'.$td_class.'">'.$shtml.'</td>';
  733. }
  734. $table .= '</tr>';
  735. }
  736. // -------------------------------------------
  737. // Delete Row buttons
  738. // -------------------------------------------
  739. $table .= '<tr>'
  740. . '<td class="matrix-breakleft"></td>';
  741. foreach ($cols as &$col)
  742. {
  743. $table .= '<td class="matrix-breakdown"><a class="matrix-btn" title="'.$LANG->line('delete_col').'"></a></td>';
  744. }
  745. $table .= '</tr>'
  746. . '</tbody>'
  747. . '</table>'
  748. . '<a class="matrix-btn matrix-add" title="'.$LANG->line('add_col').'"></a>'
  749. . '</div></div>';
  750. $r[] = array(
  751. $SD->label('matrix_configuration')
  752. . $table
  753. );
  754. // -------------------------------------------
  755. // Initialize the configurator js
  756. // -------------------------------------------
  757. $namespace = 'ftype[ftype_id_'.$this->_fieldtype_id.']';
  758. // add json lib if < PHP 5.2
  759. require_once FT_PATH.'matrix/includes/jsonwrapper/jsonwrapper.php';
  760. $js = 'MatrixConf.EE1 = true;' . NL
  761. . 'var matrixConf = new MatrixConf("'.$namespace.'", '.json_encode($celltypes_js).', '.json_encode($cols_js).', '.json_encode($col_settings).');';
  762. if ($new) $js .= NL.'matrixConf.totalNewCols = 2;';
  763. $this->insert_js($js);
  764. return array('rows' => $r);
  765. }
  766. // --------------------------------------------------------------------
  767. /**
  768. * Save Field Settings
  769. */
  770. function save_field_settings($post)
  771. {
  772. global $DB, $PREFS;
  773. $celltypes = $this->_get_celltypes();
  774. // -------------------------------------------
  775. // Delete any removed columns
  776. // -------------------------------------------
  777. if (isset($post['deleted_cols']))
  778. {
  779. foreach ($post['deleted_cols'] as $col_id)
  780. {
  781. $col_id = substr($col_id, 7);
  782. // delete the rows from exp_matrix_cols
  783. $DB->query('DELETE FROM exp_matrix_cols WHERE col_id = '.$col_id);
  784. // delete the actual column from exp_matrix_data
  785. $DB->query('ALTER TABLE exp_matrix_data DROP COLUMN `col_id_'.$col_id.'`');
  786. }
  787. }
  788. // -------------------------------------------
  789. // Add/update columns
  790. // -------------------------------------------
  791. $settings = array(
  792. 'min_rows' => (isset($post['min_rows']) && $post['min_rows'] ? $post['min_rows'] : '0'),
  793. 'max_rows' => (isset($post['max_rows']) && $post['max_rows'] ? $post['max_rows'] : ''),
  794. 'col_ids' => array()
  795. );
  796. $matrix_data_columns = array();
  797. foreach ($post['col_order'] as $col_order => $col_id)
  798. {
  799. $col = $post['cols'][$col_id];
  800. $cell_settings = isset($col['settings']) ? $col['settings'] : array();
  801. // give the celltype a chance to override
  802. $celltype = $celltypes[$col['type']];
  803. if (method_exists($celltype, 'save_cell_settings'))
  804. {
  805. $cell_settings = $celltype->save_cell_settings($cell_settings);
  806. }
  807. $col_data = array(
  808. 'col_name' => $col['name'],
  809. 'col_label' => str_replace('$', '&#36;', $col['label']),
  810. 'col_instructions' => str_replace('$', '&#36;', $col['instructions']),
  811. 'col_type' => $col['type'],
  812. 'col_required' => (isset($col['required']) && $col['required'] ? 'y' : 'n'),
  813. 'col_search' => (isset($col['search']) && $col['search'] ? 'y' : 'n'),
  814. 'col_width' => $col['width'],
  815. 'col_order' => $col_order,
  816. 'col_settings' => base64_encode(serialize($cell_settings))
  817. );
  818. $new = (substr($col_id, 0, 8) == 'col_new_');
  819. if ($new)
  820. {
  821. $col_data['site_id'] = $PREFS->ini('site_id');
  822. // insert the column
  823. $col_id = $this->_add_col($col_data);
  824. }
  825. else
  826. {
  827. $col_id = substr($col_id, 7);
  828. // just update the existing row
  829. $DB->query($DB->update_string('exp_matrix_cols', $col_data, 'col_id = '.$col_id));
  830. }
  831. // add the col_id to the field settings
  832. // - it's unfortunate that we can't just place the field_id in the matrix_cols
  833. // data, but alas, the future field_id is unknowable on new fields
  834. $settings['col_ids'][] = $col_id;
  835. }
  836. return $settings;
  837. }
  838. // --------------------------------------------------------------------
  839. /**
  840. * Display Field
  841. */
  842. function display_field($field_name, $field_data, $field_settings)
  843. {
  844. global $DB, $PREFS, $FF, $IN, $LANG;
  845. // -------------------------------------------
  846. // Include dependencies
  847. // - this needs to happen *before* we load the celltypes,
  848. // in case the celltypes are loading their own JS
  849. // -------------------------------------------
  850. if (! isset($this->included_dependencies))
  851. {
  852. // load the language file
  853. $LANG->fetch_language_file('matrix');
  854. // include css and js
  855. $this->_include_theme_css('styles/matrix.css');
  856. $this->_include_theme_js('scripts/matrix.js');
  857. // menu language
  858. $this->insert_js('Matrix.lang = { '
  859. . 'options: "'.$LANG->line('options').'", '
  860. . 'add_row_above: "'.$LANG->line('add_row_above').'", '
  861. . 'add_row_below: "'.$LANG->line('add_row_below').'", '
  862. . 'delete_row: "'.$LANG->line('delete_row').'" };');
  863. $this->included_dependencies = TRUE;
  864. }
  865. // -------------------------------------------
  866. // Initialize the field
  867. // -------------------------------------------
  868. $field_id = isset($FF->field_id) ? $FF->field_id : $FF->row['field_id'];
  869. $entry_id = isset($FF->row['entry_id']) ? $FF->row['entry_id'] : $IN->GBL('entry_id');
  870. $celltypes = $this->_get_celltypes();
  871. $min_rows = isset($field_settings['min_rows']) ? (int) $field_settings['min_rows'] : 0;
  872. $max_rows = isset($field_settings['max_rows']) ? (int) $field_settings['max_rows'] : 0;
  873. // default $min_rows to 1 if the field is required
  874. if (! $min_rows && $FF->row['field_required'] == 'y') $min_rows = 1;
  875. // single-row mode?
  876. $single_row_mode = ($min_rows == 1 && $max_rows == 1);
  877. $col_ids = isset($field_settings['col_ids']) ? $field_settings['col_ids'] : FALSE;
  878. if (! $col_ids) return;
  879. $cols = $this->_get_field_cols($col_ids);
  880. $total_cols = count($cols);
  881. if (! $total_cols) return;
  882. $col_settings = array();
  883. $select_col_ids = '';
  884. $show_instructions = FALSE;
  885. $cols_js = array();
  886. foreach ($cols as &$col)
  887. {
  888. // index the col by ID
  889. $select_col_ids .= ', col_id_'.$col['col_id'];
  890. // show instructions?
  891. if ($col['col_instructions']) $show_instructions = TRUE;
  892. // active cell type?
  893. if (! isset($celltypes[$col['col_type']]))
  894. {
  895. $col['col_type'] = 'text';
  896. }
  897. $celltype = $celltypes[$col['col_type']];
  898. // include this->settings in col settings
  899. $col_settings[$col['col_id']] = array_merge(
  900. (isset($celltype->default_cell_settings) ? $celltype->default_cell_settings : array()),
  901. (is_array($col['col_settings']) ? $col['col_settings'] : array())
  902. );
  903. $new_cell_html = $celltype->display_cell('{DEFAULT}', '', $col_settings[$col['col_id']]);
  904. $new_cell_settings = FALSE;
  905. $new_cell_class = FALSE;
  906. if (is_array($new_cell_html))
  907. {
  908. if (isset($new_cell_html['settings']))
  909. {
  910. $new_cell_settings = $new_cell_html['settings'];
  911. }
  912. if (isset($new_cell_html['class']))
  913. {
  914. $new_cell_class = $new_cell_html['class'];
  915. }
  916. $new_cell_html = $new_cell_html['data'];
  917. }
  918. // store the js-relevant stuff in $cols_js
  919. $cols_js[] = array(
  920. 'id' => 'col_id_'.$col['col_id'],
  921. 'name' => $col['col_name'],
  922. 'label' => $col['col_label'],
  923. 'required' => ($col['col_required'] == 'y' ? TRUE : FALSE),
  924. 'settings' => $col['col_settings'],
  925. 'type' => $col['col_type'],
  926. 'newCellHtml' => $new_cell_html,
  927. 'newCellSettings' => $new_cell_settings,
  928. 'newCellClass' => $new_cell_class
  929. );
  930. }
  931. // -------------------------------------------
  932. // Get the data
  933. // -------------------------------------------
  934. $data = array();
  935. // is this an existing entry?
  936. if ($entry_id)
  937. {
  938. $site_id = $PREFS->ini('site_id');
  939. $sql = 'SELECT row_id'.$select_col_ids.' FROM exp_matrix_data
  940. WHERE site_id = '.$site_id.' AND field_id = '.$field_id.' AND entry_id = '.$entry_id.'
  941. ORDER BY row_order';
  942. if ($max_rows)
  943. {
  944. $sql .= ' LIMIT '.$max_rows;
  945. }
  946. $query = $DB->query($sql);
  947. // is this a clone?
  948. $clone = ($IN->GBL('clone') == 'y');
  949. // re-index the query data
  950. foreach ($query->result as $count => $row)
  951. {
  952. $key = $clone ? 'row_new_'.$count : 'row_id_'.$row['row_id'];
  953. $data[$key] = $row;
  954. }
  955. }
  956. // -------------------------------------------
  957. // Reach the Minimum Rows count
  958. // -------------------------------------------
  959. $total_rows = count($data);
  960. if ($total_rows < $min_rows)
  961. {
  962. $extra_rows = $min_rows - $total_rows;
  963. for ($i = 0; $i < $extra_rows; $i++)
  964. {
  965. foreach ($cols as &$col)
  966. {
  967. $data['row_new_'.$i]['col_id_'.$col['col_id']] = '';
  968. }
  969. }
  970. $total_rows = $min_rows;
  971. }
  972. // -------------------------------------------
  973. // Table Head
  974. // -------------------------------------------
  975. $thead = '<thead class="matrix">';
  976. $headings = '';
  977. $instructions = '';
  978. // add left gutters if there can be more than one row
  979. if (! $single_row_mode)
  980. {
  981. $headings .= '<th class="matrix matrix-first matrix-tr-header"></th>';
  982. if ($show_instructions)
  983. {
  984. $instructions .= '<td class="matrix matrix-first matrix-tr-header"></td>';
  985. }
  986. }
  987. // add the labels and instructions
  988. foreach ($cols as $col_index => &$col)
  989. {
  990. $col_count = $col_index + 1;
  991. $class = 'matrix';
  992. if ($single_row_mode && $col_count == 1) $class .= ' matrix-first';
  993. if ($col_count == $total_cols) $class .= ' matrix-last';
  994. $headings .= '<th class="'.$class.'" scope="col" width="'.$col['col_width'].'">'.$col['col_label'].'</th>';
  995. if ($show_instructions)
  996. {
  997. $instructions .= '<td class="'.$class.'">'.nl2br($col['col_instructions']).'</td>';
  998. }
  999. }
  1000. $thead = '<thead class="matrix">'
  1001. . '<tr class="matrix matrix-first'.($show_instructions ? '' : ' matrix-last').'">' . $headings . '</tr>'
  1002. . ($show_instructions ? '<tr class="matrix matrix-last">' . $instructions . '</tr>' : '')
  1003. . '</thead>';
  1004. // -------------------------------------------
  1005. // Table Body
  1006. // -------------------------------------------
  1007. $rows_js = array();
  1008. $tbody = '<tbody class="matrix">';
  1009. $row_count = 0;
  1010. $total_new_rows = 0;
  1011. foreach ($data as $row_id => &$row)
  1012. {
  1013. $row_count ++;
  1014. // new?
  1015. $new = (substr($row_id, 0, 8) == 'row_new_');
  1016. if ($new) $total_new_rows++;
  1017. $row_js = array('id' => $row_id, 'cellSettings' => array());
  1018. $tr_class = 'matrix';
  1019. if ($row_count == 1) $tr_class .= ' matrix-first';
  1020. if ($row_count == $total_rows) $tr_class .= ' matrix-last';
  1021. $tbody .= '<tr class="'.$tr_class.'">';
  1022. // add left heading if there can be more than one row
  1023. if (! $single_row_mode)
  1024. {
  1025. $tbody .= '<th class="matrix matrix-first matrix-tr-header">'
  1026. . '<div><span>'.$row_count.'</span><a title="'.$LANG->line('options').'"></a></div>'
  1027. . '<input type="hidden" name="'.$field_name.'[row_order][]" value="'.$row_id.'" />'
  1028. . '</th>';
  1029. }
  1030. // add the cell data
  1031. foreach ($cols as $col_index => &$col)
  1032. {
  1033. $col_id = 'col_id_'.$col['col_id'];
  1034. $col_count = $col_index + 1;
  1035. $td_class = 'matrix';
  1036. // is this the first data cell?
  1037. if ($col_count == 1)
  1038. {
  1039. // is this also the first cell in the <tr>?
  1040. if ($single_row_mode) $td_class .= ' matrix-first';
  1041. // use .matrix-firstcell for active state
  1042. $td_class .= ' matrix-firstcell';
  1043. }
  1044. if ($col_count == $total_cols) $td_class .= ' matrix-last';
  1045. // get new instance of this celltype
  1046. $celltype = $celltypes[$col['col_type']];
  1047. $cell_name = $field_name.'['.$row_id.']['.$col_id.']';
  1048. $cell_data = $FF->_unserialize($row['col_id_'.$col['col_id']]);
  1049. // get the cell html
  1050. $cell_html = $celltype->display_cell($cell_name, $cell_data, $col_settings[$col['col_id']]);
  1051. // is the celltype sending settings too?
  1052. if (is_array($cell_html))
  1053. {
  1054. if (isset($cell_html['settings']))
  1055. {
  1056. $row_js['cellSettings'][$col_id] = $cell_html['settings'];
  1057. }
  1058. if (isset($cell_html['class']))
  1059. {
  1060. $td_class .= ' '.$cell_html['class'];
  1061. }
  1062. $cell_html = $cell_html['data'];
  1063. }
  1064. $tbody .= '<td class="'.$td_class.'">'.$cell_html.'</td>';
  1065. }
  1066. $tbody .= '</tr>';
  1067. $rows_js[] = $row_js;
  1068. }
  1069. $tbody .= '</tbody>';
  1070. // -------------------------------------------
  1071. // Plug it all together
  1072. // -------------------------------------------
  1073. $r = '<div id="'.$field_name.'" class="matrix" style="margin: 5px 8px 12px 0">'
  1074. . '<table class="matrix'.($data ? '' : ' matrix-nodata').'" cellspacing="0" cellpadding="0" border="0">'
  1075. . $thead
  1076. . $tbody
  1077. . '</table>';
  1078. if ($single_row_mode)
  1079. {
  1080. // no <th>s in the <tbody>, so we need to store the row_order outside of the table
  1081. $r .= '<input type="hidden" name="'.$field_name.'[row_order][]" value="'.$rows_js[0]['id'].'" />';
  1082. }
  1083. else
  1084. {
  1085. // add the '+' button
  1086. $r .= '<a class="matrix-btn matrix-add'.($max_rows && $max_rows == $total_rows ? ' matrix-btn-disabled' : '').'" title="'.$LANG->line('add_row').'"></a>';
  1087. }
  1088. $r .= '</div>';
  1089. // add json lib if < PHP 5.2
  1090. require_once FT_PATH.'matrix/includes/jsonwrapper/jsonwrapper.php';
  1091. $field_label = isset($FF->row['field_label']) ? $FF->row['field_label'] : '';
  1092. // initialize the field js
  1093. $js = 'jQuery(document).ready(function(){'
  1094. . 'var m = new Matrix("'.$field_name . '", '
  1095. . '"' . addslashes($field_label) . '", '
  1096. . json_encode($cols_js) . ', '
  1097. . json_encode($rows_js) . ', '
  1098. . $min_rows . ', '
  1099. . $max_rows
  1100. . ');' . NL
  1101. . 'm.totalNewRows = '.$total_new_rows.';'
  1102. . '})';
  1103. $this->insert_js($js);
  1104. return $r;
  1105. }
  1106. // --------------------------------------------------------------------
  1107. /**
  1108. * Flatten Data
  1109. */
  1110. private function _flatten_data($data)
  1111. {
  1112. $r = array();
  1113. if (is_array($data))
  1114. {
  1115. foreach ($data as $val)
  1116. {
  1117. $r[] = $this->_flatten_data($val);
  1118. }
  1119. }
  1120. else
  1121. {
  1122. $r[] = $data;
  1123. }
  1124. return implode(NL, array_filter($r));
  1125. }
  1126. // --------------------------------------------------------------------
  1127. /**
  1128. * Save Field
  1129. */
  1130. function save_field($data, $field_settings, $entry_id)
  1131. {
  1132. global $DB, $PREFS, $FF, $REGX;
  1133. // -------------------------------------------
  1134. // Get the celltypes and cols
  1135. // -------------------------------------------
  1136. $col_ids = isset($field_settings['col_ids']) ? $field_settings['col_ids'] : FALSE;
  1137. $cols = $this->_get_field_cols($col_ids);
  1138. $celltypes = $this->_get_celltypes();
  1139. // prep the cols
  1140. foreach ($cols as &$col)
  1141. {
  1142. $celltype = $celltypes[$col['col_type']];
  1143. $col['has_save_cell'] = method_exists($celltype, 'save_cell');
  1144. $col['has_post_save_cell'] = method_exists($celltype, 'post_save_cell');
  1145. if ($col['has_save_cell'] || $col['has_post_save_cell'])
  1146. {
  1147. $col['celltype_settings'] = array_merge((isset($celltype->default_cell_settings) ? $celltype->default_cell_settings : array()), $col['col_settings']);
  1148. }
  1149. }
  1150. // -------------------------------------------
  1151. // Delete the deleted rows
  1152. // -------------------------------------------
  1153. if (isset($data['deleted_rows']) && $data['deleted_rows'])
  1154. {
  1155. foreach ($data['deleted_rows'] as $row_name)
  1156. {
  1157. $delete_rows[] = substr($row_name, 7);
  1158. }
  1159. $this->_delete_rows($delete_rows);
  1160. }
  1161. // -------------------------------------------
  1162. // Add/update rows
  1163. // -------------------------------------------
  1164. if (isset($data['row_order']) && $data['row_order'] && $cols)
  1165. {
  1166. $r = '';
  1167. foreach ($data['row_order'] as $row_order => $row_name)
  1168. {
  1169. // get the row data
  1170. $row = isset($data[$row_name]) ? $data[$row_name] : array();
  1171. // is this a new row?
  1172. $new = (substr($row_name, 0, 8) == 'row_new_');
  1173. if (! $new)
  1174. {
  1175. $row_id = substr($row_name, 7);
  1176. }
  1177. // -------------------------------------------
  1178. // Prep the row's DB data
  1179. // -------------------------------------------
  1180. $vanilla_row_data = array();
  1181. $row_data = array(
  1182. 'row_order' => $row_order
  1183. );
  1184. foreach ($cols as &$col)
  1185. {
  1186. $cell_data = isset($row['col_id_'.$col['col_id']]) ? $row['col_id_'.$col['col_id']] : '';
  1187. // give the celltype a chance to do what it wants with it
  1188. if ($col['has_save_cell'])
  1189. {
  1190. $celltype = $celltypes[$col['col_type']];
  1191. // put these vars in place as to not break nGen File Field
  1192. $this->row_count = $row_name;
  1193. $this->col_id = 'col_id_'.$col['col_id'];
  1194. $cell_data = $celltype->save_cell($cell_data, $col['celltype_settings'], $entry_id);
  1195. unset($this->row_count, $this->col_id);
  1196. }
  1197. // save the vanilla cell data for post_save_cell()
  1198. $vanilla_row_data['col_id_'.$col['col_id']] = $cell_data;
  1199. // searchable?
  1200. if ($col['col_search'] == 'y')
  1201. {
  1202. $flattened_cell_data = $this->_flatten_data($cell_data);
  1203. if (strlen($flattened_cell_data))
  1204. {
  1205. $r .= $flattened_cell_data . NL;
  1206. }
  1207. }
  1208. // serialize the cell data if necessary
  1209. if (is_array($cell_data))
  1210. {
  1211. $cell_data = $FF->_serialize($cell_data);
  1212. }
  1213. else if (is_string($cell_data) && $PREFS->ini('auto_convert_high_ascii') == 'y')
  1214. {
  1215. $cell_data = $REGX->ascii_to_entities($cell_data);
  1216. }
  1217. $row_data['col_id_'.$col['col_id']] = $cell_data;
  1218. }
  1219. // -------------------------------------------
  1220. // Save or update the row
  1221. // -------------------------------------------
  1222. if ($new)
  1223. {
  1224. $row_data['site_id'] = $PREFS->ini('site_id');
  1225. $row_data['entry_id'] = $entry_id;
  1226. $row_data['field_id'] = $FF->field_id;
  1227. // insert the row
  1228. $DB->query($DB->insert_string('exp_matrix_data', $row_data));
  1229. // get the new row id
  1230. $row_id = $DB->insert_id;
  1231. }
  1232. else
  1233. {
  1234. // just update the existing row
  1235. $DB->query($DB->update_string('exp_matrix_data', $row_data, 'row_id = '.$row_id));
  1236. }
  1237. // -------------------------------------------
  1238. // post_save_cell()
  1239. // -------------------------------------------
  1240. foreach ($cols as &$col)
  1241. {
  1242. if ($col['has_post_save_cell'])
  1243. {
  1244. $celltype = $celltypes[$col['col_type']];
  1245. $this->col_id = $col['col_id'];
  1246. $this->col_name = 'col_id_'.$col['col_id'];
  1247. $this->row_id = $row_id;
  1248. $this->row_name = $row_name;
  1249. $cell_data = $vanilla_row_data['col_id_'.$col['col_id']];
  1250. $celltype->post_save_cell($cell_data, $col['celltype_settings'], $entry_id, $row_id);
  1251. unset($this->col_id, $this->col_name, $this->row_id, $this->row_name);
  1252. }
  1253. }
  1254. }
  1255. return $r ? $r : '1';
  1256. }
  1257. // no rows, so return blank
  1258. return '';
  1259. }
  1260. // --------------------------------------------------------------------
  1261. /**
  1262. * Delete Rows
  1263. */
  1264. private function _delete_rows($row_ids)
  1265. {
  1266. global $DB;
  1267. // -------------------------------------------
  1268. // Notify the celltypes
  1269. // -------------------------------------------
  1270. $celltypes = $this->_get_celltypes();
  1271. foreach ($celltypes as $name => $celltype)
  1272. {
  1273. if (method_exists($celltype, 'delete_rows'))
  1274. {
  1275. $celltype->delete_rows($row_ids);
  1276. }
  1277. }
  1278. // -------------------------------------------
  1279. // Delete the rows
  1280. // -------------------------------------------
  1281. $DB->query('DELETE FROM exp_matrix_data WHERE row_id IN ('.implode(',', $row_ids).')');
  1282. }
  1283. /**
  1284. * Delete Entries - Loop
  1285. */
  1286. function delete_entries_loop($entry_id, $weblog_id)
  1287. {
  1288. global $DB;
  1289. $rows = $DB->query('SELECT row_id FROM exp_matrix_data WHERE entry_id = '.$entry_id);
  1290. if ($rows->num_rows)
  1291. {
  1292. $row_ids = array();
  1293. foreach ($rows->result as $row)
  1294. {
  1295. $row_ids[] = $row['row_id'];
  1296. }
  1297. $this->_delete_rows($row_ids);
  1298. }
  1299. }
  1300. // --------------------------------------------------------------------
  1301. /**
  1302. * Data Query
  1303. */
  1304. private function _data_query($params, $cols, $select_mode = 'data', $select_aggregate = '')
  1305. {
  1306. global $PREFS, $DB, $FF, $FNS, $EXT;
  1307. if (! $cols) return FALSE;
  1308. // -------------------------------------------
  1309. // What's and Where's
  1310. // -------------------------------------------
  1311. $col_ids_by_name = array();
  1312. $select = 'row_id';
  1313. $where = '';
  1314. $use_where = FALSE;
  1315. foreach ($cols as &$col)
  1316. {
  1317. $col_id = 'col_id_'.$col['col_id'];
  1318. if ($select_mode == 'data') $select .= ', '.$col_id;
  1319. $col_ids_by_name[$col['col_name']] = $col['col_id'];
  1320. if (isset($params['search:'.$col['col_name']]))
  1321. {
  1322. $use_where = TRUE;
  1323. $terms = $params['search:'.$col['col_name']];
  1324. if (strncmp($terms, '=', 1) == 0)
  1325. {
  1326. // -------------------------------------------
  1327. // Exact Match e.g.: search:body="=pickle"
  1328. // -------------------------------------------
  1329. $terms = substr($terms, 1);
  1330. // special handling for IS_EMPTY
  1331. if (strpos($terms, 'IS_EMPTY') !== FALSE)
  1332. {
  1333. $terms = str_replace('IS_EMPTY', '', $terms);
  1334. $add_search = $FNS->sql_andor_string($terms, $col_id);
  1335. // remove the first AND output by $FNS->sql_andor_string() so we can parenthesize this clause
  1336. $add_search = substr($add_search, 3);
  1337. $not = (strncmp($terms, 'not ', 4) == 0);
  1338. $conj = ($add_search != '' && ! $not) ? 'OR' : 'AND';
  1339. if ($not)
  1340. {
  1341. $where .= 'AND ('.$add_search.' '.$conj.' '.$col_id.' != "") ';
  1342. }
  1343. else
  1344. {
  1345. $where .= 'AND ('.$add_search.' '.$conj.' '.$col_id.' = "") ';
  1346. }
  1347. }
  1348. else
  1349. {
  1350. $where .= $FNS->sql_andor_string($terms, $col_id).' ';
  1351. }
  1352. }
  1353. else
  1354. {
  1355. // -------------------------------------------
  1356. // "Contains" e.g.: search:body="pickle"
  1357. // -------------------------------------------
  1358. if (strncmp($terms, 'not ', 4) == 0)
  1359. {
  1360. $terms = substr($terms, 4);
  1361. $like = 'NOT LIKE';
  1362. }
  1363. else
  1364. {
  1365. $like = 'LIKE';
  1366. }
  1367. if (strpos($terms, '&&') !== FALSE)
  1368. {
  1369. $terms = explode('&&', $terms);
  1370. $andor = (strncmp($like, 'NOT', 3) == 0) ? 'OR' : 'AND';
  1371. }
  1372. else
  1373. {
  1374. $terms = explode('|', $terms);
  1375. $andor = (strncmp($like, 'NOT', 3) == 0) ? 'AND' : 'OR';
  1376. }
  1377. $where .= ' AND (';
  1378. foreach ($terms as $term)
  1379. {
  1380. if ($term == 'IS_EMPTY')
  1381. {
  1382. $where .= ' '.$col_id.' '.$like.' "" '.$andor;
  1383. }
  1384. else if (preg_match('/^[<>]=?/', $term, $match)) // less than/greater than
  1385. {
  1386. $term = substr($term, strlen($match[0]));
  1387. $where .= ' '.$col_id.' '.$match[0].' "'.$DB->escape_str($term).'" '.$andor;
  1388. }
  1389. else if (strpos($term, '\W') !== FALSE) // full word only, no partial matches
  1390. {
  1391. $not = ($like == 'LIKE') ? ' ' : ' NOT ';
  1392. // Note: MySQL's nutty POSIX regex word boundary is [[:>:]]
  1393. $term = '([[:<:]]|^)'.preg_quote(str_replace('\W', '', $term)).'([[:>:]]|$)';
  1394. $where .= ' '.$col_id.$not.'REGEXP "'.$DB->escape_str($term).'" '.$andor;
  1395. }
  1396. else
  1397. {
  1398. $where .= ' '.$col_id.' '.$like.' "%'.$DB->escape_like_str($term).'%" '.$andor;
  1399. }
  1400. }
  1401. $where = substr($where, 0, -strlen($andor)).') ';
  1402. }
  1403. }
  1404. }
  1405. // -------------------------------------------
  1406. // Row IDs
  1407. // -------------------------------------------
  1408. if ($fixed_order = (!! $params['fixed_order']))
  1409. {
  1410. $params['row_id'] = $params['fixed_order'];
  1411. }
  1412. if ($params['row_id'])
  1413. {
  1414. $use_where = TRUE;
  1415. if (strncmp($params['row_id'], 'not ', 4) == 0)
  1416. {
  1417. $not = 'NOT ';
  1418. $params['row_id'] = substr($params['row_id'], 4);
  1419. }
  1420. else
  1421. {
  1422. $not = '';
  1423. }
  1424. $row_ids = explode('|', $params['row_id']);
  1425. $where .= ' AND row_id '.$not.'IN (' . implode(',', $row_ids) . ')';
  1426. }
  1427. $sql = 'SELECT '.($select_mode == 'aggregate' ? $select_aggregate.' aggregate' : $select).'
  1428. FROM exp_matrix_data
  1429. WHERE field_id = '.$FF->field_id.'
  1430. AND entry_id = '.$FF->row['entry_id'].'
  1431. '.($use_where ? $where : '');
  1432. // -------------------------------------------
  1433. // Orberby + Sort
  1434. // -------------------------------------------
  1435. $orderbys = ($params['orderby']) ? explode('|', $params['orderby']) : array('row_order');
  1436. $sorts = ($params['sort']) ? explode('|', $params['sort']) : array();
  1437. $all_orderbys = array();
  1438. foreach ($orderbys as $i => $name)
  1439. {
  1440. $name = (isset($col_ids_by_name[$name])) ? 'col_id_'.$col_ids_by_name[$name] : $name;
  1441. $sort = (isset($sorts[$i]) && strtoupper($sorts[$i]) == 'DESC') ? 'DESC' : 'ASC';
  1442. $all_orderbys[] = $name.' '.$sort;
  1443. }
  1444. $sql .= ' ORDER BY '.implode(', ', $all_orderbys);
  1445. // -------------------------------------------
  1446. // Offset and Limit
  1447. // -------------------------------------------
  1448. if ((! $params['sort'] || $params['sort'] != 'random') && ($params['limit'] || $params['offset']))
  1449. {
  1450. $offset = ($params['offset'] && $params['offset'] > 0) ? $params['offset'] . ', ' : '';
  1451. $limit = ($params['limit']) && $params['limit'] > 0 ? $params['limit'] : 100;
  1452. $sql .= ' LIMIT ' . $offset . $limit;
  1453. }
  1454. // -------------------------------------------
  1455. // Run and return
  1456. // -------------------------------------------
  1457. // -------------------------------------------
  1458. // 'matrix_data_query' hook
  1459. // - Override the SQL query
  1460. //
  1461. if ($select_mode == 'data' && $EXT->active_hook('matrix_data_query'))
  1462. {
  1463. $query = $EXT->call_extension('matrix_data_query', $this, $params, $sql);
  1464. }
  1465. else
  1466. {
  1467. $query = $DB->query($sql);
  1468. }
  1469. //
  1470. // -------------------------------------------
  1471. switch ($select_mode)
  1472. {
  1473. case 'data':
  1474. $data = $query->result;
  1475. if ($fixed_order)
  1476. {
  1477. $data_by_id = array();
  1478. foreach ($data as $row)
  1479. {
  1480. $data_by_id[$row['row_id']] = $row;
  1481. }
  1482. $data = array();
  1483. foreach ($row_ids as $row_id)
  1484. {
  1485. if (isset($data_by_id[$row_id]))
  1486. {
  1487. $data[] = $data_by_id[$row_id];
  1488. }
  1489. }
  1490. }
  1491. return $data ? $data : FALSE;
  1492. case 'aggregate':
  1493. return $query->row['aggregate'];
  1494. case 'row_ids':
  1495. $row_ids = array();
  1496. foreach ($query->result as $row)
  1497. {
  1498. $row_ids[] = $row['row_id'];
  1499. }
  1500. return $row_ids;
  1501. }
  1502. }
  1503. // --------------------------------------------------------------------
  1504. /**
  1505. * Display Tag
  1506. */
  1507. function display_tag($params, $tagdata, $field_data, $field_settings)
  1508. {
  1509. global $IN, $FF, $TMPL, $FNS, $DB;
  1510. // ignore if no tagdata
  1511. if (! $tagdata) return;
  1512. // dynamic params
  1513. if ($params['dynamic_parameters'])
  1514. {
  1515. $dynamic_parameters = explode('|', $params['dynamic_parameters']);
  1516. foreach ($dynamic_parameters as $param)
  1517. {
  1518. if (($val = $IN->GBL($param, 'POST')) !== FALSE)
  1519. {
  1520. $params[$param] = $DB->escape_str($val);
  1521. }
  1522. }
  1523. }
  1524. $r = '';
  1525. // -------------------------------------------
  1526. // Get the columns
  1527. // -------------------------------------------
  1528. $col_ids = isset($field_settings['col_ids']) ? $field_settings['col_ids'] : array();
  1529. $cols = $this->_get_field_cols($col_ids);
  1530. if (! $cols) return $r;
  1531. // -------------------------------------------
  1532. // Get the data
  1533. // -------------------------------------------
  1534. $data = $this->_data_query($params, $cols);
  1535. if (! $data) return $r;
  1536. // -------------------------------------------
  1537. // Randomize
  1538. // -------------------------------------------
  1539. if ($params['sort'] == 'random')
  1540. {
  1541. shuffle($data);
  1542. // apply the limit now, since we didn't do it in the original query
  1543. if ($params['limit'])
  1544. {
  1545. $data = array_splice($data, 0, $params['limit']);
  1546. }
  1547. }
  1548. // -------------------------------------------
  1549. // Tagdata
  1550. // -------------------------------------------
  1551. $celltypes = $this->_get_celltypes();
  1552. // get the full list of row IDs
  1553. $field_row_ids_params = array_merge($params, array('row_id' => '', 'limit' => '', 'offset' => ''));
  1554. $this->_field_row_ids = $this->_data_query($field_row_ids_params, $cols, 'row_ids');
  1555. $this->_field_total_rows = count($this->_field_row_ids);
  1556. // are {prev_row} or {next_row} being used?
  1557. $siblings_in_use = ((strstr($tagdata, 'prev_row') !== FALSE) || (strstr($tagdata, 'next_row') !== FALSE));
  1558. if ($siblings_in_use) $this->_field_settings = $field_settings;
  1559. // {total_rows} and {field_total_rows}
  1560. $vars = array(
  1561. 'total_rows' => count($data),
  1562. 'field_total_rows' => $this->_field_total_rows
  1563. );
  1564. $tagdata = $FNS->var_swap($tagdata, $vars);
  1565. $tagdata = $FNS->prep_conditionals($tagdata, $vars);
  1566. // process each row
  1567. foreach ($data as $this->_row_index => &$row)
  1568. {
  1569. $row_tagdata = $tagdata;
  1570. // get the row's index within the entire field
  1571. $this->_field_row_index = array_search($row['row_id'], $this->_field_row_ids);
  1572. // parse sibling tags
  1573. if ($siblings_in_use)
  1574. {
  1575. $conditionals = array(
  1576. 'prev_row' => ($this->_field_row_index > 0 ? 'y' : ''),
  1577. 'next_row' => ($this->_field_row_index < $this->_field_total_rows-1 ? 'y' : '')
  1578. );
  1579. $row_tagdata = $FNS->prep_conditionals($row_tagdata, $conditionals);
  1580. // {prev_row} and {next_row} tag pairs
  1581. $row_tagdata = preg_replace_callback('/'.LD.'(prev_row|next_row)'.RD.'(.*)'.LD.'&#47;\1'.RD.'/sU', array(&$this, '_parse_sibling_tag'), $row_tagdata);
  1582. }
  1583. $tags = array();
  1584. foreach ($cols as &$col)
  1585. {
  1586. $col_id = 'col_id_'.$col['col_id'];
  1587. $celltype = isset($celltypes[$col['col_type']]) ? $celltypes[$col['col_type']] : $celltypes['text'];
  1588. $tags[$col['col_name']] = array(
  1589. 'data' => $FF->_unserialize($row[$col_id]),
  1590. 'settings' => array_merge(
  1591. (isset($celltype->default_cell_settings) ? $celltype->default_cell_settings : array()),
  1592. (isset($col['col_settings']) ? $col['col_settings'] : array())
  1593. ),
  1594. 'ftype' => $celltype,
  1595. 'helpers' => array()
  1596. );
  1597. }
  1598. if ($tags)
  1599. {
  1600. $FF->_parse_tagdata($row_tagdata, $tags);
  1601. }
  1602. $vars = array(
  1603. 'field_row_index' => $this->_field_row_index,
  1604. 'field_row_count' => $this->_field_row_index + 1,
  1605. 'row_index' => $this->_row_index,
  1606. 'row_count' => $this->_row_index + 1,
  1607. 'row_id' => $row['row_id']
  1608. );
  1609. $row_tagdata = $FNS->var_swap($row_tagdata, $vars);
  1610. $row_tagdata = $FNS->prep_conditionals($row_tagdata, $vars);
  1611. // {switch} tags
  1612. $row_tagdata = preg_replace_callback('/'.LD.'switch\s*=\s*([\'\"])([^\1]+)\1'.RD.'/sU', array(&$this, '_parse_switch_tag'), $row_tagdata);
  1613. $r .= $row_tagdata;
  1614. }
  1615. unset($this->_field_row_ids, $this->_field_total_rows, $this->_field_row_index, $this->_row_index, $this->_field_settings);
  1616. if (isset($params['backspace']) && $params['backspace'])
  1617. {
  1618. $r = substr($r, 0, -$params['backspace']);
  1619. }
  1620. return $r;
  1621. }
  1622. // --------------------------------------------------------------------
  1623. /**
  1624. * Parse Step Tag
  1625. */
  1626. private function _parse_sibling_tag($match)
  1627. {
  1628. if ($match[1] == 'prev_row')
  1629. {
  1630. // ignore if this is the first row
  1631. if ($this->_field_row_index == 0) return;
  1632. $row_id = $this->_field_row_ids[$this->_field_row_index-1];
  1633. }
  1634. else
  1635. {
  1636. // ignore if this is the last row
  1637. if ($this->_field_row_index == $this->_field_total_rows - 1) return;
  1638. $row_id = $this->_field_row_ids[$this->_field_row_index+1];
  1639. }
  1640. $obj = new Matrix_ft();
  1641. $obj->_fieldtype_id = $this->_fieldtype_id;
  1642. $obj->field_cols = $this->field_cols;
  1643. $params = array_merge($this->default_tag_params, array('row_id' => $row_id));
  1644. return $obj->display_tag($params, $match[2], '', $this->_field_settings);
  1645. }
  1646. /**
  1647. * Parse Switch Tag
  1648. */
  1649. private function _parse_switch_tag($match)
  1650. {
  1651. $options = explode('|', $match[2]);
  1652. $option = $this->_row_index % count($options);
  1653. return $options[$option];
  1654. }
  1655. // --------------------------------------------------------------------
  1656. /**
  1657. * Table
  1658. */
  1659. function table($params, $tagdata, $field_data, $field_settings)
  1660. {
  1661. // get the cols
  1662. $col_ids = isset($field_settings['col_ids']) ? $field_settings['col_ids'] : array();
  1663. $cols = $this->_get_field_cols($col_ids);
  1664. if (! $cols) return '';
  1665. // which table features do they want?
  1666. $set_row_ids = (isset($params['set_row_ids']) && $params['set_row_ids'] == 'yes');
  1667. $set_classes = (isset($params['set_classes']) && $params['set_classes'] == 'yes');
  1668. $set_widths = (isset($params['set_widths']) && $params['set_widths'] == 'yes');
  1669. $thead = '';
  1670. $tagdata = ' <tr'.($set_row_ids ? ' id="row_id_'.LD.'row_id'.RD.'"' : '').'>' . "\n";
  1671. foreach ($cols as &$col)
  1672. {
  1673. $attr = '';
  1674. if ($set_classes) $attr .= ' class="'.$col['col_name'].'"';
  1675. if ($set_widths) $attr .= ' width="'.$col['col_width'].'"';
  1676. $thead .= ' <th scope="col"'.$attr.'>'.$col['col_label'].'</th>' . "\n";
  1677. $tagdata .= ' <td'.$attr.'>'.LD.$col['col_name'].RD.'</td>' . "\n";
  1678. }
  1679. $tagdata .= ' </tr>' . "\n";
  1680. $attr = '';
  1681. if (isset($params['cellspacing'])) $attr .= ' cellspacing="'.$params['cellspacing'].'"';
  1682. if (isset($params['cellpadding'])) $attr .= ' cellpadding="'.$params['cellpadding'].'"';
  1683. if (isset($params['border'])) $attr .= ' border="'.$params['border'].'"';
  1684. if (isset($params['width'])) $attr .= ' width="'.$params['width'].'"';
  1685. if (isset($params['class'])) $attr .= ' class="'.$params['class'].'"';
  1686. return '<table'.$attr.'>' . "\n"
  1687. . ' <thead>' . "\n"
  1688. . ' <tr>' . "\n"
  1689. . $thead
  1690. . ' </tr>' . "\n"
  1691. . ' </thead>' . "\n"
  1692. . ' <tbody>' . "\n"
  1693. . $this->display_tag($params, $tagdata, $field_data, $field_settings)
  1694. . ' </tbody>' . "\n"
  1695. . '</table>';
  1696. }
  1697. // --------------------------------------------------------------------
  1698. /**
  1699. * Replace Sibling Tag
  1700. */
  1701. private function _replace_sibling_tag($params, $tagdata, $settings, $which)
  1702. {
  1703. // ignore if no tagdata
  1704. if (! $tagdata) return;
  1705. // get the cols
  1706. $col_ids = isset($settings['col_ids']) ? $settings['col_ids'] : array();
  1707. $cols = $this->_get_field_cols($col_ids);
  1708. if (! $cols) return;
  1709. // get the full list of row IDs
  1710. $field_row_ids_params = array_merge($params, array('row_id' => '', 'limit' => '', 'offset' => ''));
  1711. $field_row_ids = $this->_data_query($field_row_ids_params, $cols, 'row_ids');
  1712. $field_total_rows = count($field_row_ids);
  1713. // get the starting row's ID
  1714. if ($params['row_id'])
  1715. {
  1716. $row_id = $params['row_id'];
  1717. }
  1718. else
  1719. {
  1720. $query_params = array_merge($params, array('limit' => '1'));
  1721. $query = $this->_data_query($query_params, $cols, 'row_ids');
  1722. $row_id = $query[0];
  1723. }
  1724. // get the starting row's index within the entire field
  1725. $field_row_index = array_search($row_id, $field_row_ids);
  1726. if ($which == 'prev')
  1727. {
  1728. // ignore if this is the first row
  1729. if ($field_row_index == 0) return;
  1730. $sibling_row_id = $field_row_ids[$field_row_index-1];
  1731. }
  1732. else
  1733. {
  1734. // ignore if this is the last row
  1735. if ($field_row_index == $field_total_rows - 1) return;
  1736. $sibling_row_id = $field_row_ids[$field_row_index+1];
  1737. }
  1738. $params = array_merge($this->default_tag_params, array('row_id' => $sibling_row_id));
  1739. return $this->display_tag($params, $tagdata, '', $settings);
  1740. }
  1741. /**
  1742. * Previous Row
  1743. */
  1744. function prev_row($params, $tagdata, $data, $settings)
  1745. {
  1746. return $this->_replace_sibling_tag($params, $tagdata, $settings, 'prev');
  1747. }
  1748. /**
  1749. * Next Row
  1750. */
  1751. function next_row($params, $tagdata, $data, $settings)
  1752. {
  1753. return $this->_replace_sibling_tag($params, $tagdata, $settings, 'next');
  1754. }
  1755. // --------------------------------------------------------------------
  1756. /**
  1757. * Total Rows
  1758. */
  1759. function total_rows($params, $tagdata, $field_data, $field_settings)
  1760. {
  1761. $col_ids = isset($field_settings['col_ids']) ? $field_settings['col_ids'] : array();
  1762. $cols = $this->_get_field_cols($col_ids);
  1763. if (! $cols) return 0;
  1764. return $this->_data_query($params, $cols, 'aggregate', 'COUNT(row_id)');
  1765. }
  1766. // --------------------------------------------------------------------
  1767. /**
  1768. * Get Col ID by Name
  1769. */
  1770. function _get_col_id_by_name($col_name, $cols)
  1771. {
  1772. // find the target col
  1773. foreach ($cols as $col)
  1774. {
  1775. if ($col['col_name'] == $col_name) return $col['col_id'];
  1776. }
  1777. }
  1778. /**
  1779. * Format Number
  1780. */
  1781. private function _format_number($params, $num)
  1782. {
  1783. $decimals = isset($params['decimals']) ? (int) $params['decimals'] : 0;
  1784. $dec_point = isset($params['dec_point']) ? $params['dec_point'] : '.';
  1785. $thousands_sep = isset($params['thousands_sep']) ? $params['thousands_sep'] : ',';
  1786. return number_format($num, $decimals, $dec_point, $thousands_sep);
  1787. }
  1788. /**
  1789. * Aggregate Column
  1790. */
  1791. private function _aggregate_col($params, $settings, $func)
  1792. {
  1793. // ignore if no col= param is set
  1794. if (! isset($params['col']) || ! $params['col']) return;
  1795. $col_ids = isset($settings['col_ids']) ? $settings['col_ids'] : array();
  1796. $cols = $this->_get_field_cols($col_ids);
  1797. if (! $cols) return 0;
  1798. // get the col_id
  1799. $col_id = $this->_get_col_id_by_name($params['col'], $cols);
  1800. // ignore if that col doesn't exist
  1801. if (! $col_id) return;
  1802. $num = $this->_data_query($params, $cols, 'aggregate', "{$func}(col_id_{$col_id})");
  1803. return $this->_format_number($params, $num);
  1804. }
  1805. /**
  1806. * Average
  1807. */
  1808. function average($params, $tagdata, $data, $settings)
  1809. {
  1810. return $this->_aggregate_col($params, $settings, 'AVG');
  1811. }
  1812. /**
  1813. * Sum
  1814. */
  1815. function sum($params, $tagdata, $data, $settings)
  1816. {
  1817. return $this->_aggregate_col($params, $settings, 'SUM');
  1818. }
  1819. // --------------------------------------------------------------------
  1820. private function _get_numeric_col_values($params, $settings)
  1821. {
  1822. // ignore if no col= param is set
  1823. if (! isset($params['col']) || ! $params['col']) return;
  1824. $col_ids = isset($settings['col_ids']) ? $settings['col_ids'] : array();
  1825. $cols = $this->_get_field_cols($col_ids);
  1826. if (! $cols) return 0;
  1827. // get the col_id
  1828. $col_id = $this->_get_col_id_by_name($params['col'], $cols);
  1829. // ignore if that col doesn't exist
  1830. if (! $col_id) return;
  1831. $data = $this->_data_query($params, $cols);
  1832. $values = array();
  1833. foreach ($data as $row)
  1834. {
  1835. $val = $row['col_id_'.$col_id];
  1836. if (is_numeric($val)) $values[] = $val;
  1837. }
  1838. return $values;
  1839. }
  1840. /**
  1841. * Lowest
  1842. */
  1843. function lowest($params, $tagdata, $data, $settings)
  1844. {
  1845. $values = $this->_get_numeric_col_values($params, $settings);
  1846. if ($values) return $this->_format_number($params, min($values));
  1847. }
  1848. /**
  1849. * Highest
  1850. */
  1851. function highest($params, $tagdata, $data, $settings)
  1852. {
  1853. $values = $this->_get_numeric_col_values($params, $settings);
  1854. if ($values) return $this->_format_number($params, max($values));
  1855. }
  1856. }