PageRenderTime 28ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/_backkup/HB_BACKUP_APRIL5_2012/public_html/system/expressionengine/third_party/matrix/ft.matrix.php

https://bitbucket.org/sims/heartbeets
PHP | 2766 lines | 1693 code | 564 blank | 509 comment | 288 complexity | 018b3b5fd34a5a9cff496a705e212d5b MD5 | raw file
  1. <?php if (! defined('BASEPATH')) exit('No direct script access allowed');
  2. require_once PATH_THIRD.'matrix/config.php';
  3. /**
  4. * Matrix Fieldtype Class for EE2
  5. *
  6. * @package Matrix
  7. * @author Brandon Kelly <brandon@pixelandtonic.com>
  8. * @copyright Copyright (c) 2011 Pixel & Tonic, Inc
  9. */
  10. class Matrix_ft extends EE_Fieldtype {
  11. var $info = array(
  12. 'name' => MATRIX_NAME,
  13. 'version' => MATRIX_VER
  14. );
  15. var $has_array_data = TRUE;
  16. var $bundled_celltypes = array('text', 'date', 'file');
  17. /**
  18. * Fieldtype Constructor
  19. */
  20. function __construct()
  21. {
  22. parent::__construct();
  23. // -------------------------------------------
  24. // Prepare Cache
  25. // -------------------------------------------
  26. if (! isset($this->EE->session->cache['matrix']))
  27. {
  28. $this->EE->session->cache['matrix'] = array('celltypes' => array());
  29. }
  30. $this->cache =& $this->EE->session->cache['matrix'];
  31. }
  32. // --------------------------------------------------------------------
  33. /**
  34. * Update Field Column Associations
  35. *
  36. * Before Matrix 2.2, Matrix would associate Matrix columns to fields via the fields’ col_ids setting.
  37. * But now (on EE2 only), those associations are made via the exp_matrix_cols.field_id column.
  38. * This function populates that field_id column accordingly, and also duplicates any Matrix columns that belong to more than one field (via MSM field duplication)
  39. */
  40. function _update_field_col_associations($field_id = FALSE)
  41. {
  42. $this->EE->load->dbforge();
  43. $affected_cols = 0;
  44. // get each of the Matrix fields
  45. $this->EE->db->select('field_id, site_id, field_settings')
  46. ->where('field_type', 'matrix');
  47. if ($field_id)
  48. {
  49. $this->EE->db->where('field_id', $field_id);
  50. }
  51. $fields = $this->EE->db->order_by('site_id')
  52. ->get('channel_fields');
  53. if ($fields->num_rows())
  54. {
  55. foreach ($fields->result() as $field)
  56. {
  57. // unserialize the field settings
  58. $field_settings = unserialize(base64_decode($field->field_settings));
  59. $new_col_ids = array();
  60. // make sure the col_ids setting is in-tact
  61. if (isset($field_settings['col_ids']))
  62. {
  63. foreach (array_filter($field_settings['col_ids']) as $col_id)
  64. {
  65. // get the column data
  66. $col = $this->EE->db->get_where('matrix_cols', array('col_id' => $col_id));
  67. if ($col->num_rows())
  68. {
  69. $data = $col->row_array();
  70. // does this col already belong to this field?
  71. if ($data['field_id'] == $field->field_id)
  72. {
  73. $new_col_ids[] = $col_id;
  74. continue;
  75. }
  76. $affected_cols++;
  77. // does it belong to another?
  78. if ($data['field_id'])
  79. {
  80. // get the existing columns
  81. $celltype = $this->_get_celltype($data['col_type']);
  82. $old_data_col_names = array_keys($this->_apply_settings_modify_matrix_column($celltype, $data, 'get_data'));
  83. // duplicate it
  84. unset($data['col_id']);
  85. $data['site_id'] = $field->site_id;
  86. $data['field_id'] = $field->field_id;
  87. $this->EE->db->insert('matrix_cols', $data);
  88. // get the new col_id
  89. $data['col_id'] = $this->EE->db->insert_id();
  90. // add the new data column(s)
  91. $new_data_cols = $this->_apply_settings_modify_matrix_column($celltype, $data, 'add');
  92. $this->EE->dbforge->add_column('matrix_data', $new_data_cols);
  93. // migrate the data
  94. $sql = 'UPDATE exp_matrix_data SET ';
  95. foreach (array_keys($new_data_cols) as $i => $new_col_name)
  96. {
  97. if ($i) $sql .= ', ';
  98. $old_col_name = $old_data_col_names[$i];
  99. $sql .= "{$new_col_name} = {$old_col_name}, {$old_col_name} = NULL";
  100. }
  101. $sql .= " WHERE field_id = {$field->field_id}";
  102. $this->EE->db->query($sql);
  103. $new_col_ids[] = $data['col_id'];
  104. }
  105. else
  106. {
  107. // just assign it to this field
  108. $data = array(
  109. 'site_id' => $field->site_id,
  110. 'field_id' => $field->field_id
  111. );
  112. $this->EE->db->where('col_id', $col_id)
  113. ->update('matrix_cols', $data);
  114. $new_col_ids[] = $col_id;
  115. }
  116. }
  117. }
  118. }
  119. // update the field settings with the new col_ids array
  120. $field_settings['col_ids'] = $new_col_ids;
  121. $this->EE->db->where('field_id', $field->field_id)
  122. ->update('channel_fields', array('field_settings' => base64_encode(serialize($field_settings))));
  123. }
  124. }
  125. return $affected_cols;
  126. }
  127. /**
  128. * Install
  129. */
  130. function install()
  131. {
  132. $this->EE->load->dbforge();
  133. // -------------------------------------------
  134. // Create the exp_matrix_cols table
  135. // -------------------------------------------
  136. if (! $this->EE->db->table_exists('matrix_cols'))
  137. {
  138. $this->EE->dbforge->add_field(array(
  139. 'col_id' => array('type' => 'int', 'constraint' => 6, 'unsigned' => TRUE, 'auto_increment' => TRUE),
  140. 'site_id' => array('type' => 'int', 'constraint' => 4, 'unsigned' => TRUE, 'default' => 1),
  141. 'field_id' => array('type' => 'int', 'constraint' => 6, 'unsigned' => TRUE),
  142. 'col_name' => array('type' => 'varchar', 'constraint' => 32),
  143. 'col_label' => array('type' => 'varchar', 'constraint' => 50),
  144. 'col_instructions' => array('type' => 'text'),
  145. 'col_type' => array('type' => 'varchar', 'constraint' => 50, 'default' => 'text'),
  146. 'col_required' => array('type' => 'char', 'constraint' => 1, 'default' => 'n'),
  147. 'col_search' => array('type' => 'char', 'constraint' => 1, 'default' => 'n'),
  148. 'col_order' => array('type' => 'int', 'constraint' => 3, 'unsigned' => TRUE),
  149. 'col_width' => array('type' => 'varchar', 'constraint' => 4),
  150. 'col_settings' => array('type' => 'text')
  151. ));
  152. $this->EE->dbforge->add_key('col_id', TRUE);
  153. $this->EE->dbforge->add_key('site_id');
  154. $this->EE->dbforge->add_key('field_id');
  155. $this->EE->dbforge->create_table('matrix_cols');
  156. }
  157. // -------------------------------------------
  158. // Create the exp_matrix_data table
  159. // -------------------------------------------
  160. if (! $this->EE->db->table_exists('matrix_data'))
  161. {
  162. $this->EE->dbforge->add_field(array(
  163. 'row_id' => array('type' => 'int', 'constraint' => 10, 'unsigned' => TRUE, 'auto_increment' => TRUE),
  164. 'site_id' => array('type' => 'int', 'constraint' => 4, 'unsigned' => TRUE, 'default' => 1),
  165. 'entry_id' => array('type' => 'int', 'constraint' => 10, 'unsigned' => TRUE),
  166. 'field_id' => array('type' => 'int', 'constraint' => 6, 'unsigned' => TRUE),
  167. 'row_order' => array('type' => 'int', 'constraint' => 4, 'unsigned' => TRUE)
  168. ));
  169. $this->EE->dbforge->add_key('row_id', TRUE);
  170. $this->EE->dbforge->add_key('site_id');
  171. $this->EE->dbforge->add_key('entry_id');
  172. $this->EE->dbforge->add_key('field_id');
  173. $this->EE->dbforge->create_table('matrix_data');
  174. }
  175. // -------------------------------------------
  176. // EE1 Conversion
  177. // -------------------------------------------
  178. if (! class_exists('FF2EE2')) require_once PATH_THIRD.'matrix/includes/ff2ee2/ff2ee2.php';
  179. // FF Matrix 1 conversion
  180. $converter = new FF2EE2(array('ff_matrix', 'matrix'), array(&$this, '_convert_ff_matrix_field'));
  181. // Matrix 2 conversion
  182. $converter = new FF2EE2('matrix', array(&$this, '_convert_ee1_matrix2_field'));
  183. return $converter->global_settings;
  184. }
  185. /**
  186. * Convert FF Matrix Field
  187. *
  188. * @todo - find unique words and add them to the exp_channel_data cell
  189. */
  190. function _convert_ff_matrix_field($settings, $field)
  191. {
  192. $settings['col_ids'] = array();
  193. if (isset($settings['cols']))
  194. {
  195. if ($settings['cols'])
  196. {
  197. // -------------------------------------------
  198. // Add the rows to exp_matrix_cols
  199. // -------------------------------------------
  200. $col_ids_by_key = array();
  201. $matrix_data_columns = array();
  202. foreach ($settings['cols'] as $col_key => $col)
  203. {
  204. $col_type = $col['type'];
  205. $col_settings = $col['settings'];
  206. switch ($col_type)
  207. {
  208. case 'ff_checkbox':
  209. case 'ff_checkbox_group':
  210. if ($col_type == 'ff_checkbox')
  211. {
  212. $col_settings = array('options' => array('y' => $col_settings['label']));
  213. }
  214. $col_type = 'pt_checkboxes';
  215. break;
  216. case 'ff_select':
  217. $col_type = 'pt_dropdown';
  218. break;
  219. case 'ff_multiselect':
  220. $col_type = 'pt_multiselect';
  221. break;
  222. case 'ff_radio_group':
  223. $col_type = 'pt_radio_buttons';
  224. break;
  225. case 'ff_matrix_text':
  226. case 'ff_matrix_textarea':
  227. $col_settings['multiline'] = ($col_type == 'ff_matrix_text' ? 'n' : 'y');
  228. $col_type = 'text';
  229. break;
  230. case 'ff_matrix_date':
  231. $col_type = 'date';
  232. break;
  233. }
  234. $this->EE->db->insert('matrix_cols', array(
  235. 'site_id' => $field['site_id'],
  236. 'field_id' => $field['field_id'],
  237. 'col_name' => $col['name'],
  238. 'col_label' => $col['label'],
  239. 'col_type' => $col_type,
  240. 'col_search' => $field['field_search'],
  241. 'col_order' => $col_key,
  242. 'col_settings' => base64_encode(serialize($col_settings))
  243. ));
  244. // get the new col_id
  245. $col_id = $this->EE->db->insert_id();
  246. // add it to the matrix_data_columns queue
  247. $matrix_data_columns['col_id_'.$col_id] = array('type' => 'text');
  248. // map the col_id to the col_key for later
  249. $col_ids_by_key[$col_key] = $col_id;
  250. }
  251. // -------------------------------------------
  252. // Add the columns to matrix_data
  253. // -------------------------------------------
  254. $this->EE->dbforge->add_column('matrix_data', $matrix_data_columns);
  255. // -------------------------------------------
  256. // Move the field data into exp_matrix_data
  257. // -------------------------------------------
  258. $field_id = 'field_id_'.$field['field_id'];
  259. $this->EE->db->select('entry_id, '.$field_id);
  260. $this->EE->db->where($field_id.' !=', '');
  261. $entries = $this->EE->db->get('channel_data');
  262. foreach ($entries->result_array() as $entry)
  263. {
  264. // unserialize the data
  265. $old_data = FF2EE2::_unserialize($entry[$field_id]);
  266. foreach ($old_data as $row_count => $row)
  267. {
  268. $data = array(
  269. 'site_id' => $field['site_id'],
  270. 'entry_id' => $entry['entry_id'],
  271. 'field_id' => $field['field_id'],
  272. 'row_order' => $row_count+1
  273. );
  274. foreach ($row as $col_key => $cell_data)
  275. {
  276. // does this col exist?
  277. if (! isset($col_ids_by_key[$col_key])) continue;
  278. // get the col_id
  279. $col_id = $col_ids_by_key[$col_key];
  280. // flatten the cell data if necessary
  281. $cell_data = $this->_flatten_data($cell_data);
  282. // queue it up
  283. $data['col_id_'.$col_id] = $cell_data;
  284. }
  285. // add the row to exp_matrix_data
  286. $this->EE->db->insert('matrix_data', $data);
  287. }
  288. // clear out the old field data from exp_channel_data
  289. $new_data = $this->_flatten_data($old_data);
  290. $this->EE->db->where('entry_id', $entry['entry_id']);
  291. $this->EE->db->update('channel_data', array($field_id => $new_data));
  292. }
  293. }
  294. // -------------------------------------------
  295. // Remove 'cols' from field settings
  296. // -------------------------------------------
  297. unset($settings['cols']);
  298. }
  299. return $settings;
  300. }
  301. /**
  302. * Convert EE1 Matrix 2 Field
  303. */
  304. function _convert_ee1_matrix2_field($settings, $field)
  305. {
  306. $this->_update_field_col_associations($field['field_id']);
  307. return $settings;
  308. }
  309. /**
  310. * Update
  311. */
  312. function update($from)
  313. {
  314. if (! $from || $from == MATRIX_VER) return FALSE;
  315. if (version_compare($from, '2.2', '<'))
  316. {
  317. $this->_update_field_col_associations();
  318. }
  319. return TRUE;
  320. }
  321. // --------------------------------------------------------------------
  322. /**
  323. * Theme URL
  324. */
  325. private function _theme_url()
  326. {
  327. if (! isset($this->cache['theme_url']))
  328. {
  329. $theme_folder_url = $this->EE->config->item('theme_folder_url');
  330. if (substr($theme_folder_url, -1) != '/') $theme_folder_url .= '/';
  331. $this->cache['theme_url'] = $theme_folder_url.'third_party/matrix/';
  332. }
  333. return $this->cache['theme_url'];
  334. }
  335. /**
  336. * Include Theme CSS
  337. */
  338. private function _include_theme_css($file)
  339. {
  340. $this->EE->cp->add_to_head('<link rel="stylesheet" type="text/css" href="'.$this->_theme_url().$file.'?'.MATRIX_VER.'" />');
  341. }
  342. /**
  343. * Include Theme JS
  344. */
  345. private function _include_theme_js($file)
  346. {
  347. $this->EE->cp->add_to_foot('<script type="text/javascript" src="'.$this->_theme_url().$file.'?'.MATRIX_VER.'"></script>');
  348. }
  349. // --------------------------------------------------------------------
  350. /**
  351. * Insert CSS
  352. */
  353. private function _insert_css($css)
  354. {
  355. $this->EE->cp->add_to_head('<style type="text/css">'.$css.'</style>');
  356. }
  357. /**
  358. * Insert JS
  359. */
  360. private function _insert_js($js)
  361. {
  362. $this->EE->cp->add_to_foot('<script type="text/javascript">'.$js.'</script>');
  363. }
  364. // --------------------------------------------------------------------
  365. /**
  366. * Prepare Params
  367. */
  368. private function _prep_params(&$params)
  369. {
  370. $params = array_merge(array(
  371. 'cellspacing' => '1',
  372. 'cellpadding' => '10',
  373. 'dynamic_parameters' => '',
  374. 'row_id' => '',
  375. 'orderby' => '',
  376. 'sort' => 'asc',
  377. 'offset' => '',
  378. 'limit' => '',
  379. 'backspace' => ''
  380. ), $params);
  381. }
  382. // --------------------------------------------------------------------
  383. /**
  384. * Display Global Settings
  385. */
  386. function display_global_settings()
  387. {
  388. $license_key = isset($this->settings['license_key']) ? $this->settings['license_key'] : '';
  389. // load the language file
  390. $this->EE->lang->loadfile('matrix');
  391. // load the table lib
  392. $this->EE->load->library('table');
  393. // use the default template known as
  394. // $cp_pad_table_template in the views
  395. $this->EE->table->set_template(array(
  396. 'table_open' => '<table class="mainTable padTable" border="0" cellspacing="0" cellpadding="0">',
  397. 'row_start' => '<tr class="even">',
  398. 'row_alt_start' => '<tr class="odd">'
  399. ));
  400. $this->EE->table->set_heading(array('data' => lang('preference'), 'style' => 'width: 50%'), lang('setting'));
  401. $this->EE->table->add_row(
  402. lang('license_key', 'license_key'),
  403. form_input('license_key', $license_key, 'id="license_key" size="40"')
  404. );
  405. return $this->EE->table->generate();
  406. }
  407. /**
  408. * Save Global Settings
  409. */
  410. function save_global_settings()
  411. {
  412. return array(
  413. 'license_key' => isset($_POST['license_key']) ? $_POST['license_key'] : ''
  414. );
  415. }
  416. // --------------------------------------------------------------------
  417. /**
  418. * Get Field Cols
  419. */
  420. private function _get_field_cols($field_id)
  421. {
  422. if (! isset($this->cache['field_cols'][$field_id]))
  423. {
  424. $query = $this->EE->db->select('col_id, col_type, col_label, col_name, col_instructions, col_width, col_required, col_search, col_settings')
  425. ->where('field_id', $field_id)
  426. ->order_by('col_order')
  427. ->get('matrix_cols');
  428. if (! $query->num_rows())
  429. {
  430. if ($this->_update_field_col_associations())
  431. {
  432. // probably need to update the fieldtypes version number so update() doesn't get called...
  433. $this->EE->db->where('name', 'matrix')
  434. ->update('fieldtypes', array('version' => MATRIX_VER));
  435. // try again
  436. return $this->_get_field_cols($field_id);
  437. }
  438. $cols = array();
  439. }
  440. else
  441. {
  442. $cols = $query->result_array();
  443. // unserialize the settings and cache
  444. foreach ($cols as &$col)
  445. {
  446. $col['col_settings'] = unserialize(base64_decode($col['col_settings']));
  447. if (! is_array($col['col_settings'])) $col['col_settings'] = array();
  448. }
  449. }
  450. $this->cache['field_cols'][$field_id] = $cols;
  451. }
  452. return $this->cache['field_cols'][$field_id];
  453. }
  454. // --------------------------------------------------------------------
  455. /**
  456. * Get Celltype Class
  457. */
  458. private function _get_celltype_class($name, $text_fallback = FALSE)
  459. {
  460. // $name should look like exp_fieldtypes.name values
  461. if (substr($name, -3) == '_ft') $name = substr($name, 0, -3);
  462. $name = strtolower($name);
  463. // is this a bundled celltype?
  464. if (in_array($name, $this->bundled_celltypes))
  465. {
  466. $class = 'Matrix_'.$name.'_ft';
  467. if (! class_exists($class))
  468. {
  469. // load it from matrix/celltypes/
  470. require_once PATH_THIRD.'matrix/celltypes/'.$name.EXT;
  471. }
  472. }
  473. else
  474. {
  475. $class = ucfirst($name).'_ft';
  476. $this->EE->api_channel_fields->include_handler($name);
  477. }
  478. if (class_exists($class))
  479. {
  480. // method_exists() is supposed to accept the class name (string),
  481. // but running into at least one server where that's not the case...
  482. $ft = new $class();
  483. if (method_exists($ft, 'display_cell'))
  484. {
  485. if (! isset($this->cache['celltype_global_settings'][$name]))
  486. {
  487. $this->EE->db->select('settings');
  488. $this->EE->db->where('name', $name);
  489. $query = $this->EE->db->get('fieldtypes');
  490. $settings = $query->row('settings');
  491. $this->cache['celltype_global_settings'][$name] = is_array($settings) ? $settings : unserialize(base64_decode($settings));
  492. }
  493. return $class;
  494. }
  495. }
  496. return $text_fallback ? $this->_get_celltype_class('text') : FALSE;
  497. }
  498. // --------------------------------------------------------------------
  499. /**
  500. * Get Celltype
  501. */
  502. private function _get_celltype($name, $text_fallback = FALSE)
  503. {
  504. $class = $this->_get_celltype_class($name, $text_fallback);
  505. if (! $class) return FALSE;
  506. $celltype = new $class();
  507. $global_settings = $this->cache['celltype_global_settings'][$name];
  508. $celltype->settings = $global_settings && is_array($global_settings) ? $global_settings : array();
  509. return $celltype;
  510. }
  511. // --------------------------------------------------------------------
  512. /**
  513. * Get All Celltypes
  514. */
  515. private function _get_all_celltypes()
  516. {
  517. // this is only called once, from display_settings(),
  518. // so don't worry about caching the results
  519. // begin with what we already know about
  520. $ft_names = array_merge($this->bundled_celltypes);
  521. // get the fieldtypes from exp_fieldtypes
  522. $query = $this->EE->db->select('name, settings')
  523. ->get('fieldtypes');
  524. if (! isset($this->cache['celltype_global_settings']))
  525. {
  526. $this->cache['celltype_global_settings'] = array();
  527. }
  528. foreach ($query->result_array() as $ft)
  529. {
  530. $ft_names[] = $ft['name'];
  531. $this->cache['celltype_global_settings'][$ft['name']] = unserialize(base64_decode($ft['settings']));
  532. }
  533. // now get the actual celltype instances
  534. $celltypes = array();
  535. foreach ($ft_names as $name)
  536. {
  537. if (($ct = $this->_get_celltype($name)) !== FALSE)
  538. {
  539. $celltypes[$name] = $ct;
  540. }
  541. }
  542. // sort them alphabetically
  543. ksort($celltypes);
  544. return $celltypes;
  545. }
  546. // --------------------------------------------------------------------
  547. /**
  548. * Add Package Path
  549. */
  550. private function _add_package_path($celltype)
  551. {
  552. $name = strtolower(substr(get_class($celltype), 0, -3));
  553. $path = PATH_THIRD.$name.'/';
  554. $this->EE->load->add_package_path($path);
  555. // manually add the view path if this is less than EE 2.1.5
  556. if (version_compare(APP_VER, '2.1.5', '<'))
  557. {
  558. $this->EE->load->_ci_view_path = $path.'views/';
  559. }
  560. }
  561. // --------------------------------------------------------------------
  562. /**
  563. * Namespace Settings
  564. */
  565. function _namespace_settings(&$settings, $namespace)
  566. {
  567. $settings = preg_replace('/(name=([\'\"]))([^\'"\[\]]+)([^\'"]*)(\2)/i', '$1'.$namespace.'[$3]$4$5', $settings);
  568. }
  569. // --------------------------------------------------------------------
  570. /**
  571. * Celltype Settings HTML
  572. */
  573. private function _celltype_settings_html($namespace, $celltype, $data = array())
  574. {
  575. if (method_exists($celltype, 'display_cell_settings'))
  576. {
  577. $this->_add_package_path($celltype);
  578. $returned = $celltype->display_cell_settings($data);
  579. // should we create the html for them?
  580. if (is_array($returned))
  581. {
  582. $r = '<table class="matrix-col-settings" cellspacing="0" cellpadding="0" border="0">';
  583. $total_cell_settings = count($returned);
  584. foreach ($returned as $cs_key => $cell_setting)
  585. {
  586. $tr_class = '';
  587. if ($cs_key == 0) $tr_class .= ' matrix-first';
  588. if ($cs_key == $total_cell_settings-1) $tr_class .= ' matrix-last';
  589. $r .= '<tr class="'.$tr_class.'">'
  590. . '<th class="matrix-first">'.$cell_setting[0].'</th>'
  591. . '<td class="matrix-last">'.$cell_setting[1].'</td>'
  592. . '</tr>';
  593. }
  594. $r .= '</table>';
  595. }
  596. else
  597. {
  598. $r = $returned;
  599. }
  600. $this->_namespace_settings($r, $namespace);
  601. }
  602. else
  603. {
  604. $r = '';
  605. }
  606. return $r;
  607. }
  608. // --------------------------------------------------------------------
  609. /**
  610. * Display Field Settings
  611. */
  612. function display_settings($data)
  613. {
  614. $min_rows = isset($data['min_rows']) ? $data['min_rows'] : '0';
  615. $max_rows = isset($data['max_rows']) ? $data['max_rows'] : '';
  616. // include css and js
  617. $this->_include_theme_css('styles/matrix.css');
  618. $this->_include_theme_js('scripts/matrix.js');
  619. $this->_include_theme_js('scripts/matrix_text.js');
  620. $this->_include_theme_js('scripts/matrix_conf.js');
  621. // language
  622. $this->_insert_js('MatrixConf.lang = { '
  623. . 'delete_col: "'.lang('delete_col').'" };');
  624. // load the language file
  625. $this->EE->lang->loadfile('matrix');
  626. // -------------------------------------------
  627. // Get the celltypes
  628. // -------------------------------------------
  629. $celltypes = $this->_get_all_celltypes();
  630. $celltypes_select_options = array();
  631. $celltypes_js = array();
  632. foreach ($celltypes as $name => $celltype)
  633. {
  634. $celltypes_select_options[$name] = $celltype->info['name'];
  635. // default cell settings
  636. $celltypes_js[$name] = $this->_celltype_settings_html('matrix[cols][{COL_ID}][settings]', $celltype, $data);
  637. }
  638. // -------------------------------------------
  639. // Get the columns
  640. // -------------------------------------------
  641. // is this an existing Matrix field?
  642. if ($data['field_id'] && $data['field_type'] == 'matrix')
  643. {
  644. $cols = $this->_get_field_cols($data['field_id']);
  645. }
  646. if (isset($cols) && $cols)
  647. {
  648. $new = FALSE;
  649. }
  650. else
  651. {
  652. $new = TRUE;
  653. // start off with a couple text cells
  654. $cols = array(
  655. 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')),
  656. 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'))
  657. );
  658. }
  659. $cols_js = array();
  660. foreach ($cols as &$col)
  661. {
  662. $cols_js[] = array(
  663. 'id' => ($new ? 'col_new_' : 'col_id_') . $col['col_id'],
  664. 'type' => $col['col_type']
  665. );
  666. }
  667. // -------------------------------------------
  668. // Minimum Rows
  669. // -------------------------------------------
  670. $this->EE->table->add_row(
  671. lang('min_rows', 'matrix_min_rows'),
  672. form_input('matrix[min_rows]', $min_rows, 'id="matrix_min_rows" style="width: 3em;"')
  673. );
  674. // -------------------------------------------
  675. // Maximum Rows
  676. // -------------------------------------------
  677. $this->EE->table->add_row(
  678. lang('max_rows', 'matrix_max_rows'),
  679. form_input('matrix[max_rows]', $max_rows, 'id="matrix_max_rows" style="width: 3em;"')
  680. );
  681. // -------------------------------------------
  682. // Matrix Configuration
  683. // -------------------------------------------
  684. $total_cols = count($cols);
  685. $table = '<div id="matrix-conf-container"><div id="matrix-conf">'
  686. . '<table class="matrix matrix-conf" cellspacing="0" cellpadding="0" border="0" style="background: #ecf1f4;">'
  687. . '<thead class="matrix">'
  688. . '<tr class="matrix matrix-first">'
  689. . '<td class="matrix-breakleft"></td>';
  690. // -------------------------------------------
  691. // Labels
  692. // -------------------------------------------
  693. foreach ($cols as $col_index => &$col)
  694. {
  695. $col_id = $new ? 'col_new_'.$col_index : 'col_id_'.$col['col_id'];
  696. $class = 'matrix';
  697. if ($col_index == 0) $class .= ' matrix-first';
  698. if ($col_index == $total_cols - 1) $class .= ' matrix-last';
  699. $table .= '<th class="'.$class.'" scope="col">'
  700. . '<input type="hidden" name="matrix[col_order][]" value="'.$col_id.'" />'
  701. . '<span>'.$col['col_label'].'</span>'
  702. . '</th>';
  703. }
  704. $table .= '</tr>'
  705. . '<tr class="matrix matrix-last">'
  706. . '<td class="matrix-breakleft"></td>';
  707. // -------------------------------------------
  708. // Instructions
  709. // -------------------------------------------
  710. foreach ($cols as $col_index => &$col)
  711. {
  712. $class = 'matrix';
  713. if ($col_index == 0) $class .= ' matrix-first';
  714. if ($col_index == $total_cols - 1) $class .= ' matrix-last';
  715. $table .= '<td class="'.$class.'">'.($col['col_instructions'] ? nl2br($col['col_instructions']) : '&nbsp;').'</td>';
  716. }
  717. $table .= '</tr>'
  718. . '</thead>'
  719. . '<tbody class="matrix">';
  720. // -------------------------------------------
  721. // Col Settings
  722. // -------------------------------------------
  723. $col_settings = array('type', 'label', 'name', 'instructions', 'width', 'required', 'search', 'settings');
  724. $total_settings = count($col_settings);
  725. foreach ($col_settings as $row_index => $col_setting)
  726. {
  727. $tr_class = 'matrix';
  728. if ($row_index == 0) $tr_class .= ' matrix-first';
  729. if ($row_index == $total_settings - 1) $tr_class .= ' matrix-last';
  730. $table .= '<tr class="'.$tr_class.'">'
  731. . '<th class="matrix-breakleft" scope="row">'.lang('col_'.$col_setting).'</th>';
  732. foreach ($cols as $col_index => &$col)
  733. {
  734. $col_id = $new ? 'col_new_'.$col_index : 'col_id_'.$col['col_id'];
  735. $setting_name = 'matrix[cols]['.$col_id.']['.$col_setting.']';
  736. $td_class = 'matrix';
  737. if ($col_index == 0) $td_class .= ' matrix-first';
  738. if ($col_index == $total_cols - 1) $td_class .= ' matrix-last';
  739. switch ($col_setting)
  740. {
  741. case 'type':
  742. $shtml = form_dropdown($setting_name, $celltypes_select_options, $col['col_'.$col_setting]);
  743. break;
  744. case 'name':
  745. case 'width':
  746. $td_class .= ' matrix-text';
  747. $shtml = form_input($setting_name, $col['col_'.$col_setting], 'class="matrix-textarea"');
  748. break;
  749. case 'required':
  750. case 'search':
  751. $shtml = form_checkbox($setting_name, 'y', ($col['col_'.$col_setting] == 'y'));
  752. break;
  753. case 'settings':
  754. $cell_data = array_merge($data, is_array($col['col_'.$col_setting]) ? $col['col_'.$col_setting] : array());
  755. if (! ($shtml = $this->_celltype_settings_html($setting_name, $celltypes[$col['col_type']], $cell_data)))
  756. {
  757. $td_class .= ' matrix-disabled';
  758. $shtml = '&nbsp;';
  759. }
  760. break;
  761. default:
  762. $td_class .= ' matrix-text';
  763. $shtml = '<textarea class="matrix-textarea" name="'.$setting_name.'" rows="1">'.$col['col_'.$col_setting].'</textarea>';
  764. }
  765. $table .= '<td class="'.$td_class.'">'.$shtml.'</td>';
  766. }
  767. $table .= '</tr>';
  768. }
  769. // -------------------------------------------
  770. // Delete Row buttons
  771. // -------------------------------------------
  772. $table .= '<tr>'
  773. . '<td class="matrix-breakleft"></td>';
  774. foreach ($cols as &$col)
  775. {
  776. $table .= '<td class="matrix-breakdown"><a class="matrix-btn" title="'.lang('delete_col').'"></a></td>';
  777. }
  778. $table .= '</tr>'
  779. . '</tbody>'
  780. . '</table>'
  781. . '<a class="matrix-btn matrix-add" title="'.lang('add_col').'"></a>'
  782. . '</div></div>';
  783. $this->EE->table->add_row(array(
  784. 'colspan' => '2',
  785. 'data' => lang('matrix_configuration', 'matrix_configuration')
  786. . $table
  787. ));
  788. // -------------------------------------------
  789. // Initialize the configurator js
  790. // -------------------------------------------
  791. $js = 'MatrixConf.EE2 = true;' . NL
  792. . 'var matrixConf = new MatrixConf("matrix", '
  793. . $this->EE->javascript->generate_json($celltypes_js, TRUE) . ', '
  794. . $this->EE->javascript->generate_json($cols_js, TRUE) . ', '
  795. . $this->EE->javascript->generate_json($col_settings, TRUE)
  796. . ');';
  797. if ($new) $js .= NL.'matrixConf.totalNewCols = 2;';
  798. $this->_insert_js($js);
  799. }
  800. /**
  801. * Save Field Settings
  802. */
  803. function save_settings($data)
  804. {
  805. // cross the T's
  806. $settings['field_fmt'] = 'none';
  807. $settings['field_show_fmt'] = 'n';
  808. $settings['field_type'] = 'matrix';
  809. return $settings;
  810. }
  811. /**
  812. * Save Field Settings
  813. */
  814. function post_save_settings($data)
  815. {
  816. $this->EE->load->dbforge();
  817. $post = $this->EE->input->post('matrix');
  818. // -------------------------------------------
  819. // Delete any removed columns
  820. // -------------------------------------------
  821. if (isset($post['deleted_cols']))
  822. {
  823. $delete_cols = array();
  824. foreach ($post['deleted_cols'] as $col_name)
  825. {
  826. $delete_cols[] = substr($col_name, 7);
  827. }
  828. $this->_delete_cols($delete_cols);
  829. }
  830. // -------------------------------------------
  831. // Add/update columns
  832. // -------------------------------------------
  833. $settings = array(
  834. 'min_rows' => (isset($post['min_rows']) && $post['min_rows'] ? $post['min_rows'] : '0'),
  835. 'max_rows' => (isset($post['max_rows']) && $post['max_rows'] ? $post['max_rows'] : ''),
  836. 'col_ids' => array()
  837. );
  838. foreach ($post['col_order'] as $col_order => $col_id)
  839. {
  840. $col = $post['cols'][$col_id];
  841. $celltype = $this->_get_celltype($col['type']);
  842. $cell_settings = isset($col['settings']) ? $col['settings'] : array();
  843. // give the celltype a chance to override
  844. if (method_exists($celltype, 'save_cell_settings'))
  845. {
  846. $cell_settings = $celltype->save_cell_settings($cell_settings);
  847. }
  848. $col_data = array(
  849. 'col_name' => $col['name'],
  850. 'col_label' => str_replace('$', '&#36;', $col['label']),
  851. 'col_instructions' => str_replace('$', '&#36;', $col['instructions']),
  852. 'col_type' => $col['type'],
  853. 'col_required' => (isset($col['required']) && $col['required'] ? 'y' : 'n'),
  854. 'col_search' => (isset($col['search']) && $col['search'] ? 'y' : 'n'),
  855. 'col_width' => $col['width'],
  856. 'col_order' => $col_order,
  857. 'col_settings' => base64_encode(serialize($cell_settings))
  858. );
  859. $new = (substr($col_id, 0, 8) == 'col_new_');
  860. if ($new)
  861. {
  862. $col_data['site_id'] = $this->EE->config->item('site_id');
  863. $col_data['field_id'] = $this->settings['field_id'];
  864. // insert the row
  865. $this->EE->db->insert('matrix_cols', $col_data);
  866. // get the col_id
  867. $col_id = $this->EE->db->insert_id();
  868. $col_data['col_id'] = $col_id;
  869. // notify the celltype
  870. $fields = $this->_apply_settings_modify_matrix_column($celltype, $col_data, 'add');
  871. // add the new column(s) to exp_matrix_data
  872. $this->EE->dbforge->add_column('matrix_data', $fields);
  873. }
  874. else
  875. {
  876. $col_id = substr($col_id, 7);
  877. $col_data['col_id'] = $col_id;
  878. $primary_col_name = 'col_id_'.$col_id;
  879. // get the previous col_type
  880. $prev_col_type = $this->EE->db->select('col_type')
  881. ->where('col_id', $col_id)
  882. ->get('matrix_cols')
  883. ->row('col_type');
  884. // has the col type changed?
  885. if ($prev_col_type != $col['type'])
  886. {
  887. // notify the old celltype
  888. $fields = $this->_apply_settings_modify_matrix_column($prev_col_type, $col_data, 'delete');
  889. // delete any extra exp_matrix_data cols
  890. unset($fields[$primary_col_name]);
  891. foreach (array_keys($fields) as $field_name)
  892. {
  893. $this->EE->dbforge->drop_column('matrix_data', $field_name);
  894. }
  895. // notify the new celltype
  896. $fields = $this->_apply_settings_modify_matrix_column($celltype, $col_data, 'add');
  897. // extract the primary field
  898. $primary_field = array($primary_col_name => $fields[$primary_col_name]);
  899. unset($fields[$primary_col_name]);
  900. // update the primary column
  901. $primary_field[$primary_col_name]['name'] = $primary_col_name;
  902. $this->EE->dbforge->modify_column('matrix_data', $primary_field);
  903. // add any extra cols
  904. $this->EE->dbforge->add_column('matrix_data', $fields);
  905. }
  906. else
  907. {
  908. // notify the celltype
  909. $fields = $this->_apply_settings_modify_matrix_column($celltype, $col_data, 'get_data');
  910. // update the columns
  911. foreach ($fields as $field_name => &$field)
  912. {
  913. $field['name'] = $field_name;
  914. }
  915. $this->EE->dbforge->modify_column('matrix_data', $fields);
  916. }
  917. // update the existing row
  918. $this->EE->db->where('col_id', $col_id);
  919. $this->EE->db->update('matrix_cols', $col_data);
  920. }
  921. // add the col_id to the field settings
  922. // - it's unfortunate that we can't just place the field_id in the matrix_cols
  923. // data, but alas, the future field_id is unknowable on new fields
  924. $settings['col_ids'][] = $col_id;
  925. }
  926. // save the settings to exp_channel_fields
  927. $data = array('field_settings' => base64_encode(serialize($settings)));
  928. $this->EE->db->where('field_id', $this->settings['field_id'])
  929. ->update('channel_fields', $data);
  930. }
  931. // --------------------------------------------------------------------
  932. /**
  933. * Delete Rows
  934. */
  935. function delete_rows($row_ids)
  936. {
  937. // -------------------------------------------
  938. // Notify the celltypes
  939. // -------------------------------------------
  940. $celltypes = $this->_get_all_celltypes();
  941. foreach ($celltypes as $name => $celltype)
  942. {
  943. if (method_exists($celltype, 'delete_rows'))
  944. {
  945. $celltype->delete_rows($row_ids);
  946. }
  947. }
  948. // -------------------------------------------
  949. // Delete the rows
  950. // -------------------------------------------
  951. $this->EE->db->where_in('row_id', $row_ids)
  952. ->delete('matrix_data');
  953. }
  954. /**
  955. * Delete Columns
  956. */
  957. private function _delete_cols($col_ids)
  958. {
  959. $this->EE->load->dbforge();
  960. $cols = $this->EE->db->select('col_id, col_type, col_label, col_name, col_instructions, col_width, col_required, col_search, col_settings')
  961. ->where_in('col_id', $col_ids)
  962. ->get('matrix_cols')
  963. ->result_array();
  964. // -------------------------------------------
  965. // exp_matrix_data
  966. // -------------------------------------------
  967. foreach ($cols as &$col)
  968. {
  969. // notify the celltype
  970. $fields = $this->_apply_settings_modify_matrix_column($col['col_type'], $col, 'delete');
  971. // drop the exp_matrix_data columns
  972. foreach (array_keys($fields) as $field_name)
  973. {
  974. $this->EE->dbforge->drop_column('matrix_data', $field_name);
  975. }
  976. }
  977. // -------------------------------------------
  978. // exp_matrix_cols
  979. // -------------------------------------------
  980. $this->EE->db->where_in('col_id', $col_ids)
  981. ->delete('matrix_cols');
  982. }
  983. // --------------------------------------------------------------------
  984. /**
  985. * Modify exp_channel_data Column Settings
  986. */
  987. function settings_modify_column($data)
  988. {
  989. if ($data['ee_action'] == 'delete')
  990. {
  991. // -------------------------------------------
  992. // Delete the field data
  993. // -------------------------------------------
  994. $rows = $this->EE->db->select('row_id')
  995. ->where('field_id', $data['field_id'])
  996. ->get('matrix_data');
  997. if ($rows->num_rows())
  998. {
  999. $delete_rows = array();
  1000. foreach ($rows->result() as $row)
  1001. {
  1002. $delete_rows[] = $row->row_id;
  1003. }
  1004. $this->delete_rows($delete_rows);
  1005. }
  1006. // -------------------------------------------
  1007. // Delete the columns
  1008. // -------------------------------------------
  1009. // decode the field settings
  1010. $query = $this->EE->db->select('col_id')->where('field_id', $data['field_id'])->get('matrix_cols');
  1011. if ($query->num_rows())
  1012. {
  1013. foreach ($query->result() as $col)
  1014. {
  1015. $col_ids[] = $col->col_id;
  1016. }
  1017. $this->_delete_cols($col_ids);
  1018. }
  1019. }
  1020. // just return the default column settings
  1021. return parent::settings_modify_column($data);
  1022. }
  1023. /**
  1024. * Apply settings_modify_matrix_column
  1025. */
  1026. private function _apply_settings_modify_matrix_column($celltype, $data, $action)
  1027. {
  1028. $primary_col_name = 'col_id_'.$data['col_id'];
  1029. if (is_string($celltype)) $celltype = $this->_get_celltype($celltype);
  1030. // give the celltype a chance to override the settings of the exp_matrix_data columns
  1031. if (method_exists($celltype, 'settings_modify_matrix_column'))
  1032. {
  1033. $data['matrix_action'] = $action;
  1034. $fields = (array) $celltype->settings_modify_matrix_column($data);
  1035. // make sure the celltype returned the required column
  1036. if (! isset($fields[$primary_col_name]))
  1037. {
  1038. $fields[$primary_col_name] = array('type' => 'text');
  1039. }
  1040. }
  1041. else
  1042. {
  1043. $fields = array($primary_col_name => array('type' => 'text'));
  1044. }
  1045. return $fields;
  1046. }
  1047. // --------------------------------------------------------------------
  1048. /**
  1049. * Display Field
  1050. */
  1051. function display_field($data)
  1052. {
  1053. // -------------------------------------------
  1054. // Include dependencies
  1055. // - this needs to happen *before* we load the celltypes,
  1056. // in case the celltypes are loading their own JS
  1057. // -------------------------------------------
  1058. if (! isset($this->cache['included_dependencies']))
  1059. {
  1060. // load the language file
  1061. $this->EE->lang->loadfile('matrix');
  1062. // include css and js
  1063. $this->_include_theme_css('styles/matrix.css');
  1064. $this->_include_theme_js('scripts/matrix.js');
  1065. // menu language
  1066. $this->_insert_js('Matrix.lang = { '
  1067. . 'options: "'.lang('options').'", '
  1068. . 'add_row_above: "'.lang('add_row_above').'", '
  1069. . 'add_row_below: "'.lang('add_row_below').'", '
  1070. . 'delete_row: "'.lang('delete_row').'", '
  1071. . 'remove_file: "'.lang('remove_file').'", '
  1072. . 'select_file_error: "'.lang('select_file_error').'" };');
  1073. $this->cache['included_dependencies'] = TRUE;
  1074. }
  1075. // -------------------------------------------
  1076. // Initialize the field
  1077. // -------------------------------------------
  1078. $field_id = $this->settings['field_id'];
  1079. $entry_id = $this->EE->input->get('entry_id');
  1080. $min_rows = isset($this->settings['min_rows']) ? (int) $this->settings['min_rows'] : 0;
  1081. $max_rows = isset($this->settings['max_rows']) ? (int) $this->settings['max_rows'] : 0;
  1082. // default $min_rows to 1 if the field is required
  1083. if (! $min_rows && $this->settings['field_required'] == 'y') $min_rows = 1;
  1084. // single-row mode?
  1085. $single_row_mode = ($min_rows == 1 && $max_rows == 1);
  1086. $cols = $this->_get_field_cols($field_id);
  1087. if (! $cols) return;
  1088. $total_cols = count($cols);
  1089. $col_settings = array();
  1090. $select_col_ids = '';
  1091. $show_instructions = FALSE;
  1092. $cols_js = array();
  1093. foreach ($cols as &$col)
  1094. {
  1095. // index the col by ID
  1096. $select_col_ids .= ', col_id_'.$col['col_id'];
  1097. // show instructions?
  1098. if ($col['col_instructions']) $show_instructions = TRUE;
  1099. // include this->settings in col settings
  1100. $col_settings[$col['col_id']] = array_merge($this->settings, (is_array($col['col_settings']) ? $col['col_settings'] : array()));
  1101. $celltype = $this->_get_celltype($col['col_type']);
  1102. $celltype->settings = array_merge($celltype->settings, $col_settings[$col['col_id']]);
  1103. $celltype->field_id = $field_id;
  1104. $celltype->field_name = $this->field_name;
  1105. $celltype->col_id = $col['col_id'];
  1106. $celltype->cell_name = '{DEFAULT}';
  1107. $this->_add_package_path($celltype);
  1108. $new_cell_html = $celltype->display_cell('');
  1109. $new_cell_settings = FALSE;
  1110. $new_cell_class = FALSE;
  1111. if (is_array($new_cell_html))
  1112. {
  1113. if (isset($new_cell_html['settings']))
  1114. {
  1115. $new_cell_settings = $new_cell_html['settings'];
  1116. }
  1117. if (isset($new_cell_html['class']))
  1118. {
  1119. $new_cell_class = $new_cell_html['class'];
  1120. }
  1121. $new_cell_html = $new_cell_html['data'];
  1122. }
  1123. // store the js-relevant stuff in $cols_js
  1124. $cols_js[] = array(
  1125. 'id' => 'col_id_'.$col['col_id'],
  1126. 'name' => $col['col_name'],
  1127. 'label' => $col['col_label'],
  1128. 'required' => ($col['col_required'] == 'y' ? TRUE : FALSE),
  1129. 'settings' => $col['col_settings'],
  1130. 'type' => $col['col_type'],
  1131. 'newCellHtml' => $new_cell_html,
  1132. 'newCellSettings' => $new_cell_settings,
  1133. 'newCellClass' => $new_cell_class
  1134. );
  1135. }
  1136. // -------------------------------------------
  1137. // Get the data
  1138. // -------------------------------------------
  1139. // autosave data?
  1140. if (is_array($data) && isset($data['row_order']))
  1141. {
  1142. unset($data['row_order']);
  1143. foreach ($data as $row_id => &$row)
  1144. {
  1145. if (substr($row_id, 0, 7) == 'row_id_')
  1146. {
  1147. $row['row_id'] = substr($row_id, 7);
  1148. }
  1149. }
  1150. }
  1151. else
  1152. {
  1153. $data = array();
  1154. // is there post data?
  1155. if (isset($_POST[$this->field_name]) && isset($_POST[$this->field_name]['row_order']) && $_POST[$this->field_name]['row_order'])
  1156. {
  1157. foreach ($_POST[$this->field_name]['row_order'] as $row_id)
  1158. {
  1159. $row = isset($_POST[$this->field_name][$row_id]) ? $_POST[$this->field_name][$row_id] : array();
  1160. foreach ($cols as &$col)
  1161. {
  1162. $data[$row_id]['col_id_'.$col['col_id']] = isset($row['col_id_'.$col['col_id']]) ? $row['col_id_'.$col['col_id']] : '';
  1163. }
  1164. }
  1165. }
  1166. else
  1167. {
  1168. // is this an existing entry?
  1169. if ($entry_id)
  1170. {
  1171. $this->EE->db->select('row_id' . $select_col_ids);
  1172. $this->EE->db->where('site_id', $this->EE->config->item('site_id'));
  1173. $this->EE->db->where('field_id', $field_id);
  1174. $this->EE->db->where('entry_id', $entry_id);
  1175. $this->EE->db->order_by('row_order');
  1176. if ($max_rows)
  1177. {
  1178. $this->EE->db->limit($max_rows);
  1179. }
  1180. $query = $this->EE->db->get('matrix_data')->result_array();
  1181. // is this a clone?
  1182. $clone = ($this->EE->input->get('clone') == 'y');
  1183. // re-index the query data
  1184. foreach ($query as $count => $row)
  1185. {
  1186. $key = $clone ? 'row_new_'.$count : 'row_id_'.$row['row_id'];
  1187. $data[$key] = $row;
  1188. }
  1189. }
  1190. }
  1191. }
  1192. // -------------------------------------------
  1193. // Reach the Minimum Rows count
  1194. // -------------------------------------------
  1195. $total_rows = count($data);
  1196. if ($total_rows < $min_rows)
  1197. {
  1198. $extra_rows = $min_rows - $total_rows;
  1199. for ($i = 0; $i < $extra_rows; $i++)
  1200. {
  1201. foreach ($cols as &$col)
  1202. {
  1203. $data['row_new_'.$i]['col_id_'.$col['col_id']] = '';
  1204. }
  1205. }
  1206. $total_rows = $min_rows;
  1207. }
  1208. // -------------------------------------------
  1209. // Table Head
  1210. // -------------------------------------------
  1211. $thead = '<thead class="matrix">';
  1212. $headings = '';
  1213. $instructions = '';
  1214. // add left gutters if there can be more than one row
  1215. if (! $single_row_mode)
  1216. {
  1217. $headings .= '<th class="matrix matrix-first matrix-tr-header"></th>';
  1218. if ($show_instructions)
  1219. {
  1220. $instructions .= '<td class="matrix matrix-first matrix-tr-header"></td>';
  1221. }
  1222. }
  1223. // add the labels and instructions
  1224. foreach ($cols as $col_index => &$col)
  1225. {
  1226. $col_count = $col_index + 1;
  1227. $class = 'matrix';
  1228. if ($single_row_mode && $col_count == 1) $class .= ' matrix-first';
  1229. if ($col_count == $total_cols) $class .= ' matrix-last';
  1230. $headings .= '<th class="'.$class.'" scope="col" width="'.$col['col_width'].'">'.$col['col_label'].'</th>';
  1231. if ($show_instructions)
  1232. {
  1233. $instructions .= '<td class="'.$class.'">'.nl2br($col['col_instructions']).'</td>';
  1234. }
  1235. }
  1236. $thead = '<thead class="matrix">'
  1237. . '<tr class="matrix matrix-first'.($show_instructions ? '' : ' matrix-last').'">' . $headings . '</tr>'
  1238. . ($show_instructions ? '<tr class="matrix matrix-last">' . $instructions . '</tr>' : '')
  1239. . '</thead>';
  1240. // -------------------------------------------
  1241. // Table Body
  1242. // -------------------------------------------
  1243. $rows_js = array();
  1244. $tbody = '<tbody class="matrix">';
  1245. $row_count = 0;
  1246. $total_new_rows = 0;
  1247. foreach ($data as $row_name => &$row)
  1248. {
  1249. $row_count ++;
  1250. // new?
  1251. $new = (substr($row_name, 0, 8) == 'row_new_');
  1252. if ($new) $total_new_rows++;
  1253. $row_js = array('id' => $row_name, 'cellSettings' => array());
  1254. $tr_class = 'matrix';
  1255. if ($row_count == 1) $tr_class .= ' matrix-first';
  1256. if ($row_count == $total_rows) $tr_class .= ' matrix-last';
  1257. $tbody .= '<tr class="'.$tr_class.'">';
  1258. // add left heading if there can be more than one row
  1259. if (! $single_row_mode)
  1260. {
  1261. $tbody .= '<th class="matrix matrix-first matrix-tr-header">'
  1262. . '<div><span>'.$row_count.'</span><a title="'.lang('options').'"></a></div>'
  1263. . '<input type="hidden" name="'.$this->field_name.'[row_order][]" value="'.$row_name.'" />'
  1264. . '</th>';
  1265. }
  1266. // add the cell data
  1267. foreach ($cols as $col_index => &$col)
  1268. {
  1269. $col_name = 'col_id_'.$col['col_id'];
  1270. $col_count = $col_index + 1;
  1271. $td_class = 'matrix';
  1272. // is this the first data cell?
  1273. if ($col_count == 1)
  1274. {
  1275. // is this also the first cell in the <tr>?
  1276. if ($single_row_mode) $td_class .= ' matrix-first';
  1277. // use .matrix-firstcell for active state
  1278. $td_class .= ' matrix-firstcell';
  1279. }
  1280. if ($col_count == $total_cols) $td_class .= ' matrix-last';
  1281. // was there a validation error for this cell?
  1282. if (isset($this->cache['cell_errors'][$field_id][$row_name][$col_name]))
  1283. {
  1284. $td_class .= ' matrix-error';
  1285. }
  1286. // get new instance of this celltype
  1287. $celltype = $this->_get_celltype($col['col_type']);
  1288. $cell_name = $this->field_name.'['.$row_name.']['.$col_name.']';
  1289. $cell_data = isset($row['col_id_'.$col['col_id']]) ? $row['col_id_'.$col['col_id']] : '';
  1290. // fill it up with crap
  1291. $celltype->settings = array_merge($celltype->settings, $col_settings[$col['col_id']]);
  1292. if (isset($row['row_id'])) $celltype->row_id = $row['row_id'];
  1293. $celltype->field_id = $field_id;
  1294. $celltype->field_name = $this->field_name;
  1295. $celltype->col_id = $col['col_id'];
  1296. $celltype->cell_name = $cell_name;
  1297. // get the cell html
  1298. $this->_add_package_path($celltype);
  1299. $cell_html = $celltype->display_cell($cell_data);
  1300. // is the celltype sending settings too?
  1301. if (is_array($cell_html))
  1302. {
  1303. if (isset($cell_html['settings']))
  1304. {
  1305. $row_js['cellSettings'][$col_name] = $cell_html['settings'];
  1306. }
  1307. if (isset($cell_html['class']))
  1308. {
  1309. $td_class .= ' '.$cell_html['class'];
  1310. }
  1311. $cell_html = $cell_html['data'];
  1312. }
  1313. $tbody .= '<td class="'.$td_class.'">'.$cell_html.'</td>';
  1314. }
  1315. $tbody .= '</tr>';
  1316. $rows_js[] = $row_js;
  1317. }
  1318. $tbody .= '</tbody>';
  1319. // -------------------------------------------
  1320. // Plug it all together
  1321. // -------------------------------------------
  1322. $r = '<div id="'.$this->field_name.'" class="matrix" style="margin: 11px 0 1px">'
  1323. . '<table class="matrix'.($data ? '' : ' matrix-nodata').'" cellspacing="0" cellpadding="0" border="0">'
  1324. . $thead
  1325. . $tbody
  1326. . '</table>';
  1327. if ($single_row_mode)
  1328. {
  1329. // no <th>s in the <tbody>, so we need to store the row_order outside of the table
  1330. $r .= '<input type="hidden" name="'.$this->field_name.'[row_order][]" value="'.$rows_js[0]['id'].'" />';
  1331. }
  1332. else
  1333. {
  1334. // add the '+' button
  1335. $r .= '<a class="matrix-btn matrix-add'.($max_rows && $max_rows == $total_rows ? ' matrix-btn-disabled' : '').'" title="'.lang('add_row').'"></a>';
  1336. }
  1337. $r .= '</div>';
  1338. // initialize the field js
  1339. $js = 'jQuery(document).ready(function(){'
  1340. . 'var m = new Matrix("'.$this->field_name . '", '
  1341. . '"' . addslashes($this->settings['field_label']) . '", '
  1342. . $this->EE->javascript->generate_json($cols_js, TRUE) . ', '
  1343. . $this->EE->javascript->generate_json($rows_js, TRUE) . ', '
  1344. . $min_rows . ', '
  1345. . $max_rows
  1346. . ');' . NL
  1347. . 'm.totalNewRows = '.$total_new_rows.';'
  1348. . '});';
  1349. $this->_insert_js($js);
  1350. return $r;
  1351. }
  1352. // --------------------------------------------------------------------
  1353. /**
  1354. * Validate
  1355. */
  1356. function validate($data)
  1357. {
  1358. $errors = array();
  1359. $field_id = $this->settings['field_id'];
  1360. $cols = $this->_get_field_cols($field_id);
  1361. if (isset($data['row_order']) && $data['row_order'] && $cols)
  1362. {
  1363. // prep the cols
  1364. foreach ($cols as &$col)
  1365. {
  1366. $celltype = $this->_get_celltype($col['col_type']);
  1367. $col['has_validate_cell'] = method_exists($celltype, 'validate_cell');
  1368. $col['has_save_cell'] = method_exists($celltype, 'save_cell');
  1369. $col['has_post_save_cell'] = method_exists($celltype, 'post_save_cell');
  1370. // are we ever going to call this celltype during the save process?
  1371. if ($col['has_validate_cell'] || $col['has_save_cell'] || $col['has_post_save_cell'])
  1372. {
  1373. // prepare the celltype's col settings
  1374. $col['celltype_settings'] = array_merge($this->settings, $celltype->settings, (is_array($col['col_settings']) ? $col['col_settings'] : array()));
  1375. $col['celltype_settings']['col_id'] = $col['col_id'];
  1376. $col['celltype_settings']['col_name'] = 'col_id_'.$col['col_id'];
  1377. $col['celltype_settings']['col_required'] = $col['col_required'];
  1378. }
  1379. }
  1380. // load the language file
  1381. $this->EE->lang->loadfile('matrix_validation', 'matrix');
  1382. foreach ($data['row_order'] as $row_index => $row_name)
  1383. {
  1384. if (isset($data[$row_name]))
  1385. {
  1386. $row = $data[$row_name];
  1387. foreach ($cols as &$col)
  1388. {
  1389. // if the celltype has a validate_cell() method, use that for validation
  1390. if ($col['has_validate_cell'])
  1391. {
  1392. $col_name = 'col_id_'.$col['col_id'];
  1393. $cell_data = isset($row[$col_name]) ? $row[$col_name] : '';
  1394. $celltype = $this->_get_celltype($col['col_type']);
  1395. $celltype->settings = $col['celltype_settings'];
  1396. $celltype->settings['row_name'] = $row_name;
  1397. if (($error = $celltype->validate_cell($cell_data)) !== TRUE)
  1398. {
  1399. $this->cache['cell_errors'][$field_id][$row_name][$col_name] = TRUE;
  1400. $errors[] = sprintf($error, $col['col_label']);
  1401. }
  1402. }
  1403. }
  1404. }
  1405. }
  1406. }
  1407. else
  1408. {
  1409. // is this a required field?
  1410. if ($this->settings['field_required'] == 'y')
  1411. {
  1412. return lang('required');
  1413. }
  1414. }
  1415. if ($errors)
  1416. {
  1417. return '<ul><li>'.implode('</li><li>', array_unique($errors)).'</li></ul>';
  1418. }
  1419. // save the cols and data for later...
  1420. $this->cache['fields'][$field_id]['cols'] = $cols;
  1421. $this->cache['fields'][$field_id]['data'] = $data;
  1422. return TRUE;
  1423. }
  1424. // --------------------------------------------------------------------
  1425. /**
  1426. * Flatten Data
  1427. */
  1428. private function _flatten_data($data)
  1429. {
  1430. $r = array();
  1431. if (is_array($data))
  1432. {
  1433. foreach ($data as $val)
  1434. {
  1435. $r[] = $this->_flatten_data($val);
  1436. }
  1437. }
  1438. else
  1439. {
  1440. $r[] = $data;
  1441. }
  1442. return implode(NL, array_filter($r));
  1443. }
  1444. // --------------------------------------------------------------------
  1445. /**
  1446. * Save
  1447. */
  1448. function save()
  1449. {
  1450. $field_id = $this->settings['field_id'];
  1451. // get the cols and data from the cache
  1452. $cols = $this->cache['fields'][$field_id]['cols'];
  1453. $data = $this->cache['fields'][$field_id]['data'];
  1454. // create a flattened string of all the searchable columns' values
  1455. if (isset($data['row_order']) && $data['row_order'] && $cols)
  1456. {
  1457. $r = '';
  1458. foreach ($data['row_order'] as $row_order => $row_id)
  1459. {
  1460. if (! isset($data[$row_id])) continue;
  1461. $row = $data[$row_id];
  1462. foreach ($cols as &$col)
  1463. {
  1464. if ($col['col_search'] == 'y')
  1465. {
  1466. $cell_data = isset($row['col_id_'.$col['col_id']]) ? $row['col_id_'.$col['col_id']] : '';
  1467. $flattened_cell_data = $this->_flatten_data($cell_data);
  1468. if (strlen($flattened_cell_data))
  1469. {
  1470. $r .= $flattened_cell_data . NL;
  1471. }
  1472. }
  1473. }
  1474. }
  1475. return $r ? $r : '1';
  1476. }
  1477. // no rows, so return blank
  1478. return '';
  1479. }
  1480. /**
  1481. * Post Save
  1482. */
  1483. function post_save()
  1484. {
  1485. $field_id = $this->settings['field_id'];
  1486. if (! isset($this->cache['fields'][$field_id])) return;
  1487. // get the cols and data from the cache
  1488. $cols = $this->cache['fields'][$field_id]['cols'];
  1489. $data = $this->cache['fields'][$field_id]['data'];
  1490. // -------------------------------------------
  1491. // Delete the deleted rows
  1492. // -------------------------------------------
  1493. if (isset($data['deleted_rows']) && $data['deleted_rows'])
  1494. {
  1495. foreach ($data['deleted_rows'] as $row_name)
  1496. {
  1497. $delete_rows[] = substr($row_name, 7);
  1498. }
  1499. $this->delete_rows($delete_rows);
  1500. }
  1501. // -------------------------------------------
  1502. // Add/update rows
  1503. // -------------------------------------------
  1504. if (isset($data['row_order']) && $data['row_order'] && $cols)
  1505. {
  1506. foreach ($data['row_order'] as $row_order => $row_name)
  1507. {
  1508. // get the row data
  1509. $row = isset($data[$row_name]) ? $data[$row_name] : array();
  1510. // is this a new row?
  1511. $new = (substr($row_name, 0, 8) == 'row_new_');
  1512. if (! $new)
  1513. {
  1514. $row_id = substr($row_name, 7);
  1515. }
  1516. // -------------------------------------------
  1517. // Prep the row's DB data
  1518. // -------------------------------------------
  1519. $row_data = array(
  1520. 'row_order' => $row_order
  1521. );
  1522. foreach ($cols as &$col)
  1523. {
  1524. $cell_data = isset($row['col_id_'.$col['col_id']]) ? $row['col_id_'.$col['col_id']] : '';
  1525. // give the celltype a chance to do what it wants with it
  1526. if ($col['has_save_cell'])
  1527. {
  1528. $celltype = $this->_get_celltype($col['col_type']);
  1529. $celltype->settings = $col['celltype_settings'];
  1530. $celltype->settings['entry_id'] = $this->settings['entry_id'];
  1531. $celltype->settings['row_name'] = $row_name;
  1532. $cell_data = $celltype->save_cell($cell_data);
  1533. }
  1534. $row_data['col_id_'.$col['col_id']] = $cell_data;
  1535. }
  1536. // -------------------------------------------
  1537. // Save or update the row
  1538. // -------------------------------------------
  1539. if ($new)
  1540. {
  1541. $row_data['site_id'] = $this->EE->config->item('site_id');
  1542. $row_data['entry_id'] = $this->settings['entry_id'];
  1543. $row_data['field_id'] = $field_id;
  1544. // insert the row
  1545. $this->EE->db->insert('matrix_data', $row_data);
  1546. // get the new row_id
  1547. $row_id = $this->EE->db->insert_id();
  1548. }
  1549. else
  1550. {
  1551. // just update the existing row
  1552. $this->EE->db->where('row_id', $row_id)
  1553. ->update('matrix_data', $row_data);
  1554. }
  1555. // -------------------------------------------
  1556. // post_save_cell()
  1557. // -------------------------------------------
  1558. foreach ($cols as &$col)
  1559. {
  1560. if ($col['has_post_save_cell'])
  1561. {
  1562. $celltype = $this->_get_celltype($col['col_type']);
  1563. $celltype->settings = $col['celltype_settings'];
  1564. $celltype->settings['entry_id'] = $this->settings['entry_id'];
  1565. $celltype->settings['row_id'] = $row_id;
  1566. $celltype->settings['row_name'] = $row_name;
  1567. $celltype->post_save_cell($row_data['col_id_'.$col['col_id']]);
  1568. }
  1569. }
  1570. }
  1571. }
  1572. }
  1573. // --------------------------------------------------------------------
  1574. /**
  1575. * Delete
  1576. */
  1577. function delete($entry_ids)
  1578. {
  1579. $rows = $this->EE->db->select('row_id')
  1580. ->where_in('entry_id', $entry_ids)
  1581. ->get('matrix_data');
  1582. if ($rows->num_rows())
  1583. {
  1584. $row_ids = array();
  1585. foreach ($rows->result() as $row)
  1586. {
  1587. $row_ids[] = $row->row_id;
  1588. }
  1589. $this->delete_rows($row_ids);
  1590. }
  1591. }
  1592. // --------------------------------------------------------------------
  1593. /**
  1594. * Data Query
  1595. */
  1596. private function _data_query($params, $cols, $select_mode = 'data', $select_aggregate = '')
  1597. {
  1598. if (! $cols) return FALSE;
  1599. // -------------------------------------------
  1600. // What's and Where's
  1601. // -------------------------------------------
  1602. $col_ids_by_name = array();
  1603. $select = 'row_id';
  1604. $where = '';
  1605. $use_where = FALSE;
  1606. foreach ($cols as &$col)
  1607. {
  1608. $col_id = 'col_id_'.$col['col_id'];
  1609. $col_ids_by_name[$col['col_name']] = $col['col_id'];
  1610. if ($select_mode == 'data') $select .= ', '.$col_id;
  1611. if (isset($params['search:'.$col['col_name']]))
  1612. {
  1613. $use_where = TRUE;
  1614. $terms = $params['search:'.$col['col_name']];
  1615. if (strncmp($terms, '=', 1) == 0)
  1616. {
  1617. // -------------------------------------------
  1618. // Exact Match e.g.: search:body="=pickle"
  1619. // -------------------------------------------
  1620. $terms = substr($terms, 1);
  1621. // special handling for IS_EMPTY
  1622. if (strpos($terms, 'IS_EMPTY') !== FALSE)
  1623. {
  1624. $terms = str_replace('IS_EMPTY', '', $terms);
  1625. $add_search = $this->EE->functions->sql_andor_string($terms, $col_id);
  1626. // remove the first AND output by $this->EE->functions->sql_andor_string() so we can parenthesize this clause
  1627. $add_search = substr($add_search, 3);
  1628. $not = (strncmp($terms, 'not ', 4) == 0);
  1629. $conj = ($add_search != '' && ! $not) ? 'OR' : 'AND';
  1630. if ($not)
  1631. {
  1632. $where .= 'AND ('.$add_search.' '.$conj.' '.$col_id.' != "") ';
  1633. }
  1634. else
  1635. {
  1636. $where .= 'AND ('.$add_search.' '.$conj.' '.$col_id.' = "") ';
  1637. }
  1638. }
  1639. else
  1640. {
  1641. $where .= $this->EE->functions->sql_andor_string($terms, $col_id).' ';
  1642. }
  1643. }
  1644. else
  1645. {
  1646. // -------------------------------------------
  1647. // "Contains" e.g.: search:body="pickle"
  1648. // -------------------------------------------
  1649. if (strncmp($terms, 'not ', 4) == 0)
  1650. {
  1651. $terms = substr($terms, 4);
  1652. $like = 'NOT LIKE';
  1653. }
  1654. else
  1655. {
  1656. $like = 'LIKE';
  1657. }
  1658. if (strpos($terms, '&&') !== FALSE)
  1659. {
  1660. $terms = explode('&&', $terms);
  1661. $andor = (strncmp($like, 'NOT', 3) == 0) ? 'OR' : 'AND';
  1662. }
  1663. else
  1664. {
  1665. $terms = explode('|', $terms);
  1666. $andor = (strncmp($like, 'NOT', 3) == 0) ? 'AND' : 'OR';
  1667. }
  1668. $where .= ' AND (';
  1669. foreach ($terms as $term)
  1670. {
  1671. if ($term == 'IS_EMPTY')
  1672. {
  1673. $where .= ' '.$col_id.' '.$like.' "" '.$andor;
  1674. }
  1675. else if (preg_match('/^[<>]=?/', $term, $match)) // less than/greater than
  1676. {
  1677. $term = substr($term, strlen($match[0]));
  1678. $where .= ' '.$col_id.' '.$match[0].' "'.$this->EE->db->escape_str($term).'" '.$andor;
  1679. }
  1680. else if (strpos($term, '\W') !== FALSE) // full word only, no partial matches
  1681. {
  1682. $not = ($like == 'LIKE') ? ' ' : ' NOT ';
  1683. // Note: MySQL's nutty POSIX regex word boundary is [[:>:]]
  1684. $term = '([[:<:]]|^)'.preg_quote(str_replace('\W', '', $term)).'([[:>:]]|$)';
  1685. $where .= ' '.$col_id.$not.'REGEXP "'.$this->EE->db->escape_str($term).'" '.$andor;
  1686. }
  1687. else
  1688. {
  1689. $where .= ' '.$col_id.' '.$like.' "%'.$this->EE->db->escape_like_str($term).'%" '.$andor;
  1690. }
  1691. }
  1692. $where = substr($where, 0, -strlen($andor)).') ';
  1693. }
  1694. }
  1695. }
  1696. // -------------------------------------------
  1697. // Row IDs
  1698. // -------------------------------------------
  1699. if ($fixed_order = (isset($params['fixed_order']) && $params['fixed_order']))
  1700. {
  1701. $params['row_id'] = $params['fixed_order'];
  1702. }
  1703. if (isset($params['row_id']) && $params['row_id'])
  1704. {
  1705. $use_where = TRUE;
  1706. if (strncmp($params['row_id'], 'not ', 4) == 0)
  1707. {
  1708. $not = 'NOT ';
  1709. $params['row_id'] = substr($params['row_id'], 4);
  1710. }
  1711. else
  1712. {
  1713. $not = '';
  1714. }
  1715. $row_ids = explode('|', $params['row_id']);
  1716. $where .= ' AND row_id '.$not.'IN (' . implode(',', $row_ids) . ')';
  1717. }
  1718. $sql = 'SELECT '.($select_mode == 'aggregate' ? $select_aggregate.' aggregate' : $select).'
  1719. FROM exp_matrix_data
  1720. WHERE field_id = '.$this->field_id.'
  1721. AND entry_id = '.$this->row['entry_id'].'
  1722. '.($use_where ? $where : '');
  1723. // -------------------------------------------
  1724. // Orberby + Sort
  1725. // -------------------------------------------
  1726. $orderbys = (isset($params['orderby']) && $params['orderby']) ? explode('|', $params['orderby']) : array('row_order');
  1727. $sorts = (isset($params['sort']) && $params['sort']) ? explode('|', $params['sort']) : array();
  1728. $all_orderbys = array();
  1729. foreach ($orderbys as $i => $name)
  1730. {
  1731. $name = (isset($col_ids_by_name[$name])) ? 'col_id_'.$col_ids_by_name[$name] : $name;
  1732. $sort = (isset($sorts[$i]) && strtoupper($sorts[$i]) == 'DESC') ? 'DESC' : 'ASC';
  1733. $all_orderbys[] = $name.' '.$sort;
  1734. }
  1735. $sql .= ' ORDER BY '.implode(', ', $all_orderbys);
  1736. // -------------------------------------------
  1737. // Offset and Limit
  1738. // -------------------------------------------
  1739. // if we're not sorting randomly, go ahead and set the offset and limit in the SQL
  1740. if ((! isset($params['sort']) || $params['sort'] != 'random') && (isset($params['limit']) || isset($params['offset'])))
  1741. {
  1742. $offset = (isset($params['offset']) && $params['offset'] && $params['offset'] > 0) ? $params['offset'] . ', ' : '';
  1743. $limit = (isset($params['limit']) && $params['limit'] && $params['limit'] > 0) ? $params['limit'] : 100;
  1744. $sql .= ' LIMIT ' . $offset . $limit;
  1745. }
  1746. // -------------------------------------------
  1747. // Run and return
  1748. // -------------------------------------------
  1749. // -------------------------------------------
  1750. // 'matrix_data_query' hook
  1751. // - Override the SQL query
  1752. //
  1753. if ($select_mode == 'data' && $this->EE->extensions->active_hook('matrix_data_query'))
  1754. {
  1755. $query = $this->EE->extensions->call('matrix_data_query', $this, $params, $sql);
  1756. }
  1757. else
  1758. {
  1759. $query = $this->EE->db->query($sql);
  1760. }
  1761. //
  1762. // -------------------------------------------
  1763. switch ($select_mode)
  1764. {
  1765. case 'data':
  1766. $data = $query->result_array();
  1767. if ($fixed_order)
  1768. {
  1769. $data_by_id = array();
  1770. foreach ($data as $row)
  1771. {
  1772. $data_by_id[$row['row_id']] = $row;
  1773. }
  1774. $data = array();
  1775. foreach ($row_ids as $row_id)
  1776. {
  1777. if (isset($data_by_id[$row_id]))
  1778. {
  1779. $data[] = $data_by_id[$row_id];
  1780. }
  1781. }
  1782. }
  1783. return $data ? $data : FALSE;
  1784. case 'aggregate':
  1785. return $query->row('aggregate');
  1786. case 'row_ids':
  1787. $row_ids = array();
  1788. foreach ($query->result() as $row)
  1789. {
  1790. $row_ids[] = $row->row_id;
  1791. }
  1792. return $row_ids;
  1793. }
  1794. }
  1795. // --------------------------------------------------------------------
  1796. /**
  1797. * Replace Tag
  1798. */
  1799. function replace_tag($data, $params = array(), $tagdata = FALSE)
  1800. {
  1801. // ignore if no tagdata
  1802. if (! $tagdata) return;
  1803. // dynamic params
  1804. if (isset($params['dynamic_parameters']))
  1805. {
  1806. $dynamic_parameters = explode('|', $params['dynamic_parameters']);
  1807. foreach ($dynamic_parameters as $param)
  1808. {
  1809. if (($val = $this->EE->input->post($param)) !== FALSE)
  1810. {
  1811. $params[$param] = $this->EE->db->escape_str($val);
  1812. }
  1813. }
  1814. }
  1815. $r = '';
  1816. // -------------------------------------------
  1817. // Get the columns
  1818. // -------------------------------------------
  1819. $cols = $this->_get_field_cols($this->field_id);
  1820. if (! $cols) return $r;
  1821. // -------------------------------------------
  1822. // Get the data
  1823. // -------------------------------------------
  1824. $data = $this->_data_query($params, $cols);
  1825. if (! $data) return $r;
  1826. // -------------------------------------------
  1827. // Randomize
  1828. // -------------------------------------------
  1829. if (isset($params['sort']) && $params['sort'] == 'random')
  1830. {
  1831. shuffle($data);
  1832. // apply the limit now, since we didn't do it in the original query
  1833. if (isset($params['limit']) && $params['limit'])
  1834. {
  1835. $data = array_splice($data, 0, $params['limit']);
  1836. }
  1837. }
  1838. // -------------------------------------------
  1839. // Tagdata
  1840. // -------------------------------------------
  1841. // get the full list of row IDs
  1842. $field_row_ids_params = array_merge((array) $params, array('row_id' => '', 'limit' => '', 'offset' => ''));
  1843. $this->_field_row_ids = $this->_data_query($field_row_ids_params, $cols, 'row_ids');
  1844. $this->_field_total_rows = count($this->_field_row_ids);
  1845. // are {prev_row} or {next_row} being used?
  1846. $siblings_in_use = ((strstr($tagdata, 'prev_row') !== FALSE) || (strstr($tagdata, 'next_row') !== FALSE));
  1847. // see which col tags are being used
  1848. foreach ($cols as &$col)
  1849. {
  1850. $col['in_use'] = preg_match('/\{'.$col['col_name'].'[\}: ]/', $tagdata) ? TRUE : FALSE;
  1851. }
  1852. // {total_rows} and {field_total_rows}
  1853. $vars = array(
  1854. 'total_rows' => count($data),
  1855. 'field_total_rows' => $this->_field_total_rows
  1856. );
  1857. $tagdata = $this->EE->functions->var_swap($tagdata, $vars);
  1858. $tagdata = $this->EE->functions->prep_conditionals($tagdata, $vars);
  1859. // process each row
  1860. foreach ($data as $this->_row_index => &$row)
  1861. {
  1862. $row_tagdata = $tagdata;
  1863. // get the row's index within the entire field
  1864. $this->_field_row_index = array_search($row['row_id'], $this->_field_row_ids);
  1865. // parse sibling tags
  1866. if ($siblings_in_use)
  1867. {
  1868. $conditionals = array(
  1869. 'prev_row' => ($this->_field_row_index > 0 ? 'y' : ''),
  1870. 'next_row' => ($this->_field_row_index < $this->_field_total_rows-1 ? 'y' : '')
  1871. );
  1872. $row_tagdata = $this->EE->functions->prep_conditionals($row_tagdata, $conditionals);
  1873. // {prev_row} and {next_row} tag pairs
  1874. $row_tagdata = preg_replace_callback('/'.LD.'(prev_row|next_row)'.RD.'(.*)'.LD.'\/\1'.RD.'/sU', array(&$this, '_parse_sibling_tag'), $row_tagdata);
  1875. }
  1876. $conditionals = array();
  1877. $tags = array();
  1878. foreach ($cols as &$col)
  1879. {
  1880. $col_name = 'col_id_'.$col['col_id'];
  1881. $cell_data = $row[$col_name];
  1882. $conditionals[$col['col_name']] = $cell_data;
  1883. if ($col['in_use'])
  1884. {
  1885. $celltype = $this->_get_celltype($col['col_type']);
  1886. $celltype_vars = array(
  1887. 'row' => $this->row,
  1888. 'field_id' => $this->field_id,
  1889. 'field_name' => $this->field_name,
  1890. 'col_id' => $col['col_id'],
  1891. 'col_name' => $col_name,
  1892. 'row_id' => $row['row_id'],
  1893. 'row_name' => 'row_id_'.$row['row_id'],
  1894. 'settings' => array_merge($this->settings, $celltype->settings, $col['col_settings'])
  1895. );
  1896. // call pre_process?
  1897. if (method_exists($celltype, 'pre_process'))
  1898. {
  1899. foreach ($celltype_vars as $key => $value)
  1900. {
  1901. $celltype->$key = $value;
  1902. }
  1903. $cell_data = $celltype->pre_process($cell_data);
  1904. }
  1905. $tags[$col['col_name']] = array(
  1906. 'data' => $cell_data,
  1907. 'type' => $col['col_type'],
  1908. 'vars' => $celltype_vars
  1909. );
  1910. }
  1911. }
  1912. $vars = array(
  1913. 'field_row_index' => $this->_field_row_index,
  1914. 'field_row_count' => $this->_field_row_index + 1,
  1915. 'row_index' => $this->_row_index,
  1916. 'row_count' => $this->_row_index + 1,
  1917. 'row_id' => $row['row_id']
  1918. );
  1919. $row_tagdata = $this->EE->functions->var_swap($row_tagdata, $vars);
  1920. $row_tagdata = $this->EE->functions->prep_conditionals($row_tagdata, array_merge($vars, $conditionals));
  1921. if ($tags)
  1922. {
  1923. $this->_parse_tagdata($row_tagdata, $tags);
  1924. }
  1925. // {switch} tags
  1926. $row_tagdata = preg_replace_callback('/'.LD.'switch\s*=\s*([\'\"])([^\1]+)\1'.RD.'/sU', array(&$this, '_parse_switch_tag'), $row_tagdata);
  1927. $r .= $row_tagdata;
  1928. }
  1929. unset($this->_field_row_ids, $this->_field_total_rows, $this->_field_row_index, $this->_row_index);
  1930. if (isset($params['backspace']) && $params['backspace'])
  1931. {
  1932. $r = substr($r, 0, -$params['backspace']);
  1933. }
  1934. return $r;
  1935. }
  1936. // --------------------------------------------------------------------
  1937. /**
  1938. * Parse Step Tag
  1939. */
  1940. private function _parse_sibling_tag($match)
  1941. {
  1942. if ($match[1] == 'prev_row')
  1943. {
  1944. // ignore if this is the first row
  1945. if ($this->_field_row_index == 0) return;
  1946. $row_id = $this->_field_row_ids[$this->_field_row_index-1];
  1947. }
  1948. else
  1949. {
  1950. // ignore if this is the last row
  1951. if ($this->_field_row_index == $this->_field_total_rows - 1) return;
  1952. $row_id = $this->_field_row_ids[$this->_field_row_index+1];
  1953. }
  1954. $obj = new Matrix_ft();
  1955. $obj->settings = $this->settings;
  1956. $obj->row = $this->row;
  1957. $obj->field_id = $this->field_id;
  1958. $obj->field_name = $this->field_name;
  1959. return $obj->replace_tag('', array('row_id' => $row_id), $match[2]);
  1960. }
  1961. /**
  1962. * Parse Switch Tag
  1963. */
  1964. private function _parse_switch_tag($match)
  1965. {
  1966. $options = explode('|', $match[2]);
  1967. $option = $this->_row_index % count($options);
  1968. return $options[$option];
  1969. }
  1970. // --------------------------------------------------------------------
  1971. /**
  1972. * Table
  1973. */
  1974. function replace_table($data, $params = array())
  1975. {
  1976. $thead = '';
  1977. $tagdata = ' <tr>' . "\n";
  1978. // get the cols
  1979. $cols = $this->_get_field_cols($this->field_id);
  1980. if (! $cols) return '';
  1981. // which table features do they want?
  1982. $set_row_ids = (isset($params['set_row_ids']) && $params['set_row_ids'] == 'yes');
  1983. $set_classes = (isset($params['set_classes']) && $params['set_classes'] == 'yes');
  1984. $set_widths = (isset($params['set_widths']) && $params['set_widths'] == 'yes');
  1985. $thead = '';
  1986. $tagdata = ' <tr'.($set_row_ids ? ' id="row_id_'.LD.'row_id'.RD.'"' : '').'>' . "\n";
  1987. foreach ($cols as &$col)
  1988. {
  1989. $attr = '';
  1990. if ($set_classes) $attr .= ' class="'.$col['col_name'].'"';
  1991. if ($set_widths) $attr .= ' width="'.$col['col_width'].'"';
  1992. $thead .= ' <th scope="col"'.$attr.'>'.$col['col_label'].'</th>' . "\n";
  1993. $tagdata .= ' <td'.$attr.'>'.LD.$col['col_name'].RD.'</td>' . "\n";
  1994. }
  1995. $tagdata .= ' </tr>' . "\n";
  1996. $attr = '';
  1997. if (isset($params['cellspacing'])) $attr .= ' cellspacing="'.$params['cellspacing'].'"';
  1998. if (isset($params['cellpadding'])) $attr .= ' cellpadding="'.$params['cellpadding'].'"';
  1999. if (isset($params['border'])) $attr .= ' border="'.$params['border'].'"';
  2000. if (isset($params['width'])) $attr .= ' width="'.$params['width'].'"';
  2001. if (isset($params['class'])) $attr .= ' class="'.$params['class'].'"';
  2002. return '<table'.$attr.'>' . "\n"
  2003. . ' <thead>' . "\n"
  2004. . ' <tr>' . "\n"
  2005. . $thead
  2006. . ' </tr>' . "\n"
  2007. . ' </thead>' . "\n"
  2008. . ' <tbody>' . "\n"
  2009. . $this->replace_tag($data, $params, $tagdata)
  2010. . ' </tbody>' . "\n"
  2011. . '</table>';
  2012. }
  2013. // --------------------------------------------------------------------
  2014. /**
  2015. * Replace Sibling Tag
  2016. */
  2017. private function _replace_sibling_tag($params, $tagdata, $which)
  2018. {
  2019. // ignore if no tagdata
  2020. if (! $tagdata) return;
  2021. // get the cols
  2022. $cols = $this->_get_field_cols($this->field_id);
  2023. if (! $cols) return;
  2024. // get the full list of row IDs
  2025. $field_row_ids_params = array_merge($params, array('row_id' => '', 'limit' => '', 'offset' => ''));
  2026. $field_row_ids = $this->_data_query($field_row_ids_params, $cols, 'row_ids');
  2027. $field_total_rows = count($field_row_ids);
  2028. // get the starting row's ID
  2029. if (isset($params['row_id']) && $params['row_id'])
  2030. {
  2031. $row_id = $params['row_id'];
  2032. }
  2033. else
  2034. {
  2035. $query_params = array_merge($params, array('limit' => '1'));
  2036. $query = $this->_data_query($query_params, $cols, 'row_ids');
  2037. $row_id = $query[0];
  2038. }
  2039. // get the starting row's index within the entire field
  2040. $field_row_index = array_search($row_id, $field_row_ids);
  2041. if ($which == 'prev')
  2042. {
  2043. // ignore if this is the first row
  2044. if ($field_row_index == 0) return;
  2045. $sibling_row_id = $field_row_ids[$field_row_index-1];
  2046. }
  2047. else
  2048. {
  2049. // ignore if this is the last row
  2050. if ($field_row_index == $field_total_rows - 1) return;
  2051. $sibling_row_id = $field_row_ids[$field_row_index+1];
  2052. }
  2053. return $this->replace_tag('', array('row_id' => $sibling_row_id), $tagdata);
  2054. }
  2055. /**
  2056. * Previous Row
  2057. */
  2058. function replace_prev_row($data, $params = array(), $tagdata = FALSE)
  2059. {
  2060. return $this->_replace_sibling_tag($params, $tagdata, 'prev');
  2061. }
  2062. /**
  2063. * Next Row
  2064. */
  2065. function replace_next_row($data, $params = array(), $tagdata = FALSE)
  2066. {
  2067. return $this->_replace_sibling_tag($params, $tagdata, 'next');
  2068. }
  2069. // --------------------------------------------------------------------
  2070. /**
  2071. * Total Rows
  2072. */
  2073. function replace_total_rows($data, $params = array())
  2074. {
  2075. $cols = $this->_get_field_cols($this->field_id);
  2076. if (! $cols) return 0;
  2077. return $this->_data_query($params, $cols, 'aggregate', 'COUNT(row_id)');
  2078. }
  2079. // --------------------------------------------------------------------
  2080. /**
  2081. * Get Col ID by Name
  2082. */
  2083. function _get_col_id_by_name($col_name, $cols)
  2084. {
  2085. // find the target col
  2086. foreach ($cols as $col)
  2087. {
  2088. if ($col['col_name'] == $col_name) return $col['col_id'];
  2089. }
  2090. }
  2091. /**
  2092. * Format Number
  2093. */
  2094. private function _format_number($params, $num)
  2095. {
  2096. $decimals = isset($params['decimals']) ? (int) $params['decimals'] : 0;
  2097. $dec_point = isset($params['dec_point']) ? $params['dec_point'] : '.';
  2098. $thousands_sep = isset($params['thousands_sep']) ? $params['thousands_sep'] : ',';
  2099. return number_format($num, $decimals, $dec_point, $thousands_sep);
  2100. }
  2101. /**
  2102. * Aggregate Column
  2103. */
  2104. private function _aggregate_col($params, $func)
  2105. {
  2106. // ignore if no col= param is set
  2107. if (! isset($params['col']) || ! $params['col']) return;
  2108. $cols = $this->_get_field_cols($this->field_id);
  2109. if (! $cols) return;
  2110. // get the col_id
  2111. $col_id = $this->_get_col_id_by_name($params['col'], $cols);
  2112. // ignore if that col doesn't exist
  2113. if (! $col_id) return;
  2114. $num = $this->_data_query($params, $cols, 'aggregate', "{$func}(col_id_{$col_id})");
  2115. return $this->_format_number($params, $num);
  2116. }
  2117. /**
  2118. * Average
  2119. */
  2120. function replace_average($data, $params = array())
  2121. {
  2122. return $this->_aggregate_col($params, 'AVG');
  2123. }
  2124. /**
  2125. * Sum
  2126. */
  2127. function replace_sum($data, $params = array())
  2128. {
  2129. return $this->_aggregate_col($params, 'SUM');
  2130. }
  2131. /**
  2132. * Lowest
  2133. */
  2134. function replace_lowest($data, $params = array())
  2135. {
  2136. return $this->_aggregate_col($params, 'MIN');
  2137. }
  2138. /**
  2139. * Highest
  2140. */
  2141. function replace_highest($data, $params = array())
  2142. {
  2143. return $this->_aggregate_col($params, 'MAX');
  2144. }
  2145. // --------------------------------------------------------------------
  2146. /**
  2147. * Parse Tagdata
  2148. */
  2149. private function _parse_tagdata(&$tagdata, $tags)
  2150. {
  2151. global $DSP;
  2152. // find the next celltype tag
  2153. $offset = 0;
  2154. while (preg_match('/'.LD.'('.implode('|', array_keys($tags)).')(:(\w+))?(\s+.*)?'.RD.'/sU', $tagdata, $matches, PREG_OFFSET_CAPTURE, $offset))
  2155. {
  2156. $field_name = $matches[1][0];
  2157. $field = $tags[$field_name];
  2158. $tag_pos = $matches[0][1];
  2159. $tag_len = strlen($matches[0][0]);
  2160. $tagdata_pos = $tag_pos + $tag_len;
  2161. $endtag = LD.'/'.$field_name.(isset($matches[2][0]) ? $matches[2][0] : '').RD;
  2162. $endtag_len = strlen($endtag);
  2163. $endtag_pos = strpos($tagdata, $endtag, $tagdata_pos);
  2164. $tag_func = (isset($matches[3][0]) && $matches[3][0]) ? 'replace_'.$matches[3][0] : '';
  2165. if (! $tag_func) $tag_func = 'replace_tag';
  2166. $class = $this->_get_celltype_class($field['type'], TRUE);
  2167. $method_exists = method_exists($class, $tag_func);
  2168. if ($method_exists)
  2169. {
  2170. // get the params
  2171. $params = array();
  2172. if (isset($matches[4][0]) && $matches[4][0] && preg_match_all('/\s+([\w-:]+)\s*=\s*([\'\"])([^\2]*)\2/sU', $matches[4][0], $param_matches))
  2173. {
  2174. for ($j = 0; $j < count($param_matches[0]); $j++)
  2175. {
  2176. $params[$param_matches[1][$j]] = $param_matches[3][$j];
  2177. }
  2178. }
  2179. // get inner tagdata
  2180. $field_tagdata = ($endtag_pos !== FALSE)
  2181. ? substr($tagdata, $tagdata_pos, $endtag_pos - $tagdata_pos)
  2182. : '';
  2183. $celltype = $this->_get_celltype($field['type']);
  2184. $this->_add_package_path($celltype);
  2185. foreach ($field['vars'] as $key => $value)
  2186. {
  2187. $celltype->$key = $value;
  2188. }
  2189. $new_tagdata = (string) $celltype->$tag_func($field['data'], $params, $field_tagdata);
  2190. }
  2191. else
  2192. {
  2193. $new_tagdata = $field['data'];
  2194. }
  2195. $offset = $tag_pos;
  2196. $tagdata = substr($tagdata, 0, $tag_pos)
  2197. . $new_tagdata
  2198. . substr($tagdata, ($endtag_pos !== FALSE ? $endtag_pos+$endtag_len : $tagdata_pos));
  2199. unset($new_tagdata);
  2200. }
  2201. }
  2202. }