1<?php 2 3/** 4 * @file 5 * Integrates client-side editors with Drupal. 6 */ 7require_once 'includes/styling.inc'; 8 9/** 10 * Implements hook_entity_info(). 11 */ 12function wysiwyg_entity_info() { 13 $types['wysiwyg_profile'] = array( 14 'label' => t('Wysiwyg profile'), 15 'base table' => 'wysiwyg', 16 'controller class' => 'WysiwygProfileController', 17 'fieldable' => FALSE, 18 // When loading all entities, DrupalDefaultEntityController::load() ignores 19 // its static cache. Therefore, wysiwyg_profile_load_all() implements a 20 // custom static cache. 21 'static cache' => FALSE, 22 'entity keys' => array( 23 'id' => 'format', 24 ), 25 ); 26 return $types; 27} 28 29/** 30 * Controller class for Wysiwyg profiles. 31 */ 32class WysiwygProfileController extends DrupalDefaultEntityController { 33 /** 34 * Overrides DrupalDefaultEntityController::attachLoad(). 35 */ 36 function attachLoad(&$queried_entities, $revision_id = FALSE) { 37 // Unserialize the profile settings. 38 foreach ($queried_entities as $key => $record) { 39 $settings = unserialize($record->settings); 40 // Profile preferences are stored with the editor settings to avoid adding 41 // an extra table column. 42 if (isset($settings['_profile_preferences'])) { 43 $preferences = $settings['_profile_preferences']; 44 unset($settings['_profile_preferences']); 45 } 46 else { 47 $preferences = array(); 48 } 49 $queried_entities[$key]->settings = $settings; 50 $queried_entities[$key]->preferences = $preferences; 51 // @todo Store the name in the profile when allowing more than one per 52 // format. 53 $queried_entities[$key]->name = 'format' . $record->format; 54 } 55 // Call the default attachLoad() method. 56 parent::attachLoad($queried_entities, $revision_id); 57 } 58} 59 60/** 61 * Implementation of hook_menu(). 62 */ 63function wysiwyg_menu() { 64 $items['admin/config/content/wysiwyg'] = array( 65 'title' => 'Wysiwyg profiles', 66 'page callback' => 'drupal_get_form', 67 'page arguments' => array('wysiwyg_profile_overview'), 68 'description' => 'Configure client-side editors.', 69 'access arguments' => array('administer filters'), 70 'file' => 'wysiwyg.admin.inc', 71 ); 72 $items['admin/config/content/wysiwyg/list'] = array( 73 'title' => 'List', 74 'type' => MENU_DEFAULT_LOCAL_TASK, 75 'weight' => -10, 76 ); 77 $items['admin/config/content/wysiwyg/profile/%wysiwyg_ui_profile_cache'] = array( 78 'title callback' => 'wysiwyg_admin_profile_title', 79 'title arguments' => array(5), 80 'page callback' => 'drupal_get_form', 81 'page arguments' => array('wysiwyg_profile_form', 5), 82 'access arguments' => array('administer filters'), 83 'file' => 'wysiwyg.admin.inc', 84 ); 85 $items['admin/config/content/wysiwyg/profile/%wysiwyg_ui_profile_cache/edit'] = array( 86 'title' => 'Edit', 87 'type' => MENU_DEFAULT_LOCAL_TASK, 88 ); 89 $items['admin/config/content/wysiwyg/profile/%wysiwyg_ui_profile_cache/delete'] = array( 90 'title' => 'Remove', 91 'page callback' => 'drupal_get_form', 92 'page arguments' => array('wysiwyg_profile_delete_confirm', 5), 93 'access arguments' => array('administer filters'), 94 'file' => 'wysiwyg.admin.inc', 95 'type' => MENU_LOCAL_TASK, 96 'weight' => 10, 97 ); 98 $items['admin/config/content/wysiwyg/profile/%wysiwyg_ui_profile_cache/break-lock'] = array( 99 'title' => 'Break lock', 100 'page callback' => 'drupal_get_form', 101 'page arguments' => array('wysiwyg_profile_break_lock_confirm', 5), 102 'access arguments' => array('administer filters'), 103 'file' => 'wysiwyg.admin.inc', 104 'type' => MENU_VISIBLE_IN_BREADCRUMB, 105 ); 106 // @see wysiwyg_dialog() 107 $items['wysiwyg/%'] = array( 108 'page callback' => 'wysiwyg_dialog', 109 'page arguments' => array(1), 110 'delivery callback' => 'wysiwyg_deliver_dialog_page', 111 'access arguments' => array('access content'), 112 'type' => MENU_CALLBACK, 113 'file' => 'wysiwyg.dialog.inc', 114 ); 115 $items['wysiwyg_theme/%'] = array( 116 'theme callback' => '_wysiwyg_theme_callback', 117 'theme arguments' => array(1), 118 'page callback' => '_wysiwyg_theme_check_active', 119 'page arguments' => array(1), 120 'delivery callback' => '_wysiwyg_delivery_dummy', 121 'access arguments' => array('access content'), 122 'type' => MENU_CALLBACK, 123 ); 124 return $items; 125} 126 127/** 128 * Display an editor profile title. 129 * 130 * @param $profile 131 * An editor profile object. 132 * 133 * @return 134 * The unfiltered name of an editor profile. 135 * Currently the same as the text format name. 136 */ 137function wysiwyg_admin_profile_title($profile) { 138 $format = filter_format_load($profile->format); 139 return $format->name; 140} 141 142/** 143 * Implements hook_admin_menu_map(). 144 */ 145function wysiwyg_admin_menu_map() { 146 if (!user_access('administer filters')) { 147 return; 148 } 149 150 $profiles = wysiwyg_profile_load_all(); 151 $map['admin/config/content/wysiwyg/profile/%wysiwyg_ui_profile_cache'] = array( 152 'parent' => 'admin/config/content/wysiwyg', 153 'hide' => 'admin/config/content/wysiwyg/list', 154 'arguments' => array( 155 array('%wysiwyg_ui_profile_cache' => array_keys($profiles)), 156 ), 157 ); 158 return $map; 159} 160 161/** 162 * Implements hook_element_info(). 163 */ 164function wysiwyg_element_info() { 165 // @see wysiwyg_dialog() 166 $types['wysiwyg_dialog_page'] = array( 167 '#theme' => 'wysiwyg_dialog_page', 168 '#theme_wrappers' => array('html'), 169 '#show_messages' => TRUE, 170 ); 171 return $types; 172} 173 174/** 175 * Implementation of hook_theme(). 176 * 177 * @see drupal_common_theme(), common.inc 178 * @see template_preprocess_page(), theme.inc 179 */ 180function wysiwyg_theme() { 181 return array( 182 'wysiwyg_profile_overview' => array( 183 'render element' => 'form', 184 ), 185 'wysiwyg_admin_button_table' => array( 186 'render element' => 'form', 187 ), 188 // @see wysiwyg_dialog() 189 'wysiwyg_dialog_page' => array( 190 'render element' => 'page', 191 'file' => 'wysiwyg.dialog.inc', 192 'template' => 'wysiwyg-dialog-page', 193 ), 194 ); 195} 196 197/** 198 * Implementation of hook_help(). 199 */ 200function wysiwyg_help($path, $arg) { 201 switch ($path) { 202 case 'admin/config/content/wysiwyg': 203 $output = '<p>' . t('A Wysiwyg profile is associated with a text format. A Wysiwyg profile defines which client-side editor is loaded with a particular text format, what buttons or themes are enabled for the editor, how the editor is displayed, and a few other editor-specific functions.') . '</p>'; 204 return $output; 205 } 206} 207 208/** 209 * Implements hook_element_info_alter(). 210 */ 211function wysiwyg_element_info_alter(&$types) { 212 $types['text_format']['#pre_render'][] = 'wysiwyg_pre_render_text_format'; 213 // For filtering stylesheets before Core aggregates them. 214 array_unshift($types['styles']['#pre_render'], '_wysiwyg_filter_editor_styles'); 215 // For recording and caching the added stylesheets. 216 $types['styles']['#pre_render'][] = '_wysiwyg_pre_render_styles'; 217} 218 219 220/** 221 * Process a text format widget to load and attach editors. 222 * 223 * The element's #id is used as reference to attach client-side editors. 224 */ 225function wysiwyg_pre_render_text_format($element) { 226 // filter_process_format() copies properties to the expanded 'value' child 227 // element. Skip this text format widget, if it contains no 'format' or when 228 // the current user does not have access to edit the value. 229 // Simplify module creates an extra incomplete 'format' on the base field. 230 if (!isset($element['format']['format']) || !empty($element['value']['#disabled'])) { 231 return $element; 232 } 233 // Allow modules to programmatically enforce no client-side editor by setting 234 // the #wysiwyg property to FALSE. 235 if (isset($element['#wysiwyg']) && !$element['#wysiwyg']) { 236 return $element; 237 } 238 239 $format_field = &$element['format']; 240 $field = &$element['value']; 241 $settings = array( 242 'field' => $field['#id'], 243 ); 244 245 // If this textarea is #resizable the 'none' editor will attach/detach it to 246 // avoid hi-jacking the UI. 247 if (!empty($field['#resizable'])) { 248 $settings['resizable'] = 1; 249 } 250 if (isset($element['summary']) && $element['summary']['#type'] == 'textarea') { 251 $settings['summary'] = $element['summary']['#id']; 252 } 253 254 if (!$format_field['format']['#access'] || (isset($format_field['#access']) && !$format_field['#access'])) { 255 // Directly specify which the single available format is. 256 $available_formats = array($format_field['format']['#value'] => $format_field['format']['#options'][$format_field['format']['#value']]); 257 $settings['activeFormat'] = $format_field['format']['#value']; 258 } 259 else { 260 // Let the client check the selectbox for the active format. 261 $available_formats = $format_field['format']['#options']; 262 $settings['select'] = $format_field['format']['#id']; 263 } 264 // Determine the available text formats. 265 foreach ($available_formats as $format_id => $format_name) { 266 $format = 'format' . $format_id; 267 268 // Fetch the profile associated to this text format. 269 $profile = wysiwyg_get_profile($format_id); 270 if ($profile) { 271 // Initialize default settings, defaulting to 'none' editor. 272 $settings[$format] = array( 273 'editor' => 'none', 274 'status' => 1, 275 'toggle' => 1, 276 ); 277 $loaded = TRUE; 278 if (isset($profile->preferences['add_to_summaries']) && !$profile->preferences['add_to_summaries']) { 279 $settings[$format]['skip_summary'] = 1; 280 } 281 $settings[$format]['editor'] = $profile->editor; 282 $settings[$format]['status'] = (int) wysiwyg_user_get_status($profile); 283 if (isset($profile->preferences['show_toggle'])) { 284 $settings[$format]['toggle'] = (int) $profile->preferences['show_toggle']; 285 } 286 // Check editor theme (and reset it if not/no longer available). 287 $theme = wysiwyg_get_editor_themes($profile, (isset($profile->settings['theme']) ? $profile->settings['theme'] : '')); 288 289 // Add plugin settings (first) for this text format. 290 wysiwyg_add_plugin_settings($profile); 291 // Add profile settings for this text format. 292 wysiwyg_add_editor_settings($profile, $theme); 293 } 294 } 295 296 // Store the unaltered content so it can be restored if no changes 297 // intentionally made by the user were detected, such as those caused by 298 // WYSIWYG editors when initially parsing and loading content. 299 if (!empty($element['value']['#value'])) { 300 $original = $element['value']['#value']; 301 $element['value']['#attributes']['data-wysiwyg-value-original'] = $original; 302 $element['value']['#attributes']['data-wysiwyg-value-is-changed'] = 'false'; 303 } 304 if (!empty($element['summary']['#value']) && $element['summary']['#type'] === 'textarea') { 305 $original = $element['summary']['#value']; 306 $element['summary']['#attributes']['data-wysiwyg-value-original'] = $original; 307 $element['summary']['#attributes']['data-wysiwyg-value-is-changed'] = 'false'; 308 } 309 310 $element['value']['#attributes']['class'][] = 'wysiwyg'; 311 $element['#attached']['js'][] = array( 312 'data' => array( 313 'wysiwyg' => array( 314 'triggers' => array( 315 $element['value']['#id'] => $settings, 316 ), 317 ), 318 ), 319 'type' => 'setting', 320 ); 321 return $element; 322} 323 324/** 325 * Determine the profile to use for a given input format id. 326 * 327 * This function also performs sanity checks for the configured editor in a 328 * profile to ensure that we do not load a malformed editor. 329 * 330 * @param $format 331 * The internal id of an input format. 332 * 333 * @return 334 * A wysiwyg profile. 335 * 336 * @see wysiwyg_load_editor(), wysiwyg_get_editor() 337 */ 338function wysiwyg_get_profile($format) { 339 if ($profile = wysiwyg_profile_load($format)) { 340 if (wysiwyg_load_editor($profile)) { 341 return $profile; 342 } 343 } 344 return FALSE; 345} 346 347/** 348 * Load an editor library and initialize basic Wysiwyg settings. 349 * 350 * @param $profile 351 * A wysiwyg editor profile. 352 * 353 * @return 354 * TRUE if the editor has been loaded, FALSE if not. 355 * 356 * @see wysiwyg_get_profile() 357 */ 358function wysiwyg_load_editor($profile) { 359 static $settings_added; 360 static $loaded = array(); 361 $path = drupal_get_path('module', 'wysiwyg'); 362 363 $name = $profile->editor; 364 // Library files must be loaded only once. 365 if (!isset($loaded[$name])) { 366 // Load editor. 367 $editor = wysiwyg_get_editor($name); 368 if ($editor) { 369 $default_library_options = array( 370 'type' => 'file', 371 'scope' => 'header', 372 'defer' => FALSE, 373 'cache' => TRUE, 374 'preprocess' => TRUE, 375 ); 376 // Determine library files to load. 377 // @todo Allow to configure the library/execMode to use. 378 if (isset($profile->preferences['library']) && isset($editor['libraries'][$profile->preferences['library']])) { 379 $library = $profile->preferences['library']; 380 $files = $editor['libraries'][$library]['files']; 381 } 382 else { 383 // Fallback to the first defined library by default (external libraries can change). 384 $library = key($editor['libraries']); 385 $files = array_shift($editor['libraries']); 386 $files = $files['files']; 387 } 388 389 // Check whether the editor requires an initialization script. 390 if (!empty($editor['init callback'])) { 391 $init = $editor['init callback']($editor, $library, $profile); 392 if (!empty($init)) { 393 // Build a file for each of the editors to hold the init scripts. 394 // @todo Aggregate all initialization scripts into one file. 395 $uri = 'public://js/wysiwyg/wysiwyg_' . $name . '_' . drupal_hash_base64($init) . '.js'; 396 $init_exists = file_exists($uri); 397 if (!$init_exists) { 398 $js_path = dirname($uri); 399 file_prepare_directory($js_path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); 400 } 401 // Attempt to create the file, or fall back to an inline script (which 402 // will not work in Ajax calls). 403 if (!$init_exists && !file_unmanaged_save_data($init, $uri, FILE_EXISTS_REPLACE)) { 404 drupal_add_js($init, array('type' => 'inline') + $default_library_options); 405 } 406 else { 407 drupal_add_js($uri, $default_library_options); 408 } 409 } 410 } 411 412 foreach ($files as $file => $options) { 413 if (is_array($options)) { 414 $options += $default_library_options; 415 drupal_add_js($editor['library path'] . '/' . $file, $options); 416 } 417 else { 418 drupal_add_js($editor['library path'] . '/' . $options); 419 } 420 } 421 // If editor defines an additional load callback, invoke it. 422 // @todo Isn't the settings callback sufficient? 423 if (isset($editor['load callback']) && function_exists($editor['load callback'])) { 424 $editor['load callback']($editor, $library); 425 } 426 // Load JavaScript integration files for this editor. 427 $files = array(); 428 if (isset($editor['js files'])) { 429 $files = $editor['js files']; 430 } 431 foreach ($files as $file) { 432 drupal_add_js($editor['js path'] . '/' . $file); 433 } 434 // Load CSS stylesheets for this editor. 435 $files = array(); 436 if (isset($editor['css files'])) { 437 $files = $editor['css files']; 438 } 439 foreach ($files as $file) { 440 drupal_add_css($editor['css path'] . '/' . $file); 441 } 442 $loaded[$name] = TRUE; 443 } 444 else { 445 $loaded[$name] = FALSE; 446 } 447 } 448 449 // Check if settings were already added on the page that makes an AJAX call. 450 if (isset($_POST['ajax_page_state']) && !empty($_POST['ajax_page_state']['js'][$path . '/wysiwyg.js'])) { 451 $settings_added = TRUE; 452 } 453 454 // Add basic Wysiwyg settings if any editor has been added. 455 if (!isset($settings_added) && $loaded[$name]) { 456 drupal_add_js(array('wysiwyg' => array( 457 'configs' => array(), 458 'plugins' => array(), 459 'disable' => t('Disable rich-text'), 460 'enable' => t('Enable rich-text'), 461 )), 'setting'); 462 463 // Initialize our namespaces in the *header* to do not force editor 464 // integration scripts to check and define Drupal.wysiwyg on its own. 465 drupal_add_js($path . '/wysiwyg.init.js', array('group' => JS_LIBRARY)); 466 467 // The 'none' editor is a special editor implementation, allowing us to 468 // attach and detach regular Drupal behaviors just like any other editor. 469 drupal_add_js($path . '/editors/js/none.js'); 470 471 // Add wysiwyg.js to the footer to ensure it's executed after the 472 // Drupal.settings array has been rendered and populated. Also, since editor 473 // library initialization functions must be loaded first by the browser, 474 // and Drupal.wysiwygInit() must be executed AFTER editors registered 475 // their callbacks and BEFORE Drupal.behaviors are applied, this must come 476 // last. 477 drupal_add_js($path . '/wysiwyg.js', array('scope' => 'footer')); 478 479 $settings_added = TRUE; 480 } 481 482 return $loaded[$name]; 483} 484 485/** 486 * Add editor settings for a given input format. 487 */ 488function wysiwyg_add_editor_settings($profile, $theme) { 489 static $formats = array(); 490 491 if (!isset($formats[$profile->format])) { 492 $config = wysiwyg_get_editor_config($profile, $theme); 493 // drupal_to_js() does not properly convert numeric array keys, so we need 494 // to use a string instead of the format id. 495 if ($config) { 496 drupal_add_js(array('wysiwyg' => array('configs' => array($profile->editor => array('format' . $profile->format => $config)))), 'setting'); 497 } 498 $formats[$profile->format] = TRUE; 499 } 500} 501 502/** 503 * Add settings for external plugins. 504 * 505 * Plugins can be used in multiple profiles, but not necessarily in all. Because 506 * of that, we need to process plugins for each profile, even if most of their 507 * settings are not stored per profile. 508 * 509 * Implementations of hook_wysiwyg_plugin() may execute different code for each 510 * editor. Therefore, we have to invoke those implementations for each editor, 511 * but process the resulting plugins separately for each profile. 512 * 513 * Drupal plugins differ to native plugins in that they have plugin-specific 514 * definitions and settings, which need to be processed only once. But they are 515 * also passed to the editor to prepare settings specific to the editor. 516 * Therefore, we load and process the Drupal plugins only once, and hand off the 517 * effective definitions for each profile to the editor. 518 * 519 * @param $profile 520 * A wysiwyg editor profile. 521 * 522 * @todo Rewrite wysiwyg_process_form() to build a registry of effective 523 * profiles in use, so we can process plugins in multiple profiles in one shot 524 * and simplify this entire function. 525 */ 526function wysiwyg_add_plugin_settings($profile) { 527 static $plugins = array(); 528 static $processed_plugins = array(); 529 static $processed_formats = array(); 530 531 // Each input format must only processed once. 532 // @todo ...as long as we do not have multiple profiles per format. 533 if (isset($processed_formats[$profile->format])) { 534 return; 535 } 536 $processed_formats[$profile->format] = TRUE; 537 538 $editor = wysiwyg_get_editor($profile->editor); 539 540 // Collect native plugins for this editor provided via hook_wysiwyg_plugin() 541 // and Drupal plugins provided via hook_wysiwyg_include_directory(). 542 if (!array_key_exists($editor['name'], $plugins)) { 543 $plugins[$editor['name']] = wysiwyg_get_plugins($editor['name']); 544 } 545 546 // Nothing to do, if there are no plugins. 547 if (empty($plugins[$editor['name']])) { 548 return; 549 } 550 551 // Determine name of proxy plugin for Drupal plugins. 552 $proxy = (isset($editor['proxy plugin']) ? key($editor['proxy plugin']) : ''); 553 554 // Process native editor plugins. 555 $profile_plugins_native = array(); 556 foreach ($plugins[$editor['name']] as $plugin => $meta) { 557 // Skip Drupal plugins (handled below) and 'core' functionality. 558 if ($plugin === $proxy || $plugin === 'default') { 559 continue; 560 } 561 // Only keep native plugins that are enabled in this profile. 562 if (isset($profile->settings['buttons'][$plugin])) { 563 $profile_plugins_native[$plugin] = $meta; 564 if (!isset($processed_plugins[$editor['name']][$plugin])) { 565 if (isset($editor['plugin meta callback'])) { 566 // Invoke the editor's plugin meta callback, so it can populate the 567 // global metadata for native plugins with required values. 568 $meta['name'] = $plugin; 569 if (($native_meta = call_user_func($editor['plugin meta callback'], $editor, $meta))) { 570 drupal_add_js(array('wysiwyg' => array('plugins' => array('native' => array($editor['name'] => array($plugin => $native_meta))))), 'setting'); 571 } 572 } 573 $processed_plugins[$editor['name']][$plugin] = $meta; 574 } 575 } 576 } 577 if (!empty($profile_plugins_native) && isset($editor['plugin settings callback'])) { 578 // Invoke the editor's plugin settings callback, so it can populate the 579 // format specific settings for native plugins with required values. 580 if (($settings_native = call_user_func($editor['plugin settings callback'], $editor, $profile, $profile_plugins_native))) { 581 drupal_add_js(array('wysiwyg' => array('plugins' => array('format' . $profile->format => array('native' => $settings_native)))), 'setting'); 582 } 583 } 584 585 // Process Drupal plugins. 586 if ($proxy && isset($editor['proxy plugin settings callback'])) { 587 $profile_plugins_drupal = array(); 588 foreach (wysiwyg_get_all_plugins() as $plugin => $meta) { 589 if (isset($profile->settings['buttons'][$proxy][$plugin])) { 590 // JavaScript and plugin-specific settings for Drupal plugins must be 591 // loaded and processed only once. Plugin information is cached 592 // statically to pass it to the editor's proxy plugin settings callback. 593 if (!isset($processed_plugins[$proxy][$plugin])) { 594 $profile_plugins_drupal[$plugin] = $processed_plugins[$proxy][$plugin] = $meta; 595 // Load the Drupal plugin's JavaScript. 596 drupal_add_js($meta['js path'] . '/' . $meta['js file']); 597 // Add plugin-specific settings. 598 $settings = (isset($meta['settings']) ? $meta['settings'] : array()); 599 $settings['title'] = $meta['title']; 600 $settings['icon'] = base_path() . $meta['icon path'] . '/' . $meta['icon file']; 601 if (!empty($meta['css path']) && !empty($meta['css file'])) { 602 $settings['css'] = base_path() . $meta['css path'] . '/' . $meta['css file']; 603 } 604 drupal_add_js(array('wysiwyg' => array('plugins' => array('drupal' => array($plugin => $settings)))), 'setting'); 605 } 606 else { 607 $profile_plugins_drupal[$plugin] = $processed_plugins[$proxy][$plugin]; 608 } 609 } 610 } 611 // Invoke the editor's proxy plugin settings callback, so it can populate 612 // the settings for Drupal plugins with custom, required values. 613 $settings_drupal = call_user_func($editor['proxy plugin settings callback'], $editor, $profile, $profile_plugins_drupal); 614 615 if ($settings_drupal) { 616 drupal_add_js(array('wysiwyg' => array('plugins' => array('format' . $profile->format => array('drupal' => $settings_drupal)))), 'setting'); 617 } 618 } 619} 620 621/** 622 * Retrieve available themes for an editor. 623 * 624 * Editor themes control the visual presentation of an editor. 625 * 626 * @param $profile 627 * A wysiwyg editor profile; passed/altered by reference. 628 * @param $selected_theme 629 * An optional theme name that ought to be used. 630 * 631 * @return 632 * An array of theme names, or a single, checked theme name if $selected_theme 633 * was given. 634 */ 635function wysiwyg_get_editor_themes(&$profile, $selected_theme = NULL) { 636 static $themes = array(); 637 638 if (!isset($themes[$profile->editor])) { 639 $editor = wysiwyg_get_editor($profile->editor); 640 if (isset($editor['themes callback']) && function_exists($editor['themes callback'])) { 641 $themes[$editor['name']] = $editor['themes callback']($editor, $profile); 642 } 643 // Fallback to 'default' otherwise. 644 else { 645 $themes[$editor['name']] = array('default'); 646 } 647 } 648 649 // Check optional $selected_theme argument, if given. 650 if (isset($selected_theme)) { 651 // If the passed theme name does not exist, use the first available. 652 if (!in_array($selected_theme, $themes[$profile->editor])) { 653 $selected_theme = $profile->settings['theme'] = $themes[$profile->editor][0]; 654 } 655 } 656 657 return isset($selected_theme) ? $selected_theme : $themes[$profile->editor]; 658} 659 660/** 661 * Return plugin metadata from the plugin registry. 662 * 663 * @param $editor_name 664 * The internal name of an editor to return plugins for. 665 * 666 * @return 667 * An array for each plugin. 668 */ 669function wysiwyg_get_plugins($editor_name) { 670 $plugins = array(); 671 if (!empty($editor_name)) { 672 $editor = wysiwyg_get_editor($editor_name); 673 // Add internal editor plugins. 674 if (isset($editor['plugin callback']) && function_exists($editor['plugin callback'])) { 675 $plugins = $editor['plugin callback']($editor); 676 } 677 // Add editor plugins provided via hook_wysiwyg_plugin(). 678 $plugins = array_merge($plugins, module_invoke_all('wysiwyg_plugin', $editor['name'], $editor['installed version'])); 679 // Add API plugins provided by Drupal modules. 680 // @todo We need to pass the filepath to the plugin icon for Drupal plugins. 681 if (isset($editor['proxy plugin'])) { 682 $plugins += $editor['proxy plugin']; 683 $proxy = key($editor['proxy plugin']); 684 foreach (wysiwyg_get_all_plugins() as $plugin_name => $info) { 685 $plugins[$proxy]['buttons'][$plugin_name] = $info['title']; 686 } 687 } 688 } 689 return $plugins; 690} 691 692/** 693 * Return an array of initial editor settings for a Wysiwyg profile. 694 */ 695function wysiwyg_get_editor_config($profile, $theme) { 696 $editor = wysiwyg_get_editor($profile->editor); 697 $settings = array(); 698 $installed_version = $editor['installed version']; 699 $settings = $profile->settings; 700 if (!empty($editor['settings callback']) && function_exists($editor['settings callback'])) { 701 if (!empty($profile->preferences['version']) && !empty($installed_version)) { 702 $profile_version = $profile->preferences['version']; 703 $version_status = version_compare($profile_version, $installed_version); 704 if ($version_status !== 0) { 705 // Installed version is different from profile version. Silently migrate 706 // the stored editor settings to the installed version if possible. 707 $migrated = FALSE; 708 if (!empty($editor['migrate settings callback']) && function_exists($editor['migrate settings callback'])) { 709 $migrated = $editor['migrate settings callback']($settings, $editor, $profile_version, $installed_version); 710 } 711 } 712 } 713 $settings = $editor['settings callback']($editor, $settings, $theme); 714 715 // Allow other modules to alter the editor settings for this format. 716 $context = array('editor' => $editor, 'profile' => $profile, 'theme' => $theme); 717 drupal_alter('wysiwyg_editor_settings', $settings, $context); 718 } 719 return $settings; 720} 721 722/** 723 * Retrieve stylesheets for HTML/IFRAME-based editors. 724 * 725 * This assumes that the content editing area only needs stylesheets defined 726 * for the scope 'theme'. 727 * 728 * When the stylesheets a theme uses are not yet known it will return a single 729 * URL pointing to a page which will gather the stylesheets so they can be 730 * loaded indirectly via import rules. 731 * 732 * Note: if set to explicitly use the current admin theme by name, no access 733 * check on the 'view the administration theme' permission is performed. 734 * 735 * @param $theme 736 * The id of a theme to get stylesheets for. Defaults to the current theme. 737 * 738 * @return 739 * An array containing CSS files, including proper base path. 740 */ 741function wysiwyg_get_css($theme = NULL) { 742 // Default to the node edit theme, if the user has access. 743 if (empty($theme)) { 744 $theme = variable_get('node_admin_theme') && user_access('view the administration theme') ? variable_get('admin_theme') : variable_get('theme_default', 'bartik'); 745 } 746 // If set to use the admin theme, ensure the user has access. 747 elseif ($theme == 'wysiwyg_theme_admin' && user_access('view the administration theme') && $admin_theme = variable_get('admin_theme')) { 748 $theme = $admin_theme; 749 } 750 // Make sure the theme system is initialized. 751 $themes = list_themes(); 752 if (!isset($themes[$theme])) { 753 drupal_theme_initialize(); 754 } 755 // Ensure the selected theme is enabled (or is the admin theme). 756 if (!drupal_theme_access($theme)) { 757 $theme = variable_get('theme_default', 'bartik'); 758 } 759 $cached = cache_get('wysiwyg_css'); 760 $css = array(); 761 // Trigger a cache update if: 762 // this is NOT the wysiwyg_theme page (avoid loop), 763 // the cache is empty or does not have the current theme, 764 // the CSS/JS cache-busting query string has changed, 765 // or the theme's aggregation state has changed. 766 $update_cache = strpos(current_path(), 'wysiwyg_theme/') === FALSE && ( 767 !$cached || ( 768 empty($cached->data[$theme]) 769 || $cached->data[$theme]['aggregated'] !== variable_get('preprocess_css', FALSE)) 770 || $cached->data['_css_js_query_string'] !== variable_get('css_js_query_string')); 771 if ($update_cache) { 772 // Make the client perform another request to update css caches. 773 $css[] = url('wysiwyg_theme/' . $theme, array('absolute' => TRUE)); 774 } 775 elseif (!empty($cached->data[$theme])) { 776 $css = $cached->data[$theme]['files']; 777 } 778 return $css; 779} 780 781/** 782 * Implements hook_themes_enabled(). 783 */ 784function wysiwyg_themes_enabled($theme_list) { 785 $cached = cache_get('wysiwyg_css'); 786 if ($cached && !empty($cached->data)) { 787 $css = $cached->data; 788 foreach ($theme_list as $theme) { 789 unset($css[$theme]); 790 } 791 cache_set('wysiwyg_css', $css); 792 } 793} 794 795/** 796 * Implements hook_form_FORM_ID_alter(). 797 * 798 * Alters the system's theme settings form to react when themes change. 799 */ 800function wysiwyg_form_system_theme_settings_alter(&$form, &$form_state, $form_id) { 801 $form['#submit'][] = '_wysiwyg_system_theme_settings_submit'; 802} 803 804/** 805 * Submit callback for the theme settings form. 806 * 807 * Removes the edited theme from the cache. 808 */ 809function _wysiwyg_system_theme_settings_submit($form, &$form_state) { 810 $theme = NULL; 811 if ($form_state['build_info']['form_id'] == 'system_theme_settings' && !empty($form_state['build_info']['args'])) { 812 $theme = $form_state['build_info']['args'][0]; 813 } 814 if ($theme !== NULL) { 815 $cached = cache_get('wysiwyg_css'); 816 if ($cached && !empty($cached->data)) { 817 $css = $cached->data; 818 unset($css[$theme]); 819 cache_set('wysiwyg_css', $css); 820 } 821 } 822 wysiwyg_get_css($theme); 823} 824 825/** 826 * Loads a profile for a given text format. 827 * 828 * Since there are commonly not many text formats, and each text format-enabled 829 * form element will possibly have to load every single profile, all existing 830 * profiles are loaded and cached once to reduce the amount of database queries. 831 * 832 * @param $format 833 * The machine-name of a text format. 834 * 835 * @return A profile if found, else FALSE. 836 */ 837function wysiwyg_profile_load($format) { 838 $profiles = wysiwyg_profile_load_all(); 839 return (isset($profiles[$format]) ? $profiles[$format] : FALSE); 840} 841 842/** 843 * Loads all profiles. 844 * 845 * @return An array of profiles keyed by format name. 846 */ 847function wysiwyg_profile_load_all() { 848 // entity_load(..., FALSE) does not re-use its own static cache upon 849 // repetitive calls, so a custom static cache is required. 850 // @see wysiwyg_entity_info() 851 $profiles = &drupal_static(__FUNCTION__); 852 853 if (!isset($profiles)) { 854 // Additional database cache to support alternative caches like memcache. 855 if ($cached = cache_get('wysiwyg_profiles')) { 856 $profiles = $cached->data; 857 } 858 else { 859 $profiles = entity_load('wysiwyg_profile', FALSE); 860 $formats = filter_formats(); 861 foreach ($profiles as $key => $profile) { 862 if (empty($profile->editor) || !isset($formats[$profile->format])) { 863 unset($profiles[$key]); 864 } 865 } 866 cache_set('wysiwyg_profiles', $profiles); 867 } 868 } 869 return $profiles; 870} 871 872/** 873 * Deletes a profile from the database. 874 */ 875function wysiwyg_profile_delete($profile) { 876 db_delete('wysiwyg') 877 ->condition('format', $profile->format) 878 ->execute(); 879 // Clear the editing caches. 880 if (module_exists('ctools')) { 881 ctools_include('object-cache'); 882 ctools_object_cache_clear_all('wysiwyg_profile', $profile->name); 883 } 884 else { 885 cache_clear_all('wysiwyg_profile:' . $profile->name, 'cache'); 886 } 887 wysiwyg_profile_cache_clear(); 888} 889 890/** 891 * Specialized menu callback to load a profile and check its locked status. 892 * 893 * @param $name 894 * The machine name of the profile. 895 * 896 * @return 897 * The profile object, with a "locked" property indicating whether or not 898 * someone else is already editing the profile. 899 */ 900function wysiwyg_ui_profile_cache_load($format) { 901 $original_profile = wysiwyg_profile_load($format); 902 $profile = FALSE; 903 $name = ($original_profile ? $original_profile->name : 'format' . $format); 904 $profile = wysiwyg_ui_profile_cache_get($name); 905 if (empty($profile)) { 906 $profile = $original_profile; 907 } 908 if (!empty($profile)) { 909 $profile->editing = TRUE; 910 return $profile; 911 } 912 return FALSE; 913} 914 915/** 916 * Specialized cache function to load a profile from the editing cache. 917 * 918 * @param $name 919 * The name of a profile to load. Currently the format name prefixed by 920 * 'format'. 921 * @return 922 * The profile object, with a "locked" property indicating whether or not 923 * someone else is already editing the profile, or FALSE if not cached. 924 */ 925function wysiwyg_ui_profile_cache_get($name) { 926 $profile = FALSE; 927 if (module_exists('ctools')) { 928 ctools_include('object-cache'); 929 $profile = ctools_object_cache_get('wysiwyg_profile', $name); 930 if ($profile) { 931 $profile->locked = ctools_object_cache_test('wysiwyg_profile', $name); 932 } 933 } 934 else { 935 // Fall back on simple caching in its own bin without locking. 936 $cached = cache_get('wysiwyg_profile:' . $name); 937 if ($cached) { 938 $profile = $cached->data; 939 $profile->locked = FALSE; 940 } 941 } 942 return $profile; 943} 944 945/** 946 * Specialized cache function to add a profile to the editing cache. 947 */ 948function wysiwyg_ui_profile_cache_set(&$profile) { 949 if (!empty($profile->locked)) { 950 drupal_set_message(t('Changes can not be made to a locked profile.'), 'error'); 951 return; 952 } 953 $profile->changed = TRUE; 954 if (module_exists('ctools')) { 955 ctools_include('object-cache'); 956 ctools_object_cache_set('wysiwyg_profile', $profile->name, $profile); 957 } 958 else { 959 cache_set('wysiwyg_profile:' . $profile->name, $profile); 960 } 961} 962 963/** 964 * Clear all Wysiwyg profile caches. 965 */ 966function wysiwyg_profile_cache_clear() { 967 entity_get_controller('wysiwyg_profile')->resetCache(); 968 drupal_static_reset('wysiwyg_profile_load_all'); 969 cache_clear_all('wysiwyg_profiles', 'cache'); 970} 971 972/** 973 * Implements hook_form_FORM_ID_alter(). 974 */ 975function wysiwyg_form_user_profile_form_alter(&$form, &$form_state, $form_id) { 976 if ($form['#user_category'] != 'account') { 977 return; 978 } 979 $account = $form['#user']; 980 $user_formats = filter_formats($account); 981 $options = array(); 982 $options_default = array(); 983 foreach (wysiwyg_profile_load_all() as $format => $profile) { 984 // Only show profiles that have user_choose enabled. 985 if (!empty($profile->preferences['user_choose']) && isset($user_formats[$format])) { 986 $options[$format] = check_plain($user_formats[$format]->name); 987 if (wysiwyg_user_get_status($profile, $account)) { 988 $options_default[] = $format; 989 } 990 } 991 } 992 if (!empty($options)) { 993 $form['wysiwyg']['wysiwyg_status'] = array( 994 '#type' => 'checkboxes', 995 '#title' => t('Text formats enabled for rich-text editing'), 996 '#options' => $options, 997 '#default_value' => $options_default, 998 ); 999 } 1000} 1001 1002/** 1003 * Implements hook_user_insert(). 1004 * 1005 * Wysiwyg's user preferences are normally not exposed on the user registration 1006 * form, but in case they are manually altered in, we invoke 1007 * wysiwyg_user_update() accordingly. 1008 */ 1009function wysiwyg_user_insert(&$edit, $account, $category) { 1010 wysiwyg_user_update($edit, $account, $category); 1011} 1012 1013/** 1014 * Implements hook_user_update(). 1015 */ 1016function wysiwyg_user_update(&$edit, $account, $category) { 1017 if (isset($edit['wysiwyg_status'])) { 1018 db_delete('wysiwyg_user') 1019 ->condition('uid', $account->uid) 1020 ->execute(); 1021 $query = db_insert('wysiwyg_user') 1022 ->fields(array('uid', 'format', 'status')); 1023 foreach ($edit['wysiwyg_status'] as $format => $status) { 1024 $query->values(array( 1025 'uid' => $account->uid, 1026 'format' => $format, 1027 'status' => (int) (bool) $status, 1028 )); 1029 } 1030 $query->execute(); 1031 } 1032} 1033 1034function wysiwyg_user_get_status($profile, $account = NULL) { 1035 global $user; 1036 1037 if (!isset($account)) { 1038 $account = $user; 1039 } 1040 1041 // Default wysiwyg editor status information is only required on forms, so we 1042 // do not pre-emptively load and attach this information on every user_load(). 1043 if (!isset($account->wysiwyg_status)) { 1044 $account->wysiwyg_status = db_query("SELECT format, status FROM {wysiwyg_user} WHERE uid = :uid", array( 1045 ':uid' => $account->uid, 1046 ))->fetchAllKeyed(); 1047 } 1048 1049 if (!empty($profile->preferences['user_choose']) && isset($account->wysiwyg_status[$profile->format])) { 1050 $status = $account->wysiwyg_status[$profile->format]; 1051 } 1052 else { 1053 $status = isset($profile->preferences['default']) ? $profile->preferences['default'] : TRUE; 1054 } 1055 1056 return (bool) $status; 1057} 1058 1059/** 1060 * @defgroup wysiwyg_api Wysiwyg API 1061 * @{ 1062 * 1063 * @todo Forked from Panels; abstract into a separate API module that allows 1064 * contrib modules to define supported include/plugin types. 1065 */ 1066 1067/** 1068 * Return library information for a given editor. 1069 * 1070 * @param $name 1071 * The internal name of an editor. 1072 * 1073 * @return 1074 * The library information for the editor, or FALSE if $name is unknown or not 1075 * installed properly. 1076 */ 1077function wysiwyg_get_editor($name) { 1078 $editors = wysiwyg_get_all_editors(); 1079 return isset($editors[$name]) && $editors[$name]['installed'] ? $editors[$name] : FALSE; 1080} 1081 1082/** 1083 * Compile a list holding all supported editors including installed editor version information. 1084 */ 1085function wysiwyg_get_all_editors() { 1086 static $editors; 1087 1088 if (isset($editors)) { 1089 return $editors; 1090 } 1091 1092 $editors = wysiwyg_load_includes('editors', 'editor'); 1093 foreach ($editors as $editor => $properties) { 1094 // Fill in required properties. 1095 $editors[$editor] += array( 1096 'title' => '', 1097 'vendor url' => '', 1098 'download url' => '', 1099 'editor path' => wysiwyg_get_path($editors[$editor]['name']), 1100 'library path' => wysiwyg_get_path($editors[$editor]['name']), 1101 'libraries' => array(), 1102 'installed' => FALSE, 1103 'version callback' => NULL, 1104 'themes callback' => NULL, 1105 'settings form callback' => NULL, 1106 'settings callback' => NULL, 1107 'plugin callback' => NULL, 1108 'plugin settings callback' => NULL, 1109 'versions' => array(), 1110 'js path' => $editors[$editor]['path'] . '/js', 1111 'css path' => $editors[$editor]['path'] . '/css', 1112 ); 1113 // Check whether library is present. 1114 if (!$editors[$editor]['installed'] && !($editors[$editor]['installed'] = file_exists($editors[$editor]['library path']))) { 1115 // Find the latest supported version. 1116 ksort($editors[$editor]['versions']); 1117 $version = key($editors[$editor]['versions']); 1118 foreach ($editors[$editor]['versions'] as $supported_version => $version_properties) { 1119 if (version_compare($version, $supported_version, '<')) { 1120 $version = $supported_version; 1121 } 1122 } 1123 // Apply library version specific definitions and overrides. 1124 // This is to show the newest installation instructions. 1125 $editors[$editor] = array_merge($editors[$editor], $editors[$editor]['versions'][$version]); 1126 continue; 1127 } 1128 $installed_version = NULL; 1129 // Detect library version. 1130 if (function_exists($editors[$editor]['version callback'])) { 1131 $installed_version = $editors[$editor]['installed version'] = $editors[$editor]['version callback']($editors[$editor]); 1132 } 1133 if (empty($installed_version)) { 1134 $editors[$editor]['error'] = t('The version of %editor could not be detected.', array('%editor' => $properties['title'])); 1135 $editors[$editor]['installed'] = FALSE; 1136 continue; 1137 } 1138 $editors[$editor]['installed version verified'] = TRUE; 1139 if (!empty($editors[$editor]['verified version range'])) { 1140 $version_range = $editors[$editor]['verified version range']; 1141 if (version_compare($installed_version, $version_range[0], '<') || version_compare($installed_version, $version_range[1], '>')) { 1142 $editors[$editor]['installed version verified'] = FALSE; 1143 } 1144 } 1145 // Determine to which supported version the installed version maps. 1146 ksort($editors[$editor]['versions']); 1147 $version = 0; 1148 foreach ($editors[$editor]['versions'] as $supported_version => $version_properties) { 1149 if (version_compare($installed_version, $supported_version, '>=')) { 1150 $version = $supported_version; 1151 } 1152 } 1153 if (!$version) { 1154 $editors[$editor]['error'] = t('The installed version %version of %editor is not supported.', array('%version' => $installed_version, '%editor' => $editors[$editor]['title'])); 1155 $editors[$editor]['installed'] = FALSE; 1156 continue; 1157 } 1158 // Apply library version specific definitions and overrides. 1159 $editors[$editor] = array_merge($editors[$editor], $editors[$editor]['versions'][$version]); 1160 unset($editors[$editor]['versions']); 1161 } 1162 drupal_alter('wysiwyg_editor', $editors); 1163 return $editors; 1164} 1165 1166/** 1167 * Invoke hook_wysiwyg_plugin() in all modules. 1168 */ 1169function wysiwyg_get_all_plugins() { 1170 static $plugins; 1171 1172 if (isset($plugins)) { 1173 return $plugins; 1174 } 1175 1176 $plugins = wysiwyg_load_includes('plugins', 'plugin'); 1177 foreach ($plugins as $name => $properties) { 1178 $plugin = &$plugins[$name]; 1179 // Fill in required/default properties. 1180 $plugin += array( 1181 'title' => $plugin['name'], 1182 'vendor url' => '', 1183 'js path' => $plugin['path'] . '/' . $plugin['name'], 1184 'js file' => $plugin['name'] . '.js', 1185 'css path' => $plugin['path'] . '/' . $plugin['name'], 1186 'css file' => $plugin['name'] . '.css', 1187 'icon path' => $plugin['path'] . '/' . $plugin['name'] . '/images', 1188 'icon file' => $plugin['name'] . '.png', 1189 'dialog path' => $plugin['name'], 1190 'dialog settings' => array(), 1191 'settings callback' => NULL, 1192 'settings form callback' => NULL, 1193 ); 1194 // Fill in default settings. 1195 $plugin['settings'] += array( 1196 'path' => base_path() . $plugin['path'] . '/' . $plugin['name'], 1197 ); 1198 // Check whether library is present. 1199 if (!($plugin['installed'] = file_exists($plugin['js path'] . '/' . $plugin['js file']))) { 1200 continue; 1201 } 1202 } 1203 return $plugins; 1204} 1205 1206/** 1207 * Load include files for wysiwyg implemented by all modules. 1208 * 1209 * @param $type 1210 * The type of includes to search for, can be 'editors'. 1211 * @param $hook 1212 * The hook name to invoke. 1213 * @param $file 1214 * An optional include file name without .inc extension to limit the search to. 1215 * 1216 * @see wysiwyg_get_directories(), _wysiwyg_process_include() 1217 */ 1218function wysiwyg_load_includes($type = 'editors', $hook = 'editor', $file = NULL) { 1219 // Determine implementations. 1220 $directories = wysiwyg_get_directories($type); 1221 $directories['wysiwyg'] = drupal_get_path('module', 'wysiwyg') . '/' . $type; 1222 $file_list = array(); 1223 foreach ($directories as $module => $path) { 1224 $file_list[$module] = drupal_system_listing("/{$file}.inc\$/", $path, 'name', 0); 1225 } 1226 1227 // Load implementations. 1228 $info = array(); 1229 foreach (array_filter($file_list) as $module => $files) { 1230 foreach ($files as $file) { 1231 include_once './' . $file->uri; 1232 $result = _wysiwyg_process_include($module, $module . '_' . $file->name, dirname($file->uri), $hook); 1233 if (is_array($result)) { 1234 $info = array_merge($info, $result); 1235 } 1236 } 1237 } 1238 drupal_alter('wysiwyg_load_includes', $info, $hook); 1239 return $info; 1240} 1241 1242/** 1243 * Helper function to build paths to libraries. 1244 * 1245 * @param $library 1246 * The external library name to return the path for. 1247 * @param $base_path 1248 * Whether to prefix the resulting path with base_path(). 1249 * 1250 * @return 1251 * The path to the specified library. 1252 * 1253 * @ingroup libraries 1254 */ 1255function wysiwyg_get_path($library, $base_path = FALSE) { 1256 static $libraries; 1257 1258 if (!isset($libraries)) { 1259 $libraries = wysiwyg_get_libraries(); 1260 } 1261 if (!isset($libraries[$library])) { 1262 // Most often, external libraries can be shared across multiple sites. 1263 return 'sites/all/libraries/' . $library; 1264 } 1265 1266 $path = ($base_path ? base_path() : ''); 1267 $path .= $libraries[$library]; 1268 1269 return $path; 1270} 1271 1272/** 1273 * Return an array of library directories. 1274 * 1275 * Returns an array of library directories from the all-sites directory 1276 * (i.e. sites/all/libraries/), the profiles directory, and site-specific 1277 * directory (i.e. sites/somesite/libraries/). The returned array will be keyed 1278 * by the library name. Site-specific libraries are prioritized over libraries 1279 * in the default directories. That is, if a library with the same name appears 1280 * in both the site-wide directory and site-specific directory, only the 1281 * site-specific version will be listed. 1282 * 1283 * @return 1284 * A list of library directories. 1285 * 1286 * @ingroup libraries 1287 */ 1288function wysiwyg_get_libraries() { 1289 if (function_exists('libraries_get_libraries')) { 1290 $directories = libraries_get_libraries(); 1291 } 1292 else { 1293 global $profile; 1294 1295 // When this function is called during Drupal's initial installation process, 1296 // the name of the profile that is about to be installed is stored in the 1297 // global $profile variable. At all other times, the regular system variable 1298 // contains the name of the current profile, and we can call variable_get() 1299 // to determine the profile. 1300 if (!isset($profile)) { 1301 $profile = variable_get('install_profile', 'default'); 1302 } 1303 1304 $directory = 'libraries'; 1305 $searchdir = array(); 1306 $config = conf_path(); 1307 1308 // The 'profiles' directory contains pristine collections of modules and 1309 // themes as organized by a distribution. It is pristine in the same way 1310 // that /modules is pristine for core; users should avoid changing anything 1311 // there in favor of sites/all or sites/<domain> directories. 1312 if (file_exists("profiles/$profile/$directory")) { 1313 $searchdir[] = "profiles/$profile/$directory"; 1314 } 1315 1316 // Always search sites/all/*. 1317 $searchdir[] = 'sites/all/' . $directory; 1318 1319 // Also search sites/<domain>/*. 1320 if (file_exists("$config/$directory")) { 1321 $searchdir[] = "$config/$directory"; 1322 } 1323 1324 // Retrieve list of directories. 1325 // @todo Core: Allow to scan for directories. 1326 $directories = array(); 1327 $nomask = array('CVS'); 1328 foreach ($searchdir as $dir) { 1329 if (is_dir($dir) && $handle = opendir($dir)) { 1330 while (FALSE !== ($file = readdir($handle))) { 1331 if (!in_array($file, $nomask) && $file[0] != '.') { 1332 if (is_dir("$dir/$file")) { 1333 $directories[$file] = "$dir/$file"; 1334 } 1335 } 1336 } 1337 closedir($handle); 1338 } 1339 } 1340 } 1341 1342 return $directories; 1343} 1344 1345/** 1346 * Return a list of directories by modules implementing wysiwyg_include_directory(). 1347 * 1348 * @param $plugintype 1349 * The type of a plugin; can be 'editors'. 1350 * 1351 * @return 1352 * An array containing module names suffixed with '_' and their defined 1353 * directory. 1354 * 1355 * @see wysiwyg_load_includes(), _wysiwyg_process_include() 1356 */ 1357function wysiwyg_get_directories($plugintype) { 1358 $directories = array(); 1359 foreach (module_implements('wysiwyg_include_directory') as $module) { 1360 $result = module_invoke($module, 'wysiwyg_include_directory', $plugintype); 1361 if (isset($result) && is_string($result)) { 1362 $directories[$module] = drupal_get_path('module', $module) . '/' . $result; 1363 } 1364 } 1365 return $directories; 1366} 1367 1368/** 1369 * Create a placeholder structure for JavaScript callbacks. 1370 * 1371 * @param $name 1372 * A string with the name of the callback, use 'object.subobject.method' 1373 * syntax for methods in nested objects. 1374 * @param $context 1375 * An optional string with the name of an object for overriding 'this' inside 1376 * the function. Use 'object.subobject' syntax for nested objects. Defaults to 1377 * the window object. 1378 * 1379 * @return 1380 * An array with placeholder information for creating a JavaScript function 1381 * reference on the client. 1382 */ 1383function wysiwyg_wrap_js_callback($name, $context = NULL) { 1384 $obj = array( 1385 'drupalWysiwygType' => 'callback', 1386 'name' => $name, 1387 ); 1388 if ($context) { 1389 $obj['context'] = $context; 1390 } 1391 return $obj; 1392} 1393 1394/** 1395 * Create a placeholder structure for JavaScript RegExp objects. 1396 * 1397 * @param $regexp 1398 * A JavaScript Regular Expression as a string, without / wrappers. 1399 * @param $modifiers 1400 * An optional string with modifiers for the RegExp object. 1401 * 1402 * @return 1403 * An array with placeholder information for creating a JavaScript RegExp 1404 * object on the client. 1405 */ 1406function wysiwyg_wrap_js_regexp($regexp, $modifiers = NULL) { 1407 $obj = array( 1408 'drupalWysiwygType' => 'regexp', 1409 'regexp' => $regexp, 1410 ); 1411 if ($modifiers) { 1412 $obj['modifiers'] = $modifiers; 1413 } 1414 return $obj; 1415} 1416 1417/** 1418 * Process a single hook implementation of a wysiwyg editor. 1419 * 1420 * @param $module 1421 * The module that owns the hook. 1422 * @param $identifier 1423 * Either the module or 'wysiwyg_' . $file->name 1424 * @param $hook 1425 * The name of the hook being invoked. 1426 */ 1427function _wysiwyg_process_include($module, $identifier, $path, $hook) { 1428 $function = $identifier . '_' . $hook; 1429 if (!function_exists($function)) { 1430 return NULL; 1431 } 1432 $result = $function(); 1433 if (!isset($result) || !is_array($result)) { 1434 return NULL; 1435 } 1436 1437 // Fill in defaults. 1438 foreach ($result as $editor => $properties) { 1439 $result[$editor]['module'] = $module; 1440 $result[$editor]['name'] = $editor; 1441 $result[$editor]['path'] = $path; 1442 } 1443 return $result; 1444} 1445 1446/** 1447 * @} End of "defgroup wysiwyg_api". 1448 */ 1449 1450/** 1451 * Implements hook_features_api(). 1452 */ 1453function wysiwyg_features_api() { 1454 return array( 1455 'wysiwyg' => array( 1456 'name' => t('Wysiwyg profiles'), 1457 'default_hook' => 'wysiwyg_default_profiles', 1458 'default_file' => FEATURES_DEFAULTS_INCLUDED, 1459 'feature_source' => TRUE, 1460 'file' => drupal_get_path('module', 'wysiwyg') . '/wysiwyg.features.inc', 1461 ), 1462 ); 1463} 1464 1465