<?php
/**
 * THtmlArea class file.
 *
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
 * @link http://www.pradosoft.com/
 * @copyright Copyright &copy; 2005-2008 PradoSoft
 * @license http://www.pradosoft.com/license/
 * @version $Id$
 * @package System.Web.UI
 */

/**
 * Includes TTextBox class
 */
Prado::using('System.Web.UI.WebControls.TTextBox');

/**
 * THtmlArea class
 *
 * THtmlArea wraps the visual editting functionalities provided by the
 * TinyMCE project {@link http://tinymce.moxiecode.com/}.
 *
 * THtmlArea displays a WYSIWYG text area on the Web page for user input
 * in the HTML format. The text displayed in the THtmlArea component is
 * specified or determined by using the <b>Text</b> property.
 *
 * To enable the visual editting on the client side, set the property
 * <b>EnableVisualEdit</b> to true (which is default value).
 * To set the size of the editor when the visual editting is enabled,
 * set the <b>Width</b> and <b>Height</b> properties instead of
 * <b>Columns</b> and <b>Rows</b> because the latter has no meaning
 * under the situation.
 *
 * The default editor gives only the basic tool bar. To change or add
 * additional tool bars, use the {@link setOptions Options} property to add additional
 * editor options with each options on a new line.
 * See http://tinymce.moxiecode.com/tinymce/docs/index.html
 * for a list of options. The options can be change/added as shown in the
 * following example.
 * <code>
 * <com:THtmlArea>
 *      <prop:Options>
 *           plugins : "contextmenu,paste"
 *           language : "zh_cn"
 *      </prop:Options>
 * </com:THtmlArea>
 * </code>
 *
 * Compatibility
 * The client-side visual editting capability is supported by
 * Internet Explorer 5.0+ for Windows and Gecko-based browser.
 * If the browser does not support the visual editting,
 * a traditional textarea will be displayed.
 *
 * Browser support
 *
 * <code>
 *                    Windows XP        MacOS X 10.4
 * ----------------------------------------------------
 * MSIE 6                  OK
 * MSIE 5.5 SP2            OK
 * MSIE 5.0                OK
 * Mozilla 1.7.x           OK              OK
 * Firefox 1.0.x           OK              OK
 * Firefox 1.5b2           OK              OK
 * Safari 2.0 (412)                        OK(1)
 * Opera 9 Preview 1       OK(1)           OK(1)
 * ----------------------------------------------------
 *    * (1) - Partialy working
 * ----------------------------------------------------
 * </code>
 *
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
 * @version $Id$
 * @package System.Web.UI.WebControls
 * @since 3.0
 */
class THtmlArea extends TTextBox
{
	/**
	 * @var array list of locale => language file pairs.
	 */
	private static $_langs = array(
			'ar' => 'ar',
			'az' => 'az',
			'be' => 'be',
			'bg' => 'bg',
			'bn' => 'bn',
			'br' => 'br',
			'bs' => 'bs',
			'ca' => 'ca',
			'ch' => 'ch',
			'cn' => 'cn',
			'cs' => 'cs',
			'cy' => 'cy',
			'da' => 'da',
			'de' => 'de',
			'dv' => 'dv',
			'el' => 'el',
			'en' => 'en',
			'eo' => 'eo',
			'es' => 'es',
			'et' => 'et',
			'eu' => 'eu',
			'fa' => 'fa',
			'fi' => 'fi',
			'fr' => 'fr',
			'gl' => 'gl',
			'gu' => 'gu',
			'he' => 'he',
			'hi' => 'hi',
			'hr' => 'hr',
			'hu' => 'hu',
			'hy' => 'hy',
			'ia' => 'ia',
			'id' => 'id',
			'is' => 'is',
			'it' => 'it',
			'ja' => 'ja',
			'ka' => 'ka',
			'kl' => 'kl',
			'km' => 'km',
			'ko' => 'ko',
			'lb' => 'lb',
			'lt' => 'lt',
			'lv' => 'lv',
			'mk' => 'mk',
			'ml' => 'ml',
			'mn' => 'mn',
			'ms' => 'ms',
			'my' => 'my',
			'nb' => 'nb',
			'nl' => 'nl',
			'nn' => 'nn',
			'no' => 'no',
			'pl' => 'pl',
			'ps' => 'ps',
			'pt' => 'pt',
			'ro' => 'ro',
			'ru' => 'ru',
			'sc' => 'sc',
			'se' => 'se',
			'si' => 'si',
			'sk' => 'sk',
			'sl' => 'sl',
			'sq' => 'sq',
			'sr' => 'sr',
			'sv' => 'sv',
			'ta' => 'ta',
			'te' => 'te',
			'th' => 'th',
			'tn' => 'tn',
			'tr' => 'tr',
			'tt' => 'tt',
			'tw' => 'tw',
			'uk' => 'vi',
			'ur' => 'vi',
			'vi' => 'vi',
			'zh_CN' => 'zh-cn',
			'zh_TW' => 'zh-tw',
			'zh' => 'zh',
			'zu' => 'zu',
		);

	/**
	 * @var array list of default plugins to load, override using getAvailablePlugins();
	 */
	private static $_plugins = array(
		'advhr',
		'advimage',
		'advlink',
		'advlist',
		'autolink',
		'autoresize',
		'autosave',
		'bbcode',
		'contextmenu',
		'directionality',
		'emotions',
		'example',
		'fullpage',
		'fullscreen',
		'iespell',
		'inlinepopups',
		'insertdatetime',
		'layer',
		'legacyoutput',
		'lists',
		'media',
		'nonbreaking',
		'noneditable',
		'pagebreak',
		'paste',
		'preview',
		'print',
		'save',
		'searchreplace',
		'spellchecker',
		'style',
		'tabfocus',
		'table',
		'template',
		'visualchars',
		'wordc',
		'wordcount',
		'xhtmlxtras'
	);

	/**
	 * @var array default themes to load
	 */
	private static $_themes = array(
		'simple',
		'advanced'
	);

	/**
	 * Constructor.
	 * Sets default width and height.
	 */
	public function __construct()
	{
		$this->setWidth('470px');
		$this->setHeight('250px');
	}

	/**
	 * Overrides the parent implementation.
	 * TextMode for THtmlArea control is always 'MultiLine'
	 * @return string the behavior mode of the THtmlArea component.
	 */
	public function getTextMode()
	{
		return 'MultiLine';
	}

	/**
	 * Overrides the parent implementation.
	 * TextMode for THtmlArea is always 'MultiLine' and cannot be changed to others.
	 * @param string the text mode
	 */
	public function setTextMode($value)
	{
		throw new TInvalidOperationException("htmlarea_textmode_readonly");
	}

	/**
	 * @return boolean whether change of the content should cause postback. Return false if EnableVisualEdit is true.
	 */
	public function getAutoPostBack()
	{
		return $this->getEnableVisualEdit() ? false : parent::getAutoPostBack();
	}

	/**
	 * @return boolean whether to show WYSIWYG text editor. Defaults to true.
	 */
	public function getEnableVisualEdit()
	{
		return $this->getViewState('EnableVisualEdit',true);
	}

	/**
	 * Sets whether to show WYSIWYG text editor.
	 * @param boolean whether to show WYSIWYG text editor
	 */
	public function setEnableVisualEdit($value)
	{
		$this->setViewState('EnableVisualEdit',TPropertyValue::ensureBoolean($value),true);
	}

	/**
	 * Gets the current culture.
	 * @return string current culture, e.g. en_AU.
	 */
	public function getCulture()
	{
		return $this->getViewState('Culture', '');
	}

	/**
	 * Sets the culture/language for the html area
	 * @param string a culture string, e.g. en_AU.
	 */
	public function setCulture($value)
	{
		$this->setViewState('Culture', $value, '');
	}

	/**
	 * Gets the list of options for the WYSIWYG (TinyMCE) editor
	 * @see http://tinymce.moxiecode.com/tinymce/docs/index.html
	 * @return string options
	 */
	public function getOptions()
	{
		return $this->getViewState('Options', '');
	}

	/**
	 * Sets the list of options for the WYSIWYG (TinyMCE) editor
	 * @see http://tinymce.moxiecode.com/tinymce/docs/index.html
	 * @param string options
	 */
	public function setOptions($value)
	{
		$this->setViewState('Options', $value, '');
	}

	/**
	 * @param string path to custom plugins to be copied.
	 */
	public function setCustomPluginPath($value)
	{
		$this->setViewState('CustomPluginPath', $value);
	}

	/**
	 * @return string path to custom plugins to be copied.
	 */
	public function getCustomPluginPath()
	{
		return $this->getViewState('CustomPluginPath');
	}

	/**
	 * @return boolean enable compression of the javascript files, default is true.
	 */
	public function getEnableCompression()
	{
		return $this->getViewState('EnableCompression', true);
	}

	/**
	 * @param boolean enable compression of the javascript files, default is true.
	 */
	public function setEnableCompression($value)
	{
		$this->setViewState('EnableCompression', TPropertyValue::ensureBoolean($value));
	}

	/**
	 * Adds attribute name-value pairs to renderer.
	 * This method overrides the parent implementation by registering
	 * additional javacript code.
	 * @param THtmlWriter the writer used for the rendering purpose
	 */
	protected function addAttributesToRender($writer)
	{
		if($this->getEnableVisualEdit() && $this->getEnabled(true))
		{
			$writer->addAttribute('id',$this->getClientID());
			$this->registerEditorClientScript($writer);
		}

		$this->loadJavascriptLibrary();
		if($this->getEnableCompression())
			$this->preLoadCompressedScript();

		parent::addAttributesToRender($writer);
	}

	/**
	 * Returns a list of plugins to be loaded.
	 * Override this method to customize.
	 * @return array list of plugins to be loaded
	 */
	public function getAvailablePlugins()
	{
		return self::$_plugins;
	}

	/**
	 * @return array list of available themese
	 */
	public function getAvailableThemes()
	{
		return self::$_themes;
	}

	protected function preLoadCompressedScript()
	{
		$scripts = $this->getPage()->getClientScript();
		$key = 'prado:THtmlArea:compressed';
		if(!$scripts->isBeginScriptRegistered($key))
		{
			$options['plugins'] = implode(',', $this->getAvailablePlugins());
			$options['themes'] = implode(',', $this->getAvailableThemes());
			$options['languages'] = $this->getLanguageSuffix($this->getCulture());
			$options['disk_cache'] = true;
			$options['debug'] = false;
			$js = TJavaScript::encode($options,true,true);
			$script = "if(typeof(tinyMCE_GZ)!='undefined'){ tinyMCE_GZ.init({$js}); }";
			$scripts->registerBeginScript($key, $script);
		}
	}

	protected function loadJavascriptLibrary()
	{
		$scripts = $this->getPage()->getClientScript();
		if(!$scripts->isScriptFileRegistered('prado:THtmlArea'))
			$scripts->registerScriptFile('prado:THtmlArea', $this->getScriptUrl());
	}

	/**
	 * Registers the editor javascript file and code to initialize the editor.
	 */
	protected function registerEditorClientScript($writer)
	{
		$scripts = $this->getPage()->getClientScript();
		$options = TJavaScript::encode($this->getEditorOptions(),true,true); // Force encoding of empty strings
		$script = "if(typeof(tinyMCE)!='undefined'){ tinyMCE.init($options); }";
		$scripts->registerEndScript('prado:THtmlArea'.$this->ClientID,$script);
	}

	/**
	 * @return string editor script URL.
	 */
	protected function getScriptUrl()
	{
		if($this->getEnableCompression())
			return $this->getScriptDeploymentPath().'/tiny_mce/tiny_mce_gzip.js';
		else
			return $this->getScriptDeploymentPath().'/tiny_mce/tiny_mce.js';
	}

	/**
	 * Gets the editor script base URL by publishing the tarred source via TTarAssetManager.
	 * @return string URL base path to the published editor script
	 */
	protected function getScriptDeploymentPath()
	{
		$tarfile = Prado::getPathOfNamespace('System.3rdParty.TinyMCE.tiny_mce', '.tar');
		$md5sum = Prado::getPathOfNamespace('System.3rdParty.TinyMCE.tiny_mce', '.md5');
		if($tarfile===null || $md5sum===null)
			throw new TConfigurationException('htmlarea_tarfile_invalid');
		$url = $this->getApplication()->getAssetManager()->publishTarFile($tarfile, $md5sum);
		$this->copyCustomPlugins($url);
		return $url;
	}

	protected function copyCustomPlugins($url)
	{
		if($plugins = $this->getCustomPluginPath())
		{
			$assets = $this->getApplication()->getAssetManager();
			$path = is_dir($plugins) ? $plugins : Prado::getPathOfNameSpace($plugins);
			$dest = $assets->getBasePath().'/'.basename($url).'/tiny_mce/plugins/';
			if(!is_dir($dest) || $this->getApplication()->getMode()!==TApplicationMode::Performance)
				$assets->copyDirectory($path, $dest);
		}
	}

	/**
	 * Default editor options gives basic tool bar only.
	 * @return array editor initialization options.
	 */
	protected function getEditorOptions()
	{
		$options['mode'] = 'exact';
		$options['elements'] = $this->getClientID();
		$options['language'] = $this->getLanguageSuffix($this->getCulture());
		$options['theme'] = 'advanced';

		//make it basic advanced to fit into 1 line of buttons.
		//$options['theme_advanced_buttons1'] = 'bold,italic,underline,strikethrough,separator,justifyleft,justifycenter,justifyright, justifyfull,separator,bullist,numlist,separator,undo,redo,separator,link,unlink,separator,charmap,separator,code,help';
		//$options['theme_advanced_buttons2'] = ' ';
		$options['theme_advanced_buttons1'] = 'formatselect,fontselect,fontsizeselect,separator,bold,italic,underline,strikethrough,sub,sup';
		$options['theme_advanced_buttons2'] = 'justifyleft,justifycenter,justifyright,justifyfull,separator,bullist,numlist,separator,outdent,indent,separator,forecolor,backcolor,separator,hr,link,unlink,image,charmap,separator,removeformat,code,help';
		$options['theme_advanced_buttons3'] = '';

		$options['theme_advanced_toolbar_location'] = 'top';
		$options['theme_advanced_toolbar_align'] = 'left';
		$options['theme_advanced_path_location'] = 'bottom';
		$options['extended_valid_elements'] = 'a[name|href|target|title|onclick],img[class|src|border=0|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name],hr[class|width|size|noshade],font[face|size|color|style],span[class|align|style]';

		$options = array_merge($options, $this->parseEditorOptions($this->getOptions()));
		return $options;
	}

	/**
	 * Parse additional options set in the Options property.
	 * @return array additional custom options
	 */
	protected function parseEditorOptions($string)
	{
		$options = array();
		$substrings = preg_split('/,\s*\n|\n/', trim($string));
		foreach($substrings as $bits)
		{
			$option = explode(":",$bits,2);

			if(count($option) == 2)
			{
				$value=trim(preg_replace('/\'|"/','',  $option[1]));
				if (($s=strtolower($value))==='false') 
					$value=false;
				elseif ($s==='true')
					$value=true;
				$options[trim($option[0])] = $value;
			}
		}
		return $options;
	}

	/**
	 * @return string localized editor interface language extension.
	 */
	protected function getLanguageSuffix($culture)
	{
		$app = $this->getApplication()->getGlobalization();
		if(empty($culture) && ($app!==null))
			$culture = $app->getCulture();
		$variants = array();
		if($app!==null)
			$variants = $app->getCultureVariants($culture);

		foreach($variants as $variant)
		{
			if(isset(self::$_langs[$variant]))
				return self::$_langs[$variant];
		}

		return 'en';
	}

	/**
	 * Gets the name of the javascript class responsible for performing postback for this control.
	 * This method overrides the parent implementation.
	 * @return string the javascript class name
	 */
	protected function getClientClassName()
	{
		return 'Prado.WebUI.THtmlArea';
	}
}