<?php
/**
 * This plugin replaces the textarea in the "Write" tab with {@link http://tinymce.moxiecode.com/ tinyMCE}.
 *
 * This file is part of the b2evolution/evocms project - {@link http://b2evolution.net/}.
 * See also {@link https://github.com/b2evolution/b2evolution}.
 *
 * @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
 *
 * @copyright 2006 by Daniel HAHLER - {@link http://daniel.hahler.de/}.
 * @copyright 2009 by Francois Planque - {@link http://fplanque.com/}.
 *
 * @package plugins
 *
 * @author blueyed: Daniel HAHLER
 * @author fplanque: Francois Planque
 * @author PhiBo: Philipp Seidel (since version 0.6)
 */
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );


/**
 * The TinyMCE plugin.
 *
 * It provides replacing edit components with the JavaScript rich text editor TinyMCE.
 *
 * @todo Make sure settings get transformed from 0.6 to 0.7 and obsolete ones get dropped from the DB!
 * @todo dh> use require_js_async() and require_js_defer() and add_js_headline() for the JavaScript includes
 * @todo fp> see bbcode plugin for an example about how to convert [tag] to <tag> on the fly for editing purposes. May be used for [img:] tags in b2evo. May also be used for b2evo smilies display. ed.onBeforeSetContent ed.onPostProcess
 * @todo fp> lang.js files should be moved to the standard language packs. Maybe served by .php files outputting javascript.
 * @todo dh> This is a nice plugin to apply classes and IDs: http://www.bram.us/projects/tinymce-plugins/tinymce-classes-and-ids-plugin-bramus_cssextras/
 * @todo dh> Integrate our Filemanager via http://wiki.moxiecode.com/index.php/TinyMCE:Configuration/file_browser_callback
 */
class tinymce_plugin extends Plugin
{
	var $code = 'evo_TinyMCE';
	var $name = 'TinyMCE';
	var $priority = 10;
	var $version = '7.2.3';
	var $group = 'editor';
	var $number_of_installs = 1;

	var $collection = NULL;
	var $post_ID = NULL;
	var $blog_ID = NULL;

	var $target_type = NULL;
	var $target_ID = NULL;
	var $temp_ID = NULL;

	function PluginInit( & $params )
	{
		$this->short_desc = $this->T_('Javascript WYSIWYG editor');
	}


	/**
	 * Define here default collection/blog settings that are to be made available in the backoffice.
	 *
	 * @param array Associative array of parameters.
	 * @return array See {@link Plugin::GetDefaultSettings()}.
	 */
	function get_coll_setting_definitions( & $params )
	{
		//$default_params = array_merge( $params, array( 'default_comment_using' => 'disabled' ) );

		//return parent::get_coll_setting_definitions( $default_params );
		return $this->get_custom_setting_definitions( $params );
	}


	/**
	 * Define here default custom settings that are to be made available
	 *     in the backoffice for collections, private messages and newsletters.
	 *
	 * @param array Associative array of parameters.
	 * @return array See {@link Plugin::get_custom_setting_definitions()}.
	 */
	function get_custom_setting_definitions( & $params )
	{
		return array(
			'back_layout_start' => array(
					'layout' => 'begin_fieldset',
					'label' => T_('Back-office'),
				),
			'coll_use_for_posts' => array(
					'label' => T_( 'Use for posts' ),
					'type' => 'checkbox',
					'note' => '',
					'defaultvalue' => 1,
				),
			'coll_use_for_comments' => array(
					'label' => T_( 'Use for comments' ),
					'type' => 'checkbox',
					'note' => '',
					'defaultvalue' => 1,
				),
			'back_layout_end' => array(
					'layout' => 'end_fieldset',
				),

			'front_layout_start' => array(
					'layout' => 'begin_fieldset',
					'label' => T_('Front-office'),
				),
			'coll_use_for_posts_front' => array(
					'label' => T_( 'Use for posts' ),
					'type' => 'checkbox',
					'note' => '',
					'defaultvalue' => 1,
				),
			'coll_use_for_comments_front' => array(
					'label' => T_( 'Use for comments' ),
					'type' => 'checkbox',
					'note' => '',
					'defaultvalue' => 1,
				),
			'front_layout_end' => array(
					'layout' => 'end_fieldset',
				),
		);
	}


	/**
	 * Define the GLOBAL settings of the plugin here. These can then be edited in the backoffice in System > Plugins.
	 *
	 * @param array Associative array of parameters (since v1.9).
	 *    'for_editing': true, if the settings get queried for editing;
	 *                   false, if they get queried for instantiating {@link Plugin::$Settings}.
	 * @return array see {@link Plugin::GetDefaultSettings()}.
	 * The array to be returned should define the names of the settings as keys (max length is 30 chars)
	 * and assign an array with the following keys to them (only 'label' is required):
	 */
	function GetDefaultSettings( & $params )
	{
		return array(
			'default_use_tinymce' => array(
				'label' => $this->T_('Use TinyMCE (Default)'),
				'type' => 'checkbox',
				'defaultvalue' => '1',
				'note' => $this->T_('This is the default, which users can override in their profile.'),
			),
			'use_gzip_compressor' => array(
				'label' => $this->T_('Use compressor'),
				'type' => 'checkbox',
				'defaultvalue' => 0,
				'note' => $this->T_('Use the TinyMCE compressor, which improves loading time.'),
			),
			/* Ugly
			'tmce_options_begin' => array(
				'label' => $this->T_('Advanced editor options'),
				'layout' => 'begin_fieldset'
			),
			*/

			'tmce_options_contextmenu' => array( // fp> keep for now
				'label' => $this->T_('Context menu'),
				'type' => 'checkbox',
				'defaultvalue' => 0,
				'note' => $this->T_('Enable this to use an extra context menu in the editor.').' <span class="red">'.T_('Enabling this will prevent browser-side spelling correction.').'</span>'
			),

			'tmce_options_spellcheck' => array( // fp> keep for now
				'label' => $this->T_('Browser spell checking'),
				'type' => 'checkbox',
				'defaultvalue' => 1,
				'note' => $this->T_('Enable browser-based spell checking.')
			),

			'tmce_options_paste' => array( // fp> keep for now
				'label' => $this->T_('Advanced paste support'),
				'type' => 'checkbox',
				'defaultvalue' => 1,
				'note' => $this->T_('Enable this to add support for pasting easily word and plain text files')
			),
			'tmce_options_directionality' => array( // keep for now
				'label' => $this->T_('Directionality support'),
				'type' => 'checkbox',
				'defaultvalue' => 1,
				'note' => $this->T_('Enable to add directionality icons to TinyMCE for better handling of right-to-left languages')
			),
			/* /Ugly
			'tmce_options_end' => array(
					'layout' => 'end_fieldset'
			),
			*/
			'tmce_custom_conf' => array( // fp> over-kill dh> I tend to leave this in, as it allows to configure it as-you-need, especially when a lot of the advanced stuff gets removed from the admin.
				'label' => $this->T_('Custom TinyMCE init'),
				'type' => 'textarea',
				'defaultvalue' => // Provide some sample:
						'height : 240',
				'note' => sprintf( $this->T_('Custom parameters to tinymce.init(). See the <a %s>TinyMCE manual</a>.'), 'href="http://wiki.moxiecode.com/index.php/TinyMCE:Configuration"' ),
			),
		);
	}

	/**
	 * Declare custom events that this plugin fires.
	 *
	 * The gallery2_plugin uses these.
	 *
	 * Plugins can set the "load_before_init" parameter with some javascript code
	 * that will be executed before tinymce.init() is called. This is most useful
	 * for inserting code to load an external tinymce plugin.
	 *
	 * Supported events are as follows:
	 * tinymce_before_init: Allows other b2evo plugins to load tinymce plugins
	 *                      before the tinymce init.
	 * Example:
	 * function tinymce_before_init( &$params ) {
	 *   $mypluginurl = \$this->get_plugin_url()."myplugin/plugin.min.js";
	 *   echo "tinymce.PluginManager.load('myplugin', '".$mypluginurl."');";
	 * }
	 *
	 * tinymce_extend_plugins: Allows b2evo plugins to extend the plugin list.
	 *                         TinyMCE often needs to be told not to load an
	 *                         external plugin during it's load phase because it's
	 *                         already been loaded. The plugin list is exposed
	 *                         in the tinymce_plugins property in the params.
	 * Example:
	 * function tinymce_extend_plugins( &$params ) {
	 *   array_push($params["tinymce_plugins"], "-myplugin");
	 * }
	 *
	 * tinymce_extend_buttons: Allows b2evo plugins to extend the buttons in the
	 *                         Third button panel.The buttons list is exposed
	 *                         in the tinymce_buttons property in the params.
	 * Example:
	 * function tinymce_extend_buttons( &$params ) {
	 *   array_push($params["tinymce_buttons"], "mypluginbutton");
	 * }
	 */
	function GetExtraEvents()
	{
		return array(
			"tinymce_before_init"    => "Event that is called before tinymce is initialized",
			"tinymce_extend_plugins" => "Event called to allow other plugins to extend the plugin list",
			"tinymce_extend_buttons" => "Event called to allow other plugins to extend the button list"
		);
	}


	/**
	 * Define the PER-USER settings of the plugin here. These can then be edited by each user.
	 *
	 * @see Plugin::GetDefaultSettings()
	 * @param array Associative array of parameters.
	 *    'for_editing': true, if the settings get queried for editing;
	 *                   false, if they get queried for instantiating
	 * @return array See {@link Plugin::GetDefaultSettings()}.
	 */
	function GetDefaultUserSettings( & $params )
	{
		$r = array(
			'use_tinymce' => array(
				'label' => $this->T_('Use TinyMCE'),
				'type' => 'checkbox',
				'defaultvalue' => $this->Settings->get('default_use_tinymce'),
				'note' => $this->T_('Check this to enable the extended Javascript editor (TinyMCE).'),
			)
		);

		/* Ugly
		$r['tmce_options_begin'] = array(
					'label' => $this->T_('Advanced editor options'),
					'layout' => 'begin_fieldset' // fp> ugly
				);
		*/

		$r['tmce_options_contextmenu'] = array( // fp> keep for now
					'label' => $this->T_('Context menu'),
					'type' => 'checkbox',
					'defaultvalue' => $this->Settings->get('tmce_options_contextmenu'),
					'note' => $this->T_('Enable this to use an extra context menu in the editor.').' <span class="red">'.T_('Enabling this will prevent browser-side spelling correction.').'</span>'
				);
		$r['tmce_options_spellcheck'] = array(
					'label' => $this->T_('Browser spell checking'),
					'type' => 'checkbox',
					'defaultvalue' => 1,
					'note' => $this->T_('Enable browser-based spell checking.')
				);
		$r['tmce_options_paste'] = array( // fp> keep for now
					'label' => $this->T_('Advanced paste support'),
					'type' => 'checkbox',
					'defaultvalue' => $this->Settings->get('tmce_options_paste'),
					'note' => $this->T_('Enable this to add support for easily pasting word and plain text files')
				);
		$r['tmce_options_directionality'] = array(
					'label' => $this->T_('Directionality support'),
					'type' => 'checkbox',
					'defaultvalue' => $this->Settings->get('tmce_options_directionality'),
					'note' => $this->T_('Enable to add directionality icons to TinyMCE that enables TinyMCE to better handle languages that is written from right to left.')
				);

		/* Ugly
		$r['tmce_options_end'] = array(
					'layout' => 'end_fieldset'
				);
		*/

		return $r;
	}


	/**
	 * We require b2evo 3.3+
	 */
	function GetDependencies()
	{
		return array(
				'requires' => array(
					'api_min' => array( 3, 3 ), // obsolete, but required for b2evo 1.8 before 1.8.3
					'app_min' => '3.3.0-rc1',
				),
			);
	}


	/**
	 * Init the TinyMCE object (in backoffice).
	 *
	 * This is done late, so that scriptaculous has been loaded before,
	 * which got used by the youtube_plugin and caused problems with tinymce.
	 *
	 * @todo dh> use jQuery's document.ready wrapper
	 *
	 * ---
	 *
	 * Event handler: Called when displaying editor buttons (in back-office).
	 *
	 * This method, if implemented, should output the buttons (probably as html INPUT elements)
	 * and return true, if button(s) have been displayed.
	 *
	 * You should provide an unique html ID with each button.
	 *
	 * @param array Associative array of parameters.
	 *   - 'target_type': either 'Comment' or 'Item'.
	 *   - 'edit_layout': "inskin", "expert", etc. (users, hackers, plugins, etc. may create their own layouts in addition to these)
	 *                    NOTE: Please respect the "inskin" mode, which should display only the most simple things!
	 * @return boolean did we display a button?
	 */
	function AdminDisplayEditorButton( & $params )
	{
		global $disable_tinymce_for_frontoffice_comment_form;

		if( empty( $params['content_id'] ) )
		{	// Value of html attribute "id" of textarea where tinymce is applied
			// Don't allow empty id:
			return false;
		}

		if( empty( $params['target_object'] ) )
		{	// Target object must be defined:
			return false;
		}

		$params = array_merge( array(
				'temp_ID' => NULL,  // Temporary LinkOwnerID
			), $params );

		switch( $params['target_type'] )
		{
			case 'Item':
				// Initialize settings for item:
				global $Collection, $Blog;

				$this->collection = $Blog->get( 'urlname' );
				$edited_Item = & $params['target_object'];
				$this->target_type = 'Item';
				$this->target_ID = $edited_Item->ID;
				$this->temp_ID = $params['temp_ID'];

				if( ! $edited_Item->get_type_setting( 'allow_html' ) )
				{	// Only when HTML is allowed in post:
					return false;
				}

				if( $edited_Item->get_type_setting( 'use_text' ) == 'never' )
				{	// Only when text is allowed for current item type:
					return false;
				}

				$item_Blog = & $edited_Item->get_Blog();

				if( $params['edit_layout'] == 'inskin' )
				{	// Front-office:
					if( ! $this->get_coll_setting( 'coll_use_for_posts_front', $item_Blog ) )
					{
						return false;
					}
				}
				else
				{
					if( ! $this->get_coll_setting( 'coll_use_for_posts', $item_Blog ) )
					{	// This plugin is disabled to use for posts:
						return false;
					}
				}

				$state_params = array(
						'type' => $params['target_type'],
						'blog' => $Blog->ID,
						'item' => $edited_Item->ID,
					);
				break;

			case 'EmailCampaign':
				// Initialize settings for email campaign:
				$edited_EmailCampaign = & $params['target_object'];
				$this->target_type = 'EmailCampaign';
				$this->target_ID = $edited_EmailCampaign->ID;

				$state_params = array(
						'type'  => $params['target_type'],
						'email' => $edited_EmailCampaign->ID,
					);
				break;

			case 'Comment':
				if( ! is_admin_page() && $disable_tinymce_for_frontoffice_comment_form )
				{	// Disable TinyMCE until JS can be fixed to defer load:
					return false;
				}

				// Initialize settings for item:
				global $Collection, $Blog;

				$edited_Comment = & $params['target_object'];
				$edited_Item = & $edited_Comment->get_Item();
				$this->target_type = 'Comment';
				$this->target_ID   = $edited_Comment->ID;
				$this->temp_ID     = $params['temp_ID'];

				if( ! empty( $Blog ) && ! $Blog->get_setting( 'allow_html_comment' ) )
				{	// Only when HTML is allowed in comment:
					return false;
				}

				$item_Blog = & $edited_Item->get_Blog();

				if( $edited_Comment->is_meta() )
				{	// Do not use TinyMCE for internal comments, never!
					return false;
				}

				if( $params['edit_layout'] == 'inskin' )
				{	// Front-office:
					if( ! $this->get_coll_setting( 'coll_use_for_comments_front', $item_Blog ) )
					{
						return false;
					}
				}
				else
				{
					if( ! $this->get_coll_setting( 'coll_use_for_comments', $item_Blog ) )
					{	// This plugin is disabled to use for comments:
						return false;
					}
				}

				// Currently shares the same editor state as Item above:
				$state_params = array(
						'type' => $params['target_type'],
						'blog' => $Blog->ID,
						'item' => $edited_Item->ID,
					);
				break;

			case 'Message':
				// Initialize settings for email campaign:
				global $Settings;

				$edited_Message = & $params['target_object'];
				$this->target_type = 'Message';
				$this->target_ID = empty( $edited_Message ) ? NULL : $edited_Message->ID;
				$this->temp_ID = $params['temp_ID'];

				if( ! $Settings->get( 'allow_html_message' ) )
				{	// Only when HTML is allowed for messages:
					return false;
				}

				$state_params = array(
						'type'    => $params['target_type'],
						'message' => empty( $edited_Message ) ? NULL : $edited_Message->ID,
					);
				break;

			default:
				// Don't allow this plugin for another things:
				return false;
		}

		// JS config:
		$tinymce_config = array(
			'content_id'  => $params['content_id'],
			'plugin_code' => $this->code,
		);

		switch( $params['edit_layout'] )
		{
			default:
				// Get init params, depending on edit mode: simple|expert
				$tmce_init = $this->get_tmce_init( $params['edit_layout'], $params['content_id'], $params['target_type'] );

				$toggle_editor_config = array(
						'save_state_html_url'    => $this->get_htsrv_url( 'save_editor_state', array_merge( $state_params, array( 'on' => 0 ) ), '&' ),
						'save_state_wysiwyg_url' => $this->get_htsrv_url( 'save_editor_state', array_merge( $state_params, array( 'on' => 1 ) ), '&' ),
					);
				$tinymce_config['toggle_editor'] = $toggle_editor_config;
				?>

				<div class="btn-group evo_tinymce_toggle_buttons" data-content-id="<?php echo format_to_output( $params['content_id'], 'htmlattr' ); ?>">
					<input id="tinymce_plugin_toggle_button_html" type="button" value="<?php echo format_to_output( $this->T_('Markup'), 'htmlattr' ); ?>" class="btn btn-default active" disabled="disabled"
						title="<?php echo format_to_output( $this->T_('Toggle to the markup/pro editor.'), 'htmlattr' ); ?>" />
					<input id="tinymce_plugin_toggle_button_wysiwyg" type="button" value="WYSIWYG" class="btn btn-default"
						title="<?php echo format_to_output( $this->T_('Toggle to the WYSIWYG editor.'), 'htmlattr' ); ?>" />
				</div>

				<?php
				// Load TinyMCE Javascript source file:
				require_js_defer( '#tinymce#', 'blog', true );
				require_js_defer( '#tinymce_jquery#', 'blog', true );
				$this->require_js_defer( 'js/evo_init_plugin_tinymce.js', true );
				$this->require_js_defer( 'js/evo_view_shortcodes.bmin.js', true );

				$use_tinymce = $this->get_editor_state( $state_params );

				$tinymce_init_config = array(
						'use_tinymce'        => $use_tinymce,
						'tmce_init'          => $tmce_init,
						'display_error_msg'  => sprintf( $this->T_('TinyMCE javascript could not be loaded. Check the "%s" plugin setting.'), $this->T_('URL to TinyMCE') ),
						'update_content_url' => $this->get_htsrv_url( 'convert_content_to_wysiwyg', array(), '&' ),
						'crumb_tinymce'      => get_crumb( 'tinymce' ),
					);
				$tinymce_config['editor'] = $tinymce_init_config;

				if( $use_tinymce )
				{	// User used MCE last time, load MCE on document.ready:
					$editor_code = $this->code;
				}

				// By default set the editor code to an empty string
				echo '<input type="hidden" name="editor_code" value="">';

				if( is_ajax_request() )
				{
					?>
					<script>
					jQuery( document ).ready( function() {
							evo_init_tinymce( <?php echo evo_json_encode( $tinymce_config ); ?> );
						} );
					</script>
					<?php
				}
				else
				{
					expose_var_to_js( 'tinymce_'.$params['content_id'], $tinymce_config, 'evo_tinymce_config' );
				}

				// We also want to save the 'last used/not-used' state: (if no NULLs, this won't change anything)
				$this->htsrv_save_editor_state( array_merge( $state_params, array( 'on' => $use_tinymce ) ) );

				return true;
		}
	}


	/**
	 * Init the TinyMCE object (in front office).
	 *
	 * Event handler: Called when displaying editor buttons (in front-office).
	 *
	 * This method, if implemented, should output the buttons (probably as html INPUT elements)
	 * and return true, if button(s) have been displayed.
	 *
	 * You should provide an unique html ID with each button.
	 *
	 * @param array Associative array of parameters.
	 *   - 'target_type': either 'Comment' or 'Item'.
	 *   - 'edit_layout': "inskin", "expert", etc. (users, hackers, plugins, etc. may create their own layouts in addition to these)
	 *                    NOTE: Please respect the "inskin" mode, which should display only the most simple things!
	 * @return boolean did we display a button?
	 */
	function DisplayEditorButton( & $params )
	{
		return $this->AdminDisplayEditorButton( $params );
	}


	/* PRIVATE */
	/**
	 * Create Options for TinyMCE.init() (non-compressor) - not TinyMCE_GZ.init (compressor)!!
	 *
	 * @todo fp> valid_elements to try to generate less validation errors
	 *
	 * @param string simple|expert
	 * @param string ID of the edited content (value of html attribure "id")
	 * @param string Item | EmailCampaign | Comment
	 * @return string|false
	 */
	function get_tmce_init( $edit_layout, $content_id, $target_type )
	{
		global $Collection, $Blog;
		global $Plugins;
		global $localtimenow, $debug, $rsc_url, $rsc_path, $skins_path, $skins_url;
		global $UserSettings;
		global $ReqHost;

		global $baseurl;

		// Get URL of TinyMCE JS files:
		$tiny_mce_js_files_url = ( is_admin_page() || empty( $Blog ) ? $rsc_url : $Blog->get_local_rsc_url() ).'ext/tiny_mce/';

		$tmce_plugins_array = array(
			'image',
			'importcss',
			'link',
			'pagebreak',
			'morebreak',
			'textcolor',
			'media',
			'nonbreaking',
			'charmap',
			'fullscreen',
			'table',
			'searchreplace',
			'autocomplete',
			'lists',
			'advlist',
			'evo_view',
			//'b2evo_shorttags',
			//'b2evo_attachments'
		);

		if( function_exists( 'enchant_broker_init' ) )
		{ // Requires Enchant spelling library
			$tmce_plugins_array[] = 'spellchecker';
		}

		$tmce_theme_advanced_buttons1_array = array();
		$tmce_theme_advanced_buttons2_array = array();
		$tmce_theme_advanced_buttons3_array = array();
		$tmce_theme_advanced_buttons4_array = array();

		if( $UserSettings->get('control_form_abortions') )
		{	// Activate bozo validator: autosave plugin in TinyMCE
			$tmce_plugins_array[] = 'autosave';
		}

		if( $this->UserSettings->get('tmce_options_contextmenu') == 1 )
		{
			$tmce_plugins_array[] = 'contextmenu';
		}

		/* ----------- button row 1 : paragraph related styles ------------ */
		$tmce_theme_advanced_buttons1_array = array(
			'formatselect',
			'alignleft aligncenter alignright alignjustify',
			'bullist numlist',
			'outdent indent'
		);
		/* ----------- button row 2 : font related styles ------------ */
		$tmce_theme_advanced_buttons2_array = array(
			'bold italic strikethrough forecolor backcolor',
			'subscript superscript',
			'fontselect fontsizeselect',
			'removeformat',
		);
		/* ----------- button row 3 : tools + insert buttons ------------ */
		$image_media_buttons = ( ( $target_type == 'Comment' ) && empty( $this->target_ID ) ? 'image media' : 'evo_image image media' );
		$tmce_theme_advanced_buttons3_array = array(
			'undo redo',
			'searchreplace',
			'fullscreen',

			$image_media_buttons,   // can evo_image work in front office?
			'link unlink',
			'nonbreaking charmap',
			'table',
		);

		if( $target_type == 'Item' )
		{
			$tmce_theme_advanced_buttons3_array[] = 'morebreak pagebreak';
		}

		if( $edit_layout != 'inskin' )
		{ // Additional toolbar for BACK-OFFICE only:
			/* ----------- button row 4 ------------ */
			$tmce_plugins_array[] = 'visualchars';
			$tmce_theme_advanced_buttons4_array = array(
				'visualchars',
			);

			if( $this->UserSettings->get('tmce_options_directionality') == 1 )
			{
				$tmce_plugins_array[] = 'directionality';
				array_push($tmce_theme_advanced_buttons4_array, 'ltr rtl');
			}

			if( $this->UserSettings->get('tmce_options_paste') == 1 )
			{
				$tmce_plugins_array[] = 'paste';
				$tmce_theme_advanced_buttons4_array[] = 'pastetext';
			}

			if( function_exists( 'enchant_broker_init' ) )
			{ // Requires Enchant spelling library
				$tmce_theme_advanced_buttons4_array[] = 'spellchecker';
			}

			$tmce_plugins_array[] = 'code';
			$tmce_theme_advanced_buttons4_array[] = 'code';

			$tmce_theme_advanced_buttons4_array =
				$Plugins->get_trigger_event("tinymce_extend_buttons",
					array("tinymce_buttons" => $tmce_theme_advanced_buttons4_array),
						"tinymce_buttons");
		}

		$tmce_theme_advanced_buttons1 = implode( ' | ' , $tmce_theme_advanced_buttons1_array );
		$tmce_theme_advanced_buttons2 = implode( ' | ' , $tmce_theme_advanced_buttons2_array );
		$tmce_theme_advanced_buttons3 = implode( ' | ' , $tmce_theme_advanced_buttons3_array );
		$tmce_theme_advanced_buttons4 = implode( ' | ' , $tmce_theme_advanced_buttons4_array );

		// PLUGIN EXTENSIONS:
		$tmce_plugins_array =
			$Plugins->get_trigger_event("tinymce_extend_plugins",
				array("tinymce_plugins" => $tmce_plugins_array),
					"tinymce_plugins");

		$tmce_plugins = implode( ',' , $tmce_plugins_array );

		global $current_locale, $plugins_path;
		$tmce_language = substr($current_locale, 0, 2);
		// waltercruz> Fallback to english if there's no tinymce equivalent to the user locale
		// to avoid some strange screens like http://www.flickr.com/photos/waltercruz/3390729964/
		$lang_path = $rsc_path.'ext/tiny_mce/langs/'.$tmce_language.'.js';
		if( !file_exists( $lang_path ) )
		{
			$tmce_language = 'en';
		}

		// Configuration: -- http://wiki.moxiecode.com/index.php/TinyMCE:Configuration
		$init_options = array();
		
		$init_options['blog_ID'] = !empty( $Blog ) ? $Blog->ID : NULL;
		$init_options['cache_suffix'] = '?v='.$this->version;
		$init_options['selector'] = 'textarea#'.$content_id;

		if( $this->Settings->get( 'use_gzip_compressor' ) )
		{	// Load script to use gzip compressor:
			$init_options['script_url'] = get_require_url( 'ext:tiny_mce/tinymce.gzip.php', 'blog', 'js' );
		}

		// B2evo plugin options
		$init_options['collection']  = $this->collection;
		$insert_inline_modal_params = array(
			'request_from' => is_admin_page() ? 'back' : 'front',
		);

		$init_options['target_ID'] = isset( $this->target_ID ) ? $this->target_ID : NULL;
		$insert_inline_modal_params['target_ID'] = isset( $this->target_ID ) ? $this->target_ID : NULL;
	
		$init_options['temp_ID'] = isset( $this->temp_ID ) ? $this->temp_ID : NULL;
		
		$init_options['target_type'] = isset( $this->target_type ) ? $this->target_type : NULL;
		$insert_inline_modal_params['target_type'] = isset( $this->target_type ) ? $this->target_type : NULL;

		$init_options['rest_url']       = get_htsrv_url().'rest.php';
		$init_options['anon_async_url'] = get_htsrv_url().'anon_async.php';

		if( $Blog )
		{
			$insert_inline_modal_params['blog'] = $Blog->ID;
		}
		if( isset( $this->temp_ID ) )
		{
			$insert_inline_modal_params['temp_ID'] = $this->temp_ID;
		}

		$init_options['modal_url'] = $this->get_htsrv_url( 'insert_inline', $insert_inline_modal_params, '&' );

		$init_options['fontsize_formats'] = '8pt 10pt 12pt 14pt 16pt 18pt 24pt 36pt';

		$init_options['theme'] = 'modern';
		$init_options['menubar'] = false;

		$init_options['plugins'] = $tmce_plugins;
		$init_options['external_plugins'] = array(
				'morebreak' => $tiny_mce_js_files_url.'plugins/morebreak/plugin.min.js'
			);
		$init_options['morebreak_separator'] = '[teaserbreak]';
		$init_options['pagebreak_separator'] = '[pagebreak]';

		// Toolbars:
		$init_options['toolbar1'] = $tmce_theme_advanced_buttons1;
		$init_options['toolbar2'] = $tmce_theme_advanced_buttons2;
		$init_options['toolbar3'] = $tmce_theme_advanced_buttons3;
		$init_options['toolbar4'] = $tmce_theme_advanced_buttons4;

		// Context menu:
		if( $this->Settings->get( 'tmce_options_contextmenu' ) == 1 )
		{
			$init_options['contextmenu'] = 'cut copy paste | link image | inserttable';
		}

		if( $this->Settings->get( 'tmce_options_spellcheck' ) == 1 )
		{
			$init_options['browser_spellcheck'] = true;
		}
		else
		{
			$init_options['browser_spellcheck'] = false;
		}

		// UI options:
		$init_options['block_formats'] = 'Paragraph=p;Preformatted=pre;Block Quote=blockquote;Heading 2=h2;Heading 3=h3;Heading 4=h4;Heading 5=h5;Heading 6=h6;Address=address;Definition Term=dt;Definition Description=dd;DIV=div';
		$init_options['resize']        = true;
		$init_options['language']      = $tmce_language;
		$init_options['language_url']  = $tiny_mce_js_files_url.'langs/'.$tmce_language.'.js';

		if( function_exists( 'enchant_broker_init' ) )
		{ // Requires Enchant spelling library
			$init_options['spellchecker_rpc_url'] = 'spellchecker.php';
		}
		// body_class : "my_class"
		// CSS used in the iframe/editable area: -- http://wiki.moxiecode.com/index.php/TinyMCE:Configuration/content_css
		// note: $version may not be needed below because of automatic suffix? not sure..
		// TODO: we don't want all of basic.css here

		// Prevent object resizing in editor
		$init_options['object_resizing'] = false;

		$init_options['extended_valid_elements'] = 'figure[class],figcaption[class]';

		// Options below should prevent insertion of <p> for every newline:
		//$init_options['force_p_newlines'] = false';
		//$init_options['forced_root_block'] = '';

		// Content CSS:
		$content_css = array();
		if( ! empty( $Blog ) )
		{	// Load the appropriate ITEM/POST styles depending on the blog's skin:
			// Note: we are not aiming for perfect wysiwyg (too heavy), just for a relevant look & feel.
			$blog_skin_ID = $Blog->get_skin_ID();
			if( ! empty( $blog_skin_ID ) )
			{
				$SkinCache = & get_SkinCache();
				/**
				 * @var Skin
				 */
				$Skin = $SkinCache->get_by_ID( $blog_skin_ID );
				$item_css_path = $skins_path.$Skin->folder.'/style.css';
				$item_css_url = $skins_url.$Skin->folder.'/style.min.css';
				// else: $item_css_url = $rsc_url.'css/item_base.css';
				if( file_exists( $item_css_path ) )
				{
					$content_css[] = $item_css_url;		// fp> TODO: this needs to be a param... "of course" -- if none: else item_default.css ?
				}

				// Load b2evo base css
				$content_css[] = $baseurl.'rsc/build/bootstrap-b2evo_base.bmin.css';
			}
			// else item_default.css -- is it still possible to have no skin ?
		}

		// Load the content css files from 3rd party code, e.g. other plugins:
		global $tinymce_content_css, $app_version_long;

		$tinymce_content_css[] = get_require_url( $this->get_plugin_url().'evo_view.css', 'absolute', 'css', $this->version.'+'.$app_version_long );
		$tinymce_content_css[] = get_require_url( $this->get_plugin_url().'editor.css', 'absolute', 'css', $this->version.'+'.$app_version_long );

		if( is_array( $tinymce_content_css ) && count( $tinymce_content_css ) )
		{
			$content_css = implode( ',', array_merge( $content_css, $tinymce_content_css ) );
		}

		$init_options['content_css'] = $content_css;

		// Generated HTML code options:
		// Do not make the path relative to "document_base_url":
		//$init_options[] = 'relative_urls : false';
		$init_options['relative_urls'] = false;

		// Do not convert absolute urls to relative if url domain is the same as current page,
		// (we should keep urls as they were entered manually, because urls can be broken if collection has different domain than back-office; also an issue with RSS feeds):
		$init_options['convert_urls'] = false;
		$init_options['entity_encoding'] = 'raw';

		// Autocomplete options:
		$init_options['autocomplete_options'] = 'window.tinymce_autocomplete_static_options'; // Must be initialize before as string with usernames that are separated by comma
		$init_options['autocomplete_options_url'] = get_restapi_url().'users/autocomplete';

		// remove_linebreaks : false,
		// not documented:	auto_cleanup_word : true,

		// Prevent auto generated <p> that wrap around the views
		//$init_options['forced_root_block'] = '';

		// Enable advanced tab for images:
		$init_options['image_advtab'] = true;

		// Disable branding:
		$init_options['branding'] = false;

		// custom conf:
		if( $tmce_custom_conf = $this->Settings->get('tmce_custom_conf') )
		{
			$tmce_custom_conf = preg_split("/\r\n|\n|\r/", $tmce_custom_conf);
			foreach( $tmce_custom_conf as $row )
			{
				list( $key, $value ) = explode( ':', $row );
				$init_options[trim( $key ) ] = trim( $value );
			}
		}

		return $init_options;
	}


	/**
	 * Get URL of file to include as "content_css" for layout and classes in TinyMCE.
	 *
	 * @return array (path, url)
	 *
	function get_item_css_path_and_url($Blog)
	{
		global $skins_url, $skins_path;

		# TODO: make this a setting
		#if( $r = $this->Settings->get('content_css') )
		#{
		#	return $r;
		#}

		// Load the appropriate ITEM/POST styles depending on the blog's skin:
		if( ! empty( $Blog->skin_ID) )
		{
			$SkinCache = & get_SkinCache();
			/**
			 * @var Skin
			 *
			$Skin = $SkinCache->get_by_ID( $Blog->skin_ID );
			$item_css_path = $Skin->folder.'/item.css';		// fp> TODO: this needs to be a param... "of course" -- if none: else item_default.css ?
			// else: $item_css_path = 'css/item_base.css';

			$item_css_path = $Skin->folder.'/style.css';

			return array($skins_path.$item_css_path, $skins_url.$item_css_path);
		}
		// else item_default.css -- is it still possible to have no skin ?

		return array(NULL, NULL);
	}
	*/

	/**
	 * AJAX callback to save editor state (on or off).
	 *
	 * @param array Params
	 */
	function htsrv_save_editor_state( $params )
	{
		if( ! isset( $params['on'] ) )
		{	// Wrong request:
			return;
		}

		switch( $params['type'] )
		{
			case 'Item':
			case 'Comment':
				// Save an edit state for item edit form:

				if( ! empty( $params['blog'] ) )
				{	// This is in order to try & recall a specific state for each blog: (will be used for new posts especially)
					$this->UserSettings->set( 'use_tinymce_coll'.intval( $params['blog'] ), intval( $params['on'] ) );
				}
				$this->UserSettings->set( 'use_tinymce', intval( $params['on'] ) );
				$this->UserSettings->dbupdate();
				break;

			case 'EmailCampaign':
				// Save an edit state for email campaign edit form:
				$EmailCampaignCache = & get_EmailCampaignCache();
				if( $EmailCampaign = & $EmailCampaignCache->get_by_ID( intval( $params['email'] ), false, false ) )
				{
					$EmailCampaign->set( 'use_wysiwyg', intval( $params['on'] ) );
					$EmailCampaign->dbupdate();
				}
				break;
		}
	}


	/**
	 * Opens modal to insert inline image tags
	 *
	 * @param array Params
	 */
	function htsrv_insert_inline( $params )
	{
		insert_image_links_block( $params );
	}


	/**
	 * Get editor state
	 *
	 * @param array Params
	 */
	function get_editor_state( $params )
	{
		switch( $params['type'] )
		{
			case 'Item':
			case 'Comment':
				// Get an edit state for item edit form:

				$ItemCache = & get_ItemCache();
				$Item = & $ItemCache->get_by_ID( $params['item'], false, false );

				$item_editor_code = ( empty( $Item ) ? NULL : $Item->get_setting( 'editor_code' ) );

				if( ! empty( $item_editor_code ) )
				{	// We have a preference for the current post, follow it:
					// Use tinymce if code matched the code of the current plugin.
					// fp> Note: this is a temporary solution; in the long term, this will be part of the API and the appropriate plugin will be selected.
					$editor_state = ( $item_editor_code == $this->code );
				}
				else
				{	// We have no pref, fall back to whatever current user has last used:

					// Has the user used MCE last time he edited this particular blog?
					$editor_state = $this->UserSettings->get( 'use_tinymce_coll'.$params['blog'] );

					if( is_null( $editor_state ) )
					{	// We don't know for this blog, check if he used MCE last time he edited anything:
						$editor_state = $this->UserSettings->get( 'use_tinymce' );
					}
				}

				return $editor_state;

			case 'EmailCampaign':
				// Get an edit state for email campaign edit form:
				$EmailCampaignCache = & get_EmailCampaignCache();
				if( $EmailCampaign = & $EmailCampaignCache->get_by_ID( intval( $params['email'] ), false, false ) )
				{
					return $EmailCampaign->get( 'use_wysiwyg' );
				}
				break;

			case 'Message':
				// Check if MCE was used last time anything was edited:
				return $this->UserSettings->get( 'use_tinymce' );
				break;
		}

		return 0;
	}


	/**
	 * HtSrv callback to get the contents of the CSS file configured for "content_css".
	 * This gets used when the CSS is not on the same domain and the browser would not
	 * allow to handle the CSS cross domain (e.g. FF 3.5).
	 *
	 * @param array Params passed to the HtSrv call
	 *              - "blog": selected blog
	 * @return string
	 *
	function htsrv_get_item_content_css($params)
	{
		$blog = $params['blog'];
		$BlogCache = get_BlogCache($blog);
		$Collection = $Blog = $BlogCache->get_by_ID($blog);
		$item_css_path_and_url = $this->get_item_css_path_and_url($Blog);
		$path = array_shift( $item_css_path_and_url );
		$r = file_get_contents($path);
		if( $r )
		{
			header('Content-Type: text/css');
			echo $r;
		}
		else
		{
			header('HTTP/1.0 404 Not Found');
		}
		exit;
	}
	*/


	/**
	 * AJAX callback to convert content for WYSIWYG mode
	 *
	 * @param array Params
	 */
	function htsrv_convert_content_to_wysiwyg( $params )
	{
		global $Plugins, $Session, $debug, $debug_jslog;

		// Check that this action request is not a CSRF hacked request:
		$Session->assert_received_crumb( 'tinymce' );

		// Do not append debug logs to response because
		// here we expect only converted content in WYISWYG edit form:
		$debug = false;
		$debug_jslog = false;

		$content = param( 'content', 'raw' );

		if( isset( $Plugins ) &&
		    ( $Plugins instanceof Plugins ) &&
		    ( $autop_Plugin = & $Plugins->get_by_classname( 'auto_p_plugin' ) ) &&
		    method_exists( $autop_Plugin, 'render_autop' ) )
		{	// Convert new lines to <p> or <br> html tags by installed plugin "Auto P":
			$content = $autop_Plugin->render_autop( $content );
		}

		echo $content;
	}


	/**
	 * Return the list of Htsrv (HTTP-Services) provided by the plugin.
	 *
	 * This implements the plugin interface for the list of methods that are valid to
	 * get called through htsrv/call_plugin.php.
	 *
	 * @return array
	 */
	function GetHtsrvMethods()
	{
		return array( 'save_editor_state', 'insert_inline'/*, 'get_item_content_css'*/, 'convert_content_to_wysiwyg' );
	}


	/**
	 * Event handler: Called as action just before updating the {@link Plugin::$Settings plugin's settings}.
	 *
	 * @return false|NULL Return false to prevent the settings from being updated to DB.
	 */
	function PluginSettingsUpdateAction()
	{
		if( $this->Settings->get( 'use_gzip_compressor' ) == 1 )
		{ // Check if the cache folder is not writable
			global $cache_path;
			$cache_folder = $cache_path.'plugins/tinymce'; // Cache path, this is where the .gz files will be stored

			if( !is_writable( $cache_folder ) )
			{
				global $Messages;
				$Messages->add( sprintf( T_( 'TinyMCE plugin cannot uses the compressor because folder %s is not writable' ), '<b>'.$cache_folder.'</b>' ), 'note' );

				// Disable gzip compressor
				$this->Settings->set( 'use_gzip_compressor', 0 );
			}
		}
	}


	/**
	 * Event handler: Called at the beginning of the skin's HTML HEAD section.
	 *
	 * Use this to add any HTML HEAD lines (like CSS styles or links to resource files (CSS, JavaScript, ..)).
	 *
	 * @param array Associative array of parameters
	 */
	function SkinBeginHtmlHead( & $params )
	{
		global $disp;

		if( $disp == 'edit' || $disp == 'single' )
		{
			$this->require_css( 'toolbar.css' );
		}
	}


	/**
	 * Event handler: Called when ending the admin html head section.
	 *
	 * @param array Associative array of parameters
	 * @return boolean did we do something?
	 */
	function AdminEndHtmlHead( & $params )
	{
		global $ctrl;

		if( $ctrl == 'items' || $ctrl == 'comments' || $ctrl == 'campaigns' )
		{
			$this->require_css( 'toolbar.css' );
		}
	}
}

?>
