PageRenderTime 68ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/system/expressionengine/third_party/matrix/ft.matrix.php

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