PageRenderTime 67ms CodeModel.GetById 26ms 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

Large files files are truncated, but you can click here to view the full 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. {

Large files files are truncated, but you can click here to view the full file