PageRenderTime 100ms CodeModel.GetById 17ms app.highlight 57ms RepoModel.GetById 13ms app.codeStats 0ms

/extensions/ext.fieldframe.php

https://github.com/pixelandtonic/fieldframe
PHP | 3181 lines | 2197 code | 341 blank | 643 comment | 238 complexity | ec07d647ec7facfa36ff39cf6d7c940c MD5 | raw file

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

   1<?php
   2
   3if ( ! defined('EXT')) exit('Invalid file request');
   4
   5// define FF constants
   6// (used by Fieldframe and Fieldframe_Main)
   7if ( ! defined('FF_CLASS'))
   8{
   9	define('FF_CLASS',   'Fieldframe');
  10	define('FF_NAME',    'FieldFrame');
  11	define('FF_VERSION', '1.4.5');
  12}
  13
  14
  15/**
  16 * FieldFrame Class
  17 *
  18 * This extension provides a framework for ExpressionEngine fieldtype development.
  19 *
  20 * @package   FieldFrame
  21 * @author    Brandon Kelly <brandon@pixelandtonic.com>
  22 * @copyright Copyright (c) 2011 Pixel & Tonic, Inc
  23 * @license   http://creativecommons.org/licenses/by-sa/3.0/ Attribution-Share Alike 3.0 Unported
  24 */
  25class Fieldframe {
  26
  27	var $name           = FF_NAME;
  28	var $version        = FF_VERSION;
  29	var $description    = 'Fieldtype Framework';
  30	var $settings_exist = 'y';
  31	var $docs_url       = 'http://pixelandtonic.com/fieldframe/docs';
  32
  33	var $hooks = array(
  34		'sessions_start' => array('priority' => 1),
  35
  36		// Edit Field Form
  37		'publish_admin_edit_field_type_pulldown',
  38		'publish_admin_edit_field_type_cellone',
  39		'publish_admin_edit_field_type_celltwo',
  40		'publish_admin_edit_field_extra_row',
  41		'publish_admin_edit_field_format',
  42		'publish_admin_edit_field_js',
  43
  44		// Field Manager
  45		'show_full_control_panel_start',
  46		'show_full_control_panel_end',
  47
  48		// Entry Form
  49		'publish_form_field_unique',
  50		'submit_new_entry_start',
  51		'submit_new_entry_end',
  52		'publish_form_start',
  53
  54		// SAEF
  55		'weblog_standalone_form_start',
  56		'weblog_standalone_form_end',
  57
  58		// Templates
  59		'weblog_entries_tagdata' => array('priority' => 1),
  60
  61		// LG Addon Updater
  62		'lg_addon_update_register_source',
  63		'lg_addon_update_register_addon'
  64	);
  65
  66	/**
  67	 * FieldFrame Class Constructor
  68	 *
  69	 * @param array  $settings
  70	 */
  71	function __construct($settings=array())
  72	{
  73		$this->_init_main($settings);
  74	}
  75
  76	/**
  77	 * Activate Extension
  78	 */
  79	function activate_extension()
  80	{
  81		global $DB;
  82
  83		// require PHP 5
  84		if (phpversion() < 5) return;
  85
  86		// Get settings
  87		$query = $DB->query('SELECT settings FROM exp_extensions WHERE class = "'.FF_CLASS.'" AND settings != "" LIMIT 1');
  88		$settings = $query->num_rows ? $this->_unserialize($query->row['settings']) : array();
  89		$this->_init_main($settings, TRUE);
  90
  91		global $FF;
  92		$FF->activate_extension($settings);
  93	}
  94
  95	/**
  96	 * Update Extension
  97	 *
  98	 * @param string  $current  Previous installed version of the extension
  99	 */
 100	function update_extension($current='')
 101	{
 102		global $DB;
 103
 104		if ( ! $current OR $current == FF_VERSION)
 105		{
 106			return FALSE;
 107		}
 108
 109		if ($current < '1.1.3')
 110		{
 111			// no longer saving settings on a per-site basis
 112
 113			$sql = array();
 114
 115			// no more per-site FF settings
 116			$query = $DB->query('SELECT settings FROM exp_extensions WHERE class = "'.FF_CLASS.'" AND settings != "" LIMIT 1');
 117			if ($query->row)
 118			{
 119				$settings = array_shift(Fieldframe_Main::_unserialize($query->row['settings']));
 120				$sql[] = $DB->update_string('exp_extensions', array('settings' => Fieldframe_Main::_serialize($settings)), 'class = "'.FF_CLASS.'"');
 121			}
 122
 123			// collect conversion info
 124			$query = $DB->query('SELECT * FROM exp_ff_fieldtypes ORDER BY enabled DESC, site_id ASC');
 125			$firsts = array();
 126			$conversions = array();
 127			foreach ($query->result as $ftype)
 128			{
 129				if ( ! isset($firsts[$ftype['class']]))
 130				{
 131					$firsts[$ftype['class']] = $ftype['fieldtype_id'];
 132				}
 133				else
 134				{
 135					$conversions[$ftype['fieldtype_id']] = $firsts[$ftype['class']];
 136				}
 137			}
 138
 139			if ($conversions)
 140			{
 141				// remove duplicate ftype rows in exp_ff_fieldtypes
 142				$sql[] = 'DELETE FROM exp_ff_fieldtypes WHERE fieldtype_id IN ('.implode(',', array_keys($conversions)).')';
 143
 144				// update field_type's in exp_weblog_fields
 145				foreach($conversions as $old_id => $new_id)
 146				{
 147					$sql[] = $DB->update_string('exp_weblog_fields', array('field_type' => 'ftype_id_'.$new_id), 'field_type = "ftype_id_'.$old_id.'"');
 148				}
 149			}
 150
 151			// remove site_id column from exp_ff_fieldtypes
 152			$sql[] = 'ALTER TABLE exp_ff_fieldtypes DROP COLUMN site_id';
 153
 154			// apply changes
 155			foreach($sql as $query)
 156			{
 157				$DB->query($query);
 158			}
 159		}
 160
 161		if ($current < '1.1.0')
 162		{
 163			// hooks have changed, so go through
 164			// the whole activate_extension() process
 165			$this->activate_extension();
 166		}
 167		else
 168		{
 169			// just update the version #s
 170			$DB->query('UPDATE exp_extensions
 171			              SET version = "'.FF_VERSION.'"
 172			              WHERE class = "'.FF_CLASS.'"');
 173		}
 174	}
 175
 176	/**
 177	 * Disable Extension
 178	 */
 179	function disable_extension()
 180	{
 181		global $DB;
 182		$DB->query($DB->update_string('exp_extensions', array('enabled' => 'n'), 'class = "'.FF_CLASS.'"'));
 183	}
 184
 185	/**
 186	 * Settings Form
 187	 *
 188	 * @param array  $settings
 189	 */
 190	function settings_form($settings=array())
 191	{
 192		$this->_init_main($settings);
 193
 194		global $FF;
 195		$FF->settings_form();
 196	}
 197
 198	function save_settings()
 199	{
 200		$this->_init_main(array(), TRUE);
 201
 202		global $FF;
 203		$FF->save_settings();
 204	}
 205
 206	/**
 207	 * Initialize Main class
 208	 *
 209	 * @param  array  $settings
 210	 * @access private
 211	 */
 212	function _init_main($settings, $force=FALSE)
 213	{
 214		global $SESS, $FF;
 215
 216		if ( ! isset($FF) OR $force)
 217		{
 218			$FF = new Fieldframe_Main($settings, $this->hooks);
 219		}
 220	}
 221
 222	/**
 223	 * __call Magic Method
 224	 *
 225	 * Routes calls to missing methods to $FF
 226	 *
 227	 * @param string  $method  Name of the missing method
 228	 * @param array   $args    Arguments sent to the missing method
 229	 */
 230	function __call($method, $args)
 231	{
 232		global $FF, $EXT;
 233
 234		if (isset($FF))
 235		{
 236			if (method_exists($FF, $method))
 237			{
 238				return call_user_func_array(array(&$FF, $method), $args);
 239			}
 240			else if (substr($method, 0, 13) == 'forward_hook:')
 241			{
 242				$ext = explode(':', $method); // [ forward_hook, hook name, priority ]
 243				return call_user_func_array(array(&$FF, 'forward_hook'), array($ext[1], $ext[2], $args));
 244			}
 245		}
 246
 247		return FALSE;
 248	}
 249
 250}
 251
 252/**
 253 * FieldFrame_Main Class
 254 *
 255 * Provides the core extension logic + hooks
 256 *
 257 * @package   FieldFrame
 258 */
 259class Fieldframe_Main {
 260
 261	function log()
 262	{
 263		foreach(func_get_args() as $var)
 264		{
 265			if (is_string($var))
 266			{
 267				echo "<code style='display:block; margin:0; padding:5px 10px;'>{$var}</code>";
 268			}
 269			else
 270			{
 271				echo '<pre style="display:block; margin:0; padding:5px 10px; width:auto">';
 272				print_r($var);
 273				echo '</pre>';
 274			}
 275		}
 276	}
 277
 278	var $errors = array();
 279	var $postponed_saves = array();
 280	var $snippets = array('head' => array(), 'body' => array());
 281
 282	var $saef = FALSE;
 283
 284	/**
 285	 * FieldFrame_Main Class Initialization
 286	 *
 287	 * @param array  $settings
 288	 */
 289	function __construct($settings, $hooks)
 290	{
 291		global $SESS, $DB;
 292
 293		$this->hooks = $hooks;
 294
 295		// merge settings with defaults
 296		$default_settings = array(
 297			'fieldtypes_url' => '',
 298			'fieldtypes_path' => '',
 299			'check_for_updates' => 'y'
 300		);
 301		$this->settings = array_merge($default_settings, $settings);
 302
 303		// set the FT_PATH and FT_URL constants
 304		$this->_define_constants();
 305	}
 306
 307	/**
 308	 * Define Constants
 309	 *
 310	 * @access private
 311	 */
 312	function _define_constants()
 313	{
 314		global $PREFS;
 315
 316		if ( ! defined('FT_PATH') AND ($ft_path = isset($PREFS->core_ini['ft_path']) ? $PREFS->core_ini['ft_path'] : $this->settings['fieldtypes_path']))
 317		{
 318			define('FT_PATH', $ft_path);
 319		}
 320
 321		if ( ! defined('FT_URL') AND ($ft_path = isset($PREFS->core_ini['ft_url']) ? $PREFS->core_ini['ft_url'] : $this->settings['fieldtypes_url']))
 322		{
 323			define('FT_URL', $ft_path);
 324			$this->snippets['body'][] = '<script type="text/javascript">;FT_URL = "'.FT_URL.'";</script>';
 325		}
 326	}
 327
 328	/**
 329	 * Array Ascii to Entities
 330	 *
 331	 * @access private
 332	 */
 333	function _array_ascii_to_entities($vals)
 334	{
 335		if (is_array($vals))
 336		{
 337			foreach ($vals as &$val)
 338			{
 339				$val = $this->_array_ascii_to_entities($val);
 340			}
 341		}
 342		else if (! is_numeric($vals))
 343		{
 344			global $REGX;
 345			$vals = $REGX->ascii_to_entities($vals);
 346		}
 347
 348		return $vals;
 349	}
 350
 351	/**
 352	 * Array Ascii to Entities
 353	 *
 354	 * @access private
 355	 */
 356	function _array_entities_to_ascii($vals)
 357	{
 358		if (is_array($vals))
 359		{
 360			foreach ($vals as &$val)
 361			{
 362				$val = $this->_array_entities_to_ascii($val);
 363			}
 364		}
 365		else
 366		{
 367			global $REGX;
 368			$vals = $REGX->entities_to_ascii($vals);
 369		}
 370
 371		return $vals;
 372	}
 373
 374	/**
 375	 * Serialize
 376	 *
 377	 * @access private
 378	 */
 379	function _serialize($vals)
 380	{
 381		global $PREFS;
 382
 383		if ($PREFS->ini('auto_convert_high_ascii') == 'y')
 384		{
 385			$vals = $this->_array_ascii_to_entities($vals);
 386		}
 387
 388     	return addslashes(serialize($vals));
 389	}
 390
 391	/**
 392	 * Unserialize
 393	 *
 394	 * @access private
 395	 */
 396	function _unserialize($vals, $convert=TRUE)
 397	{
 398		global $REGX, $PREFS;
 399
 400		if ($vals && is_string($vals) && preg_match('/^(i|s|a|o|d):(.*);/si', $vals) && ($tmp_vals = @unserialize($vals)) !== FALSE)
 401		{
 402			$vals = $REGX->array_stripslashes($tmp_vals);
 403
 404			if ($convert AND $PREFS->ini('auto_convert_high_ascii') == 'y')
 405			{
 406				$vals = $this->_array_entities_to_ascii($vals);
 407			}
 408		}
 409
 410     	return $vals;
 411	}
 412
 413	/**
 414	 * Get Fieldtypes
 415	 *
 416	 * @return array  All enabled FF fieldtypes, indexed by class name
 417	 * @access private
 418	 */
 419	function _get_ftypes()
 420	{
 421		global $DB;
 422
 423		if ( ! isset($this->ftypes))
 424		{
 425			$this->ftypes = array();
 426
 427			// get enabled fields from the DB
 428			$query = $DB->query('SELECT * FROM exp_ff_fieldtypes WHERE enabled = "y"');
 429
 430			if ($query->num_rows)
 431			{
 432				foreach($query->result as $row)
 433				{
 434					if (($ftype = $this->_init_ftype($row)) !== FALSE)
 435					{
 436						$this->ftypes[] = $ftype;
 437					}
 438				}
 439
 440				$this->_sort_ftypes($this->ftypes);
 441			}
 442		}
 443
 444		return $this->ftypes;
 445	}
 446
 447	function _get_ftype($class_name)
 448	{
 449		$ftypes = $this->_get_ftypes();
 450		return isset($ftypes[$class_name])
 451		  ?  $ftypes[$class_name]
 452		  :  FALSE;
 453	}
 454
 455	/**
 456	 * Get All Installed Fieldtypes
 457	 *
 458	 * @return array  All installed FF fieldtypes, indexed by class name
 459	 * @access private
 460	 */
 461	function _get_all_installed_ftypes()
 462	{
 463		$ftypes = array();
 464
 465		if (defined('FT_PATH'))
 466		{
 467			if ( $fp = @opendir(FT_PATH))
 468			{
 469				// iterate through the field folder contents
 470				while (($file = readdir($fp)) !== FALSE)
 471				{
 472					// skip hidden/navigational files
 473					if (substr($file, 0, 1) == '.') continue;
 474
 475					// is this a directory, and does a ftype file exist inside it?
 476					if (is_dir(FT_PATH.$file) AND is_file(FT_PATH.$file.'/ft.'.$file.EXT))
 477					{
 478						if (($ftype = $this->_init_ftype($file, FALSE)) !== FALSE)
 479						{
 480							$ftypes[] = $ftype;
 481						}
 482					}
 483				}
 484				closedir($fp);
 485
 486				$this->_sort_ftypes($ftypes);
 487			}
 488			else
 489			{
 490				$this->errors[] = 'bad_ft_path';
 491			}
 492		}
 493
 494		return $ftypes;
 495	}
 496
 497	/**
 498	 * Sort Fieldtypes
 499	 *
 500	 * @param  array  $ftypes  the array of fieldtypes
 501	 * @access private
 502	 */
 503	function _sort_ftypes(&$ftypes)
 504	{
 505		$ftypes_by_name = array();
 506		while($ftype = array_shift($ftypes))
 507		{
 508			$ftypes_by_name[$ftype->info['name']] = $ftype;
 509		}
 510		ksort($ftypes_by_name);
 511		foreach($ftypes_by_name as $ftype)
 512		{
 513			$ftypes[$ftype->_class_name] = $ftype;
 514		}
 515	}
 516
 517	/**
 518	 * Get Fieldtypes Indexed By Field ID
 519	 *
 520	 * @return array  All enabled FF fieldtypes, indexed by the weblog field ID they're used in.
 521	 *                Strong possibility that there will be duplicate fieldtypes in here,
 522	 *                but it's not a big deal because they're just object references
 523	 * @access private
 524	 */
 525	function _get_fields()
 526	{
 527		global $DB, $REGX;
 528
 529		if ( ! isset($this->ftypes_by_field_id))
 530		{
 531			$this->ftypes_by_field_id = array();
 532
 533			// get the fieldtypes
 534			if ($ftypes = $this->_get_ftypes())
 535			{
 536				// index them by ID rather than class
 537				$ftypes_by_id = array();
 538				foreach($ftypes as $class_name => $ftype)
 539				{
 540					$ftypes_by_id[$ftype->_fieldtype_id] = $ftype;
 541				}
 542
 543				// get the field info
 544				$query = $DB->query("SELECT field_id, site_id, field_name, field_type, ff_settings FROM exp_weblog_fields
 545				                       WHERE field_type IN ('ftype_id_".implode("', 'ftype_id_", array_keys($ftypes_by_id))."')");
 546				if ($query->num_rows)
 547				{
 548					// sort the current site's fields on top
 549					function sort_fields($a, $b)
 550					{
 551						global $PREFS;
 552						if ($a['site_id'] == $b['site_id']) return 0;
 553						if ($a['site_id'] == $PREFS->ini('site_id')) return 1;
 554						if ($b['site_id'] == $PREFS->ini('site_id')) return -1;
 555						return 0;
 556					}
 557					usort($query->result, 'sort_fields');
 558
 559					foreach($query->result as $row)
 560					{
 561						$ftype_id = substr($row['field_type'], 9);
 562						$this->ftypes_by_field_id[$row['field_id']] = array(
 563							'name' => $row['field_name'],
 564							'ftype' => $ftypes_by_id[$ftype_id],
 565							'settings' => array_merge(
 566							                           (isset($ftypes_by_id[$ftype_id]->default_field_settings) ? $ftypes_by_id[$ftype_id]->default_field_settings : array()),
 567							                           ($row['ff_settings'] ? (array)$this->_unserialize($row['ff_settings']) : array())
 568							                          )
 569						);
 570					}
 571				}
 572			}
 573		}
 574
 575		return $this->ftypes_by_field_id;
 576	}
 577
 578	/**
 579	 * Initialize Fieldtype
 580	 *
 581	 * @param  mixed   $ftype  fieldtype's class name or its row in exp_ff_fieldtypes
 582	 * @return object  Initialized fieldtype object
 583	 * @access private
 584	 */
 585	function _init_ftype($ftype, $req_strict=TRUE)
 586	{
 587		global $DB, $REGX, $LANG;
 588
 589		$file = is_array($ftype) ? $ftype['class'] : $ftype;
 590		$class_name = ucfirst($file);
 591
 592		if ( ! class_exists($class_name) && ! class_exists($class_name.'_ft'))
 593		{
 594			// import the file
 595			@include(FT_PATH.$file.'/ft.'.$file.EXT);
 596
 597			// skip if the class doesn't exist
 598			if ( ! class_exists($class_name) && ! class_exists($class_name.'_ft'))
 599			{
 600				return FALSE;
 601			}
 602		}
 603
 604		$_class_name = $class_name . (class_exists($class_name) ? '' : '_ft');
 605		$OBJ = new $_class_name();
 606
 607		// is this a FieldFrame fieldtype?
 608		if ( ! isset($OBJ->_fieldframe)) return FALSE;
 609
 610		$OBJ->_class_name = $file;
 611		$OBJ->_is_new     = FALSE;
 612		$OBJ->_is_enabled = FALSE;
 613		$OBJ->_is_updated = FALSE;
 614
 615		// settings
 616		$OBJ->site_settings = isset($OBJ->default_site_settings)
 617		  ?  $OBJ->default_site_settings
 618		  :  array();
 619
 620		// info
 621		if ( ! isset($OBJ->info)) $OBJ->info = array();
 622
 623		// requirements
 624		if ( ! isset($OBJ->_requires)) $OBJ->_requires = array();
 625		if (isset($OBJ->requires))
 626		{
 627			// PHP
 628			if (isset($OBJ->requires['php']) AND phpversion() < $OBJ->requires['php'])
 629			{
 630				if ($req_strict) return FALSE;
 631				else $OBJ->_requires['PHP'] = $OBJ->requires['php'];
 632			}
 633
 634			// ExpressionEngine
 635			if (isset($OBJ->requires['ee']) AND APP_VER < $OBJ->requires['ee'])
 636			{
 637				if ($req_strict) return FALSE;
 638				else $OBJ->_requires['ExpressionEngine'] = $OBJ->requires['ee'];
 639			}
 640
 641			// ExpressionEngine Build
 642			if (isset($OBJ->requires['ee_build']) AND APP_BUILD < $OBJ->requires['ee_build'])
 643			{
 644				if ($req_strict) return FALSE;
 645				else
 646				{
 647					$req = substr(strtolower($LANG->line('build')), 0, -1).' '.$OBJ->requires['ee_build'];
 648					if (isset($OBJ->_requires['ExpressionEngine'])) $OBJ->_requires['ExpressionEngine'] .= ' '.$req;
 649					else $OBJ->_requires['ExpressionEngine'] = $req;
 650				}
 651			}
 652
 653			// FieldFrame
 654			if (isset($OBJ->requires['ff']) AND FF_VERSION < $OBJ->requires['ff'])
 655			{
 656				if ($req_strict) return FALSE;
 657				else $OBJ->_requires[FF_NAME] = $OBJ->requires['ff'];
 658			}
 659
 660			// CP jQuery
 661			if (isset($OBJ->requires['cp_jquery']))
 662			{
 663				if ( ! isset($OBJ->requires['ext']))
 664				{
 665					$OBJ->requires['ext'] = array();
 666				}
 667				$OBJ->requires['ext'][] = array('class' => 'Cp_jquery', 'name' => 'CP jQuery', 'url' => 'http://www.ngenworks.com/software/ee/cp_jquery/', 'version' => $OBJ->requires['cp_jquery']);
 668			}
 669
 670			// Extensions
 671			if (isset($OBJ->requires['ext']))
 672			{
 673				if ( ! isset($this->req_ext_versions))
 674				{
 675					$this->req_ext_versions = array();
 676				}
 677				foreach($OBJ->requires['ext'] as $ext)
 678				{
 679					if ( ! isset($this->req_ext_versions[$ext['class']]))
 680					{
 681						$ext_query = $DB->query('SELECT version FROM exp_extensions
 682						                           WHERE class="'.$ext['class'].'"'
 683						                           . (isset($ext['version']) ? ' AND version >= "'.$ext['version'].'"' : '')
 684						                           . ' AND enabled = "y"
 685						                           ORDER BY version DESC
 686						                           LIMIT 1');
 687						$this->req_ext_versions[$ext['class']] = $ext_query->row
 688						  ?  $ext_query->row['version']
 689						  :  '';
 690					}
 691					if ($this->req_ext_versions[$ext['class']] < $ext['version'])
 692					{
 693						if ($req_strict) return FALSE;
 694						else
 695						{
 696							$name = isset($ext['name']) ? $ext['name'] : ucfirst($ext['class']);
 697							if ($ext['url']) $name = '<a href="'.$ext['url'].'">'.$name.'</a>';
 698							$OBJ->_requires[$name] = $ext['version'];
 699						}
 700					}
 701				}
 702			}
 703		}
 704
 705		if ( ! isset($OBJ->info['name'])) $OBJ->info['name'] = ucwords(str_replace('_', ' ', $class_name));
 706		if ( ! isset($OBJ->info['version'])) $OBJ->info['version'] = '';
 707		if ( ! isset($OBJ->info['desc'])) $OBJ->info['desc'] = '';
 708		if ( ! isset($OBJ->info['docs_url'])) $OBJ->info['docs_url'] = '';
 709		if ( ! isset($OBJ->info['author'])) $OBJ->info['author'] = '';
 710		if ( ! isset($OBJ->info['author_url'])) $OBJ->info['author_url'] = '';
 711		if ( ! isset($OBJ->info['versions_xml_url'])) $OBJ->info['versions_xml_url'] = '';
 712		if ( ! isset($OBJ->info['no_lang'])) $OBJ->info['no_lang'] = FALSE;
 713
 714		if ( ! isset($OBJ->hooks)) $OBJ->hooks = array();
 715		if ( ! isset($OBJ->postpone_saves)) $OBJ->postpone_saves = FALSE;
 716
 717		// do we already know about this fieldtype?
 718		if (is_string($ftype))
 719		{
 720			$query = $DB->query('SELECT * FROM exp_ff_fieldtypes WHERE class = "'.$file.'"');
 721			$ftype = $query->row;
 722		}
 723
 724		if ($ftype)
 725		{
 726			$OBJ->_fieldtype_id = $ftype['fieldtype_id'];
 727
 728			if ($ftype['enabled'] == 'y')
 729			{
 730				$OBJ->_is_enabled = TRUE;
 731			}
 732
 733			if ($ftype['settings'])
 734			{
 735				if (is_array($ftype_settings = $this->_unserialize($ftype['settings'])))
 736				{
 737					$OBJ->site_settings = array_merge($OBJ->site_settings, $ftype_settings);
 738				}
 739			}
 740
 741			// new version?
 742			if ($OBJ->info['version'] != $ftype['version'])
 743			{
 744				$OBJ->_is_updated = TRUE;
 745
 746				// update exp_ff_fieldtypes
 747				$DB->query($DB->update_string('exp_ff_fieldtypes',
 748				                              array('version' => $OBJ->info['version']),
 749				                              'fieldtype_id = "'.$ftype['fieldtype_id'].'"'));
 750
 751				// update the hooks
 752				$this->_insert_ftype_hooks($OBJ);
 753
 754				// call update()
 755				if (method_exists($OBJ, 'update'))
 756				{
 757					$OBJ->update($ftype['version']);
 758				}
 759			}
 760		}
 761		else
 762		{
 763			$OBJ->_is_new = TRUE;
 764		}
 765
 766		return $OBJ;
 767	}
 768
 769	/**
 770	 * Insert Fieldtype Hooks
 771	 *
 772	 * @access private
 773	 */
 774	function _insert_ftype_hooks($ftype)
 775	{
 776		global $DB, $FF;
 777
 778		// remove any existing hooks from exp_ff_fieldtype_hooks
 779		$DB->query('DELETE FROM exp_ff_fieldtype_hooks
 780		              WHERE class = "'.$ftype->_class_name.'"');
 781
 782		// (re)insert the hooks
 783		if ($ftype->hooks)
 784		{
 785			foreach($ftype->hooks as $hook => $data)
 786			{
 787				if (is_string($data))
 788				{
 789					$hook = $data;
 790					$data = array();
 791				}
 792
 793				// exp_ff_fieldtype_hooks
 794				$data = array_merge(array('method' => $hook, 'priority' => 10), $data, array('hook' => $hook, 'class' => $ftype->_class_name));
 795				$DB->query($DB->insert_string('exp_ff_fieldtype_hooks', $data));
 796
 797				// exp_extensions
 798				$hooks_q = $DB->query('SELECT extension_id FROM exp_extensions WHERE class = "'.FF_CLASS.'" AND hook = "'.$hook.'" AND priority = "'.$data['priority'].'"');
 799				if ( ! $hooks_q->num_rows)
 800				{
 801					$ext_data = array('class' => FF_CLASS, 'method' => 'forward_hook:'.$hook.':'.$data['priority'], 'hook' => $hook, 'settings' => '', 'priority' => $data['priority'], 'version' => FF_VERSION, 'enabled' => 'y');
 802					$DB->query($DB->insert_string('exp_extensions', $ext_data));
 803				}
 804			}
 805		}
 806
 807		// reset cached hooks array
 808		$this->_get_ftype_hooks(TRUE);
 809	}
 810
 811	function _get_ftype_hooks($reset=FALSE)
 812	{
 813		global $DB;
 814
 815		if ($reset OR ! isset($this->ftype_hooks))
 816		{
 817			$this->ftype_hooks = array();
 818
 819			$hooks_q = $DB->query('SELECT * FROM exp_ff_fieldtype_hooks');
 820			foreach($hooks_q->result as $hook_r)
 821			{
 822				$this->ftype_hooks[$hook_r['hook']][$hook_r['priority']][$hook_r['class']] = $hook_r['method'];
 823			}
 824		}
 825
 826		return $this->ftype_hooks;
 827	}
 828
 829	/**
 830	 * Group Inputs
 831	 *
 832	 * @param  string  $name_prefix  The Fieldtype ID
 833	 * @param  string  $settings     The Fieldtype settings
 834	 * @return string  The modified settings
 835	 * @access private
 836	 */
 837	function _group_inputs($name_prefix, $settings)
 838	{
 839		return preg_replace('/(name=([\'\"]))([^\'"\[\]]+)([^\'"]*)(\2)/i', '$1'.$name_prefix.'[$3]$4$5', $settings);
 840	}
 841
 842	/**
 843	 * Group Fieldtype Inputs
 844	 *
 845	 * @param  string  $ftype_id  The Fieldtype ID
 846	 * @param  string  $settings  The Fieldtype settings
 847	 * @return string  The modified settings
 848	 * @access private
 849	 */
 850	function _group_ftype_inputs($ftype_id, $settings)
 851	{
 852		return $this->_group_inputs('ftype['.$ftype_id.']', $settings);
 853	}
 854
 855	/**
 856	 * Activate Extension
 857	 */
 858	function activate_extension($settings)
 859	{
 860		global $DB;
 861
 862		// Delete old hooks
 863		$DB->query('DELETE FROM exp_extensions
 864		              WHERE class = "'.FF_CLASS.'"');
 865
 866		// Add new extensions
 867		$hook_tmpl = array(
 868			'class'    => FF_CLASS,
 869			'settings' => $this->_serialize($settings),
 870			'priority' => 10,
 871			'version'  => FF_VERSION,
 872			'enabled'  => 'y'
 873		);
 874
 875		foreach($this->hooks as $hook => $data)
 876		{
 877			if (is_string($data))
 878			{
 879				$hook = $data;
 880				$data = array();
 881			}
 882			$data = array_merge($hook_tmpl, array('hook' => $hook, 'method' => $hook), $data);
 883			$DB->query($DB->insert_string('exp_extensions', $data));
 884		}
 885
 886		// exp_ff_fieldtypes
 887		if ( ! $DB->table_exists('exp_ff_fieldtypes'))
 888		{
 889			$DB->query("CREATE TABLE exp_ff_fieldtypes (
 890			              `fieldtype_id` int(10) unsigned NOT NULL auto_increment,
 891			              `class`        varchar(50)      NOT NULL default '',
 892			              `version`      varchar(10)      NOT NULL default '',
 893			              `settings`     text             NOT NULL default '',
 894			              `enabled`      char(1)          NOT NULL default 'n',
 895			              PRIMARY KEY (`fieldtype_id`)
 896			            )");
 897		}
 898
 899		// exp_ff_fieldtype_hooks
 900		if ( ! $DB->table_exists('exp_ff_fieldtype_hooks'))
 901		{
 902			$DB->query("CREATE TABLE exp_ff_fieldtype_hooks (
 903			              `hook_id`  int(10) unsigned NOT NULL auto_increment,
 904			              `class`    varchar(50)      NOT NULL default '',
 905			              `hook`     varchar(50)      NOT NULL default '',
 906			              `method`   varchar(50)      NOT NULL default '',
 907			              `priority` int(2)           NOT NULL DEFAULT '10',
 908			              PRIMARY KEY (`hook_id`)
 909			            )");
 910		}
 911
 912		// exp_weblog_fields.ff_settings
 913		$query = $DB->query('SHOW COLUMNS FROM `'.$DB->prefix.'weblog_fields` LIKE "ff_settings"');
 914		if ( ! $query->num_rows)
 915		{
 916			$DB->query('ALTER TABLE `'.$DB->prefix.'weblog_fields` ADD COLUMN `ff_settings` text NOT NULL');
 917		}
 918
 919		// reset all ftype hooks
 920		foreach($this->_get_ftypes() as $class_name => $ftype)
 921		{
 922			$this->_insert_ftype_hooks($ftype);
 923		}
 924	}
 925
 926	/**
 927	 * Settings Form
 928	 *
 929	 * @param array  $current  Current extension settings (not site-specific)
 930	 * @see   http://expressionengine.com/docs/development/extensions.html#settings
 931	 */
 932	function settings_form()
 933	{
 934		global $DB, $DSP, $LANG, $IN, $SD, $PREFS;
 935
 936		// Breadcrumbs
 937		$DSP->crumbline = TRUE;
 938		$DSP->title = $LANG->line('extension_settings');
 939		$DSP->crumb = $DSP->anchor(BASE.AMP.'C=admin'.AMP.'area=utilities', $LANG->line('utilities'))
 940		            . $DSP->crumb_item($DSP->anchor(BASE.AMP.'C=admin'.AMP.'M=utilities'.AMP.'P=extensions_manager', $LANG->line('extensions_manager')))
 941		            . $DSP->crumb_item(FF_NAME);
 942		$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'));
 943
 944		// Donations button
 945	    $DSP->body .= '<div class="donations">'
 946	                . '<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=2181794" target="_blank">'
 947	                . $LANG->line('donate')
 948	                . '</a>'
 949	                . '</div>';
 950
 951		// open form
 952		$DSP->body .= '<h1>'.FF_NAME.' <small>'.FF_VERSION.'</small></h1>'
 953		            . $DSP->form_open(
 954		                  array(
 955		                    'action' => 'C=admin'.AMP.'M=utilities'.AMP.'P=save_extension_settings',
 956		                    'name'   => 'settings_subtext',
 957		                    'id'     => 'ffsettings'
 958		                  ),
 959		                  array(
 960		                    'name' => strtolower(FF_CLASS)
 961		                  ));
 962
 963		// initialize Fieldframe_SettingsDisplay
 964		$SD = new Fieldframe_SettingsDisplay();
 965
 966		// import lang files
 967		$LANG->fetch_language_file('publish_ad');
 968
 969		// fieldtypes folder
 970		$DSP->body .= $SD->block('fieldtypes_folder_title')
 971		            . $SD->info_row('fieldtypes_folder_info')
 972		            . $SD->row(array(
 973		                         $SD->label('fieldtypes_path_label', 'fieldtypes_path_subtext'),
 974		                         $SD->text('fieldtypes_path',
 975		                                       (isset($PREFS->core_ini['ft_path']) ? $PREFS->core_ini['ft_path'] : $this->settings['fieldtypes_path']),
 976		                                       array('extras' => (isset($PREFS->core_ini['ft_path']) ? ' disabled="disabled" ' : '')))
 977		                       ))
 978		            . $SD->row(array(
 979		                         $SD->label('fieldtypes_url_label', 'fieldtypes_url_subtext'),
 980		                         $SD->text('fieldtypes_url',
 981		                                       (isset($PREFS->core_ini['ft_url']) ? $PREFS->core_ini['ft_url'] : $this->settings['fieldtypes_url']),
 982		                                       array('extras' => (isset($PREFS->core_ini['ft_url']) ? ' disabled="disabled" ' : '')))
 983		                       ))
 984		            . $SD->block_c();
 985
 986		// Check for Updates
 987		$lgau_query = $DB->query("SELECT class FROM exp_extensions
 988		                            WHERE class = 'Lg_addon_updater_ext' AND enabled = 'y' LIMIT 1");
 989		$lgau_enabled = $lgau_query->num_rows ? TRUE : FALSE;
 990		$DSP->body .= $SD->block('check_for_updates_title')
 991		            . $SD->info_row('check_for_updates_info')
 992		            . $SD->row(array(
 993		                           $SD->label('check_for_updates_label', 'check_for_updates_subtext'),
 994		                           $SD->radio_group('check_for_updates', (($this->settings['check_for_updates'] != 'n') ? 'y' : 'n'), array('y'=>'yes', 'n'=>'no'))
 995		                         ))
 996		            . $SD->block_c();
 997
 998		// Fieldtypes Manager
 999		$this->fieldtypes_manager(FALSE, $SD);
1000
1001		// Close form
1002		$DSP->body .= $DSP->qdiv('itemWrapperTop', $DSP->input_submit())
1003		            . $DSP->form_c();
1004
1005
1006		ob_start();
1007?>
1008<style type="text/css" charset="utf-8">
1009  .donations { float:right; }
1010  .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; }
1011  h1 { padding:7px 0; }
1012</style>
1013<?php
1014		$this->snippets['head'][] = ob_get_contents();
1015		ob_end_clean();
1016	}
1017
1018	/**
1019	 * Add Slash to URL/Path
1020	 *
1021	 * @param  string  $path  The user-submitted path
1022	 * @return string  $path with a slash at the end
1023	 * @access private
1024	 */
1025	function _add_slash($path)
1026	{
1027		if (substr($path, -1) != '/')
1028		{
1029			$path .= '/';
1030		}
1031		return $path;
1032	}
1033
1034	/**
1035	 * Save Settings
1036	 */
1037	function save_settings()
1038	{
1039		global $DB;
1040
1041		// save new FF site settings
1042		$this->settings = array(
1043			'fieldtypes_url'    => ((isset($_POST['fieldtypes_url']) AND $_POST['fieldtypes_url']) ? $this->_add_slash($_POST['fieldtypes_url']) : ''),
1044			'fieldtypes_path'   => ((isset($_POST['fieldtypes_path']) AND $_POST['fieldtypes_path']) ? $this->_add_slash($_POST['fieldtypes_path']) : ''),
1045			'check_for_updates' => ($_POST['check_for_updates'] != 'n' ? 'y' : 'n')
1046		);
1047		$DB->query($DB->update_string('exp_extensions', array('settings' => $this->_serialize($this->settings)), 'class = "'.FF_CLASS.'"'));
1048
1049		// set the FT_PATH and FT_URL constants
1050		$this->_define_constants();
1051
1052		// save Fieldtypes Manager data
1053		$this->save_fieldtypes_manager();
1054	}
1055
1056	/**
1057	 * Fieldtypes Manager
1058	 */
1059	function fieldtypes_manager($standalone=TRUE, $SD=NULL)
1060	{
1061		global $DB, $DSP, $LANG, $IN, $PREFS, $SD;
1062
1063		// are they allowed to access this?
1064		if (! $DSP->allowed_group('can_admin_utilities'))
1065		{
1066			return $DSP->no_access_message();
1067		}
1068
1069		if ( ! $SD)
1070		{
1071			// initialize Fieldframe_SettingsDisplay
1072			$SD = new Fieldframe_SettingsDisplay();
1073		}
1074
1075		if ($standalone)
1076		{
1077			// save submitted settings
1078			if ($this->save_fieldtypes_manager())
1079			{
1080				$DSP->body .= $DSP->qdiv('box', $DSP->qdiv('success', $LANG->line('settings_update')));
1081			}
1082
1083			// load language file
1084			$LANG->fetch_language_file('fieldframe');
1085
1086			// Breadcrumbs
1087			$DSP->crumbline = TRUE;
1088			$DSP->title = $LANG->line('fieldtypes_manager');
1089			$DSP->crumb = $DSP->anchor(BASE.AMP.'C=admin'.AMP.'area=utilities', $LANG->line('utilities'))
1090			            . $DSP->crumb_item($LANG->line('fieldtypes_manager'));
1091
1092			// open form
1093			$DSP->body .= $DSP->form_open(
1094			                  array(
1095			                    'action' => 'C=admin'.AMP.'M=utilities'.AMP.'P=fieldtypes_manager',
1096			                    'name'   => 'settings_subtext',
1097			                    'id'     => 'ffsettings'
1098			                  ),
1099			                  array(
1100			                    'name' => strtolower(FF_CLASS)
1101			                  ));
1102		}
1103
1104		// fieldtype settings
1105		$DSP->body .= $SD->block('fieldtypes_manager', 5);
1106
1107		// initialize fieldtypes
1108		if ($ftypes = $this->_get_all_installed_ftypes())
1109		{
1110			// add the headers
1111			$DSP->body .= $SD->heading_row(array(
1112			                                   $LANG->line('fieldtype'),
1113			                                   $LANG->line('fieldtype_enabled'),
1114			                                   $LANG->line('settings'),
1115			                                   $LANG->line('documentation')
1116			                                 ));
1117
1118			$row_ids = array();
1119
1120			foreach($ftypes as $class_name => $ftype)
1121			{
1122				$row_id = 'ft_'.$ftype->_class_name;
1123				$row_ids[] = '"'.$row_id.'"';
1124
1125				if (method_exists($ftype, 'display_site_settings'))
1126				{
1127					if ( ! $ftype->info['no_lang']) $LANG->fetch_language_file($class_name);
1128					$site_settings = $ftype->display_site_settings();
1129				}
1130				else
1131				{
1132					$site_settings = FALSE;
1133				}
1134
1135				$DSP->body .= $SD->row(array(
1136				                         $SD->label($ftype->info['name'].NBS.$DSP->qspan('xhtmlWrapperLight defaultSmall', $ftype->info['version']), $ftype->info['desc']),
1137				                         ($ftype->_requires
1138				                            ?  '--'
1139				                            :  $SD->radio_group('ftypes['.$class_name.'][enabled]', ($ftype->_is_enabled ? 'y' : 'n'), array('y'=>'yes', 'n'=>'no'))),
1140				                         ($site_settings
1141				                            ?  '<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>'
1142				                               . '<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>'
1143				                            :  '--'),
1144				                         ($ftype->info['docs_url'] ? '<a href="'.stripslashes($ftype->info['docs_url']).'">'.$LANG->line('documentation').'</a>' : '--')
1145				                       ), NULL, array('id' => $row_id));
1146
1147				if ($ftype->_requires)
1148				{
1149					$data = '<p>'.$ftype->info['name'].' '.$LANG->line('requires').':</p>'
1150					      . '<ul>';
1151					foreach($ftype->_requires as $dependency => $version)
1152					{
1153						$data .= '<li class="default">'.$dependency.' '.$version.' '.$LANG->line('or_later').'</li>';
1154					}
1155					$data .= '</ul>';
1156					$DSP->body .= $SD->row(array('', $data), $SD->row_class);
1157				}
1158				else if ($site_settings)
1159				{
1160					$data = '<div class="ftsettings">'
1161					      .   $this->_group_ftype_inputs($ftype->_class_name, $site_settings)
1162					      . $DSP->div_c();
1163					$DSP->body .= $SD->row(array($data), '', array('id' => $row_id.'_settings', 'style' => 'display:none;'));
1164				}
1165			}
1166
1167			ob_start();
1168?>
1169<script type="text/javascript" charset="utf-8">
1170	;var urlParts = document.location.href.split('#'),
1171		anchor = urlParts[1];
1172	function ffEnable(ft) {
1173		ft.show.className = "toggle show";
1174		ft.show.onclick = function() {
1175			ft.show.style.display = "none";
1176			ft.hide.style.display = "block";
1177			ft.settings.style.display = "table-row";
1178		};
1179		ft.hide.onclick = function() {
1180			ft.show.style.display = "block";
1181			ft.hide.style.display = "none";
1182			ft.settings.style.display = "none";
1183		};
1184	}
1185	function ffDisable(ft) {
1186		ft.show.className = "toggle show disabled";
1187		ft.show.onclick = null;
1188		ft.show.style.display = "block";
1189		ft.hide.style.display = "none";
1190		ft.settings.style.display = "none";
1191	}
1192	function ffInitRow(rowId) {
1193		var ft = {
1194			tr: document.getElementById(rowId),
1195			show: document.getElementById(rowId+"_show"),
1196			hide: document.getElementById(rowId+"_hide"),
1197			settings: document.getElementById(rowId+"_settings")
1198		};
1199		if (ft.settings) {
1200			ft.toggles = ft.tr.getElementsByTagName("input");
1201			ft.toggles[0].onchange = function() { ffEnable(ft); };
1202			ft.toggles[1].onchange = function() { ffDisable(ft); };
1203			if (ft.toggles[0].checked) ffEnable(ft);
1204			else ffDisable(ft);
1205			if (anchor == rowId+'_settings') {
1206				ft.show.onclick();
1207			}
1208		}
1209	}
1210	var ffRowIds = [<?php echo implode(',', $row_ids) ?>];
1211	for (var i = 0; i < ffRowIds.length; i++) {
1212		ffInitRow(ffRowIds[i]);
1213	}
1214</script>
1215<?php
1216			$this->snippets['body'][] = ob_get_contents();
1217			ob_end_clean();
1218		}
1219		else if ( ! defined('FT_PATH'))
1220		{
1221			$DSP->body .= $SD->info_row('no_fieldtypes_path');
1222		}
1223		else if (in_array('bad_ft_path', $this->errors))
1224		{
1225			$DSP->body .= $SD->info_row('bad_fieldtypes_path');
1226		}
1227		else
1228		{
1229			$DSP->body .= $SD->info_row('no_fieldtypes');
1230		}
1231
1232		$DSP->body .= $SD->block_c();
1233
1234		if ($standalone)
1235		{
1236			// Close form
1237			$DSP->body .= $DSP->qdiv('itemWrapperTop', $DSP->input_submit($LANG->line('apply')))
1238			            . $DSP->form_c();
1239		}
1240
1241		ob_start();
1242?>
1243<style type="text/css" charset="utf-8">
1244	#ffsettings a.toggle { display:block; cursor:pointer; }
1245	#ffsettings a.toggle.hide { display:none; }
1246	#ffsettings a.toggle.disabled { color:#000; opacity:0.4; cursor:default; }
1247	#ffsettings .ftsettings { margin:-3px -1px -1px; }
1248	#ffsettings .ftsettings, #ffsettings .ftsettings .tableCellOne, #ffsettings .ftsettings .tableCellTwo, #ffsettings .ftsettings .box { background:#262e33; color:#999; }
1249	#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)); }
1250	#ffsettings .ftsettings table tr td .box { margin: -1px -8px; }
1251	#ffsettings .ftsettings a, #ffsettings .ftsettings p, #ffsettings .ftsettings .subtext { color: #999; }
1252	#ffsettings .ftsettings input.input, #ffsettings .ftsettings textarea { background:#fff; color:#333; }
1253	#ffsettings .ftsettings table { border:none; }
1254	#ffsettings .ftsettings table tr td { border-top:1px solid #1d2326; padding-left:8px; padding-right:8px;  }
1255	#ffsettings .ftsettings table tr td.tableHeading { color:#ddd; background:#232a2e; }
1256	#ffsettings .ftsettings .defaultBold { color:#ccc; }
1257	#ffsettings .ftsettings .tableCellOne, #ffsettings .ftsettings .tableCellOneBold, #ffsettings .ftsettings .tableCellTwo, #ffsettings .ftsettings .tableCellTwoBold { border-bottom:none; }
1258</style>
1259<?php
1260		$this->snippets['head'][] = ob_get_contents();
1261		ob_end_clean();
1262	}
1263
1264	/**
1265	 * Save Fieldtypes Manager Settings
1266	 */
1267	function save_fieldtypes_manager()
1268	{
1269		global $DB;
1270
1271		// fieldtype settings
1272		if (isset($_POST['ftypes']))
1273		{
1274			foreach($_POST['ftypes'] as $file => $ftype_post)
1275			{
1276				// Initialize
1277				if (($ftype = $this->_init_ftype($file)) !== FALSE)
1278				{
1279					$enabled = ($ftype_post['enabled'] == 'y');
1280
1281					// skip if new and not enabled yet
1282					if ( ! $enabled AND $ftype->_is_new) continue;
1283
1284					// insert a new row if it's new
1285					if ($enabled AND $ftype->_is_new)
1286					{
1287						$DB->query($DB->insert_string('exp_ff_fieldtypes', array(
1288							'class'   => $file,
1289							'version' => $ftype->info['version']
1290						)));
1291
1292						// get the fieldtype_id
1293						$query = $DB->query('SELECT fieldtype_id FROM exp_ff_fieldtypes WHERE class = "'.$file.'"');
1294						$ftype->_fieldtype_id = $query->row['fieldtype_id'];
1295
1296						// insert hooks
1297						$this->_insert_ftype_hooks($ftype);
1298
1299						// call update()
1300						if (method_exists($ftype, 'update'))
1301						{
1302							$ftype->update(FALSE);
1303						}
1304					}
1305
1306					$data = array(
1307						'enabled' => ($enabled ? 'y' : 'n')
1308					);
1309
1310					if ($enabled)
1311					{
1312						$settings = (isset($_POST['ftype']) AND isset($_POST['ftype'][$ftype->_class_name]))
1313						  ?  $_POST['ftype'][$ftype->_class_name]
1314						  :  array();
1315
1316						// let the fieldtype do what it wants with them
1317						if (method_exists($ftype, 'save_site_settings'))
1318						{
1319							$settings = $ftype->save_site_settings($settings);
1320							if ( ! is_array($settings)) $settings = array();
1321						}
1322						$data['settings'] = $this->_serialize($settings);
1323					}
1324
1325					$DB->query($DB->update_string('exp_ff_fieldtypes', $data, 'fieldtype_id = "'.$ftype->_fieldtype_id.'"'));
1326				}
1327			}
1328
1329			return TRUE;
1330		}
1331
1332		return FALSE;
1333	}
1334
1335	/**
1336	 * Get Last Call
1337	 *
1338	 * @param  mixed  $param  Parameter sent by extension hook
1339	 * @return mixed  Return value of last extension call if any, or $param
1340	 * @access private
1341	 */
1342	function get_last_call($param=FALSE)
1343	{
1344		global $EXT;
1345		return isset($this->_last_call)
1346		  ?  $this->_last_call
1347		  :  ($EXT->last_call !== FALSE ? $EXT->last_call : $param);
1348	}
1349
1350	/**
1351	 * Forward hook to fieldtype
1352	 */
1353	function forward_hook($hook, $priority, $args=array())
1354	{
1355		$ftype_hooks = $this->_get_ftype_hooks();
1356		if (isset($ftype_hooks[$hook]) AND isset($ftype_hooks[$hook][$priority]))
1357		{
1358			$ftypes = $this->_get_ftypes();
1359
1360			foreach ($ftype_hooks[$hook][$priority] as $class_name => $method)
1361			{
1362				if (isset($ftypes[$class_name]) AND method_exists($ftypes[$class_name], $method))
1363				{
1364					$this->_last_call = call_user_func_array(array(&$ftypes[$class_name], $method), $args);
1365				}
1366			}
1367		}
1368		if (isset($this->_last_call))
1369		{
1370			$r = $this->_last_call;
1371			unset($this->_last_call);
1372		}
1373		else
1374		{
1375			$r = $this->get_last_call();
1376		}
1377		return $r;
1378	}
1379
1380	function forward_ff_hook($hook, $args=array(), $r=TRUE)
1381	{
1382		$this->_last_call = $r;
1383		//           ------------ These braces brought to you by Leevi Graham ------------
1384		//          ↓                                                                     ↓
1385		$priority = (isset($this->hooks[$hook]) AND isset($this->hooks[$hook]['priority']))
1386		  ?  $this->hooks[$hook]['priority']
1387		  :  10;
1388		return $this->forward_hook($hook, $priority, $args);
1389	}
1390
1391	/**
1392	 * Get Line
1393	 *
1394	 * @param  string  $line  unlocalized string or the name of a $LANG line
1395	 * @return string  Localized string
1396	 */
1397	function get_line($line)
1398	{
1399		global $LANG;
1400		$loc_line = $LANG->line($line);
1401		return $loc_line ? $loc_line : $line;
1402	}
1403
1404	/**
1405	 * Sessions Start
1406	 *
1407	 * - Reset any session class variable
1408	 * - Override the whole session check
1409	 * - Modify default/guest settings
1410	 *
1411	 * @param object  $this  The current instantiated Session class with all of its variables and functions,
1412	 *                       use a reference in your functions to modify.
1413	 * @see   http://expressionengine.com/developers/extension_hooks/sessions_start/
1414	 */
1415	function sessions_start($sess)
1416	{
1417		global $IN;
1418
1419		// are we saving a field?
1420		if($IN->GBL('M', 'GET') == 'blog_admin' AND $IN->GBL('P', 'GET') == 'update_weblog_fields' AND isset($_POST['field_type']))
1421		{
1422			$this->publish_admin_edit_field_save();
1423		}
1424
1425		$args = func_get_args();
1426		return $this->forward_ff_hook('sessions_start', $args);
1427	}
1428
1429	/**
1430	 * Publish Admin - Edit Field Form - Fieldtype Menu
1431	 *
1432	 * Allows modifying or adding onto Custom Weblog Fieldtype Pulldown
1433	 *
1434	 * @param  array   $data  The data about this field from the database
1435	 * @param  string  $typemenu  The contents of the type menu
1436	 * @return string  The modified $typemenu
1437	 * @see    http://expressionengine.com/developers/extension_hooks/publish_admin_edit_field_type_pulldown/
1438	 */
1439	function publish_admin_edit_field_type_pulldown($data, $typemenu)
1440	{
1441		global $DSP;
1442
1443		$r = $this->get_last_call($typemenu);
1444
1445		foreach($this->_get_ftypes() as $class_name => $ftype)
1446		{
1447			// only list normal fieldtypes
1448			if (method_exists($ftype, 'display_field'))
1449			{
1450				$field_type = 'ftype_id_'.$ftype->_fieldtype_id;
1451				$r .= $DSP->input_select_option($field_type, $ftype->info['name'], ($data['field_type'] == $field_type ? 1 : 0));
1452			}
1453		}
1454
1455		$args = func_get_args();
1456		return $this->forward_ff_hook('publish_admin_edit_field_type_pulldown', $args, $r);
1457	}
1458
1459	/**
1460	 * Publish Admin - Edit Field Form - Javascript
1461	 *
1462	 * Allows modifying or adding onto Custom Weblog Field JS
1463	 *
1464	 * @param  array   $data  The data about this field from the database
1465	 * @param  string  $js    Currently existing javascript
1466	 * @return string  The modified $js
1467	 * @see    http://expressionengine.com/developers/extension_hooks/publish_admin_edit_field_js/
1468	 */
1469	function publish_admin_edit_field_js($data, $js)
1470	{
1471		global $LANG, $REGX;
1472
1473		// Fetch the FF lang file
1474		$LANG->fetch_language_file('fieldframe');
1475
1476		// Prepare fieldtypes for following Publish Admin hooks
1477		$field_settings_tmpl = array(
1478			'cell1' => '',
1479			'cell2' => '',
1480			'rows' => array(),
1481			'formatting_available' => FALSE,
1482			'direction_available' => FALSE
1483		);
1484
1485		$formatting_available = array();
1486		$direction_available = array();
1487		$prev_ftype_id = '';
1488
1489		$this->data = $data;
1490
1491		foreach($this->_get_ftypes() as $class_name => $ftype)
1492		{
1493			$ftype_id = 'ftype_id_'.$ftype->_fieldtype_id;
1494			$selected = ($ftype_id == $this->data['field_type']) ? TRUE : FALSE;
1495			if (method_exists($ftype, 'display_field_settings'))
1496			{
1497				// Load the language file
1498				if ( ! $ftype->info['no_lang']) $LANG->fetch_language_file($class_name);
1499
1500				$field_settings = array_merge(
1501					(isset($ftype->default_field_settings) ? $ftype->default_field_settings : array()),
1502					($selected ? $this->_unserialize($this->data['ff_settings']) : array())
1503				);
1504				$ftype->_field_settings = array_merge($field_settings_tmpl, $ftype->display_field_settings($field_settings));
1505			}
1506			else
1507			{
1508				$ftype->_field_settings = $field_settings_tmpl;
1509			}
1510
1511			if ($ftype->_field_settings['formatting_available']) $formatting_available[] = $ftype->_fieldtype_id;
1512			if ($ftype->_field_settings['direction_available']) $direction_available[] = $ftype->_fieldtype_id;
1513
1514			if ($selected) $prev_ftype_id = $ftype_id;
1515		}
1516
1517		unset($this->data);
1518
1519		// Add the JS
1520		ob_start();
1521?>
1522var prev_ftype_id = '<?php echo $prev_ftype_id ?>';
1523
1524	$1
1525		if (prev_ftype_id)
1526		{
1527			var c=1, r=1;
1528			while(cell = document.getElementById(prev_ftype_id+'_cell'+c))
1529			{
1530				cell.style.display = 'none';
1531				c++;
1532			}
1533			while(row = document.getElementById(prev_ftype_id+'_row'+r))
1534			{
1535				row.style.display = 'none';
1536				r++;
1537			}
1538		}
1539
1540		if (id.match(/^ftype_id_\d+$/))
1541		{
1542			var c=1, r=1;
1543
1544			// show cells
1545			while(cell = document.getElementById(id+'_cell'+c))
1546			{
1547				//var showDiv = document.getElementById(id+'_cell'+c);
1548				var divs = cell.parentNode.childNodes;
1549				for(var i=0; i < divs.length; i++)
1550				{
1551					var div = divs[i];
1552					if ( ! (div.nodeType == 1 && div.id)) continue;
1553					div.style.display = (div == cell) ? 'block' : 'none';
1554				}
1555				c++;
1556			}
1557
1558			// show rows
1559			while(row = document.getElementById(id+'_row'+r))
1560			{
1561				row.style.display = 'table-row';
1562				r++;
1563			}
1564
1565			// show/hide formatting
1566			if ([<?php echo implode(',', $formatting_available) ?>].indexOf(parseInt(id.substring(9))) != -1)
1567			{
1568				document.getElementById('formatting_block').style.display = 'block';
1569				document.getElementById('formatting_unavailable').style.display = 'none';
1570			}
1571			else
1572			{
1573				document.getElementById('formatting_block').style.display = 'none';
1574				document.getElementById('formatting_unavailable').style.display = 'block';
1575			}
1576
1577			// show/hide direction
1578			if ([<?php echo implode(',', $direction_available) ?>].indexOf(parseInt(id.substring(9))) != -1)
1579			{
1580				document.getElementById('direction_available').style.display = 'block';
1581				document.getElementById('direction_unavailable').style.display = 'none';
1582			}
1583			else
1584			{
1585				document.getElementById('direction_available').style.display = 'none';
1586				document.getElementById('direction_unavailable').style.display = 'block';
1587			}
1588
1589			prev_ftype_id = id;
1590		}
1591<?php
1592		$r = $this->get_last_call($js);
1593		$r = preg_replace('/(function\s+showhide_element\(\s*id\s*\)\s*{)/is', ob_get_contents(), $r);
1594		ob_end_clean();
1595
1596		$args = func_get_args();
1597		return $this->forward_ff_hook('publish_admin_edit_field_js', $args, $r);
1598	}
1599
1600	/**
1601	 * Publish Admin - Edit Field Form - Cell
1602	 *
1603	 * @param  array   $data   The data about this field from the database
1604	 * @param  string  $cell   The contents of the cell
1605	 * @param  string  $index  The cell index
1606	 * @return string  The modified $cell
1607	 * @access private
1608	 */
1609	function _publish_admin_edit_field_type_cell($data, $cell, $index)
1610	{
1611		$r = $this->get_last_call($cell);
1612
1613		foreach($this->_get_ftypes() as $class_name => $ftype)
1614		{
1615			$ftype_id = 'ftype_id_'.$ftype->_fieldtype_id;
1616			$selected = ($data['field_type'] == $ftype_id);
1617
1618			$field_settings = $this->_group_ftype_inputs($ftype_id, $ftype->_field_settings['cell'.$index]);
1619
1620			$r .= '<div id="'.$ftype_id.'_cell'.$index.'" style="margin-top:5px; display:'.($selected ? 'block' : 'none').';">'
1621			    . $field_settings
1622			    . '</div>';
1623		}
1624
1625		return $r;
1626	}
1627
1628	/**
1629	 * Publish Admin - Edit Field Form - Cell One
1630	 *
1631	 * Allows modifying or adding onto Custom Weblog Fieldtype - First Table Cell
1632	 *
1633	 * @param  array   $data  The data about this field from the database
1634	 * @param  string  $cell  The contents of the cell
1635	 * @return string  The modified $cell
1636	 * @see    http://expressionengine.com/developers/extension_hooks/publish_admin_edit_field_type_cellone/
1637	 */
1638	function publish_admin_edit_field_type_cellone($data, $cell)
1639	{
1640		global $DSP;
1641
1642		$r = $this->_publish_admin_edit_field_type_cell($data, $cell, '1');
1643
1644		// formatting
1645		foreach($this->_get_ftypes() as $class_name => $ftype)
1646		{
1647			$ftype_id = 'ftype_id_'.$ftype->_fieldtype_id;
1648			$r .= $DSP->input_hidden('ftype['.$ftype_id.'][formatting_available]', ($ftype->_field_settings['formatting_available'] ? 'y' : 'n'));
1649		}
1650
1651		$args = func_get_args();
1652		return $this->forward_ff_hook('publish_admin_edit_field_type_cellone', $args, $r);
1653	}
1654
1655	/**
1656	 * Publish Admin - Edit Field Form - Cell Two
1657	 *
1658	 * Allows modifying or adding onto Custom Weblog Fieldtype - Second Table Cell
1659	 *
1660	 * @param  array   $data  The data about this field from the database
1661	 * @param  string  $cell  The contents of the cell
1662	 * @return string  The modified $cell
1663	 * @see    http://expressionengine.com/developers/extension_hooks/publish_admin_edit_field_type_celltwo/
1664	 */
1665	function publish_admin_edit_field_type_celltwo($data, $cell)
1666	{
1667		$r = $this->_publish_admin_edit_field_type_cell($data, $cell, '2');
1668		$args = func_get_args();
1669		return $this->forward_ff_hook('publish_admin_edit_field_type_celltwo', $args, $r);
1670	}
1671
1672	/**
1673	 * Publish Admin - Edit Field Form - Format
1674	 *
1675	 * Allows modifying or adding onto Default Text Formatting Cell
1676	 *
1677	 * @param  array   $data  The data about this field from the database
1678	 * @param  string  $y     The current contents of the format cell
1679	 * @return string  The modified $y
1680	 * @see    http://expressionengine.com/developers/extension_hooks/publish_admin_edit_field_format/
1681	 */
1682	function publish_admin_edit_field_format($data, $y)
1683	{
1684		$y = $this->get_last_call($y);
1685		$args = func_get_args();
1686		return $this->forward_ff_hook('publish_admin_edit_field_format', $args, $y);
1687	}
1688
1689	/**
1690	 * Publish Admin - Edit Field Form - Extra Row
1691	 *
1692	 * Allows modifying or adding onto the Custom Field settings table
1693	 *
1694	 * @param  array   $data  The data about this field from the database
1695	 * @param  string  $r     The current contents of the page
1696	 * @return string  The modified $r
1697	 * @see    http://expressionengine.com/developers/extension_hooks/publish_admin_edit_field_extra_row/
1698	 */
1699	function publish_admin_edit_field_extra_row($data, $r)
1700	{
1701		global $DSP, $LANG;
1702
1703		$r = $this->get_last_call($r);
1704
1705		if ($ftypes = $this->_get_ftypes())
1706		{
1707			$rows = '';
1708			foreach($ftypes as $class_name => $ftype)
1709			{
1710				$ftype_id = 'ftype_id_'.$ftype->_fieldtype_id;
1711				$selected = ($data['field_type'] == $ftype_id);
1712
1713				foreach($ftype->_field_settings['rows'] as $index => $row)
1714				{
1715					$class = $index % 2 ? 'tableCellOne' : 'tableCellTwo';
1716					$rows .= '<tr id="'.$ftype_id.'_row'.($index+1).'"' . ($selected ? '' : ' style="display:none;"') . '>'
1717					       . '<td class="'.$class.'"'.(isset($row[1]) ? '' : ' colspan="2"').'>'
1718					       . $this->_group_ftype_inputs($ftype_id, $row[0])
1719					       . $DSP->td_c()
1720					       . (isset($row[1])
1721					            ?  $DSP->td($class)
1722					             . $this->_group_ftype_inputs($ftype_id, $row[1])
1723					             . $DSP->td_c()
1724					             . $DSP->tr_c()
1725					            : '');
1726				}
1727
1728				if ($selected)
1729				{
1730					// show/hide formatting
1731					if ($ftype->_field_settings['formatting_available'])
1732					{
1733						$formatting_search = 'none';
1734						$formatting_replace = 'block';
1735					}
1736					else
1737					{
1738						$formatting_search = 'block';
1739						$formatting_replace = 'none';
1740					}
1741					$r = preg_replace('/(\sid\s*=\s*([\'\"])formatting_block\2.*display\s*:\s*)'…

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