b2evolution PHP Cross Reference Blogging Systems

Source: /plugins/tinymce_plugin/_tinymce.plugin.php - 1094 lines - 35090 bytes - Summary - Text - Print

Description: 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 http://sourceforge.net/projects/evocms/}.

   1  <?php
   2  /**
   3   * This plugin replaces the textarea in the "Write" tab with {@link http://tinymce.moxiecode.com/ tinyMCE}.
   4   *
   5   * This file is part of the b2evolution/evocms project - {@link http://b2evolution.net/}.
   6   * See also {@link http://sourceforge.net/projects/evocms/}.
   7   *
   8   * @copyright 2006 by Daniel HAHLER - {@link http://daniel.hahler.de/}.
   9   * @copyright 2009 by Francois Planque - {@link http://fplanque.com/}.
  10   *
  11   * @license http://b2evolution.net/about/license.html GNU General Public License (GPL)
  12   *
  13   * @package plugins
  14   *
  15   * @author blueyed: Daniel HAHLER
  16   * @author fplanque: Francois Planque
  17   * @author PhiBo: Philipp Seidel (since version 0.6)
  18   *
  19   * @version $Id: _tinymce.plugin.php 4990 2013-10-16 09:50:51Z attila $
  20   */
  21  if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
  22  
  23  
  24  /**
  25   * The TinyMCE plugin.
  26   *
  27   * It provides replacing edit components with the JavaScript rich text editor TinyMCE.
  28   *
  29   * @todo Make sure settings get transformed from 0.6 to 0.7 and obsolete ones get dropped from the DB!
  30   * @todo dh> use require_js() and add_js_headline() for the JavaScript includes
  31   * @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
  32   * @todo fp> lang.js files should be moved to the standard language packs. Maybe served by .php files outputting javascript.
  33   * @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/
  34   * @todo dh> Integrate our Filemanager via http://wiki.moxiecode.com/index.php/TinyMCE:Configuration/file_browser_callback
  35   */
  36  class tinymce_plugin extends Plugin
  37  {
  38      var $code = 'evo_TinyMCE';
  39      var $name = 'TinyMCE';
  40      var $priority = 10;
  41      var $version = '5.0.0';
  42      var $group = 'editor';
  43      var $number_of_installs = 1;
  44  
  45      /**
  46       * @var string ID of the textarea to bind to (?)
  47       * @todo fp>should be a param passed from core to pluginevent (editor can be invoked in many different places)
  48       */
  49      var $tmce_editor_id = 'itemform_post_content';
  50  
  51  	function PluginInit()
  52      {
  53          $this->short_desc = $this->T_('Javascript WYSIWYG editor');
  54      }
  55  
  56  
  57      /**
  58       * These are the plugins settings + defaults that will apply to all users unless they override
  59       */
  60  	function GetDefaultSettings()
  61      {
  62          return array(
  63              'default_use_tinymce' => array(
  64                  'label' => $this->T_('Use TinyMCE (Default)'),
  65                  'type' => 'checkbox',
  66                  'defaultvalue' => '1',
  67                  'note' => $this->T_('This is the default, which users can override in their profile.'),
  68              ),
  69              'use_gzip_compressor' => array(
  70                  'label' => $this->T_('Use compressor'),
  71                  'type' => 'checkbox',
  72                  'defaultvalue' => 0,
  73                  'note' => $this->T_('Use the TinyMCE compressor, which improves loading time.'),
  74              ),
  75              /* Ugly
  76              'tmce_options_begin' => array(
  77                  'label' => $this->T_('Advanced editor options'),
  78                  'layout' => 'begin_fieldset'
  79              ),
  80              */
  81              'tmce_options_contextmenu' => array( // fp> keep for now
  82                  'label' => $this->T_('Context menu'),
  83                  'type' => 'checkbox',
  84                  'defaultvalue' => 1,
  85                  'note' => $this->T_('Enable this to use an extra context menu in the editor')
  86              ),
  87              'tmce_options_paste' => array( // fp> keep for now
  88                  'label' => $this->T_('Advanced paste support'),
  89                  'type' => 'checkbox',
  90                  'defaultvalue' => 1,
  91                  'note' => $this->T_('Enable this to add support for pasting easily word and plain text files')
  92              ),
  93              'tmce_options_xhtmlxtras' => array( // keep for now
  94                  'label' => $this->T_('Show XHTML extras'),
  95                  'type' => 'checkbox',
  96                  'defaultvalue' => 0,
  97                  'note' => $this->T_('Enable this to add support for XHTML elements (cite, ins, del, abbr, and acronym)')
  98              ),
  99              'tmce_options_directionality' => array( // keep for now
 100                  'label' => $this->T_('Directionality support'),
 101                  'type' => 'checkbox',
 102                  'defaultvalue' => 1,
 103                  'note' => $this->T_('Enable to add directionality icons to TinyMCE for better handling of right-to-left languages')
 104              ),
 105              /* /Ugly
 106              'tmce_options_end' => array(
 107                      'layout' => 'end_fieldset'
 108              ),
 109              */
 110              '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.
 111                  'label' => $this->T_('Custom TinyMCE init'),
 112                  'type' => 'textarea',
 113                  'defaultvalue' => // Provide some sample:
 114                          'theme_advanced_toolbar_location : "top", '."\n"
 115                          .'height : "400"',
 116                  '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"' ),
 117              ),
 118          );
 119      }
 120  
 121      /**
 122       * Declare custom events that this plugin fires.
 123       *
 124       * The gallery2_plugin uses these.
 125       *
 126       * Plugins can set the "load_before_init" parameter with some javascript code
 127       * that will be executed before tinyMCE.init() is called. This is most useful
 128       * for inserting code to load an external tinyMCE plugin.
 129       *
 130       * Supported events are as follows:
 131       * tinymce_before_init: Allows other b2evo plugins to load tinymce plugins
 132       *                      before the tinymce init.
 133       * Example:
 134       * function tinymce_before_init( &$params ) {
 135       *   $mypluginurl = \$this->get_plugin_url()."myplugin/editor_plugin.js";
 136       *   echo "tinymce.PluginManager.load('myplugin', '".$mypluginurl."');";
 137       * }
 138       *
 139       * tinymce_extend_plugins: Allows b2evo plugins to extend the plugin list.
 140       *                         TinyMCE often needs to be told not to load an
 141       *                         external plugin during it's load phase because it's
 142       *                         already been loaded. The plugin list is exposed
 143       *                         in the tinymce_plugins property in the params.
 144       * Example:
 145       * function tinymce_extend_plugins( &$params ) {
 146       *   array_push($params["tinymce_plugins"], "-myplugin");
 147       * }
 148       *
 149       * tinymce_extend_buttons: Allows b2evo plugins to extend the buttons in the
 150       *                         Third button panel.The buttons list is exposed
 151       *                         in the tinymce_buttons property in the params.
 152       * Example:
 153       * function tinymce_extend_buttons( &$params ) {
 154       *   array_push($params["tinymce_buttons"], "mypluginbutton");
 155       * }
 156       */
 157  	function GetExtraEvents()
 158      {
 159          return array(
 160              "tinymce_before_init"    => "Event that is called before tinymce is initialized",
 161              "tinymce_extend_plugins" => "Event called to allow other plugins to extend the plugin list",
 162              "tinymce_extend_buttons" => "Event called to allow other plugins to extend the button list"
 163          );
 164      }
 165  
 166  
 167      /**
 168       * We allow each user to disable the TinyMCE and override some of the default settings.
 169       */
 170  	function GetDefaultUserSettings()
 171      {
 172          $r = array(
 173              'use_tinymce' => array(
 174                  'label' => $this->T_('Use TinyMCE'),
 175                  'type' => 'checkbox',
 176                  'defaultvalue' => $this->Settings->get('default_use_tinymce'),
 177                  'note' => $this->T_('Check this to enable the extended Javascript editor (TinyMCE).'),
 178              )
 179          );
 180  
 181          /* Ugly
 182          $r['tmce_options_begin'] = array(
 183                      'label' => $this->T_('Advanced editor options'),
 184                      'layout' => 'begin_fieldset' // fp> ugly
 185                  );
 186          */
 187  
 188          $r['tmce_options_contextmenu'] = array( // fp> keep for now
 189                      'label' => $this->T_('Context menu'),
 190                      'type' => 'checkbox',
 191                      'defaultvalue' => $this->Settings->get('tmce_options_contextmenu'),
 192                      'note' => $this->T_('Enable this to use an extra context menu in the editor')
 193                  );
 194          $r['tmce_options_paste'] = array( // fp> keep for now
 195                      'label' => $this->T_('Advanced paste support'),
 196                      'type' => 'checkbox',
 197                      'defaultvalue' => $this->Settings->get('tmce_options_paste'),
 198                      'note' => $this->T_('Enable this to add support for easily pasting word and plain text files')
 199                  );
 200          $r['tmce_options_xhtmlxtras'] = array( // keep for now
 201                      'label' => $this->T_('Show XHTML extras'),
 202                      'type' => 'checkbox',
 203                      'defaultvalue' => $this->Settings->get('tmce_options_xhtmlxtras'),
 204                      'note' => $this->T_('Enable this to add support for XHTML elements (cite, ins, del, abbr, and acronym)')
 205                  );
 206          $r['tmce_options_directionality'] = array(
 207                      'label' => $this->T_('Directionality support'),
 208                      'type' => 'checkbox',
 209                      'defaultvalue' => $this->Settings->get('tmce_options_directionality'),
 210                      'note' => $this->T_('Enable to add directionality icons to TinyMCE that enables TinyMCE to better handle languages that is written from right to left.')
 211                  );
 212  
 213          /* Ugly
 214          $r['tmce_options_end'] = array(
 215                      'layout' => 'end_fieldset'
 216                  );
 217          */
 218  
 219          return $r;
 220      }
 221  
 222  
 223      /**
 224       * We require b2evo 3.3+
 225       */
 226  	function GetDependencies()
 227      {
 228          return array(
 229                  'requires' => array(
 230                      'api_min' => array( 3, 3 ), // obsolete, but required for b2evo 1.8 before 1.8.3
 231                      'app_min' => '3.3.0-rc1',
 232                  ),
 233              );
 234      }
 235  
 236  
 237      /**
 238       * Get the URL to include TinyMCE.
 239       * @return string
 240       */
 241  	function get_tinymce_src_url()
 242      {
 243          $base_path = $this->get_plugin_url().'tiny_mce/';
 244          if( $this->Settings->get('use_gzip_compressor') )
 245          {
 246              $url = $base_path.'tiny_mce_gzip.js';
 247              // dh> suffix of the file to compress. Looking at tiny_mce_gzip.php it only allows "_src". Needs investigation - maybe the tiny_mce_jquery.js would actually work when "_jquery" would be allowed.
 248          }
 249          else
 250          {
 251              $url = $base_path.'tiny_mce.js';
 252          }
 253          // Append version string, so it gets updated on updates.
 254          $url = url_add_param($url, 'v='.$this->version);
 255          return $url;
 256      }
 257  
 258  
 259      /**
 260       * Init the TinyMCE object (in backoffice).
 261       *
 262       * This is done late, so that scriptaculous has been loaded before,
 263       * which got used by the youtube_plugin and caused problems with tinyMCE.
 264       *
 265       * @todo dh> use jQuery's document.ready wrapper
 266       *
 267       * @return boolean
 268       */
 269  	function AdminDisplayEditorButton( & $params )
 270      {
 271          global $Blog;
 272          /**
 273           * @global Item
 274           */
 275          global $edited_Item;
 276  
 277          if( $params['target_type'] != 'Item' )
 278          { // only for Items:
 279              return;
 280          }
 281  
 282          if( !empty( $Blog ) )
 283          {
 284              if( !$Blog->get_setting( 'allow_html_post' ) )
 285              {    // Only when HTML is allowed in post
 286                  return false;
 287              }
 288          }
 289  
 290          // Get init params, depending on edit mode: simple|expert
 291          $tmce_init = $this->get_tmce_init( $params['edit_layout'] );
 292  
 293          ?>
 294  
 295          <input id="tinymce_plugin_toggle_button"
 296              type="button"
 297              value="WYSIWYG"
 298              style="display:none"
 299              title="<?php echo htmlspecialchars($this->T_('Toggle between WYSIWYG and plain HTML editor')); ?>" />
 300  
 301          <script type="text/javascript">
 302              jQuery("#tinymce_plugin_toggle_button").click( function() {
 303                  tinymce_plugin_toggleEditor('<?php echo $this->tmce_editor_id; ?>'); } );
 304              /**
 305               * Toggle TinyMCE editor on/off.
 306               * This updates the corresponding PluginUserSetting, too.
 307               */
 308  			function tinymce_plugin_toggleEditor(id)
 309              {
 310                  jQuery('#tinymce_plugin_toggle_button').attr("disabled", "disabled");
 311                  if( ! tinymce_plugin_init_done )
 312                  {
 313                      tinymce_plugin_init_done = true;
 314                      // call this method on init again, with "null" id, so that mceAddControl gets called.
 315                      tinymce_plugin_load_tinymce( function() {tinymce_plugin_toggleEditor(null)} );
 316                      return;
 317                  }
 318  
 319                  if( ! tinyMCE.getInstanceById(id))
 320                  {
 321                      tinyMCE.execCommand('mceAddControl', false, id);
 322                      jQuery.get('<?php echo $this->get_htsrv_url('save_editor_state', array('on'=>1, 'blog'=>$Blog->ID, 'item'=>$edited_Item->ID), '&'); ?>');
 323                      jQuery('#tinymce_plugin_toggle_button').attr('value', 'HTML');
 324                      jQuery('[name="editor_code"]').attr('value', '<?php echo $this->code; ?>');
 325                  }
 326                  else
 327                  {
 328                      tinyMCE.execCommand('mceRemoveControl', false, id);
 329                      jQuery.get('<?php echo $this->get_htsrv_url('save_editor_state', array('on'=>0, 'blog'=>$Blog->ID, 'item'=>$edited_Item->ID), '&'); ?>');
 330                      jQuery('#tinymce_plugin_toggle_button').attr('value', 'WYSIWYG');
 331                      jQuery('[name="editor_code"]').attr('value', 'html');
 332                  }
 333                  jQuery('#tinymce_plugin_toggle_button').removeAttr("disabled");
 334              }
 335  
 336              // Make the "toggle" button visible using JS:
 337              jQuery('#tinymce_plugin_toggle_button').css('display', '');
 338  
 339              var tmce_init={<?php echo $tmce_init; ?>};
 340              var tinymce_plugin_displayed_error = false;
 341              var tinymce_plugin_init_done = false;
 342  
 343              </script>
 344  
 345              <?php
 346              // Load TinyMCE Javascript source file:
 347              // This cannot be done through AJAX, since there appear to be scope problems on init then (TinyMCE problem?! - "u not defined").
 348              // Anyway, not using AJAX to fetch the file makes it more cachable anyway.
 349              echo '<script type="text/javascript" src="'.htmlspecialchars($this->get_tinymce_src_url()).'"></script>';
 350              ?>
 351  
 352              <script type="text/javascript">
 353              /**
 354               * Javascript function to load and init TinyMCE.
 355               * This gets done dynamically, either "on loading" or by AJAX, if the toggle button
 356               * enables the editor.
 357               * @param function to call after init
 358               */
 359  			function tinymce_plugin_load_tinymce( oninit )
 360              {
 361                  <?php
 362                  // Load TinyMCE Javascript source file:
 363                  if( $this->Settings->get('use_gzip_compressor') )
 364                  {
 365                      ?>
 366  
 367                      <!-- Init tinyMCE_GZ: -->
 368                      if( typeof tinyMCE_GZ == "undefined" )
 369                      {
 370                          alert( '<?php echo str_replace("'", "\'",
 371                              sprintf( $this->T_('Compressed TinyMCE javascript could not be loaded. Check the "%s" plugin setting.'),
 372                                  $this->T_('URL to TinyMCE') ) ) ?>' );
 373                          tinymce_plugin_displayed_error = true;
 374                          try {
 375                              tinymce_plugin_init_tinymce(oninit);
 376                          } catch(e) {}
 377                      }
 378                      else
 379                      {
 380                          tinyMCE_GZ.init({
 381                              themes: tmce_init.theme,
 382                              plugins: tmce_init.plugins,
 383                              languages: tmce_init.language,
 384                              disk_cache: true,
 385                              debug: false
 386                          }, function() {tinymce_plugin_init_tinymce(oninit)} );
 387                      }
 388  
 389                      <?php
 390                  }
 391                  else
 392                  {    // if not using compressor...
 393                      ?>
 394                      tinymce_plugin_init_tinymce(oninit);
 395                      <?php
 396                  }
 397                  ?>
 398              }
 399  
 400  
 401  			function tinymce_plugin_init_tinymce(oninit)
 402              {
 403                  // Init tinyMCE:
 404                  if( typeof tinyMCE == "undefined" )
 405                  {
 406                      if( ! tinymce_plugin_displayed_error )
 407                      {
 408                          alert( '<?php echo str_replace("'", "\'",
 409                              sprintf( $this->T_('TinyMCE javascript could not be loaded. Check the "%s" plugin setting.'),
 410                              $this->T_('URL to TinyMCE') ) ) ?>' );
 411                          tinymce_plugin_displayed_error = true;
 412                      }
 413                  }
 414                  else
 415                  {
 416                      <?php
 417                      global $Plugins;
 418                      $Plugins->trigger_event('tinymce_before_init');
 419                      ?>
 420  
 421                      // Define oninit function for TinyMCE
 422                      if( typeof tmce_init.oninit != "undefined" )
 423                      {
 424                          oninit = function() {
 425                              tmce_init.oninit();
 426                              oninit();
 427                          }
 428                      }
 429  
 430                      tmce_init.oninit = function ()
 431                      {
 432                          oninit();
 433  
 434                          // Provide hooks for textarea manipulation (where other plugins should hook into):
 435                          var ed = tinyMCE.get("<?php echo $this->tmce_editor_id ?>");
 436                          if( ed && typeof b2evo_Callbacks == "object" )
 437                          {
 438                              // add a callback, that returns the selected (raw) html:
 439                              b2evo_Callbacks.register_callback( "get_selected_text_for_itemform_post_content", function(value) {
 440                                      var inst = tinyMCE.get("<?php echo $this->tmce_editor_id ?>");
 441                                      if( ! inst ) return null;
 442                                      return inst.selection.getContent();
 443                                  } );
 444  
 445                              // add a callback, that wraps a selection:
 446                              b2evo_Callbacks.register_callback( "wrap_selection_for_itemform_post_content", function(params) {
 447                                      var inst = tinyMCE.get("<?php echo $this->tmce_editor_id ?>");
 448                                      if( ! inst ) return null;
 449                                      var sel = inst.selection.getContent();
 450  
 451                                      if( params.replace )
 452                                      {
 453                                          var value = params.before + params.after;
 454                                      }
 455                                      else
 456                                      {
 457                                          var value = params.before + sel + params.after;
 458                                      }
 459                                      inst.selection.setContent(value);
 460  
 461                                      return true;
 462                                  } );
 463  
 464                              // add a callback, that lets us insert raw content:
 465                              // DEPRECATED, used in b2evo 1.10.x
 466                              b2evo_Callbacks.register_callback( "insert_raw_into_itemform_post_content", function(value) {
 467                                      tinyMCE.execInstanceCommand( "<?php echo $this->tmce_editor_id ?>", "mceInsertRawHTML", false, value );
 468                                      return true;
 469                              } );
 470                          }
 471                      }
 472                      tinyMCE.init(tmce_init);
 473                  }
 474              }
 475  
 476          </script>
 477  
 478          <?php
 479          $item_editor_code = ( is_object($edited_Item) ) ? $edited_Item->get_setting( 'editor_code' ) : NULL;
 480          if( !empty( $item_editor_code ) )
 481          {    // We have a preference for the current post, follow it:
 482              // Use tinyMCE if code matched the code of the current plugin.
 483              // 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.
 484              $use_tinymce = ($item_editor_code == $this->code);
 485          }
 486          else
 487          {    // We have no pref, fall back to whatever current user has last used:
 488  
 489              // Has the user used MCE last time he edited this particular blog?
 490              $use_tinymce = $this->UserSettings->get('use_tinymce_coll'.$Blog->ID );
 491  
 492              if( is_null($use_tinymce) )
 493              {    // We don't know for this blog, check if he used MCE last time he edited anything:
 494                  $use_tinymce = $this->UserSettings->get('use_tinymce');
 495              }
 496          }
 497  
 498          $editor_code = 'html';
 499          if( $use_tinymce )
 500          { // User used MCE last time, load MCE on document.ready:
 501              $editor_code = $this->code;
 502              echo '<script type="text/javascript">jQuery( tinymce_plugin_toggleEditor("'.$this->tmce_editor_id.'") );</script>';
 503          }
 504          // By default set the editor code to an empty string
 505          echo '<input type="hidden" name="editor_code" value="">';
 506          // If the js is enabled set the editor code to the currently used value
 507          echo '<script type="text/javascript">jQuery(\'[name="editor_code"]\').attr(\'value\', \''.$editor_code.'\');</script>';
 508  
 509          // We also want to save the 'last used/not-used' state: (if no NULLs, this won't change anything)
 510          $this->htsrv_save_editor_state( array('on'=>$use_tinymce, 'blog'=>$Blog->ID, 'item'=>$edited_Item->ID ) );
 511  
 512          return true;
 513      }
 514  
 515  
 516      /**
 517       * Init the TinyMCE object (in front office).
 518       *
 519       * @return boolean
 520       */
 521  	function DisplayEditorButton( & $params )
 522      {
 523          return $this->AdminDisplayEditorButton($params);
 524      }
 525  
 526  
 527      /**
 528       * Wrapper to either use {@link fetch_remote_page()} from b2evo or our own copy.
 529       *
 530       * @todo fp> why do we need all this code? Just for backward compatibility with versions of b2evo below 1.10.x ???
 531       *       dh> Yes. Can get dropped (and GetDependencies adjusted, to e.g. 2.0 - if _url.funcs is included there always)
 532       *
 533       * @return string|false
 534       */
 535  	function my_fetch_remote_page( $url, & $info )
 536      {
 537          global $inc_path;
 538  
 539          if( file_exists( $inc_path.'_core/_url.funcs.php' ) )
 540          { // b2evo 2.0
 541              require_once $inc_path.'_core/_url.funcs.php';
 542          }
 543          elseif( file_exists( $inc_path.'_misc/_url.funcs.php' ) )
 544          { // b2evo 1.10.x(?)
 545              require_once $inc_path.'_misc/_url.funcs.php';
 546          }
 547          if( function_exists('fetch_remote_page') )
 548          {
 549              return fetch_remote_page( $url, $info );
 550          }
 551  
 552          // Copied from b2evo HEAD (blogs/inc/_misc/_url.funcs.php): {{{
 553          $info = array(
 554              'error' => '',
 555              'status' => NULL );
 556  
 557          // CURL:
 558          if( extension_loaded('curl') )
 559          {
 560              $info['used_method'] = 'curl';
 561  
 562              $ch = curl_init();
 563              curl_setopt($ch, CURLOPT_URL, $url);
 564              curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 565              if( ! empty($params['method']) && $params['method'] == 'HEAD'  )
 566              {
 567                  curl_setopt($ch, CURLOPT_NOBODY, true);
 568              }
 569              $r = curl_exec($ch);
 570              $info['status'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
 571              $info['error'] = curl_error($ch);
 572              if( curl_errno($ch) )
 573              {
 574                  $info['error'] .= '(#'.curl_errno($ch).')';
 575              }
 576              curl_close($ch);
 577  
 578              return $r;
 579          }
 580  
 581  
 582          // URL FOPEN (fallback to fsockopen, if fopen() fails):
 583          if( ini_get('allow_url_fopen') && function_exists('stream_get_meta_data') /* PHP 4.3, may also be disabled!? */ )
 584          {
 585              $info['used_method'] = 'fopen';
 586  
 587              $fp = @fopen($url, 'r');
 588              if( $fp )
 589              { // this will be false e.g. for "404", but it's not trivial to get the status error for this, so we retry with fsockopen further down
 590                  // headers:
 591                  $meta = stream_get_meta_data($fp);
 592                  if( ! $meta || ! preg_match( '~^HTTP/\d+\.\d+ (\d+)~', $meta['wrapper_data'][0], $match ) )
 593                  {
 594                      $info['error'] = 'Invalid response.';
 595                      $r = false;
 596                  }
 597                  else
 598                  {
 599                      $info['status'] = $match[1];
 600                      $r = '';
 601                      while( $buf = fread($fp, 4096) )
 602                      { //read the complete file (binary safe)
 603                          $r .= $buf;
 604                      }
 605                  }
 606                  fclose($fp);
 607  
 608                  return $r;
 609              }
 610          }
 611  
 612  
 613          // As a last resort, try fsockopen:
 614          if( ! function_exists('fsockopen') )
 615          { // may have been disabled
 616              $info['used_method'] = null;
 617              $info['error'] = 'No method available to access URL!';
 618              return false;
 619          }
 620  
 621          $info['used_method'] = 'fsockopen';
 622          $url_parsed = parse_url($url);
 623          if( empty($url_parsed['scheme']) ) {
 624              $url_parsed = parse_url('http://'.$url);
 625          }
 626  
 627          $host = $url_parsed['host'];
 628          $port = ( empty($url_parsed['port']) ? 80 : $url_parsed['port'] );
 629          $path = empty($url_parsed['path']) ? '/' : $url_parsed['path'];
 630          if( ! empty($url_parsed['query']) )
 631          {
 632              $path .= '?'.$url_parsed['query'];
 633          }
 634  
 635          $out = "GET $path HTTP/1.0\r\n";
 636          $out .= "Host: $host:$port\r\n";
 637          $out .= "Connection: Close\r\n\r\n";
 638  
 639          $fp = @fsockopen($host, $port, $errno, $errstr, 30);
 640          if( ! $fp )
 641          {
 642              $info['error'] = $errstr.' (#'.$errno.')';
 643              return false;
 644          }
 645  
 646          // Set timeout for data:
 647          if( function_exists('stream_set_timeout') )
 648              stream_set_timeout( $fp, 20 ); // PHP 4.3.0
 649          else
 650              socket_set_timeout( $fp, 20 ); // PHP 4
 651  
 652          // Send request:
 653          fwrite($fp, $out);
 654  
 655          // Read response:
 656          $r = '';
 657          // First line:
 658          $s = fgets($fp, 4096);
 659          if( ! preg_match( '~^HTTP/\d+\.\d+ (\d+)~', $s, $match ) )
 660          {
 661              $info['error'] = 'Invalid response.';
 662              $r = false;
 663          }
 664          else
 665          {
 666              $info['status'] = $match[1];
 667  
 668              $foundBody = false;
 669              while( ! feof($fp) )
 670              {
 671                  $s = fgets($fp, 4096);
 672                  if( $s == "\r\n" )
 673                  {
 674                      $foundBody = true;
 675                      continue;
 676                  }
 677                  if( $foundBody )
 678                  {
 679                      $r .= $s;
 680                  }
 681              }
 682          }
 683          fclose($fp);
 684  
 685          return $r;
 686          // }}}
 687      }
 688  
 689  
 690      /* PRIVATE */
 691      /**
 692       * Create Options for TinyMCE.init() (non-compressor) - not TinyMCE_GZ.init (compressor)!!
 693       *
 694       * @todo fp> valid_elements to try to generate less validation errors
 695       *
 696       * @param string simple|expert
 697       * @return string|false
 698       */
 699  	function get_tmce_init( $edit_layout )
 700      {
 701          global $Blog;
 702          global $Plugins;
 703          global $localtimenow, $debug, $rsc_url, $skins_url;
 704          global $UserSettings;
 705          global $ReqHost;
 706  
 707          $tmce_plugins_array = array( 'more', 'pagebreak', 'searchreplace', 'inlinepopups', 'table', 'media', 'visualchars', 'nonbreaking', 'safari', 'fullscreen' );
 708  
 709          // Requires cURL extension since fsockopen + ssl produce fatal error
 710          // if PHP configured without openSSL
 711          if( extension_loaded('curl') )
 712          {
 713              $tmce_plugins_array[] = 'spellchecker';
 714          }
 715  
 716          $tmce_theme_advanced_buttons1_array = array();
 717          $tmce_theme_advanced_buttons2_array = array();
 718          $tmce_theme_advanced_buttons3_array = array();
 719          $tmce_theme_advanced_buttons4_array = array();
 720  
 721          if( $UserSettings->get('control_form_abortions') )
 722          {    // Activate bozo validator: autosave plugin in TinyMCE
 723              $tmce_plugins_array[] = 'autosave';
 724          }
 725  
 726          if( $this->UserSettings->get('tmce_options_contextmenu') == 1 )
 727          {
 728              $tmce_plugins_array[] = 'contextmenu';
 729          }
 730  
 731          if( $edit_layout == 'inskin' )
 732          {    // In-skin editing mode
 733  
 734              /* ----------- button row 1 ------------ */
 735  
 736              $tmce_theme_advanced_buttons1_array = array(
 737                  'bold,italic,strikethrough,forecolor,backcolor',
 738                  'fontselect,fontsizeselect',
 739                  'removeformat',
 740                  'nonbreaking,charmap',
 741                  'image,media'
 742              );
 743  
 744              /* ----------- button row 2 ------------ */
 745  
 746              $tmce_theme_advanced_buttons2_array = array(
 747                  'formatselect,styleselect',
 748                  'bullist,numlist',
 749                  'outdent,indent',
 750                  'justifyleft,justifycenter,justifyright,justifyfull',
 751                  'morebtn,pagebreak',
 752                  'fullscreen'
 753              );
 754  
 755              /* ----------- button row 3 ------------ */
 756  
 757              $tmce_theme_advanced_buttons3_array = array(
 758                  'link,unlink',
 759                  'undo,redo',
 760                  'search,replace'
 761              );
 762          }
 763          else
 764          {    // Simple & Expert modes
 765  
 766              /* ----------- button row 1 ------------ */
 767  
 768              $tmce_theme_advanced_buttons1_array = array(
 769                  'bold,italic,strikethrough,forecolor,backcolor',
 770                  'fontselect,fontsizeselect',
 771                  'removeformat',
 772                  'nonbreaking,charmap',
 773                  'image,media',
 774                  'link,unlink',
 775                  'fullscreen'
 776              );
 777  
 778              /* ----------- button row 2 ------------ */
 779  
 780              $tmce_theme_advanced_buttons2_array = array(
 781                  'formatselect,styleselect',
 782                  'bullist,numlist',
 783                  'outdent,indent',
 784                  'justifyleft,justifycenter,justifyright,justifyfull',
 785                  'morebtn,pagebreak',
 786                  'undo,redo',
 787                  'search,replace'
 788              );
 789          }
 790  
 791          if( $edit_layout == 'expert' )
 792          {    // Simple needs to be simpler than expert
 793  
 794              /* ----------- button row 3 ------------ */
 795  
 796              $tmce_theme_advanced_buttons3_array = array(
 797                  'visualchars,visualaid',
 798                  'table,row_props,cell_props,delete_col,delete_row,col_after,col_before,row_after,row_before,row_after,row_before,split_cells,merge_cells',
 799                  'sub,sup'
 800              );
 801  
 802              if( $this->UserSettings->get('tmce_options_directionality') == 1 )
 803              {
 804                  $tmce_plugins_array[] = 'directionality';
 805                  array_push($tmce_theme_advanced_buttons3_array, 'ltr,rtl');
 806              }
 807  
 808              if( $this->UserSettings->get('tmce_options_paste') == 1 )
 809              {
 810                  $tmce_plugins_array[] = 'paste';
 811                  $tmce_theme_advanced_buttons3_array[] = 'pastetext,pasteword';
 812              }
 813  
 814              // Requires cURL extension since fsockopen + ssl produce fatal error
 815              // if PHP configured without openSSL
 816              if( extension_loaded('curl') )
 817              {
 818                  $tmce_theme_advanced_buttons3_array[] = 'spellchecker';
 819              }
 820  
 821              $tmce_theme_advanced_buttons3_array[] = 'code,cleanup,|,help';
 822  
 823              /* ----------- button row 4 ------------ */
 824  
 825              $tmce_theme_advanced_buttons4_array = array();
 826  
 827              if( $this->UserSettings->get('tmce_options_xhtmlxtras') == 1 )
 828              {
 829                  array_push($tmce_plugins_array,'xhtmlxtras');
 830                  array_push($tmce_theme_advanced_buttons4_array,'cite,abbr,acronym,del,ins');
 831              }
 832  
 833              $tmce_theme_advanced_buttons4_array =
 834                  $Plugins->get_trigger_event("tinymce_extend_buttons",
 835                      array("tinymce_buttons" => $tmce_theme_advanced_buttons4_array),
 836                          "tinymce_buttons");
 837          }
 838  
 839          $tmce_theme_advanced_buttons1 = implode( ',|,' , $tmce_theme_advanced_buttons1_array );
 840          $tmce_theme_advanced_buttons2 = implode( ',|,' , $tmce_theme_advanced_buttons2_array );
 841          $tmce_theme_advanced_buttons3 = implode( ',|,' , $tmce_theme_advanced_buttons3_array );
 842          $tmce_theme_advanced_buttons4 = implode( ',|,' , $tmce_theme_advanced_buttons4_array );
 843  
 844          // PLUGIN EXTENSIONS:
 845          $tmce_plugins_array =
 846              $Plugins->get_trigger_event("tinymce_extend_plugins",
 847                  array("tinymce_plugins" => $tmce_plugins_array),
 848                      "tinymce_plugins");
 849  
 850          $tmce_plugins = implode( ',' , $tmce_plugins_array );
 851  
 852          global $current_locale, $plugins_path;
 853          $tmce_language = substr($current_locale, 0, 2);
 854          // waltercruz> Fallback to english if there's no tinymce equivalent to the user locale
 855          // to avoid some strange screens like http://www.flickr.com/photos/waltercruz/3390729964/
 856          $lang_path = $plugins_path.$this->classname.'/tiny_mce/langs/'.$tmce_language.'.js';
 857          if( !file_exists( $lang_path ) )
 858          {
 859              $tmce_language = 'en';
 860          }
 861  
 862          // Configuration: -- http://wiki.moxiecode.com/index.php/TinyMCE:Configuration
 863          $init_options = array();
 864          // Convert one specifc textarea to use TinyMCE:
 865          $init_options[] = 'mode : "exact"';
 866          $init_options[] = 'elements : "'.$this->tmce_editor_id.'"';
 867          // TinyMCE Theme+Skin+Variant to use:
 868          $init_options[] = 'theme : "advanced"';
 869          $init_options[] = 'skin : "o2k7"';
 870          $init_options[] = 'skin_variant : "silver"';
 871          // comma separated list of plugins: -- http://wiki.moxiecode.com/index.php/TinyMCE:Plugins
 872          $init_options[] = 'plugins : "'.$tmce_plugins.'"';
 873          $init_options[] = 'more_separator : "<!--more-->"';
 874          $init_options[] = 'pagebreak_separator : "<!--nextpage-->"';
 875          // Toolbars:
 876          $init_options[] = 'theme_advanced_toolbar_location : "top"';
 877          $init_options[] = 'theme_advanced_toolbar_align : "center"'; // just to be consistent with current toolbars for now
 878          $init_options[] = 'theme_advanced_buttons1 : "'.$tmce_theme_advanced_buttons1.'"';
 879          $init_options[] = 'theme_advanced_buttons2 : "'.$tmce_theme_advanced_buttons2.'"';
 880          $init_options[] = 'theme_advanced_buttons3 : "'.$tmce_theme_advanced_buttons3.'"';
 881          $init_options[] = 'theme_advanced_buttons4 : "'.$tmce_theme_advanced_buttons4.'"';
 882          // UI options:
 883          $init_options[] = 'theme_advanced_blockformats : "p,pre,blockquote,h2,h3,h4,h5,h6,address,dt,dd,div"';
 884          // if( $edit_layout == 'expert' )
 885          {
 886              $init_options[] = 'theme_advanced_path_location : "bottom"';
 887              $init_options[] = 'theme_advanced_resizing : true';
 888              $init_options[] = 'theme_advanced_resize_horizontal : false';
 889          }
 890          $init_options[] = 'language : "'.$tmce_language.'"';
 891          // body_class : "my_class"
 892          // CSS used in the iframe/editable area: -- http://wiki.moxiecode.com/index.php/TinyMCE:Configuration/content_css
 893          // note: $version may not be needed below because of automatic suffix? not sure..
 894          // TODO: we don't want all of basic.css here
 895  
 896          // Load the appropriate ITEM/POST styles depending on the blog's skin:
 897          // dh>This has to be the skins whole CSS to get real WYSIWYG handling.
 898          // fp> we are not aiming for perfect wysiwyg (too heavy), just for a relevant look & feel.
 899          // dh>We can/should use class_filter to only keep useful classes.
 900          // fp> how???
 901              $content_css = '';
 902              $blog_skin_ID = $Blog->get_skin_ID();
 903              if( ! empty( $blog_skin_ID ) )
 904              {
 905                  $SkinCache = & get_SkinCache();
 906                  /**
 907                   * @var Skin
 908                   */
 909                  $Skin = $SkinCache->get_by_ID( $blog_skin_ID );
 910                  $item_css_url = $skins_url.$Skin->folder.'/item.css';
 911                  // else: $item_css_url = $rsc_url.'css/item_base.css';
 912                  $content_css .= ','.$item_css_url;        // fp> TODO: this needs to be a param... "of course" -- if none: else item_default.css ?
 913              }
 914              // else item_default.css -- is it still possible to have no skin ?
 915  
 916          $init_options[] = 'content_css : "'.$this->get_plugin_url().'editor.css?v='.( $debug ? $localtimenow : $this->version )
 917                                      .$content_css.'"';
 918  
 919  /* fp> the following seems like something that filters classes but the way it's done doesn't make sense to me.
 920     fp> the skin should provide a list of classes to include (with a default setting in the default skin class)
 921          // Add callback which filters classes from content_css by classname and/or rule
 922          // Another option would be to use theme_advanced_styles (http://wiki.moxiecode.com/index.php/TinyMCE:Configuration/theme_advanced_styles)
 923          $init_options[] = 'class_filter : function(cls, rule) {
 924              var m = rule.match(/^\.bPost (.*)/);
 925              if( m ) {
 926                  return cls;
 927              }
 928  
 929              if( cls == "center" || cls == "right" || cls == "left" ) { // TODO: dh> could get translated
 930                  return cls;
 931              }
 932  
 933              // console.log(cls, rule);
 934              return false;
 935          }';
 936  */
 937  
 938          // Generated HTML code options:
 939          // do not make the path relative to "document_base_url":
 940          $init_options[] = 'relative_urls : false';
 941          $init_options[] = 'entity_encoding : "raw"';
 942  
 943          // remove_linebreaks : false,
 944          // not documented:    auto_cleanup_word : true,
 945  
 946          $init = implode( ",\n", $init_options ).',
 947              plugin_insertdate_dateFormat : "%Y-%m-%d",
 948              plugin_insertdate_timeFormat : "%H:%M:%S",
 949  
 950              paste_create_paragraphs : true,
 951              paste_create_linebreaks : true,
 952              paste_use_dialog : true,
 953              paste_convert_headers_to_strong : false,
 954              paste_convert_middot_lists : true
 955  
 956          ';
 957          // custom conf:
 958          if( $tmce_custom_conf = $this->Settings->get('tmce_custom_conf') )
 959          {
 960              $init .= ",\n// tmce_custom_conf (from PluginSettings):\n".$tmce_custom_conf;
 961          }
 962          return $init;
 963      }
 964  
 965  
 966      /**
 967       * Get URL of file to include as "content_css" for layout and classes in TinyMCE.
 968       *
 969       * @return array (path, url)
 970       *
 971      function get_item_css_path_and_url($Blog)
 972      {
 973          global $skins_url, $skins_path;
 974  
 975          # TODO: make this a setting
 976          #if( $r = $this->Settings->get('content_css') )
 977          #{
 978          #    return $r;
 979          #}
 980  
 981          // Load the appropriate ITEM/POST styles depending on the blog's skin:
 982          if( ! empty( $Blog->skin_ID) )
 983          {
 984              $SkinCache = & get_SkinCache();
 985              /**
 986               * @var Skin
 987               *
 988              $Skin = $SkinCache->get_by_ID( $Blog->skin_ID );
 989              $item_css_path = $Skin->folder.'/item.css';        // fp> TODO: this needs to be a param... "of course" -- if none: else item_default.css ?
 990              // else: $item_css_path = 'css/item_base.css';
 991  
 992              $item_css_path = $Skin->folder.'/style.css';
 993  
 994              return array($skins_path.$item_css_path, $skins_url.$item_css_path);
 995          }
 996          // else item_default.css -- is it still possible to have no skin ?
 997  
 998          return array(NULL, NULL);
 999      }
1000      */
1001  
1002      /**
1003       * AJAX callback to save editor state (on or off).
1004       */
1005  	function htsrv_save_editor_state($params)
1006      {
1007          /**
1008           * @var DB
1009           */
1010          global $DB;
1011  
1012          if( ! isset($params['on']) )
1013          {
1014              return;
1015          }
1016  
1017          // Save plugin usersettings
1018          if( !empty($params['blog']) )
1019          {    // This is in order to try & recall a specific state for each blog: (will be used for new posts especially)
1020              $this->UserSettings->set( 'use_tinymce_coll'.(int)$params['blog'], (int)$params['on'] );
1021          }
1022          $this->UserSettings->set( 'use_tinymce', (int)$params['on'] );
1023          $this->UserSettings->dbupdate();
1024      }
1025  
1026  
1027      /**
1028       * HtSrv callback to get the contents of the CSS file configured for "content_css".
1029       * This gets used when the CSS is not on the same domain and the browser would not
1030       * allow to handle the CSS cross domain (e.g. FF 3.5).
1031       *
1032       * @param array Params passed to the HtSrv call
1033       *              - "blog": selected blog
1034       * @return string
1035       *
1036      function htsrv_get_item_content_css($params)
1037      {
1038          $blog = $params['blog'];
1039          $BlogCache = get_BlogCache($blog);
1040          $Blog = $BlogCache->get_by_ID($blog);
1041          $path = array_shift($this->get_item_css_path_and_url($Blog));
1042          $r = file_get_contents($path);
1043          if( $r )
1044          {
1045              header('Content-Type: text/css');
1046              echo $r;
1047          }
1048          else
1049          {
1050              header('HTTP/1.0 404 Not Found');
1051          }
1052          exit;
1053      }
1054      */
1055  
1056      /**
1057       * Return the list of Htsrv (HTTP-Services) provided by the plugin.
1058       *
1059       * This implements the plugin interface for the list of methods that are valid to
1060       * get called through htsrv/call_plugin.php.
1061       *
1062       * @return array
1063       */
1064  	function GetHtsrvMethods()
1065      {
1066          return array('save_editor_state', 'get_item_content_css');
1067      }
1068  
1069  
1070      /**
1071       * Event handler: Called as action just before updating the {@link Plugin::$Settings plugin's settings}.
1072       *
1073       * @return false|NULL Return false to prevent the settings from being updated to DB.
1074       */
1075  	function PluginSettingsUpdateAction()
1076      {
1077          if( $this->Settings->get( 'use_gzip_compressor' ) == 1 )
1078          { // Check if the cache folder is not writable
1079              global $cache_path;
1080              $cache_folder = $cache_path.'plugins/tinymce'; // Cache path, this is where the .gz files will be stored
1081  
1082              if( !is_writable( $cache_folder ) )
1083              {
1084                  global $Messages;
1085                  $Messages->add( sprintf( T_( 'TinyMCE plugin cannot uses the compressor because folder %s is not writable' ), '<b>'.$cache_folder.'</b>' ), 'note' );
1086  
1087                  // Disable gzip compressor
1088                  $this->Settings->set( 'use_gzip_compressor', 0 );
1089              }
1090          }
1091      }
1092  }
1093  
1094  ?>

title

Description

title

Description

title

Description

title

title

Body