PageRenderTime 36ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/extensions/ext.fieldframe.php

https://github.com/pixelandtonic/fieldframe
PHP | 3181 lines | 2197 code | 341 blank | 643 comment | 238 complexity | ec07d647ec7facfa36ff39cf6d7c940c MD5 | raw file
  1. <?php
  2. if ( ! defined('EXT')) exit('Invalid file request');
  3. // define FF constants
  4. // (used by Fieldframe and Fieldframe_Main)
  5. if ( ! defined('FF_CLASS'))
  6. {
  7. define('FF_CLASS', 'Fieldframe');
  8. define('FF_NAME', 'FieldFrame');
  9. define('FF_VERSION', '1.4.5');
  10. }
  11. /**
  12. * FieldFrame Class
  13. *
  14. * This extension provides a framework for ExpressionEngine fieldtype development.
  15. *
  16. * @package FieldFrame
  17. * @author Brandon Kelly <brandon@pixelandtonic.com>
  18. * @copyright Copyright (c) 2011 Pixel & Tonic, Inc
  19. * @license http://creativecommons.org/licenses/by-sa/3.0/ Attribution-Share Alike 3.0 Unported
  20. */
  21. class Fieldframe {
  22. var $name = FF_NAME;
  23. var $version = FF_VERSION;
  24. var $description = 'Fieldtype Framework';
  25. var $settings_exist = 'y';
  26. var $docs_url = 'http://pixelandtonic.com/fieldframe/docs';
  27. var $hooks = array(
  28. 'sessions_start' => array('priority' => 1),
  29. // Edit Field Form
  30. 'publish_admin_edit_field_type_pulldown',
  31. 'publish_admin_edit_field_type_cellone',
  32. 'publish_admin_edit_field_type_celltwo',
  33. 'publish_admin_edit_field_extra_row',
  34. 'publish_admin_edit_field_format',
  35. 'publish_admin_edit_field_js',
  36. // Field Manager
  37. 'show_full_control_panel_start',
  38. 'show_full_control_panel_end',
  39. // Entry Form
  40. 'publish_form_field_unique',
  41. 'submit_new_entry_start',
  42. 'submit_new_entry_end',
  43. 'publish_form_start',
  44. // SAEF
  45. 'weblog_standalone_form_start',
  46. 'weblog_standalone_form_end',
  47. // Templates
  48. 'weblog_entries_tagdata' => array('priority' => 1),
  49. // LG Addon Updater
  50. 'lg_addon_update_register_source',
  51. 'lg_addon_update_register_addon'
  52. );
  53. /**
  54. * FieldFrame Class Constructor
  55. *
  56. * @param array $settings
  57. */
  58. function __construct($settings=array())
  59. {
  60. $this->_init_main($settings);
  61. }
  62. /**
  63. * Activate Extension
  64. */
  65. function activate_extension()
  66. {
  67. global $DB;
  68. // require PHP 5
  69. if (phpversion() < 5) return;
  70. // Get settings
  71. $query = $DB->query('SELECT settings FROM exp_extensions WHERE class = "'.FF_CLASS.'" AND settings != "" LIMIT 1');
  72. $settings = $query->num_rows ? $this->_unserialize($query->row['settings']) : array();
  73. $this->_init_main($settings, TRUE);
  74. global $FF;
  75. $FF->activate_extension($settings);
  76. }
  77. /**
  78. * Update Extension
  79. *
  80. * @param string $current Previous installed version of the extension
  81. */
  82. function update_extension($current='')
  83. {
  84. global $DB;
  85. if ( ! $current OR $current == FF_VERSION)
  86. {
  87. return FALSE;
  88. }
  89. if ($current < '1.1.3')
  90. {
  91. // no longer saving settings on a per-site basis
  92. $sql = array();
  93. // no more per-site FF settings
  94. $query = $DB->query('SELECT settings FROM exp_extensions WHERE class = "'.FF_CLASS.'" AND settings != "" LIMIT 1');
  95. if ($query->row)
  96. {
  97. $settings = array_shift(Fieldframe_Main::_unserialize($query->row['settings']));
  98. $sql[] = $DB->update_string('exp_extensions', array('settings' => Fieldframe_Main::_serialize($settings)), 'class = "'.FF_CLASS.'"');
  99. }
  100. // collect conversion info
  101. $query = $DB->query('SELECT * FROM exp_ff_fieldtypes ORDER BY enabled DESC, site_id ASC');
  102. $firsts = array();
  103. $conversions = array();
  104. foreach ($query->result as $ftype)
  105. {
  106. if ( ! isset($firsts[$ftype['class']]))
  107. {
  108. $firsts[$ftype['class']] = $ftype['fieldtype_id'];
  109. }
  110. else
  111. {
  112. $conversions[$ftype['fieldtype_id']] = $firsts[$ftype['class']];
  113. }
  114. }
  115. if ($conversions)
  116. {
  117. // remove duplicate ftype rows in exp_ff_fieldtypes
  118. $sql[] = 'DELETE FROM exp_ff_fieldtypes WHERE fieldtype_id IN ('.implode(',', array_keys($conversions)).')';
  119. // update field_type's in exp_weblog_fields
  120. foreach($conversions as $old_id => $new_id)
  121. {
  122. $sql[] = $DB->update_string('exp_weblog_fields', array('field_type' => 'ftype_id_'.$new_id), 'field_type = "ftype_id_'.$old_id.'"');
  123. }
  124. }
  125. // remove site_id column from exp_ff_fieldtypes
  126. $sql[] = 'ALTER TABLE exp_ff_fieldtypes DROP COLUMN site_id';
  127. // apply changes
  128. foreach($sql as $query)
  129. {
  130. $DB->query($query);
  131. }
  132. }
  133. if ($current < '1.1.0')
  134. {
  135. // hooks have changed, so go through
  136. // the whole activate_extension() process
  137. $this->activate_extension();
  138. }
  139. else
  140. {
  141. // just update the version #s
  142. $DB->query('UPDATE exp_extensions
  143. SET version = "'.FF_VERSION.'"
  144. WHERE class = "'.FF_CLASS.'"');
  145. }
  146. }
  147. /**
  148. * Disable Extension
  149. */
  150. function disable_extension()
  151. {
  152. global $DB;
  153. $DB->query($DB->update_string('exp_extensions', array('enabled' => 'n'), 'class = "'.FF_CLASS.'"'));
  154. }
  155. /**
  156. * Settings Form
  157. *
  158. * @param array $settings
  159. */
  160. function settings_form($settings=array())
  161. {
  162. $this->_init_main($settings);
  163. global $FF;
  164. $FF->settings_form();
  165. }
  166. function save_settings()
  167. {
  168. $this->_init_main(array(), TRUE);
  169. global $FF;
  170. $FF->save_settings();
  171. }
  172. /**
  173. * Initialize Main class
  174. *
  175. * @param array $settings
  176. * @access private
  177. */
  178. function _init_main($settings, $force=FALSE)
  179. {
  180. global $SESS, $FF;
  181. if ( ! isset($FF) OR $force)
  182. {
  183. $FF = new Fieldframe_Main($settings, $this->hooks);
  184. }
  185. }
  186. /**
  187. * __call Magic Method
  188. *
  189. * Routes calls to missing methods to $FF
  190. *
  191. * @param string $method Name of the missing method
  192. * @param array $args Arguments sent to the missing method
  193. */
  194. function __call($method, $args)
  195. {
  196. global $FF, $EXT;
  197. if (isset($FF))
  198. {
  199. if (method_exists($FF, $method))
  200. {
  201. return call_user_func_array(array(&$FF, $method), $args);
  202. }
  203. else if (substr($method, 0, 13) == 'forward_hook:')
  204. {
  205. $ext = explode(':', $method); // [ forward_hook, hook name, priority ]
  206. return call_user_func_array(array(&$FF, 'forward_hook'), array($ext[1], $ext[2], $args));
  207. }
  208. }
  209. return FALSE;
  210. }
  211. }
  212. /**
  213. * FieldFrame_Main Class
  214. *
  215. * Provides the core extension logic + hooks
  216. *
  217. * @package FieldFrame
  218. */
  219. class Fieldframe_Main {
  220. function log()
  221. {
  222. foreach(func_get_args() as $var)
  223. {
  224. if (is_string($var))
  225. {
  226. echo "<code style='display:block; margin:0; padding:5px 10px;'>{$var}</code>";
  227. }
  228. else
  229. {
  230. echo '<pre style="display:block; margin:0; padding:5px 10px; width:auto">';
  231. print_r($var);
  232. echo '</pre>';
  233. }
  234. }
  235. }
  236. var $errors = array();
  237. var $postponed_saves = array();
  238. var $snippets = array('head' => array(), 'body' => array());
  239. var $saef = FALSE;
  240. /**
  241. * FieldFrame_Main Class Initialization
  242. *
  243. * @param array $settings
  244. */
  245. function __construct($settings, $hooks)
  246. {
  247. global $SESS, $DB;
  248. $this->hooks = $hooks;
  249. // merge settings with defaults
  250. $default_settings = array(
  251. 'fieldtypes_url' => '',
  252. 'fieldtypes_path' => '',
  253. 'check_for_updates' => 'y'
  254. );
  255. $this->settings = array_merge($default_settings, $settings);
  256. // set the FT_PATH and FT_URL constants
  257. $this->_define_constants();
  258. }
  259. /**
  260. * Define Constants
  261. *
  262. * @access private
  263. */
  264. function _define_constants()
  265. {
  266. global $PREFS;
  267. if ( ! defined('FT_PATH') AND ($ft_path = isset($PREFS->core_ini['ft_path']) ? $PREFS->core_ini['ft_path'] : $this->settings['fieldtypes_path']))
  268. {
  269. define('FT_PATH', $ft_path);
  270. }
  271. if ( ! defined('FT_URL') AND ($ft_path = isset($PREFS->core_ini['ft_url']) ? $PREFS->core_ini['ft_url'] : $this->settings['fieldtypes_url']))
  272. {
  273. define('FT_URL', $ft_path);
  274. $this->snippets['body'][] = '<script type="text/javascript">;FT_URL = "'.FT_URL.'";</script>';
  275. }
  276. }
  277. /**
  278. * Array Ascii to Entities
  279. *
  280. * @access private
  281. */
  282. function _array_ascii_to_entities($vals)
  283. {
  284. if (is_array($vals))
  285. {
  286. foreach ($vals as &$val)
  287. {
  288. $val = $this->_array_ascii_to_entities($val);
  289. }
  290. }
  291. else if (! is_numeric($vals))
  292. {
  293. global $REGX;
  294. $vals = $REGX->ascii_to_entities($vals);
  295. }
  296. return $vals;
  297. }
  298. /**
  299. * Array Ascii to Entities
  300. *
  301. * @access private
  302. */
  303. function _array_entities_to_ascii($vals)
  304. {
  305. if (is_array($vals))
  306. {
  307. foreach ($vals as &$val)
  308. {
  309. $val = $this->_array_entities_to_ascii($val);
  310. }
  311. }
  312. else
  313. {
  314. global $REGX;
  315. $vals = $REGX->entities_to_ascii($vals);
  316. }
  317. return $vals;
  318. }
  319. /**
  320. * Serialize
  321. *
  322. * @access private
  323. */
  324. function _serialize($vals)
  325. {
  326. global $PREFS;
  327. if ($PREFS->ini('auto_convert_high_ascii') == 'y')
  328. {
  329. $vals = $this->_array_ascii_to_entities($vals);
  330. }
  331. return addslashes(serialize($vals));
  332. }
  333. /**
  334. * Unserialize
  335. *
  336. * @access private
  337. */
  338. function _unserialize($vals, $convert=TRUE)
  339. {
  340. global $REGX, $PREFS;
  341. if ($vals && is_string($vals) && preg_match('/^(i|s|a|o|d):(.*);/si', $vals) && ($tmp_vals = @unserialize($vals)) !== FALSE)
  342. {
  343. $vals = $REGX->array_stripslashes($tmp_vals);
  344. if ($convert AND $PREFS->ini('auto_convert_high_ascii') == 'y')
  345. {
  346. $vals = $this->_array_entities_to_ascii($vals);
  347. }
  348. }
  349. return $vals;
  350. }
  351. /**
  352. * Get Fieldtypes
  353. *
  354. * @return array All enabled FF fieldtypes, indexed by class name
  355. * @access private
  356. */
  357. function _get_ftypes()
  358. {
  359. global $DB;
  360. if ( ! isset($this->ftypes))
  361. {
  362. $this->ftypes = array();
  363. // get enabled fields from the DB
  364. $query = $DB->query('SELECT * FROM exp_ff_fieldtypes WHERE enabled = "y"');
  365. if ($query->num_rows)
  366. {
  367. foreach($query->result as $row)
  368. {
  369. if (($ftype = $this->_init_ftype($row)) !== FALSE)
  370. {
  371. $this->ftypes[] = $ftype;
  372. }
  373. }
  374. $this->_sort_ftypes($this->ftypes);
  375. }
  376. }
  377. return $this->ftypes;
  378. }
  379. function _get_ftype($class_name)
  380. {
  381. $ftypes = $this->_get_ftypes();
  382. return isset($ftypes[$class_name])
  383. ? $ftypes[$class_name]
  384. : FALSE;
  385. }
  386. /**
  387. * Get All Installed Fieldtypes
  388. *
  389. * @return array All installed FF fieldtypes, indexed by class name
  390. * @access private
  391. */
  392. function _get_all_installed_ftypes()
  393. {
  394. $ftypes = array();
  395. if (defined('FT_PATH'))
  396. {
  397. if ( $fp = @opendir(FT_PATH))
  398. {
  399. // iterate through the field folder contents
  400. while (($file = readdir($fp)) !== FALSE)
  401. {
  402. // skip hidden/navigational files
  403. if (substr($file, 0, 1) == '.') continue;
  404. // is this a directory, and does a ftype file exist inside it?
  405. if (is_dir(FT_PATH.$file) AND is_file(FT_PATH.$file.'/ft.'.$file.EXT))
  406. {
  407. if (($ftype = $this->_init_ftype($file, FALSE)) !== FALSE)
  408. {
  409. $ftypes[] = $ftype;
  410. }
  411. }
  412. }
  413. closedir($fp);
  414. $this->_sort_ftypes($ftypes);
  415. }
  416. else
  417. {
  418. $this->errors[] = 'bad_ft_path';
  419. }
  420. }
  421. return $ftypes;
  422. }
  423. /**
  424. * Sort Fieldtypes
  425. *
  426. * @param array $ftypes the array of fieldtypes
  427. * @access private
  428. */
  429. function _sort_ftypes(&$ftypes)
  430. {
  431. $ftypes_by_name = array();
  432. while($ftype = array_shift($ftypes))
  433. {
  434. $ftypes_by_name[$ftype->info['name']] = $ftype;
  435. }
  436. ksort($ftypes_by_name);
  437. foreach($ftypes_by_name as $ftype)
  438. {
  439. $ftypes[$ftype->_class_name] = $ftype;
  440. }
  441. }
  442. /**
  443. * Get Fieldtypes Indexed By Field ID
  444. *
  445. * @return array All enabled FF fieldtypes, indexed by the weblog field ID they're used in.
  446. * Strong possibility that there will be duplicate fieldtypes in here,
  447. * but it's not a big deal because they're just object references
  448. * @access private
  449. */
  450. function _get_fields()
  451. {
  452. global $DB, $REGX;
  453. if ( ! isset($this->ftypes_by_field_id))
  454. {
  455. $this->ftypes_by_field_id = array();
  456. // get the fieldtypes
  457. if ($ftypes = $this->_get_ftypes())
  458. {
  459. // index them by ID rather than class
  460. $ftypes_by_id = array();
  461. foreach($ftypes as $class_name => $ftype)
  462. {
  463. $ftypes_by_id[$ftype->_fieldtype_id] = $ftype;
  464. }
  465. // get the field info
  466. $query = $DB->query("SELECT field_id, site_id, field_name, field_type, ff_settings FROM exp_weblog_fields
  467. WHERE field_type IN ('ftype_id_".implode("', 'ftype_id_", array_keys($ftypes_by_id))."')");
  468. if ($query->num_rows)
  469. {
  470. // sort the current site's fields on top
  471. function sort_fields($a, $b)
  472. {
  473. global $PREFS;
  474. if ($a['site_id'] == $b['site_id']) return 0;
  475. if ($a['site_id'] == $PREFS->ini('site_id')) return 1;
  476. if ($b['site_id'] == $PREFS->ini('site_id')) return -1;
  477. return 0;
  478. }
  479. usort($query->result, 'sort_fields');
  480. foreach($query->result as $row)
  481. {
  482. $ftype_id = substr($row['field_type'], 9);
  483. $this->ftypes_by_field_id[$row['field_id']] = array(
  484. 'name' => $row['field_name'],
  485. 'ftype' => $ftypes_by_id[$ftype_id],
  486. 'settings' => array_merge(
  487. (isset($ftypes_by_id[$ftype_id]->default_field_settings) ? $ftypes_by_id[$ftype_id]->default_field_settings : array()),
  488. ($row['ff_settings'] ? (array)$this->_unserialize($row['ff_settings']) : array())
  489. )
  490. );
  491. }
  492. }
  493. }
  494. }
  495. return $this->ftypes_by_field_id;
  496. }
  497. /**
  498. * Initialize Fieldtype
  499. *
  500. * @param mixed $ftype fieldtype's class name or its row in exp_ff_fieldtypes
  501. * @return object Initialized fieldtype object
  502. * @access private
  503. */
  504. function _init_ftype($ftype, $req_strict=TRUE)
  505. {
  506. global $DB, $REGX, $LANG;
  507. $file = is_array($ftype) ? $ftype['class'] : $ftype;
  508. $class_name = ucfirst($file);
  509. if ( ! class_exists($class_name) && ! class_exists($class_name.'_ft'))
  510. {
  511. // import the file
  512. @include(FT_PATH.$file.'/ft.'.$file.EXT);
  513. // skip if the class doesn't exist
  514. if ( ! class_exists($class_name) && ! class_exists($class_name.'_ft'))
  515. {
  516. return FALSE;
  517. }
  518. }
  519. $_class_name = $class_name . (class_exists($class_name) ? '' : '_ft');
  520. $OBJ = new $_class_name();
  521. // is this a FieldFrame fieldtype?
  522. if ( ! isset($OBJ->_fieldframe)) return FALSE;
  523. $OBJ->_class_name = $file;
  524. $OBJ->_is_new = FALSE;
  525. $OBJ->_is_enabled = FALSE;
  526. $OBJ->_is_updated = FALSE;
  527. // settings
  528. $OBJ->site_settings = isset($OBJ->default_site_settings)
  529. ? $OBJ->default_site_settings
  530. : array();
  531. // info
  532. if ( ! isset($OBJ->info)) $OBJ->info = array();
  533. // requirements
  534. if ( ! isset($OBJ->_requires)) $OBJ->_requires = array();
  535. if (isset($OBJ->requires))
  536. {
  537. // PHP
  538. if (isset($OBJ->requires['php']) AND phpversion() < $OBJ->requires['php'])
  539. {
  540. if ($req_strict) return FALSE;
  541. else $OBJ->_requires['PHP'] = $OBJ->requires['php'];
  542. }
  543. // ExpressionEngine
  544. if (isset($OBJ->requires['ee']) AND APP_VER < $OBJ->requires['ee'])
  545. {
  546. if ($req_strict) return FALSE;
  547. else $OBJ->_requires['ExpressionEngine'] = $OBJ->requires['ee'];
  548. }
  549. // ExpressionEngine Build
  550. if (isset($OBJ->requires['ee_build']) AND APP_BUILD < $OBJ->requires['ee_build'])
  551. {
  552. if ($req_strict) return FALSE;
  553. else
  554. {
  555. $req = substr(strtolower($LANG->line('build')), 0, -1).' '.$OBJ->requires['ee_build'];
  556. if (isset($OBJ->_requires['ExpressionEngine'])) $OBJ->_requires['ExpressionEngine'] .= ' '.$req;
  557. else $OBJ->_requires['ExpressionEngine'] = $req;
  558. }
  559. }
  560. // FieldFrame
  561. if (isset($OBJ->requires['ff']) AND FF_VERSION < $OBJ->requires['ff'])
  562. {
  563. if ($req_strict) return FALSE;
  564. else $OBJ->_requires[FF_NAME] = $OBJ->requires['ff'];
  565. }
  566. // CP jQuery
  567. if (isset($OBJ->requires['cp_jquery']))
  568. {
  569. if ( ! isset($OBJ->requires['ext']))
  570. {
  571. $OBJ->requires['ext'] = array();
  572. }
  573. $OBJ->requires['ext'][] = array('class' => 'Cp_jquery', 'name' => 'CP jQuery', 'url' => 'http://www.ngenworks.com/software/ee/cp_jquery/', 'version' => $OBJ->requires['cp_jquery']);
  574. }
  575. // Extensions
  576. if (isset($OBJ->requires['ext']))
  577. {
  578. if ( ! isset($this->req_ext_versions))
  579. {
  580. $this->req_ext_versions = array();
  581. }
  582. foreach($OBJ->requires['ext'] as $ext)
  583. {
  584. if ( ! isset($this->req_ext_versions[$ext['class']]))
  585. {
  586. $ext_query = $DB->query('SELECT version FROM exp_extensions
  587. WHERE class="'.$ext['class'].'"'
  588. . (isset($ext['version']) ? ' AND version >= "'.$ext['version'].'"' : '')
  589. . ' AND enabled = "y"
  590. ORDER BY version DESC
  591. LIMIT 1');
  592. $this->req_ext_versions[$ext['class']] = $ext_query->row
  593. ? $ext_query->row['version']
  594. : '';
  595. }
  596. if ($this->req_ext_versions[$ext['class']] < $ext['version'])
  597. {
  598. if ($req_strict) return FALSE;
  599. else
  600. {
  601. $name = isset($ext['name']) ? $ext['name'] : ucfirst($ext['class']);
  602. if ($ext['url']) $name = '<a href="'.$ext['url'].'">'.$name.'</a>';
  603. $OBJ->_requires[$name] = $ext['version'];
  604. }
  605. }
  606. }
  607. }
  608. }
  609. if ( ! isset($OBJ->info['name'])) $OBJ->info['name'] = ucwords(str_replace('_', ' ', $class_name));
  610. if ( ! isset($OBJ->info['version'])) $OBJ->info['version'] = '';
  611. if ( ! isset($OBJ->info['desc'])) $OBJ->info['desc'] = '';
  612. if ( ! isset($OBJ->info['docs_url'])) $OBJ->info['docs_url'] = '';
  613. if ( ! isset($OBJ->info['author'])) $OBJ->info['author'] = '';
  614. if ( ! isset($OBJ->info['author_url'])) $OBJ->info['author_url'] = '';
  615. if ( ! isset($OBJ->info['versions_xml_url'])) $OBJ->info['versions_xml_url'] = '';
  616. if ( ! isset($OBJ->info['no_lang'])) $OBJ->info['no_lang'] = FALSE;
  617. if ( ! isset($OBJ->hooks)) $OBJ->hooks = array();
  618. if ( ! isset($OBJ->postpone_saves)) $OBJ->postpone_saves = FALSE;
  619. // do we already know about this fieldtype?
  620. if (is_string($ftype))
  621. {
  622. $query = $DB->query('SELECT * FROM exp_ff_fieldtypes WHERE class = "'.$file.'"');
  623. $ftype = $query->row;
  624. }
  625. if ($ftype)
  626. {
  627. $OBJ->_fieldtype_id = $ftype['fieldtype_id'];
  628. if ($ftype['enabled'] == 'y')
  629. {
  630. $OBJ->_is_enabled = TRUE;
  631. }
  632. if ($ftype['settings'])
  633. {
  634. if (is_array($ftype_settings = $this->_unserialize($ftype['settings'])))
  635. {
  636. $OBJ->site_settings = array_merge($OBJ->site_settings, $ftype_settings);
  637. }
  638. }
  639. // new version?
  640. if ($OBJ->info['version'] != $ftype['version'])
  641. {
  642. $OBJ->_is_updated = TRUE;
  643. // update exp_ff_fieldtypes
  644. $DB->query($DB->update_string('exp_ff_fieldtypes',
  645. array('version' => $OBJ->info['version']),
  646. 'fieldtype_id = "'.$ftype['fieldtype_id'].'"'));
  647. // update the hooks
  648. $this->_insert_ftype_hooks($OBJ);
  649. // call update()
  650. if (method_exists($OBJ, 'update'))
  651. {
  652. $OBJ->update($ftype['version']);
  653. }
  654. }
  655. }
  656. else
  657. {
  658. $OBJ->_is_new = TRUE;
  659. }
  660. return $OBJ;
  661. }
  662. /**
  663. * Insert Fieldtype Hooks
  664. *
  665. * @access private
  666. */
  667. function _insert_ftype_hooks($ftype)
  668. {
  669. global $DB, $FF;
  670. // remove any existing hooks from exp_ff_fieldtype_hooks
  671. $DB->query('DELETE FROM exp_ff_fieldtype_hooks
  672. WHERE class = "'.$ftype->_class_name.'"');
  673. // (re)insert the hooks
  674. if ($ftype->hooks)
  675. {
  676. foreach($ftype->hooks as $hook => $data)
  677. {
  678. if (is_string($data))
  679. {
  680. $hook = $data;
  681. $data = array();
  682. }
  683. // exp_ff_fieldtype_hooks
  684. $data = array_merge(array('method' => $hook, 'priority' => 10), $data, array('hook' => $hook, 'class' => $ftype->_class_name));
  685. $DB->query($DB->insert_string('exp_ff_fieldtype_hooks', $data));
  686. // exp_extensions
  687. $hooks_q = $DB->query('SELECT extension_id FROM exp_extensions WHERE class = "'.FF_CLASS.'" AND hook = "'.$hook.'" AND priority = "'.$data['priority'].'"');
  688. if ( ! $hooks_q->num_rows)
  689. {
  690. $ext_data = array('class' => FF_CLASS, 'method' => 'forward_hook:'.$hook.':'.$data['priority'], 'hook' => $hook, 'settings' => '', 'priority' => $data['priority'], 'version' => FF_VERSION, 'enabled' => 'y');
  691. $DB->query($DB->insert_string('exp_extensions', $ext_data));
  692. }
  693. }
  694. }
  695. // reset cached hooks array
  696. $this->_get_ftype_hooks(TRUE);
  697. }
  698. function _get_ftype_hooks($reset=FALSE)
  699. {
  700. global $DB;
  701. if ($reset OR ! isset($this->ftype_hooks))
  702. {
  703. $this->ftype_hooks = array();
  704. $hooks_q = $DB->query('SELECT * FROM exp_ff_fieldtype_hooks');
  705. foreach($hooks_q->result as $hook_r)
  706. {
  707. $this->ftype_hooks[$hook_r['hook']][$hook_r['priority']][$hook_r['class']] = $hook_r['method'];
  708. }
  709. }
  710. return $this->ftype_hooks;
  711. }
  712. /**
  713. * Group Inputs
  714. *
  715. * @param string $name_prefix The Fieldtype ID
  716. * @param string $settings The Fieldtype settings
  717. * @return string The modified settings
  718. * @access private
  719. */
  720. function _group_inputs($name_prefix, $settings)
  721. {
  722. return preg_replace('/(name=([\'\"]))([^\'"\[\]]+)([^\'"]*)(\2)/i', '$1'.$name_prefix.'[$3]$4$5', $settings);
  723. }
  724. /**
  725. * Group Fieldtype Inputs
  726. *
  727. * @param string $ftype_id The Fieldtype ID
  728. * @param string $settings The Fieldtype settings
  729. * @return string The modified settings
  730. * @access private
  731. */
  732. function _group_ftype_inputs($ftype_id, $settings)
  733. {
  734. return $this->_group_inputs('ftype['.$ftype_id.']', $settings);
  735. }
  736. /**
  737. * Activate Extension
  738. */
  739. function activate_extension($settings)
  740. {
  741. global $DB;
  742. // Delete old hooks
  743. $DB->query('DELETE FROM exp_extensions
  744. WHERE class = "'.FF_CLASS.'"');
  745. // Add new extensions
  746. $hook_tmpl = array(
  747. 'class' => FF_CLASS,
  748. 'settings' => $this->_serialize($settings),
  749. 'priority' => 10,
  750. 'version' => FF_VERSION,
  751. 'enabled' => 'y'
  752. );
  753. foreach($this->hooks as $hook => $data)
  754. {
  755. if (is_string($data))
  756. {
  757. $hook = $data;
  758. $data = array();
  759. }
  760. $data = array_merge($hook_tmpl, array('hook' => $hook, 'method' => $hook), $data);
  761. $DB->query($DB->insert_string('exp_extensions', $data));
  762. }
  763. // exp_ff_fieldtypes
  764. if ( ! $DB->table_exists('exp_ff_fieldtypes'))
  765. {
  766. $DB->query("CREATE TABLE exp_ff_fieldtypes (
  767. `fieldtype_id` int(10) unsigned NOT NULL auto_increment,
  768. `class` varchar(50) NOT NULL default '',
  769. `version` varchar(10) NOT NULL default '',
  770. `settings` text NOT NULL default '',
  771. `enabled` char(1) NOT NULL default 'n',
  772. PRIMARY KEY (`fieldtype_id`)
  773. )");
  774. }
  775. // exp_ff_fieldtype_hooks
  776. if ( ! $DB->table_exists('exp_ff_fieldtype_hooks'))
  777. {
  778. $DB->query("CREATE TABLE exp_ff_fieldtype_hooks (
  779. `hook_id` int(10) unsigned NOT NULL auto_increment,
  780. `class` varchar(50) NOT NULL default '',
  781. `hook` varchar(50) NOT NULL default '',
  782. `method` varchar(50) NOT NULL default '',
  783. `priority` int(2) NOT NULL DEFAULT '10',
  784. PRIMARY KEY (`hook_id`)
  785. )");
  786. }
  787. // exp_weblog_fields.ff_settings
  788. $query = $DB->query('SHOW COLUMNS FROM `'.$DB->prefix.'weblog_fields` LIKE "ff_settings"');
  789. if ( ! $query->num_rows)
  790. {
  791. $DB->query('ALTER TABLE `'.$DB->prefix.'weblog_fields` ADD COLUMN `ff_settings` text NOT NULL');
  792. }
  793. // reset all ftype hooks
  794. foreach($this->_get_ftypes() as $class_name => $ftype)
  795. {
  796. $this->_insert_ftype_hooks($ftype);
  797. }
  798. }
  799. /**
  800. * Settings Form
  801. *
  802. * @param array $current Current extension settings (not site-specific)
  803. * @see http://expressionengine.com/docs/development/extensions.html#settings
  804. */
  805. function settings_form()
  806. {
  807. global $DB, $DSP, $LANG, $IN, $SD, $PREFS;
  808. // Breadcrumbs
  809. $DSP->crumbline = TRUE;
  810. $DSP->title = $LANG->line('extension_settings');
  811. $DSP->crumb = $DSP->anchor(BASE.AMP.'C=admin'.AMP.'area=utilities', $LANG->line('utilities'))
  812. . $DSP->crumb_item($DSP->anchor(BASE.AMP.'C=admin'.AMP.'M=utilities'.AMP.'P=extensions_manager', $LANG->line('extensions_manager')))
  813. . $DSP->crumb_item(FF_NAME);
  814. $DSP->right_crumb($LANG->line('disable_extension'), BASE.AMP.'C=admin'.AMP.'M=utilities'.AMP.'P=toggle_extension_confirm'.AMP.'which=disable'.AMP.'name='.$IN->GBL('name'));
  815. // Donations button
  816. $DSP->body .= '<div class="donations">'
  817. . '<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=2181794" target="_blank">'
  818. . $LANG->line('donate')
  819. . '</a>'
  820. . '</div>';
  821. // open form
  822. $DSP->body .= '<h1>'.FF_NAME.' <small>'.FF_VERSION.'</small></h1>'
  823. . $DSP->form_open(
  824. array(
  825. 'action' => 'C=admin'.AMP.'M=utilities'.AMP.'P=save_extension_settings',
  826. 'name' => 'settings_subtext',
  827. 'id' => 'ffsettings'
  828. ),
  829. array(
  830. 'name' => strtolower(FF_CLASS)
  831. ));
  832. // initialize Fieldframe_SettingsDisplay
  833. $SD = new Fieldframe_SettingsDisplay();
  834. // import lang files
  835. $LANG->fetch_language_file('publish_ad');
  836. // fieldtypes folder
  837. $DSP->body .= $SD->block('fieldtypes_folder_title')
  838. . $SD->info_row('fieldtypes_folder_info')
  839. . $SD->row(array(
  840. $SD->label('fieldtypes_path_label', 'fieldtypes_path_subtext'),
  841. $SD->text('fieldtypes_path',
  842. (isset($PREFS->core_ini['ft_path']) ? $PREFS->core_ini['ft_path'] : $this->settings['fieldtypes_path']),
  843. array('extras' => (isset($PREFS->core_ini['ft_path']) ? ' disabled="disabled" ' : '')))
  844. ))
  845. . $SD->row(array(
  846. $SD->label('fieldtypes_url_label', 'fieldtypes_url_subtext'),
  847. $SD->text('fieldtypes_url',
  848. (isset($PREFS->core_ini['ft_url']) ? $PREFS->core_ini['ft_url'] : $this->settings['fieldtypes_url']),
  849. array('extras' => (isset($PREFS->core_ini['ft_url']) ? ' disabled="disabled" ' : '')))
  850. ))
  851. . $SD->block_c();
  852. // Check for Updates
  853. $lgau_query = $DB->query("SELECT class FROM exp_extensions
  854. WHERE class = 'Lg_addon_updater_ext' AND enabled = 'y' LIMIT 1");
  855. $lgau_enabled = $lgau_query->num_rows ? TRUE : FALSE;
  856. $DSP->body .= $SD->block('check_for_updates_title')
  857. . $SD->info_row('check_for_updates_info')
  858. . $SD->row(array(
  859. $SD->label('check_for_updates_label', 'check_for_updates_subtext'),
  860. $SD->radio_group('check_for_updates', (($this->settings['check_for_updates'] != 'n') ? 'y' : 'n'), array('y'=>'yes', 'n'=>'no'))
  861. ))
  862. . $SD->block_c();
  863. // Fieldtypes Manager
  864. $this->fieldtypes_manager(FALSE, $SD);
  865. // Close form
  866. $DSP->body .= $DSP->qdiv('itemWrapperTop', $DSP->input_submit())
  867. . $DSP->form_c();
  868. ob_start();
  869. ?>
  870. <style type="text/css" charset="utf-8">
  871. .donations { float:right; }
  872. .donations a { display:block; margin:-2px 10px 0 0; padding:5px 0 5px 67px; width:193px; height:15px; font-size:12px; line-height:15px; background:url(http://brandon-kelly.com/images/shared/donations.png) no-repeat 0 0; color:#000; font-weight:bold; }
  873. h1 { padding:7px 0; }
  874. </style>
  875. <?php
  876. $this->snippets['head'][] = ob_get_contents();
  877. ob_end_clean();
  878. }
  879. /**
  880. * Add Slash to URL/Path
  881. *
  882. * @param string $path The user-submitted path
  883. * @return string $path with a slash at the end
  884. * @access private
  885. */
  886. function _add_slash($path)
  887. {
  888. if (substr($path, -1) != '/')
  889. {
  890. $path .= '/';
  891. }
  892. return $path;
  893. }
  894. /**
  895. * Save Settings
  896. */
  897. function save_settings()
  898. {
  899. global $DB;
  900. // save new FF site settings
  901. $this->settings = array(
  902. 'fieldtypes_url' => ((isset($_POST['fieldtypes_url']) AND $_POST['fieldtypes_url']) ? $this->_add_slash($_POST['fieldtypes_url']) : ''),
  903. 'fieldtypes_path' => ((isset($_POST['fieldtypes_path']) AND $_POST['fieldtypes_path']) ? $this->_add_slash($_POST['fieldtypes_path']) : ''),
  904. 'check_for_updates' => ($_POST['check_for_updates'] != 'n' ? 'y' : 'n')
  905. );
  906. $DB->query($DB->update_string('exp_extensions', array('settings' => $this->_serialize($this->settings)), 'class = "'.FF_CLASS.'"'));
  907. // set the FT_PATH and FT_URL constants
  908. $this->_define_constants();
  909. // save Fieldtypes Manager data
  910. $this->save_fieldtypes_manager();
  911. }
  912. /**
  913. * Fieldtypes Manager
  914. */
  915. function fieldtypes_manager($standalone=TRUE, $SD=NULL)
  916. {
  917. global $DB, $DSP, $LANG, $IN, $PREFS, $SD;
  918. // are they allowed to access this?
  919. if (! $DSP->allowed_group('can_admin_utilities'))
  920. {
  921. return $DSP->no_access_message();
  922. }
  923. if ( ! $SD)
  924. {
  925. // initialize Fieldframe_SettingsDisplay
  926. $SD = new Fieldframe_SettingsDisplay();
  927. }
  928. if ($standalone)
  929. {
  930. // save submitted settings
  931. if ($this->save_fieldtypes_manager())
  932. {
  933. $DSP->body .= $DSP->qdiv('box', $DSP->qdiv('success', $LANG->line('settings_update')));
  934. }
  935. // load language file
  936. $LANG->fetch_language_file('fieldframe');
  937. // Breadcrumbs
  938. $DSP->crumbline = TRUE;
  939. $DSP->title = $LANG->line('fieldtypes_manager');
  940. $DSP->crumb = $DSP->anchor(BASE.AMP.'C=admin'.AMP.'area=utilities', $LANG->line('utilities'))
  941. . $DSP->crumb_item($LANG->line('fieldtypes_manager'));
  942. // open form
  943. $DSP->body .= $DSP->form_open(
  944. array(
  945. 'action' => 'C=admin'.AMP.'M=utilities'.AMP.'P=fieldtypes_manager',
  946. 'name' => 'settings_subtext',
  947. 'id' => 'ffsettings'
  948. ),
  949. array(
  950. 'name' => strtolower(FF_CLASS)
  951. ));
  952. }
  953. // fieldtype settings
  954. $DSP->body .= $SD->block('fieldtypes_manager', 5);
  955. // initialize fieldtypes
  956. if ($ftypes = $this->_get_all_installed_ftypes())
  957. {
  958. // add the headers
  959. $DSP->body .= $SD->heading_row(array(
  960. $LANG->line('fieldtype'),
  961. $LANG->line('fieldtype_enabled'),
  962. $LANG->line('settings'),
  963. $LANG->line('documentation')
  964. ));
  965. $row_ids = array();
  966. foreach($ftypes as $class_name => $ftype)
  967. {
  968. $row_id = 'ft_'.$ftype->_class_name;
  969. $row_ids[] = '"'.$row_id.'"';
  970. if (method_exists($ftype, 'display_site_settings'))
  971. {
  972. if ( ! $ftype->info['no_lang']) $LANG->fetch_language_file($class_name);
  973. $site_settings = $ftype->display_site_settings();
  974. }
  975. else
  976. {
  977. $site_settings = FALSE;
  978. }
  979. $DSP->body .= $SD->row(array(
  980. $SD->label($ftype->info['name'].NBS.$DSP->qspan('xhtmlWrapperLight defaultSmall', $ftype->info['version']), $ftype->info['desc']),
  981. ($ftype->_requires
  982. ? '--'
  983. : $SD->radio_group('ftypes['.$class_name.'][enabled]', ($ftype->_is_enabled ? 'y' : 'n'), array('y'=>'yes', 'n'=>'no'))),
  984. ($site_settings
  985. ? '<a class="toggle show" id="'.$row_id.'_show" href="#ft_'.$class_name.'_settings"><img src="'.$PREFS->ini('theme_folder_url', 1).'cp_global_images/expand.gif" border="0">  '.$LANG->line('show').'</a>'
  986. . '<a class="toggle hide" id="'.$row_id.'_hide"><img src="'.$PREFS->ini('theme_folder_url', 1).'cp_global_images/collapse.gif" border="0">  '.$LANG->line('hide').'</a>'
  987. : '--'),
  988. ($ftype->info['docs_url'] ? '<a href="'.stripslashes($ftype->info['docs_url']).'">'.$LANG->line('documentation').'</a>' : '--')
  989. ), NULL, array('id' => $row_id));
  990. if ($ftype->_requires)
  991. {
  992. $data = '<p>'.$ftype->info['name'].' '.$LANG->line('requires').':</p>'
  993. . '<ul>';
  994. foreach($ftype->_requires as $dependency => $version)
  995. {
  996. $data .= '<li class="default">'.$dependency.' '.$version.' '.$LANG->line('or_later').'</li>';
  997. }
  998. $data .= '</ul>';
  999. $DSP->body .= $SD->row(array('', $data), $SD->row_class);
  1000. }
  1001. else if ($site_settings)
  1002. {
  1003. $data = '<div class="ftsettings">'
  1004. . $this->_group_ftype_inputs($ftype->_class_name, $site_settings)
  1005. . $DSP->div_c();
  1006. $DSP->body .= $SD->row(array($data), '', array('id' => $row_id.'_settings', 'style' => 'display:none;'));
  1007. }
  1008. }
  1009. ob_start();
  1010. ?>
  1011. <script type="text/javascript" charset="utf-8">
  1012. ;var urlParts = document.location.href.split('#'),
  1013. anchor = urlParts[1];
  1014. function ffEnable(ft) {
  1015. ft.show.className = "toggle show";
  1016. ft.show.onclick = function() {
  1017. ft.show.style.display = "none";
  1018. ft.hide.style.display = "block";
  1019. ft.settings.style.display = "table-row";
  1020. };
  1021. ft.hide.onclick = function() {
  1022. ft.show.style.display = "block";
  1023. ft.hide.style.display = "none";
  1024. ft.settings.style.display = "none";
  1025. };
  1026. }
  1027. function ffDisable(ft) {
  1028. ft.show.className = "toggle show disabled";
  1029. ft.show.onclick = null;
  1030. ft.show.style.display = "block";
  1031. ft.hide.style.display = "none";
  1032. ft.settings.style.display = "none";
  1033. }
  1034. function ffInitRow(rowId) {
  1035. var ft = {
  1036. tr: document.getElementById(rowId),
  1037. show: document.getElementById(rowId+"_show"),
  1038. hide: document.getElementById(rowId+"_hide"),
  1039. settings: document.getElementById(rowId+"_settings")
  1040. };
  1041. if (ft.settings) {
  1042. ft.toggles = ft.tr.getElementsByTagName("input");
  1043. ft.toggles[0].onchange = function() { ffEnable(ft); };
  1044. ft.toggles[1].onchange = function() { ffDisable(ft); };
  1045. if (ft.toggles[0].checked) ffEnable(ft);
  1046. else ffDisable(ft);
  1047. if (anchor == rowId+'_settings') {
  1048. ft.show.onclick();
  1049. }
  1050. }
  1051. }
  1052. var ffRowIds = [<?php echo implode(',', $row_ids) ?>];
  1053. for (var i = 0; i < ffRowIds.length; i++) {
  1054. ffInitRow(ffRowIds[i]);
  1055. }
  1056. </script>
  1057. <?php
  1058. $this->snippets['body'][] = ob_get_contents();
  1059. ob_end_clean();
  1060. }
  1061. else if ( ! defined('FT_PATH'))
  1062. {
  1063. $DSP->body .= $SD->info_row('no_fieldtypes_path');
  1064. }
  1065. else if (in_array('bad_ft_path', $this->errors))
  1066. {
  1067. $DSP->body .= $SD->info_row('bad_fieldtypes_path');
  1068. }
  1069. else
  1070. {
  1071. $DSP->body .= $SD->info_row('no_fieldtypes');
  1072. }
  1073. $DSP->body .= $SD->block_c();
  1074. if ($standalone)
  1075. {
  1076. // Close form
  1077. $DSP->body .= $DSP->qdiv('itemWrapperTop', $DSP->input_submit($LANG->line('apply')))
  1078. . $DSP->form_c();
  1079. }
  1080. ob_start();
  1081. ?>
  1082. <style type="text/css" charset="utf-8">
  1083. #ffsettings a.toggle { display:block; cursor:pointer; }
  1084. #ffsettings a.toggle.hide { display:none; }
  1085. #ffsettings a.toggle.disabled { color:#000; opacity:0.4; cursor:default; }
  1086. #ffsettings .ftsettings { margin:-3px -1px -1px; }
  1087. #ffsettings .ftsettings, #ffsettings .ftsettings .tableCellOne, #ffsettings .ftsettings .tableCellTwo, #ffsettings .ftsettings .box { background:#262e33; color:#999; }
  1088. #ffsettings .ftsettings .box { margin: 0; padding: 10px 15px; border: none; border-top: 1px solid #283036; background: -webkit-gradient(linear, 0 0, 0 100%, from(#262e33), to(#22292e)); }
  1089. #ffsettings .ftsettings table tr td .box { margin: -1px -8px; }
  1090. #ffsettings .ftsettings a, #ffsettings .ftsettings p, #ffsettings .ftsettings .subtext { color: #999; }
  1091. #ffsettings .ftsettings input.input, #ffsettings .ftsettings textarea { background:#fff; color:#333; }
  1092. #ffsettings .ftsettings table { border:none; }
  1093. #ffsettings .ftsettings table tr td { border-top:1px solid #1d2326; padding-left:8px; padding-right:8px; }
  1094. #ffsettings .ftsettings table tr td.tableHeading { color:#ddd; background:#232a2e; }
  1095. #ffsettings .ftsettings .defaultBold { color:#ccc; }
  1096. #ffsettings .ftsettings .tableCellOne, #ffsettings .ftsettings .tableCellOneBold, #ffsettings .ftsettings .tableCellTwo, #ffsettings .ftsettings .tableCellTwoBold { border-bottom:none; }
  1097. </style>
  1098. <?php
  1099. $this->snippets['head'][] = ob_get_contents();
  1100. ob_end_clean();
  1101. }
  1102. /**
  1103. * Save Fieldtypes Manager Settings
  1104. */
  1105. function save_fieldtypes_manager()
  1106. {
  1107. global $DB;
  1108. // fieldtype settings
  1109. if (isset($_POST['ftypes']))
  1110. {
  1111. foreach($_POST['ftypes'] as $file => $ftype_post)
  1112. {
  1113. // Initialize
  1114. if (($ftype = $this->_init_ftype($file)) !== FALSE)
  1115. {
  1116. $enabled = ($ftype_post['enabled'] == 'y');
  1117. // skip if new and not enabled yet
  1118. if ( ! $enabled AND $ftype->_is_new) continue;
  1119. // insert a new row if it's new
  1120. if ($enabled AND $ftype->_is_new)
  1121. {
  1122. $DB->query($DB->insert_string('exp_ff_fieldtypes', array(
  1123. 'class' => $file,
  1124. 'version' => $ftype->info['version']
  1125. )));
  1126. // get the fieldtype_id
  1127. $query = $DB->query('SELECT fieldtype_id FROM exp_ff_fieldtypes WHERE class = "'.$file.'"');
  1128. $ftype->_fieldtype_id = $query->row['fieldtype_id'];
  1129. // insert hooks
  1130. $this->_insert_ftype_hooks($ftype);
  1131. // call update()
  1132. if (method_exists($ftype, 'update'))
  1133. {
  1134. $ftype->update(FALSE);
  1135. }
  1136. }
  1137. $data = array(
  1138. 'enabled' => ($enabled ? 'y' : 'n')
  1139. );
  1140. if ($enabled)
  1141. {
  1142. $settings = (isset($_POST['ftype']) AND isset($_POST['ftype'][$ftype->_class_name]))
  1143. ? $_POST['ftype'][$ftype->_class_name]
  1144. : array();
  1145. // let the fieldtype do what it wants with them
  1146. if (method_exists($ftype, 'save_site_settings'))
  1147. {
  1148. $settings = $ftype->save_site_settings($settings);
  1149. if ( ! is_array($settings)) $settings = array();
  1150. }
  1151. $data['settings'] = $this->_serialize($settings);
  1152. }
  1153. $DB->query($DB->update_string('exp_ff_fieldtypes', $data, 'fieldtype_id = "'.$ftype->_fieldtype_id.'"'));
  1154. }
  1155. }
  1156. return TRUE;
  1157. }
  1158. return FALSE;
  1159. }
  1160. /**
  1161. * Get Last Call
  1162. *
  1163. * @param mixed $param Parameter sent by extension hook
  1164. * @return mixed Return value of last extension call if any, or $param
  1165. * @access private
  1166. */
  1167. function get_last_call($param=FALSE)
  1168. {
  1169. global $EXT;
  1170. return isset($this->_last_call)
  1171. ? $this->_last_call
  1172. : ($EXT->last_call !== FALSE ? $EXT->last_call : $param);
  1173. }
  1174. /**
  1175. * Forward hook to fieldtype
  1176. */
  1177. function forward_hook($hook, $priority, $args=array())
  1178. {
  1179. $ftype_hooks = $this->_get_ftype_hooks();
  1180. if (isset($ftype_hooks[$hook]) AND isset($ftype_hooks[$hook][$priority]))
  1181. {
  1182. $ftypes = $this->_get_ftypes();
  1183. foreach ($ftype_hooks[$hook][$priority] as $class_name => $method)
  1184. {
  1185. if (isset($ftypes[$class_name]) AND method_exists($ftypes[$class_name], $method))
  1186. {
  1187. $this->_last_call = call_user_func_array(array(&$ftypes[$class_name], $method), $args);
  1188. }
  1189. }
  1190. }
  1191. if (isset($this->_last_call))
  1192. {
  1193. $r = $this->_last_call;
  1194. unset($this->_last_call);
  1195. }
  1196. else
  1197. {
  1198. $r = $this->get_last_call();
  1199. }
  1200. return $r;
  1201. }
  1202. function forward_ff_hook($hook, $args=array(), $r=TRUE)
  1203. {
  1204. $this->_last_call = $r;
  1205. // ------------ These braces brought to you by Leevi Graham ------------
  1206. // ↓ ↓
  1207. $priority = (isset($this->hooks[$hook]) AND isset($this->hooks[$hook]['priority']))
  1208. ? $this->hooks[$hook]['priority']
  1209. : 10;
  1210. return $this->forward_hook($hook, $priority, $args);
  1211. }
  1212. /**
  1213. * Get Line
  1214. *
  1215. * @param string $line unlocalized string or the name of a $LANG line
  1216. * @return string Localized string
  1217. */
  1218. function get_line($line)
  1219. {
  1220. global $LANG;
  1221. $loc_line = $LANG->line($line);
  1222. return $loc_line ? $loc_line : $line;
  1223. }
  1224. /**
  1225. * Sessions Start
  1226. *
  1227. * - Reset any session class variable
  1228. * - Override the whole session check
  1229. * - Modify default/guest settings
  1230. *
  1231. * @param object $this The current instantiated Session class with all of its variables and functions,
  1232. * use a reference in your functions to modify.
  1233. * @see http://expressionengine.com/developers/extension_hooks/sessions_start/
  1234. */
  1235. function sessions_start($sess)
  1236. {
  1237. global $IN;
  1238. // are we saving a field?
  1239. if($IN->GBL('M', 'GET') == 'blog_admin' AND $IN->GBL('P', 'GET') == 'update_weblog_fields' AND isset($_POST['field_type']))
  1240. {
  1241. $this->publish_admin_edit_field_save();
  1242. }
  1243. $args = func_get_args();
  1244. return $this->forward_ff_hook('sessions_start', $args);
  1245. }
  1246. /**
  1247. * Publish Admin - Edit Field Form - Fieldtype Menu
  1248. *
  1249. * Allows modifying or adding onto Custom Weblog Fieldtype Pulldown
  1250. *
  1251. * @param array $data The data about this field from the database
  1252. * @param string $typemenu The contents of the type menu
  1253. * @return string The modified $typemenu
  1254. * @see http://expressionengine.com/developers/extension_hooks/publish_admin_edit_field_type_pulldown/
  1255. */
  1256. function publish_admin_edit_field_type_pulldown($data, $typemenu)
  1257. {
  1258. global $DSP;
  1259. $r = $this->get_last_call($typemenu);
  1260. foreach($this->_get_ftypes() as $class_name => $ftype)
  1261. {
  1262. // only list normal fieldtypes
  1263. if (method_exists($ftype, 'display_field'))
  1264. {
  1265. $field_type = 'ftype_id_'.$ftype->_fieldtype_id;
  1266. $r .= $DSP->input_select_option($field_type, $ftype->info['name'], ($data['field_type'] == $field_type ? 1 : 0));
  1267. }
  1268. }
  1269. $args = func_get_args();
  1270. return $this->forward_ff_hook('publish_admin_edit_field_type_pulldown', $args, $r);
  1271. }
  1272. /**
  1273. * Publish Admin - Edit Field Form - Javascript
  1274. *
  1275. * Allows modifying or adding onto Custom Weblog Field JS
  1276. *
  1277. * @param array $data The data about this field from the database
  1278. * @param string $js Currently existing javascript
  1279. * @return string The modified $js
  1280. * @see http://expressionengine.com/developers/extension_hooks/publish_admin_edit_field_js/
  1281. */
  1282. function publish_admin_edit_field_js($data, $js)
  1283. {
  1284. global $LANG, $REGX;
  1285. // Fetch the FF lang file
  1286. $LANG->fetch_language_file('fieldframe');
  1287. // Prepare fieldtypes for following Publish Admin hooks
  1288. $field_settings_tmpl = array(
  1289. 'cell1' => '',
  1290. 'cell2' => '',
  1291. 'rows' => array(),
  1292. 'formatting_available' => FALSE,
  1293. 'direction_available' => FALSE
  1294. );
  1295. $formatting_available = array();
  1296. $direction_available = array();
  1297. $prev_ftype_id = '';
  1298. $this->data = $data;
  1299. foreach($this->_get_ftypes() as $class_name => $ftype)
  1300. {
  1301. $ftype_id = 'ftype_id_'.$ftype->_fieldtype_id;
  1302. $selected = ($ftype_id == $this->data['field_type']) ? TRUE : FALSE;
  1303. if (method_exists($ftype, 'display_field_settings'))
  1304. {
  1305. // Load the language file
  1306. if ( ! $ftype->info['no_lang']) $LANG->fetch_language_file($class_name);
  1307. $field_settings = array_merge(
  1308. (isset($ftype->default_field_settings) ? $ftype->default_field_settings : array()),
  1309. ($selected ? $this->_unserialize($this->data['ff_settings']) : array())
  1310. );
  1311. $ftype->_field_settings = array_merge($field_settings_tmpl, $ftype->display_field_settings($field_settings));
  1312. }
  1313. else
  1314. {
  1315. $ftype->_field_settings = $field_settings_tmpl;
  1316. }
  1317. if ($ftype->_field_settings['formatting_available']) $formatting_available[] = $ftype->_fieldtype_id;
  1318. if ($ftype->_field_settings['direction_available']) $direction_available[] = $ftype->_fieldtype_id;
  1319. if ($selected) $prev_ftype_id = $ftype_id;
  1320. }
  1321. unset($this->data);
  1322. // Add the JS
  1323. ob_start();
  1324. ?>
  1325. var prev_ftype_id = '<?php echo $prev_ftype_id ?>';
  1326. $1
  1327. if (prev_ftype_id)
  1328. {
  1329. var c=1, r=1;
  1330. while(cell = document.getElementById(prev_ftype_id+'_cell'+c))
  1331. {
  1332. cell.style.display = 'none';
  1333. c++;
  1334. }
  1335. while(row = document.getElementById(prev_ftype_id+'_row'+r))
  1336. {
  1337. row.style.display = 'none';
  1338. r++;
  1339. }
  1340. }
  1341. if (id.match(/^ftype_id_\d+$/))
  1342. {
  1343. var c=1, r=1;
  1344. // show cells
  1345. while(cell = document.getElementById(id+'_cell'+c))
  1346. {
  1347. //var showDiv = document.getElementById(id+'_cell'+c);
  1348. var divs = cell.parentNode.childNodes;
  1349. for(var i=0; i < divs.length; i++)
  1350. {
  1351. var div = divs[i];
  1352. if ( ! (div.nodeType == 1 && div.id)) continue;
  1353. div.style.display = (div == cell) ? 'block' : 'none';
  1354. }
  1355. c++;
  1356. }
  1357. // show rows
  1358. while(row = document.getElementById(id+'_row'+r))
  1359. {
  1360. row.style.display = 'table-row';
  1361. r++;
  1362. }
  1363. // show/hide formatting
  1364. if ([<?php echo implode(',', $formatting_available) ?>].indexOf(parseInt(id.substring(9))) != -1)
  1365. {
  1366. document.getElementById('formatting_block').style.display = 'block';
  1367. document.getElementById('formatting_unavailable').style.display = 'none';
  1368. }
  1369. else
  1370. {
  1371. document.getElementById('formatting_block').style.display = 'none';
  1372. document.getElementById('formatting_unavailable').style.display = 'block';
  1373. }
  1374. // show/hide direction
  1375. if ([<?php echo implode(',', $direction_available) ?>].indexOf(parseInt(id.substring(9))) != -1)
  1376. {
  1377. document.getElementById('direction_available').style.display = 'block';
  1378. document.getElementById('direction_unavailable').style.display = 'none';
  1379. }
  1380. else
  1381. {
  1382. document.getElementById('direction_available').style.display = 'none';
  1383. document.getElementById('direction_unavailable').style.display = 'block';
  1384. }
  1385. prev_ftype_id = id;
  1386. }
  1387. <?php
  1388. $r = $this->get_last_call($js);
  1389. $r = preg_replace('/(function\s+showhide_element\(\s*id\s*\)\s*{)/is', ob_get_contents(), $r);
  1390. ob_end_clean();
  1391. $args = func_get_args();
  1392. return $this->forward_ff_hook('publish_admin_edit_field_js', $args, $r);
  1393. }
  1394. /**
  1395. * Publish Admin - Edit Field Form - Cell
  1396. *
  1397. * @param array $data The data about this field from the database
  1398. * @param string $cell The contents of the cell
  1399. * @param string $index The cell index
  1400. * @return string The modified $cell
  1401. * @access private
  1402. */
  1403. function _publish_admin_edit_field_type_cell($data, $cell, $index)
  1404. {
  1405. $r = $this->get_last_call($cell);
  1406. foreach($this->_get_ftypes() as $class_name => $ftype)
  1407. {
  1408. $ftype_id = 'ftype_id_'.$ftype->_fieldtype_id;
  1409. $selected = ($data['field_type'] == $ftype_id);
  1410. $field_settings = $this->_group_ftype_inputs($ftype_id, $ftype->_field_settings['cell'.$index]);
  1411. $r .= '<div id="'.$ftype_id.'_cell'.$index.'" style="margin-top:5px; display:'.($selected ? 'block' : 'none').';">'
  1412. . $field_settings
  1413. . '</div>';
  1414. }
  1415. return $r;
  1416. }
  1417. /**
  1418. * Publish Admin - Edit Field Form - Cell One
  1419. *
  1420. * Allows modifying or adding onto Custom Weblog Fieldtype - First Table Cell
  1421. *
  1422. * @param array $data The data about this field from the database
  1423. * @param string $cell The contents of the cell
  1424. * @return string The modified $cell
  1425. * @see http://expressionengine.com/developers/extension_hooks/publish_admin_edit_field_type_cellone/
  1426. */
  1427. function publish_admin_edit_field_type_cellone($data, $cell)
  1428. {
  1429. global $DSP;
  1430. $r = $this->_publish_admin_edit_field_type_cell($data, $cell, '1');
  1431. // formatting
  1432. foreach($this->_get_ftypes() as $class_name => $ftype)
  1433. {
  1434. $ftype_id = 'ftype_id_'.$ftype->_fieldtype_id;
  1435. $r .= $DSP->input_hidden('ftype['.$ftype_id.'][formatting_available]', ($ftype->_field_settings['formatting_available'] ? 'y' : 'n'));
  1436. }
  1437. $args = func_get_args();
  1438. return $this->forward_ff_hook('publish_admin_edit_field_type_cellone', $args, $r);
  1439. }
  1440. /**
  1441. * Publish Admin - Edit Field Form - Cell Two
  1442. *
  1443. * Allows modifying or adding onto Custom Weblog Fieldtype - Second Table Cell
  1444. *
  1445. * @param array $data The data about this field from the database
  1446. * @param string $cell The contents of the cell
  1447. * @return string The modified $cell
  1448. * @see http://expressionengine.com/developers/extension_hooks/publish_admin_edit_field_type_celltwo/
  1449. */
  1450. function publish_admin_edit_field_type_celltwo($data, $cell)
  1451. {
  1452. $r = $this->_publish_admin_edit_field_type_cell($data, $cell, '2');
  1453. $args = func_get_args();
  1454. return $this->forward_ff_hook('publish_admin_edit_field_type_celltwo', $args, $r);
  1455. }
  1456. /**
  1457. * Publish Admin - Edit Field Form - Format
  1458. *
  1459. * Allows modifying or adding onto Default Text Formatting Cell
  1460. *
  1461. * @param array $data The data about this field from the database
  1462. * @param string $y The current contents of the format cell
  1463. * @return string The modified $y
  1464. * @see http://expressionengine.com/developers/extension_hooks/publish_admin_edit_field_format/
  1465. */
  1466. function publish_admin_edit_field_format($data, $y)
  1467. {
  1468. $y = $this->get_last_call($y);
  1469. $args = func_get_args();
  1470. return $this->forward_ff_hook('publish_admin_edit_field_format', $args, $y);
  1471. }
  1472. /**
  1473. * Publish Admin - Edit Field Form - Extra Row
  1474. *
  1475. * Allows modifying or adding onto the Custom Field settings table
  1476. *
  1477. * @param array $data The data about this field from the database
  1478. * @param string $r The current contents of the page
  1479. * @return string The modified $r
  1480. * @see http://expressionengine.com/developers/extension_hooks/publish_admin_edit_field_extra_row/
  1481. */
  1482. function publish_admin_edit_field_extra_row($data, $r)
  1483. {
  1484. global $DSP, $LANG;
  1485. $r = $this->get_last_call($r);
  1486. if ($ftypes = $this->_get_ftypes())
  1487. {
  1488. $rows = '';
  1489. foreach($ftypes as $class_name => $ftype)
  1490. {
  1491. $ftype_id = 'ftype_id_'.$ftype->_fieldtype_id;
  1492. $selected = ($data['field_type'] == $ftype_id);
  1493. foreach($ftype->_field_settings['rows'] as $index => $row)
  1494. {
  1495. $class = $index % 2 ? 'tableCellOne' : 'tableCellTwo';
  1496. $rows .= '<tr id="'.$ftype_id.'_row'.($index+1).'"' . ($selected ? '' : ' style="display:none;"') . '>'
  1497. . '<td class="'.$class.'"'.(isset($row[1]) ? '' : ' colspan="2"').'>'
  1498. . $this->_group_ftype_inputs($ftype_id, $row[0])
  1499. . $DSP->td_c()
  1500. . (isset($row[1])
  1501. ? $DSP->td($class)
  1502. . $this->_group_ftype_inputs($ftype_id, $row[1])
  1503. . $DSP->td_c()
  1504. . $DSP->tr_c()
  1505. : '');
  1506. }
  1507. if ($selected)
  1508. {
  1509. // show/hide formatting
  1510. if ($ftype->_field_settings['formatting_available'])
  1511. {
  1512. $formatting_search = 'none';
  1513. $formatting_replace = 'block';
  1514. }
  1515. else
  1516. {
  1517. $formatting_search = 'block';
  1518. $formatting_replace = 'none';
  1519. }
  1520. $r = preg_replace('/(\sid\s*=\s*([\'\"])formatting_block\2.*display\s*:\s*)'.$formatting_search.'(\s*;)/isU', '$1'.$formatting_replace.'$3', $r);
  1521. $r = preg_replace('/(\sid\s*=\s*([\'\"])formatting_unavailable\2.*display\s*:\s*)'.$formatting_replace.'(\s*;)/isU', '$1'.$formatting_search.'$3', $r);
  1522. // show/hide direction
  1523. if ($ftype->_field_settings['direction_available'])
  1524. {
  1525. $direction_search = 'none';
  1526. $direction_replace = 'block';
  1527. }
  1528. else
  1529. {
  1530. $direction_search = 'block';
  1531. $direction_replace = 'none';
  1532. }
  1533. $r = preg_replace('/(\sid\s*=\s*([\'\"])direction_available\2.*display\s*:\s*)'.$direction_search.'(\s*;)/isU', '$1'.$direction_replace.'$3', $r);
  1534. $r = preg_replace('/(\sid\s*=\s*([\'\"])direction_unavailable\2.*display\s*:\s*)'.$direction_replace.'(\s*;)/isU', '$1'.$direction_search.'$3', $r);
  1535. }
  1536. }
  1537. $r = preg_replace('/(<tr>\s*<td[^>]*>\s*<div[^>]*>\s*'.$LANG->line('deft_field_formatting').'\s*<\/div>)/is', $rows.'$1', $r);
  1538. }
  1539. $args = func_get_args();
  1540. return $this->forward_ff_hook('publish_admin_edit_field_extra_row', $args, $r);
  1541. }
  1542. /**
  1543. * Publish Admin - Edit Field Form - Save Field
  1544. *
  1545. * Made-up hook called by sessions_start
  1546. * when $_POST['field_type'] is set
  1547. */
  1548. function publish_admin_edit_field_save()
  1549. {
  1550. global $DB;
  1551. // is this a FF fieldtype?
  1552. if (preg_match('/^ftype_id_(\d+)$/', $_POST['field_type'], $matches))
  1553. {
  1554. if (isset($matches[1]))
  1555. {
  1556. $ftype_id = $matches[1];
  1557. $settings = (isset($_POST['ftype']) AND isset($_POST['ftype'][$_POST['field_type']]))
  1558. ? $_POST['ftype'][$_POST['field_type']]
  1559. : array();
  1560. // formatting
  1561. if (isset($settings['formatting_available']))
  1562. {
  1563. if ($settings['formatting_available'] == 'n')
  1564. {
  1565. $_POST['field_fmt'] = 'none';
  1566. $_POST['field_show_fmt'] = 'n';
  1567. }
  1568. unset($settings['formatting_available']);
  1569. }
  1570. // initialize the fieldtype
  1571. $query = $DB->query('SELECT * FROM exp_ff_fieldtypes WHERE fieldtype_id = "'.$ftype_id.'"');
  1572. if ($query->row)
  1573. {
  1574. // let the fieldtype modify the settings
  1575. if (($ftype = $this->_init_ftype($query->row)) !== FALSE)
  1576. {
  1577. if (method_exists($ftype, 'save_field_settings'))
  1578. {
  1579. $settings = $ftype->save_field_settings($settings);
  1580. if ( ! is_array($settings)) $settings = array();
  1581. }
  1582. }
  1583. }
  1584. // save settings as a post var
  1585. $_POST['ff_settings'] = $this->_serialize($settings);
  1586. }
  1587. }
  1588. // unset extra FF post vars
  1589. foreach($_POST as $key => $value)
  1590. {
  1591. if (substr($key, 0, 5) == 'ftype')
  1592. {
  1593. unset($_POST[$key]);
  1594. }
  1595. }
  1596. }
  1597. /**
  1598. * Display - Show Full Control Panel - Start
  1599. *
  1600. * - Rewrite CP's HTML
  1601. * - Find/Replace stuff, etc.
  1602. *
  1603. * @param string $end The content of the admin page to be outputted
  1604. * @return string The modified $out
  1605. * @see http://expressionengine.com/developers/extension_hooks/show_full_control_panel_end/
  1606. */
  1607. function show_full_control_panel_start($out = '')
  1608. {
  1609. global $IN, $DB, $REGX, $DSP;
  1610. $out = $this->get_last_call($out);
  1611. // are we displaying the custom field list?
  1612. if ($IN->GBL('C', 'GET') == 'admin' AND $IN->GBL('M', 'GET') == 'utilities' AND $IN->GBL('P', 'GET') == 'fieldtypes_manager' AND defined('FT_PATH'))
  1613. {
  1614. $this->fieldtypes_manager();
  1615. }
  1616. $args = func_get_args();
  1617. return $this->forward_ff_hook('show_full_control_panel_start', $args, $out);
  1618. }
  1619. /**
  1620. * Display - Show Full Control Panel - End
  1621. *
  1622. * - Rewrite CP's HTML
  1623. * - Find/Replace stuff, etc.
  1624. *
  1625. * @param string $end The content of the admin page to be outputted
  1626. * @return string The modified $out
  1627. * @see http://expressionengine.com/developers/extension_hooks/show_full_control_panel_end/
  1628. */
  1629. function show_full_control_panel_end($out = '')
  1630. {
  1631. global $IN, $DB, $REGX, $DSP, $LANG;
  1632. $out = $this->get_last_call($out);
  1633. // are we displaying the custom field list?
  1634. if ($IN->GBL('M', 'GET') == 'blog_admin' AND in_array($IN->GBL('P', 'GET'), array('field_editor', 'update_weblog_fields', 'delete_field', 'update_field_order')))
  1635. {
  1636. // get the FF fieldtypes
  1637. foreach($this->_get_fields() as $field_id => $field)
  1638. {
  1639. // add fieldtype name to this field
  1640. $out = preg_replace("/(C=admin&amp;M=blog_admin&amp;P=edit_field&amp;field_id={$field_id}[\'\"].*?<\/td>.*?<td.*?>.*?<\/td>.*?)<\/td>/is",
  1641. '$1'.$REGX->form_prep($field['ftype']->info['name']).'</td>', $out);
  1642. }
  1643. }
  1644. // is this the main admin page?
  1645. else if ($IN->GBL('C', 'GET') == 'admin' AND ! ($IN->GBL('M', 'GET') AND $IN->GBL('P', 'GET')))
  1646. {
  1647. // are they allowed to access the fieldtypes manager?
  1648. if ($DSP->allowed_group('can_admin_utilities'))
  1649. {
  1650. $LANG->fetch_language_file('fieldframe');
  1651. $out = preg_replace('/(<li><a href=.+C=admin&amp;M=utilities&amp;P=extensions_manager.+<\/a><\/li>)/',
  1652. "$1\n<li>".$DSP->anchor(BASE.AMP.'C=admin'.AMP.'M=utilities'.AMP.'P=fieldtypes_manager', $LANG->line('fieldtypes_manager')).'</li>', $out, 1);
  1653. }
  1654. }
  1655. foreach($this->snippets as $placement => $snippets)
  1656. {
  1657. $placement = '</'.$placement.'>';
  1658. foreach(array_unique($snippets) as $snippet)
  1659. {
  1660. $out = str_replace($placement, NL.$snippet.NL.$placement, $out);
  1661. }
  1662. }
  1663. $args = func_get_args();
  1664. return $this->forward_ff_hook('show_full_control_panel_end', $args, $out);
  1665. }
  1666. /**
  1667. * Publish Form - Unique Field
  1668. *
  1669. * Allows adding of unique custom fields via extensions
  1670. *
  1671. * @param array $row Parameters for the field from the database
  1672. * @param array $field_data If entry is not new, this will have field's current value
  1673. * @return string The field's HTML
  1674. * @see http://expressionengine.com/developers/extension_hooks/publish_form_field_unique/
  1675. */
  1676. function publish_form_field_unique($row, $field_data)
  1677. {
  1678. global $REGX, $DSP;
  1679. // return if this isn't a FieldFrame fieldtype
  1680. if (substr($row['field_type'], 0, 9) != 'ftype_id_')
  1681. {
  1682. return $this->get_last_call();
  1683. }
  1684. $field_name = 'field_id_'.$row['field_id'];
  1685. $fields = $this->_get_fields();
  1686. if (array_key_exists($row['field_id'], $fields))
  1687. {
  1688. $field = $fields[$row['field_id']];
  1689. // is there post data available?
  1690. if (isset($_POST[$field_name])) $field_data = $_POST[$field_name];
  1691. $this->row = $row;
  1692. $r = $DSP->qdiv('ff-ft', $field['ftype']->display_field($field_name, $this->_unserialize($field_data), $field['settings']));
  1693. unset($this->row);
  1694. }
  1695. // place field data in a basic textfield if the fieldtype
  1696. // wasn't enabled or didn't have a display_field method
  1697. if ( ! isset($r))
  1698. {
  1699. $r = $DSP->input_textarea($field_name, $field_data, 1, 'textarea', '100%');
  1700. }
  1701. $r = '<input type="hidden" name="field_ft_'.$row['field_id'].'" value="none" />'.$r;
  1702. $args = func_get_args();
  1703. return $this->forward_ff_hook('publish_form_field_unique', $args, $r);
  1704. }
  1705. /**
  1706. * Publish Form - Submit New Entry - Start
  1707. *
  1708. * Add More Stuff to do when you first submit an entry
  1709. *
  1710. * @see http://expressionengine.com/developers/extension_hooks/submit_new_entry_start/
  1711. */
  1712. function submit_new_entry_start()
  1713. {
  1714. $this->_save_fields();
  1715. return $this->forward_ff_hook('submit_new_entry_start');
  1716. }
  1717. /**
  1718. * Save Fields
  1719. *
  1720. * @access private
  1721. */
  1722. function _save_fields()
  1723. {
  1724. foreach($this->_get_fields() as $this->field_id => $field)
  1725. {
  1726. $this->field_name = 'field_id_'.$this->field_id;
  1727. if (isset($_POST[$this->field_name]))
  1728. {
  1729. if (method_exists($field['ftype'], 'save_field'))
  1730. {
  1731. if ($field['ftype']->postpone_saves)
  1732. {
  1733. // save it for later
  1734. $field['data'] = $_POST[$this->field_name];
  1735. $this->postponed_saves[$this->field_id] = $field;
  1736. // prevent the system from overwriting the current data
  1737. unset($_POST[$this->field_name]);
  1738. }
  1739. else
  1740. {
  1741. $_POST[$this->field_name] = $field['ftype']->save_field($_POST[$this->field_name], $field['settings']);
  1742. }
  1743. }
  1744. if (isset($_POST[$this->field_name]) AND is_array($_POST[$this->field_name]))
  1745. {
  1746. // serialize for DB storage
  1747. $_POST[$this->field_name] = $_POST[$this->field_name]
  1748. ? $this->_serialize($_POST[$this->field_name])
  1749. : '';
  1750. }
  1751. // unset extra FF post vars
  1752. $prefix = $this->field_name.'_';
  1753. $length = strlen($prefix);
  1754. foreach($_POST as $key => $value)
  1755. {
  1756. if (substr($key, 0, $length) == $prefix)
  1757. {
  1758. unset($_POST[$key]);
  1759. }
  1760. }
  1761. }
  1762. }
  1763. if (isset($this->field_id)) unset($this->field_id);
  1764. if (isset($this->field_name)) unset($this->field_name);
  1765. }
  1766. /**
  1767. * Publish Form - Submit New Entry - End
  1768. *
  1769. * After an entry is submitted, do more processing
  1770. *
  1771. * @param string $entry_id Entry's ID
  1772. * @param array $data Array of data about entry (title, url_title)
  1773. * @param string $ping_message Error message if trackbacks or pings have failed to be sent
  1774. * @see http://expressionengine.com/developers/extension_hooks/submit_new_entry_end/
  1775. */
  1776. function submit_new_entry_end($entry_id, $data, $ping_message)
  1777. {
  1778. $this->_postponed_save($entry_id);
  1779. $args = func_get_args();
  1780. return $this->forward_ff_hook('submit_new_entry_end', $args);
  1781. }
  1782. /**
  1783. * Publish Form - Start
  1784. *
  1785. * Allows complete rewrite of Publish page
  1786. *
  1787. * @param string $which new, preview, edit, or save
  1788. * @param string $submission_error submission error, if any
  1789. * @param string $entry_id Entry ID being sent to the form
  1790. * @see http://expressionengine.com/developers/extension_hooks/publish_form_start/
  1791. */
  1792. function publish_form_start($which, $submission_error, $entry_id)
  1793. {
  1794. global $IN;
  1795. $this->which = $which;
  1796. // is this a quicksave/preview?
  1797. if (in_array($this->which, array('save', 'preview')))
  1798. {
  1799. if ($this->which == 'preview')
  1800. {
  1801. // submit_new_entry_start() doesn't get called on preview
  1802. // so fill in for it here
  1803. $this->_save_fields();
  1804. }
  1805. if ( ! $entry_id) $entry_id = $IN->GBL('entry_id');
  1806. $this->_postponed_save($entry_id);
  1807. }
  1808. unset($this->which);
  1809. $args = func_get_args();
  1810. return $this->forward_ff_hook('publish_form_start', $args);
  1811. }
  1812. /**
  1813. * Postponed Save
  1814. *
  1815. * @access private
  1816. */
  1817. function _postponed_save($entry_id)
  1818. {
  1819. global $DB;
  1820. foreach($this->postponed_saves as $this->field_id => $field)
  1821. {
  1822. $this->field_name = 'field_id_'.$this->field_id;
  1823. $_POST[$this->field_name] = $field['ftype']->save_field($field['data'], $field['settings'], $entry_id);
  1824. if (is_array($_POST[$this->field_name]))
  1825. {
  1826. $_POST[$this->field_name] = $_POST[$this->field_name]
  1827. ? $this->_serialize($_POST[$this->field_name])
  1828. : '';
  1829. }
  1830. // manually save it to the db
  1831. if ($entry_id && (! isset($this->which) || $this->which == 'save'))
  1832. {
  1833. $DB->query($DB->update_string('exp_weblog_data', array($this->field_name => $_POST[$this->field_name]), 'entry_id = '.$entry_id));
  1834. }
  1835. }
  1836. if (isset($this->field_id)) unset($this->field_id);
  1837. if (isset($this->field_name)) unset($this->field_name);
  1838. }
  1839. /**
  1840. * Weblog - SAEF - Start
  1841. *
  1842. * Rewrite the SAEF completely
  1843. *
  1844. * @param bool $return_form Return the No Cache version of the form or not
  1845. * @param string $captcha Cached CAPTCHA value from preview
  1846. * @param string $weblog_id Weblog ID for this form
  1847. * @see http://expressionengine.com/developers/extension_hooks/weblog_standalone_form_start/
  1848. */
  1849. function weblog_standalone_form_start($return_form, $captcha, $weblog_id)
  1850. {
  1851. global $DSP, $DB;
  1852. // initialize Display
  1853. if ( ! $DSP)
  1854. {
  1855. if ( ! class_exists('Display'))
  1856. {
  1857. require PATH_CP.'cp.display'.EXT;
  1858. }
  1859. $DSP = new Display();
  1860. }
  1861. // remember this is a SAEF for publish_form_field_unique
  1862. $this->saef = TRUE;
  1863. $this->saef_tag_count = 0;
  1864. $args = func_get_args();
  1865. return $this->forward_ff_hook('weblog_standalone_form_start', $args);
  1866. }
  1867. /**
  1868. * Weblog - SAEF - End
  1869. *
  1870. * Allows adding to end of submission form
  1871. *
  1872. * @param string $tagdata The tag data for the submission form at the end of processing
  1873. * @return string Modified $tagdata
  1874. * @see http://expressionengine.com/developers/extension_hooks/weblog_standalone_form_end/
  1875. */
  1876. function weblog_standalone_form_end($tagdata)
  1877. {
  1878. global $DSP;
  1879. $tagdata = $this->get_last_call($tagdata);
  1880. // parse fieldtype tags
  1881. $tagdata = $this->weblog_entries_tagdata($tagdata);
  1882. $all_snippets = array();
  1883. foreach($this->snippets as $placement => $snippets)
  1884. {
  1885. $all_snippets = array_merge($all_snippets, array_unique($snippets));
  1886. $this->snippets[$placement] = array();
  1887. }
  1888. //$tagdata = str_replace('</form>', '</form>'.implode(NL, $all_snippets), $tagdata);
  1889. $tagdata .= NL.implode(NL, $all_snippets).NL;
  1890. $this->saef = FALSE;
  1891. unset($this->saef_tag_count);
  1892. $args = func_get_args();
  1893. return $this->forward_ff_hook('weblog_standalone_form_end', $args, $tagdata);
  1894. }
  1895. /**
  1896. * Weblog - Entry Tag Data
  1897. *
  1898. * Modify the tagdata for the weblog entries before anything else is parsed
  1899. *
  1900. * @param string $tagdata The Weblog Entries tag data
  1901. * @param array $row Array of data for the current entry
  1902. * @param object $weblog The current Weblog object including all data relating to categories and custom fields
  1903. * @return string Modified $tagdata
  1904. * @see http://expressionengine.com/developers/extension_hooks/weblog_entries_tagdata/
  1905. */
  1906. function weblog_entries_tagdata($tagdata, $row=array(), $weblog=NULL)
  1907. {
  1908. global $REGX;
  1909. $this->tagdata = $this->get_last_call($tagdata);
  1910. $this->row = $row;
  1911. $this->weblog = &$weblog;
  1912. if ($fields = $this->_get_fields())
  1913. {
  1914. $fields_by_name = array();
  1915. foreach($fields as $this->field_id => $this->field)
  1916. {
  1917. $fields_by_name[$this->field['name']] = array(
  1918. 'data' => (isset($row['field_id_'.$this->field_id]) ? $this->_unserialize($row['field_id_'.$this->field_id], FALSE) : ''),
  1919. 'settings' => $this->field['settings'],
  1920. 'ftype' => $this->field['ftype'],
  1921. 'helpers' => array('field_id' => $this->field_id, 'field_name' => $this->field['name'])
  1922. );
  1923. }
  1924. if (isset($this->field_id)) unset($this->field_id);
  1925. if (isset($this->field)) unset($this->field);
  1926. $this->_parse_tagdata($this->tagdata, $fields_by_name, TRUE);
  1927. }
  1928. // unset temporary helper vars
  1929. $tagdata = $this->tagdata;
  1930. unset($this->tagdata);
  1931. unset($this->row);
  1932. unset($this->weblog);
  1933. $args = func_get_args();
  1934. return $this->forward_ff_hook('weblog_entries_tagdata', $args, $tagdata);
  1935. }
  1936. /**
  1937. * Parse Tagdata
  1938. *
  1939. * @param string $tagdata The Weblog Entries tagdata
  1940. * @param string $field_name Name of the field to search for
  1941. * @param mixed $field_data The field's value
  1942. * @param array $field_settings The field's settings
  1943. * @param object $ftype The field's fieldtype object
  1944. * @access private
  1945. */
  1946. function _parse_tagdata(&$tagdata, $fields, $skip_unmatched_tags = FALSE)
  1947. {
  1948. global $FNS, $DSP;
  1949. // -------------------------------------------
  1950. // Tag parsing
  1951. // -------------------------------------------
  1952. // find the next ftype tag
  1953. $offset = 0;
  1954. while (preg_match('/'.LD.'('.implode('|', array_keys($fields)).')(:(\w+))?(\s+.*)?'.RD.'/sU', $tagdata, $matches, PREG_OFFSET_CAPTURE, $offset))
  1955. {
  1956. $field_name = $matches[1][0];
  1957. $field = $fields[$field_name];
  1958. $tag_pos = $matches[0][1];
  1959. $tag_len = strlen($matches[0][0]);
  1960. $tagdata_pos = $tag_pos + $tag_len;
  1961. $endtag = LD.SLASH.$field_name.(isset($matches[2][0]) ? $matches[2][0] : '').RD;
  1962. $endtag_len = strlen($endtag);
  1963. $endtag_pos = strpos($tagdata, $endtag, $tagdata_pos);
  1964. $tag_func = (isset($matches[3][0]) AND $matches[3][0]) ? $matches[3][0] : '';
  1965. // is this SAEF?
  1966. if ($this->saef AND ! $tag_func)
  1967. {
  1968. // call display_field rather than display_tag
  1969. foreach($field['helpers'] as $name => $value)
  1970. {
  1971. $this->$name = $value;
  1972. }
  1973. $new_tagdata = $DSP->qdiv('ff-ft', $field['ftype']->display_field('field_id_'.$field['helpers']['field_id'], $field['data'], $field['settings']));
  1974. foreach($field['helpers'] as $name => $value)
  1975. {
  1976. unset($this->$name);
  1977. }
  1978. // update the tag count
  1979. $this->saef_tag_count++;
  1980. }
  1981. else
  1982. {
  1983. if ( ! $tag_func) $tag_func = 'display_tag';
  1984. $method_exists = method_exists($field['ftype'], $tag_func);
  1985. if ($method_exists || ! $skip_unmatched_tags)
  1986. {
  1987. // get the params
  1988. $params = isset($field['ftype']->default_tag_params)
  1989. ? $field['ftype']->default_tag_params
  1990. : array();
  1991. if (isset($matches[4][0]) AND $matches[4][0] AND preg_match_all('/\s+([\w-:]+)\s*=\s*([\'\"])([^\2]*)\2/sU', $matches[4][0], $param_matches))
  1992. {
  1993. for ($j = 0; $j < count($param_matches[0]); $j++)
  1994. {
  1995. $params[$param_matches[1][$j]] = $param_matches[3][$j];
  1996. }
  1997. }
  1998. // is this a tag pair?
  1999. $field_tagdata = ($endtag_pos !== FALSE)
  2000. ? substr($tagdata, $tagdata_pos, $endtag_pos - $tagdata_pos)
  2001. : '';
  2002. if ( ! $tag_func) $tag_func = 'display_tag';
  2003. if ($method_exists)
  2004. {
  2005. foreach($field['helpers'] as $name => $value)
  2006. {
  2007. $this->$name = $value;
  2008. }
  2009. $new_tagdata = (string) $field['ftype']->$tag_func($params, $field_tagdata, $field['data'], $field['settings']);
  2010. foreach($field['helpers'] as $name => $value)
  2011. {
  2012. unset($this->$name);
  2013. }
  2014. }
  2015. else
  2016. {
  2017. $new_tagdata = $field['data'];
  2018. }
  2019. }
  2020. }
  2021. if (isset($new_tagdata))
  2022. {
  2023. $offset = $tag_pos;
  2024. $tagdata = substr($tagdata, 0, $tag_pos)
  2025. . $new_tagdata
  2026. . substr($tagdata, ($endtag_pos !== FALSE ? $endtag_pos+$endtag_len : $tagdata_pos));
  2027. unset($new_tagdata);
  2028. }
  2029. else
  2030. {
  2031. $offset = $tag_pos + $tag_len;
  2032. }
  2033. }
  2034. // -------------------------------------------
  2035. // Conditionals
  2036. // -------------------------------------------
  2037. $conditionals = array();
  2038. foreach ($fields as $name => $field)
  2039. {
  2040. $conditionals[$name] = is_array($field['data']) ? ($field['data'] ? '1' : '') : $field['data'];
  2041. }
  2042. $tagdata = $FNS->prep_conditionals($tagdata, $conditionals);
  2043. }
  2044. /**
  2045. * LG Addon Updater - Register a New Addon Source
  2046. *
  2047. * @param array $sources The existing sources
  2048. * @return array The new source list
  2049. * @see http://leevigraham.com/cms-customisation/expressionengine/lg-addon-updater/
  2050. */
  2051. function lg_addon_update_register_source($sources)
  2052. {
  2053. $sources = $this->get_last_call($sources);
  2054. if ($this->settings['check_for_updates'] == 'y')
  2055. {
  2056. // add FieldFrame source
  2057. $source = 'http://pixelandtonic.com/ee/versions.xml';
  2058. if ( ! in_array($source, $sources))
  2059. {
  2060. $sources[] = $source;
  2061. }
  2062. // add ftype sources
  2063. foreach($this->_get_ftypes() as $class_name => $ftype)
  2064. {
  2065. $source = $ftype->info['versions_xml_url'];
  2066. if ($source AND ! in_array($source, $sources))
  2067. {
  2068. $sources[] = $source;
  2069. }
  2070. }
  2071. }
  2072. $args = func_get_args();
  2073. return $this->forward_ff_hook('lg_addon_update_register_source', $args, $sources);
  2074. }
  2075. /**
  2076. * LG Addon Updater - Register a New Addon ID
  2077. *
  2078. * @param array $addons The existing sources
  2079. * @return array The new addon list
  2080. * @see http://leevigraham.com/cms-customisation/expressionengine/lg-addon-updater/
  2081. */
  2082. function lg_addon_update_register_addon($addons)
  2083. {
  2084. $addons = $this->get_last_call($addons);
  2085. if ($this->settings['check_for_updates'] == 'y')
  2086. {
  2087. // add FieldFrame
  2088. $addons[FF_CLASS] = FF_VERSION;
  2089. // add ftypes
  2090. foreach($this->_get_ftypes() as $class_name => $ftype)
  2091. {
  2092. $addons[$class_name] = $ftype->info['version'];
  2093. }
  2094. }
  2095. $args = func_get_args();
  2096. return $this->forward_ff_hook('lg_addon_update_register_addon', $args, $addons);
  2097. }
  2098. }
  2099. /**
  2100. * Settings Display Class
  2101. *
  2102. * Provides FieldFrame settings-specific display methods
  2103. *
  2104. * @package FieldFrame
  2105. * @author Brandon Kelly <brandon@pixelandtonic.com>
  2106. */
  2107. class Fieldframe_SettingsDisplay {
  2108. /**
  2109. * Fieldframe_SettingsDisplay Constructor
  2110. */
  2111. function __construct()
  2112. {
  2113. // initialize Display Class
  2114. global $DSP;
  2115. if ( ! $DSP)
  2116. {
  2117. if ( ! class_exists('Display'))
  2118. {
  2119. require PATH_CP.'cp.display'.EXT;
  2120. }
  2121. $DSP = new Display();
  2122. }
  2123. $this->block_count = 0;
  2124. }
  2125. /**
  2126. * Open Settings Block
  2127. *
  2128. * @param string $title_line The block's title
  2129. * @return string The block's head
  2130. */
  2131. function block($title_line=FALSE, $num_cols=2)
  2132. {
  2133. global $DSP;
  2134. $this->row_count = 0;
  2135. $this->num_cols = $num_cols;
  2136. $r = $DSP->table_open(array(
  2137. 'class' => 'tableBorder',
  2138. 'border' => '0',
  2139. 'style' => 'margin:'.($this->block_count ? '18px' : '0').' 0 0 0; width:100%;'.($title_line ? '' : ' border-top:1px solid #CACFD4;')
  2140. ));
  2141. if ($title_line)
  2142. {
  2143. $r .= $this->row(array($this->get_line($title_line)), 'tableHeading');
  2144. }
  2145. $this->block_count++;
  2146. return $r;
  2147. }
  2148. /**
  2149. * Close Settings Block
  2150. */
  2151. function block_c()
  2152. {
  2153. global $DSP;
  2154. return $DSP->table_c();
  2155. }
  2156. /**
  2157. * Settings Row
  2158. *
  2159. * @param array $col_data Each column's contents
  2160. * @param string $row_class CSS class to be added to each cell
  2161. * @param array $attr HTML attributes to add onto the <tr>
  2162. * @return string The settings row
  2163. */
  2164. function row($col_data, $row_class=NULL, $attr=array())
  2165. {
  2166. global $DSP;
  2167. // get the alternating row class
  2168. if ($row_class === NULL)
  2169. {
  2170. $this->row_count++;
  2171. $this->row_class = ($this->row_count % 2)
  2172. ? 'tableCellOne'
  2173. : 'tableCellTwo';
  2174. }
  2175. else
  2176. {
  2177. $this->row_class = $row_class;
  2178. }
  2179. $r = '<tr';
  2180. foreach($attr as $key => $value) $r .= ' '.$key.'="'.$value.'"';
  2181. $r .= '>';
  2182. $num_cols = count($col_data);
  2183. foreach($col_data as $i => $col)
  2184. {
  2185. $width = ($i == 0)
  2186. ? '50%'
  2187. : ($i < $num_cols-1 ? floor(50/($num_cols-1)).'%' : '');
  2188. $colspan = ($i == $num_cols-1) ? $this->num_cols - $i : NULL;
  2189. $r .= $DSP->td($this->row_class, $width, $colspan)
  2190. . $col
  2191. . $DSP->td_c();
  2192. }
  2193. $r .= $DSP->tr_c();
  2194. return $r;
  2195. }
  2196. /**
  2197. * Heading Row
  2198. *
  2199. * @param array $cols Each column's heading line
  2200. * @return string The settings heading row
  2201. */
  2202. function heading_row($cols)
  2203. {
  2204. return $this->row($cols, 'tableHeadingAlt');
  2205. }
  2206. /**
  2207. * Info Row
  2208. *
  2209. * @param string $info_line Info text
  2210. * @return string The settings info row
  2211. */
  2212. function info_row($info_line, $styles=TRUE)
  2213. {
  2214. return $this->row(array(
  2215. '<div class="box"' . ($styles ? ' style="border-width:0 0 1px 0; margin:0; padding:10px 5px"' : '') . '>'
  2216. . '<p>'.$this->get_line($info_line).'</p>'
  2217. . '</div>'
  2218. ), '');
  2219. }
  2220. /**
  2221. * Label
  2222. *
  2223. * @param string $label_line The main label text
  2224. * @param string $subtext_line The label's subtext
  2225. * @return string The label
  2226. */
  2227. function label($label_line, $subtext_line='')
  2228. {
  2229. global $DSP;
  2230. $r = $DSP->qdiv('defaultBold', $this->get_line($label_line));
  2231. if ($subtext_line) $r .= $DSP->qdiv('subtext', $this->get_line($subtext_line));
  2232. return $r;
  2233. }
  2234. /**
  2235. * Settings Text Input
  2236. *
  2237. * @param string $name Name of the text field
  2238. * @param string $value Initial value
  2239. * @param array $attr Input variables
  2240. * @return string The text field
  2241. */
  2242. function text($name, $value, $attr=array())
  2243. {
  2244. global $DSP;
  2245. $attr = array_merge(array('size'=>'','maxlength'=>'','style'=>'input','width'=>'90%','extras'=>'','convert'=>FALSE), $attr);
  2246. return $DSP->input_text($name, $value, $attr['size'], $attr['maxlength'], $attr['style'], $attr['width'], $attr['extras'], $attr['convert']);
  2247. }
  2248. /**
  2249. * Textarea
  2250. *
  2251. * @param string $name Name of the textarea
  2252. * @param string $value Initial value
  2253. * @param array $attr Input variables
  2254. * @return string The textarea
  2255. */
  2256. function textarea($name, $value, $attr=array())
  2257. {
  2258. global $DSP;
  2259. $attr = array_merge(array('rows'=>'3','style'=>'textarea','width'=>'91%','extras'=>'','convert'=>FALSE), $attr);
  2260. return $DSP->input_textarea($name, $value, $attr['rows'], $attr['style'], $attr['width'], $attr['extras'], $attr['convert']);
  2261. }
  2262. /**
  2263. * Select Options
  2264. *
  2265. * @param string $value initial selected value(s)
  2266. * @param array $options list of the options
  2267. * @return string the select/multi-select options HTML
  2268. */
  2269. function _select_options($value, $options)
  2270. {
  2271. global $DSP;
  2272. $r = '';
  2273. foreach($options as $option_value => $option_line)
  2274. {
  2275. if (is_array($option_line))
  2276. {
  2277. $r .= '<optgroup label="'.$option_value.'">'."\n"
  2278. . $this->_select_options($value, $option_line)
  2279. . '</optgroup>'."\n";
  2280. }
  2281. else
  2282. {
  2283. $selected = is_array($value)
  2284. ? in_array($option_value, $value)
  2285. : ($option_value == $value);
  2286. $r .= $DSP->input_select_option($option_value, $this->get_line($option_line), $selected ? 1 : 0);
  2287. }
  2288. }
  2289. return $r;
  2290. }
  2291. /**
  2292. * Select input
  2293. *
  2294. * @param string $name Name of the select
  2295. * @param mixed $value Initial selected value(s)
  2296. * @param array $options List of the options
  2297. * @param array $attr Input variables
  2298. * @return string The select input
  2299. */
  2300. function select($name, $value, $options, $attr=array())
  2301. {
  2302. global $DSP;
  2303. $attr = array_merge(array('multi'=>NULL, 'size'=>0, 'width'=>''), $attr);
  2304. return $DSP->input_select_header($name, $attr['multi'], $attr['size'], $attr['width'])
  2305. . $this->_select_options($value, $options)
  2306. . $DSP->input_select_footer();
  2307. }
  2308. /**
  2309. * Multiselect Input
  2310. *
  2311. * @param string $name Name of the textfield
  2312. * @param array $values Initial selected values
  2313. * @param array $options List of the options
  2314. * @param array $attr Input variables
  2315. * @return string The multiselect input
  2316. */
  2317. function multiselect($name, $values, $options, $attr=array())
  2318. {
  2319. $attr = array_merge($attr, array('multi' => 1));
  2320. return $this->select($name, $values, $options, $attr);
  2321. }
  2322. /**
  2323. * Radio Group
  2324. *
  2325. * @param string $name Name of the radio inputs
  2326. * @param string $value Initial selected value
  2327. * @param array $options List of the options
  2328. * @param array $attr Input variables
  2329. * @return string The text input
  2330. */
  2331. function radio_group($name, $value, $options, $attr=array())
  2332. {
  2333. global $DSP;
  2334. $attr = array_merge(array('extras'=>''), $attr);
  2335. $r = '';
  2336. foreach($options as $option_value => $option_name)
  2337. {
  2338. if ($r) $r .= NBS.NBS.' ';
  2339. $r .= '<label style="white-space:nowrap;">'
  2340. . $DSP->input_radio($name, $option_value, ($option_value == $value) ? 1 : 0, $attr['extras'])
  2341. . ' '.$this->get_line($option_name)
  2342. . '</label>';
  2343. }
  2344. return $r;
  2345. }
  2346. /**
  2347. * Get Line
  2348. *
  2349. * @param string $line unlocalized string or the name of a $LANG line
  2350. * @return string Localized string
  2351. */
  2352. function get_line($line)
  2353. {
  2354. global $FF;
  2355. return $FF->get_line($line);
  2356. }
  2357. }
  2358. /**
  2359. * Fieldframe Fieldtype Base Class
  2360. *
  2361. * Provides FieldFrame fieldtypes with a couple handy methods
  2362. *
  2363. * @package FieldFrame
  2364. * @author Brandon Kelly <brandon@pixelandtonic.com>
  2365. */
  2366. class Fieldframe_Fieldtype {
  2367. var $_fieldframe = TRUE;
  2368. function get_last_call($param=FALSE)
  2369. {
  2370. global $FF;
  2371. return $FF->get_last_call($param);
  2372. }
  2373. function insert($at, $html)
  2374. {
  2375. global $FF;
  2376. $FF->snippets[$at][] = $html;
  2377. }
  2378. function insert_css($css)
  2379. {
  2380. $this->insert('head', '<style type="text/css" charset="utf-8">'.NL.$css.NL.'</style>');
  2381. }
  2382. function insert_js($js)
  2383. {
  2384. $this->insert('body', '<script type="text/javascript">;'.NL.$js.NL.'</script>');
  2385. }
  2386. function include_css($filename)
  2387. {
  2388. $this->insert('head', '<link rel="stylesheet" type="text/css" href="'.FT_URL.$this->_class_name.'/'.$filename.'" charset="utf-8" />');
  2389. }
  2390. function include_js($filename)
  2391. {
  2392. $this->insert('body', '<script type="text/javascript" src="'.FT_URL.$this->_class_name.'/'.$filename.'" charset="utf-8"></script>');
  2393. }
  2394. function options_setting($options=array(), $indent = '')
  2395. {
  2396. $r = '';
  2397. foreach($options as $name => $label)
  2398. {
  2399. if ($r !== '') $r .= "\n";
  2400. // force strings
  2401. $name = (string) $name;
  2402. $label = (string) $label;
  2403. // is this just a blank option?
  2404. if ($name === '' && $label === '') $name = $label = ' ';
  2405. $r .= $indent . htmlentities($name, ENT_COMPAT, 'UTF-8');
  2406. // is this an optgroup?
  2407. if (is_array($label))
  2408. {
  2409. $r .= "\n".$this->options_setting($label, $indent.' ');
  2410. }
  2411. else if ($name !== $label)
  2412. {
  2413. $r .= ' : '.$label;
  2414. }
  2415. }
  2416. return $r;
  2417. }
  2418. function save_options_setting($options = '', $total_levels = 1)
  2419. {
  2420. // prepare options
  2421. $options = preg_split('/[\r\n]+/', $options);
  2422. foreach($options as &$option)
  2423. {
  2424. $option_parts = preg_split('/\s:\s/', $option, 2);
  2425. $option = array();
  2426. $option['indent'] = preg_match('/^\s+/', $option_parts[0], $matches) ? strlen(str_replace("\t", ' ', $matches[0])) : 0;
  2427. $option['name'] = trim($option_parts[0]);
  2428. $option['value'] = isset($option_parts[1]) ? trim($option_parts[1]) : $option['name'];
  2429. }
  2430. return $this->_structure_options($options, $total_levels);
  2431. }
  2432. function _structure_options(&$options, $total_levels, $level = 1, $indent = -1)
  2433. {
  2434. $r = array();
  2435. while ($options)
  2436. {
  2437. if ($indent == -1 || $options[0]['indent'] > $indent)
  2438. {
  2439. $option = array_shift($options);
  2440. $children = ( ! $total_levels OR $level < $total_levels)
  2441. ? $this->_structure_options($options, $total_levels, $level+1, $option['indent']+1)
  2442. : FALSE;
  2443. $r[(string)$option['name']] = $children ? $children : (string)$option['value'];
  2444. }
  2445. else if ($options[0]['indent'] <= $indent)
  2446. {
  2447. break;
  2448. }
  2449. }
  2450. return $r;
  2451. }
  2452. function prep_iterators(&$tagdata)
  2453. {
  2454. // find {switch} tags
  2455. $this->_switches = array();
  2456. $tagdata = preg_replace_callback('/'.LD.'switch\s*=\s*([\'\"])([^\1]+)\1'.RD.'/sU', array(&$this, '_get_switch_options'), $tagdata);
  2457. $this->_count_tag = 'count';
  2458. $this->_iterator_count = 0;
  2459. }
  2460. function _get_switch_options($match)
  2461. {
  2462. global $FNS;
  2463. $marker = LD.'SWITCH['.$FNS->random('alpha', 8).']SWITCH'.RD;
  2464. $this->_switches[] = array('marker' => $marker, 'options' => explode('|', $match[2]));
  2465. return $marker;
  2466. }
  2467. function parse_iterators(&$tagdata)
  2468. {
  2469. global $TMPL;
  2470. // {switch} tags
  2471. foreach($this->_switches as $i => $switch)
  2472. {
  2473. $option = $this->_iterator_count % count($switch['options']);
  2474. $tagdata = str_replace($switch['marker'], $switch['options'][$option], $tagdata);
  2475. }
  2476. // update the count
  2477. $this->_iterator_count++;
  2478. // {count} tags
  2479. $tagdata = $TMPL->swap_var_single($this->_count_tag, $this->_iterator_count, $tagdata);
  2480. }
  2481. }
  2482. /**
  2483. * Fieldframe Multi-select Fieldtype Base Class
  2484. *
  2485. * Provides Multi-select fieldtypes with their base functionality
  2486. *
  2487. * @package FieldFrame
  2488. * @author Brandon Kelly <brandon@pixelandtonic.com>
  2489. */
  2490. class Fieldframe_Multi_Fieldtype extends Fieldframe_Fieldtype {
  2491. var $default_field_settings = array(
  2492. 'options' => array(
  2493. 'Option 1' => 'Option 1',
  2494. 'Option 2' => 'Option 2',
  2495. 'Option 3' => 'Option 3'
  2496. )
  2497. );
  2498. var $default_cell_settings = array(
  2499. 'options' => array(
  2500. 'Opt 1' => 'Opt 1',
  2501. 'Opt 2' => 'Opt 2'
  2502. )
  2503. );
  2504. var $default_tag_params = array(
  2505. 'sort' => '',
  2506. 'backspace' => '0'
  2507. );
  2508. var $settings_label = 'field_list_items';
  2509. var $total_option_levels = 1;
  2510. /**
  2511. * Display Field Settings
  2512. *
  2513. * @param array $field_settings The field's settings
  2514. * @return array Settings HTML (cell1, cell2, rows)
  2515. */
  2516. function display_field_settings($field_settings)
  2517. {
  2518. global $DSP, $LANG;
  2519. $cell2 = $DSP->qdiv('defaultBold', $LANG->line($this->settings_label))
  2520. . $DSP->qdiv('default', $LANG->line('field_list_instructions'))
  2521. . $DSP->input_textarea('options', $this->options_setting($field_settings['options']), '6', 'textarea', '99%')
  2522. . $DSP->qdiv('default', $LANG->line('option_setting_examples'));
  2523. return array('cell2' => $cell2);
  2524. }
  2525. /**
  2526. * Display Cell Settings
  2527. *
  2528. * @param array $cell_settings The cell's settings
  2529. * @return string Settings HTML
  2530. */
  2531. function display_cell_settings($cell_settings)
  2532. {
  2533. global $FFM, $DSP, $LANG;
  2534. if (version_compare($FFM->info['version'], '2.0', '<'))
  2535. {
  2536. return '<label class="itemWrapper">'
  2537. . $DSP->qdiv('defaultBold', $LANG->line($this->settings_label))
  2538. . $DSP->input_textarea('options', $this->options_setting($cell_settings['options']), '4', 'textarea', '140px')
  2539. . '</label>';
  2540. }
  2541. return array(
  2542. array(
  2543. $LANG->line($this->settings_label),
  2544. '<textarea class="matrix-textarea" name="options" rows="4">'.$this->options_setting($cell_settings['options']).'</textarea>'
  2545. )
  2546. );
  2547. }
  2548. /**
  2549. * Save Field Settings
  2550. *
  2551. * Turn the options textarea value into an array of option names and labels
  2552. *
  2553. * @param array $field_settings The user-submitted settings, pulled from $_POST
  2554. * @return array Modified $field_settings
  2555. */
  2556. function save_field_settings($field_settings)
  2557. {
  2558. $field_settings['options'] = $this->save_options_setting($field_settings['options'], $this->total_option_levels);
  2559. return $field_settings;
  2560. }
  2561. /**
  2562. * Save Cell Settings
  2563. *
  2564. * Turn the options textarea value into an array of option names and labels
  2565. *
  2566. * @param array $cell_settings The user-submitted settings, pulled from $_POST
  2567. * @return array Modified $cell_settings
  2568. */
  2569. function save_cell_settings($cell_settings)
  2570. {
  2571. return $this->save_field_settings($cell_settings);
  2572. }
  2573. /**
  2574. * Prep Field Data
  2575. *
  2576. * Ensures $field_data is an array.
  2577. *
  2578. * @param mixed &$field_data The currently-saved $field_data
  2579. */
  2580. function prep_field_data(&$field_data)
  2581. {
  2582. if ( ! is_array($field_data))
  2583. {
  2584. $field_data = array_filter(preg_split("/[\r\n]+/", $field_data));
  2585. }
  2586. }
  2587. function _find_option($needle, $haystack)
  2588. {
  2589. foreach ($haystack as $key => $value)
  2590. {
  2591. $r = $value;
  2592. if ($needle == $key OR (is_array($value) AND (($r = $this->_find_option($needle, $value)) !== FALSE)))
  2593. {
  2594. return $r;
  2595. }
  2596. }
  2597. return FALSE;
  2598. }
  2599. /**
  2600. * Display Tag
  2601. *
  2602. * @param array $params Name/value pairs from the opening tag
  2603. * @param string $tagdata Chunk of tagdata between field tag pairs
  2604. * @param string $field_data Currently saved field value
  2605. * @param array $field_settings The field's settings
  2606. * @return string Modified $tagdata
  2607. */
  2608. function display_tag($params, $tagdata, $field_data, $field_settings)
  2609. {
  2610. global $TMPL;
  2611. if ( ! $tagdata)
  2612. {
  2613. return $this->ul($params, $tagdata, $field_data, $field_settings);
  2614. }
  2615. $this->prep_field_data($field_data);
  2616. $r = '';
  2617. if ($field_settings['options'] AND $field_data)
  2618. {
  2619. // optional sorting
  2620. if ($sort = strtolower($params['sort']))
  2621. {
  2622. if ($sort == 'asc')
  2623. {
  2624. sort($field_data);
  2625. }
  2626. else if ($sort == 'desc')
  2627. {
  2628. rsort($field_data);
  2629. }
  2630. }
  2631. // prepare for {switch} and {count} tags
  2632. $this->prep_iterators($tagdata);
  2633. foreach($field_data as $option_name)
  2634. {
  2635. if (($option = $this->_find_option($option_name, $field_settings['options'])) !== FALSE)
  2636. {
  2637. // copy $tagdata
  2638. $option_tagdata = $tagdata;
  2639. // simple var swaps
  2640. $option_tagdata = $TMPL->swap_var_single('option', $option, $option_tagdata);
  2641. $option_tagdata = $TMPL->swap_var_single('option_name', $option_name, $option_tagdata);
  2642. // parse {switch} and {count} tags
  2643. $this->parse_iterators($option_tagdata);
  2644. $r .= $option_tagdata;
  2645. }
  2646. }
  2647. if ($params['backspace'])
  2648. {
  2649. $r = substr($r, 0, -$params['backspace']);
  2650. }
  2651. }
  2652. return $r;
  2653. }
  2654. /**
  2655. * Unordered List
  2656. *
  2657. * @param array $params Name/value pairs from the opening tag
  2658. * @param string $tagdata Chunk of tagdata between field tag pairs
  2659. * @param string $field_data Currently saved field value
  2660. * @param array $field_settings The field's settings
  2661. * @return string unordered list of options
  2662. */
  2663. function ul($params, $tagdata, $field_data, $field_settings)
  2664. {
  2665. return "<ul>\n"
  2666. . $this->display_tag($params, " <li>{option}</li>\n", $field_data, $field_settings)
  2667. . '</ul>';
  2668. }
  2669. /**
  2670. * Ordered List
  2671. *
  2672. * @param array $params Name/value pairs from the opening tag
  2673. * @param string $tagdata Chunk of tagdata between field tag pairs
  2674. * @param string $field_data Currently saved field value
  2675. * @param array $field_settings The field's settings
  2676. * @return string ordered list of options
  2677. */
  2678. function ol($params, $tagdata, $field_data, $field_settings)
  2679. {
  2680. return "<ol>\n"
  2681. . $this->display_tag($params, " <li>{option}</li>\n", $field_data, $field_settings)
  2682. . '</ol>';
  2683. }
  2684. /**
  2685. * All Options
  2686. *
  2687. * @param array $params Name/value pairs from the opening tag
  2688. * @param string $tagdata Chunk of tagdata between field tag pairs
  2689. * @param string $field_data Currently saved field value
  2690. * @param array $field_settings The field's settings
  2691. * @return string Modified $tagdata
  2692. */
  2693. function all_options($params, $tagdata, $field_data, $field_settings, $iterator_count = 0)
  2694. {
  2695. global $TMPL;
  2696. Fieldframe_Multi_Fieldtype::prep_field_data($field_data);
  2697. $r = '';
  2698. if ($field_settings['options'])
  2699. {
  2700. // optional sorting
  2701. if ($sort = strtolower($params['sort']))
  2702. {
  2703. if ($sort == 'asc')
  2704. {
  2705. asort($field_settings['options']);
  2706. }
  2707. else if ($sort == 'desc')
  2708. {
  2709. arsort($field_settings['options']);
  2710. }
  2711. }
  2712. // prepare for {switch} and {count} tags
  2713. $this->prep_iterators($tagdata);
  2714. $this->_iterator_count += $iterator_count;
  2715. foreach($field_settings['options'] as $option_name => $option)
  2716. {
  2717. if (is_array($option))
  2718. {
  2719. $r .= $this->all_options(array_merge($params, array('backspace' => '0')), $tagdata, $field_data, array('options' => $option), $this->_iterator_count);
  2720. }
  2721. else
  2722. {
  2723. // copy $tagdata
  2724. $option_tagdata = $tagdata;
  2725. // simple var swaps
  2726. $option_tagdata = $TMPL->swap_var_single('option', $option, $option_tagdata);
  2727. $option_tagdata = $TMPL->swap_var_single('option_name', $option_name, $option_tagdata);
  2728. $option_tagdata = $TMPL->swap_var_single('selected', (in_array($option_name, $field_data) ? 1 : 0), $option_tagdata);
  2729. // parse {switch} and {count} tags
  2730. $this->parse_iterators($option_tagdata);
  2731. $r .= $option_tagdata;
  2732. }
  2733. }
  2734. if ($params['backspace'])
  2735. {
  2736. $r = substr($r, 0, -$params['backspace']);
  2737. }
  2738. }
  2739. return $r;
  2740. }
  2741. /**
  2742. * Is Selected?
  2743. *
  2744. * @param array $params Name/value pairs from the opening tag
  2745. * @param string $tagdata Chunk of tagdata between field tag pairs
  2746. * @param string $field_data Currently saved field value
  2747. * @param array $field_settings The field's settings
  2748. * @return bool whether or not the option is selected
  2749. */
  2750. function selected($params, $tagdata, $field_data, $field_settings)
  2751. {
  2752. $this->prep_field_data($field_data);
  2753. return (isset($params['option']) AND in_array($params['option'], $field_data)) ? 1 : 0;
  2754. }
  2755. /**
  2756. * Total Selections
  2757. */
  2758. function total_selections($params, $tagdata, $field_data, $field_settings)
  2759. {
  2760. $this->prep_field_data($field_data);
  2761. return $field_data ? (string)count($field_data) : '0';
  2762. }
  2763. }