You are here

ace_editor.module in Ace Code Editor 7

Same filename and directory in other branches
  1. 8 ace_editor.module

Ace Editor module.

File

ace_editor.module
View source
<?php

/**
 * @file
 * Ace Editor module.
 */

/**
 * Implements hook_menu().
 *
 * Add a settings page to configure the module.
 */
function ace_editor_menu() {
  $items = array();
  $items['admin/config/content/ace-editor'] = array(
    'title' => 'Ace Editor',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'ace_editor_settings_form',
    ),
    'access arguments' => array(
      'configure ace editor',
    ),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'ace_editor.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_permission().
 */
function ace_editor_permission() {
  return array(
    'configure ace editor' => array(
      'title' => t('Configure Ace Editor'),
      'description' => t('Access the configuration page for Ace Editor.'),
      'restrict access' => TRUE,
    ),
  );
}

/**
 * An API function to embed read-only editors into template files.
 */
function ace_editor_add($content, $user_settings) {

  // Get the default settings and override them with settings defined by user.
  $settings = ace_editor_field_formatter_default_settings();
  foreach ($user_settings as $key => $value) {
    $settings[$key] = $value;
  }
  $settings = ace_editor_make_js_friendly_settings($settings);

  // Get a unique index for the next pre-element added by this API function.
  $pre_id = drupal_html_id('ace-editor-add');
  $pre = '<pre id="' . $pre_id . '">' . $content . '</pre>';

  // Put all instances and their settings to JS.
  $js_ettings = array();
  $js_settings['ace_editor']['editor_instances'][] = array(
    'id' => $pre_id,
    'content' => $content,
    'settings' => $settings,
  );

  // Add the javascript files needed.
  ace_editor_add_js($settings, FALSE);
  drupal_add_js($js_settings, 'setting');
  drupal_add_js(drupal_get_path('module', 'ace_editor') . '/js/ace_editor.js');
  drupal_add_css(drupal_get_path('module', 'ace_editor') . '/styles/ace_editor.css');
  return $pre;
}

/**
 * Implements hook_form_alter().
 *
 * For selected forms where Ace will be used, add the JS nessesary
 */
function ace_editor_form_alter(&$form, &$form_state, $form_id) {

  // Node edit forms.
  if (isset($form['#node_edit_form']) && $form['#node_edit_form']) {
    $form_ident = 'node_' . $form['nid']['#value'];
  }
  elseif (isset($form['module']) && $form['module']['#value'] == 'block') {
    $form_ident = 'block_' . $form['delta']['#value'];
  }
  elseif ($form_id == 'ctools_custom_content_type_edit_form') {
    $form_ident = $form_id;
  }
  elseif ($form_id == 'fieldable_panels_panes_fieldable_panels_pane_content_type_edit_form') {
    $form_ident = $form_id;
  }
  elseif (isset($form['#entity_type']) && $form['#entity_type'] == 'bean') {
    $form_ident = $form_id;
  }
  elseif ($form_id == 'devel_execute_form' || $form_id == 'devel_execute_block_form') {
    $form_ident = $form_id;
    $form['execute']['code']['#attributes']['class'][] = 'ace-enabled';
    $form['execute']['code']['#resizable'] = FALSE;
    $form['#submit'] = array(
      'ace_editor_execute_form_submit',
    );

    // Ace editor requires initial PHP tag so prepend it.
    if (strpos($form['execute']['code']['#default_value'], '<?php') !== 0) {
      $form['execute']['code']['#default_value'] = '<?php' . $form['execute']['code']['#default_value'];
    }
    $form['execute']['code']['#description'] = t('Enter some code. Use <code>&lt;?php ?&gt;</code> tags to benefit from the Ace Editor.');

    // Restore the textarea content.
    $_SESSION['devel_execute_code'] = $form['execute']['code']['#default_value'];
  }
  elseif ($form_id == 'views_ui_config_item_form' && strpos($form['#title'], 'Global: Text area') !== FALSE) {
    $form_ident = $form_id;
  }
  elseif ($form_id == 'i18n_string_translate_page_form') {
    $form_ident = $form_id;
  }
  elseif ($form_id == 'webform_configure_form') {
    $form_ident = $form_id;
  }
  elseif ($form_id == 'taxonomy_form_term') {
    $form_ident = $form_id;
  }
  else {
    return;
  }

  // Add JavaScript to the form if the ace editor library is installed.
  if (ace_editor_library_installed()) {
    $form['ace_editor_identifier'] = array(
      '#type' => 'hidden',
      '#value' => $form_ident,
    );
    $form['#after_build'] = array(
      'ace_editor_node_block_edit_form_attach_js',
    );
  }
  else {
    global $base_path;
    drupal_set_message(t('The Ace Editor JS library is missing, please check the !readme_link for installation instructions.', array(
      '!readme_link' => '<a href="' . $base_path . drupal_get_path('module', 'ace_editor') . '/README.txt" target="_blank">README</a>',
    )), 'error');
  }
}

/**
 * Add JS to the page containing the affected forms.
 */
function ace_editor_node_block_edit_form_attach_js($form) {
  global $base_path;
  $add_js_settings = array(
    'ace_src_dir' => $base_path . libraries_get_path('ace') . '/src/',
    'autocomplete' => variable_get('ace_editor_autocomplete', TRUE),
    'autowrap' => variable_get('ace_editor_autowrap', TRUE),
    'available_modes' => ace_editor_get_modes(),
    'fontsize' => variable_get('ace_editor_fontsize', '12pt'),
    'invisibles' => variable_get('ace_editor_invisibles', FALSE),
    'linehighlighting' => variable_get('ace_editor_linehighlighting', TRUE),
    'line_numbers' => variable_get('ace_editor_line_numbers', TRUE),
    'printmargin' => variable_get('ace_editor_printmargin', FALSE),
    'codefolding' => variable_get('ace_editor_codefolding', 'markbegin'),
    'syntax' => variable_get('ace_editor_default_syntax', 'html'),
    'tabsize' => variable_get('ace_editor_tabsize', 2),
    'text_formats' => array_values(variable_get('ace_editor_filter_formats', array())),
    'theme' => variable_get('ace_editor_theme', 'cobalt'),
  );
  $js_settings = array(
    'ace_editor' => array(
      'admin' => $add_js_settings,
    ),
  );
  ace_editor_add_js($add_js_settings, TRUE);
  drupal_add_js($js_settings, 'setting');
  drupal_add_js(drupal_get_path('module', 'ace_editor') . '/js/ace_editor.admin.js');
  drupal_add_js(libraries_get_path('ace') . '/src/mode-html.js');
  drupal_add_css(drupal_get_path('module', 'ace_editor') . '/styles/ace_editor.admin.css');
  return $form;
}

/**
 * Implements hook_field_formatter_info().
 */
function ace_editor_field_formatter_info() {
  return array(
    'ace_editor_code_readonly_formatter' => array(
      'label' => t('Code syntax highlighting'),
      'field types' => array(
        'text_long',
        'text_with_summary',
      ),
      'settings' => ace_editor_field_formatter_default_settings(),
    ),
  );
}

/**
 * Implements hook_field_formatter_settings_form().
 */
function ace_editor_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {

  // Get the settings.
  $settings = $instance['display'][$view_mode]['settings'];
  $element = get_setting_form_elements($settings);
  return $element;
}

/**
 * Implements hook_field_formatter_settings_summary().
 */
function ace_editor_field_formatter_settings_summary($field, $instance, $view_mode) {

  // Get the settings.
  $settings = $instance['display'][$view_mode]['settings'];
  $themes_dict = ace_editor_get_themes();
  $modes_dict = ace_editor_get_modes();
  $summary = t('Show content with code syntax highlighting for @syntax in the @theme theme.', array(
    '@syntax' => $modes_dict[$settings['syntax']],
    '@theme' => $themes_dict[$settings['theme']],
  ));
  return $summary;
}

/**
 * Implements hook_field_formatter_view().
 */
function ace_editor_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {

  // Get the settings.
  $settings = ace_editor_make_js_friendly_settings($display['settings']);
  $element = array();
  $js_settings = array(
    'ace_editor' => array(
      'editor_instances' => array(),
    ),
  );

  // Add all values to their own editor intance.
  foreach ($items as $delta => $item) {
    $element_id = $field['field_name'] . '-' . $delta . '-pre';
    $pre_element = '<pre id="' . $element_id . '" </pre>';
    $element[]['#markup'] = $pre_element;
    $js_settings['ace_editor']['editor_instances'][] = array(
      'id' => $element_id,
      'content' => $item['value'],
      'settings' => $settings,
    );
  }
  ace_editor_add_js($settings, FALSE);
  drupal_add_js($js_settings, 'setting');
  drupal_add_js(drupal_get_path('module', 'ace_editor') . '/js/ace_editor.js');
  drupal_add_css(drupal_get_path('module', 'ace_editor') . '/styles/ace_editor.css');
  return $element;
}

/**
 * Implements hook_filter_info().
 */
function ace_editor_filter_info() {
  $filters['ace_editor'] = array(
    'title' => t('Syntax highlighting'),
    'description' => t('Use &lt;ace&gt; and &lt;/ace&gt; tags to show it with syntax highlighting.
       Add attributes to <ace> tag to control formatting. <b>CAUTION:</b> This prevents content that use this filter from being cached. It is recomended not to be selected on site wide-used text formats like HTML Full/Filtered, which may have an impact on the site\'s overall performance. Enable it on a custom text format that will be only used for the content to be syntax-highlighted.'),
    'process callback' => 'ace_editor_filter_process',
    'settings callback' => 'ace_editor_filter_settings',
    'default settings' => ace_editor_field_formatter_default_settings(),
    'cache' => FALSE,
  );
  return $filters;
}

/**
 * Returns the settings form for the text filter.
 */
function ace_editor_filter_settings($form, &$form_state, $filter, $format, $defaults, $filters) {
  $settings = $filter->settings;
  if (empty($settings)) {
    $settings = $defaults;
  }
  $element = get_setting_form_elements($settings);
  array_unshift($element, array(
    '#markup' => t('<p>This is the default settings, these settings can be overridden by adding attributes<?php  ?> to the tag.</p>'),
  ));
  return $element;
}

/**
 * Implements hook_filter_FILTER_prepare().
 */
function ace_editor_filter_process($text, $filter, $format, $langcode, $cache, $cache_id) {
  if (preg_match_all("/<ace.*?>(.*?)\\s*<\\/ace>/s", $text, $match)) {
    $js_settings = array(
      'ace_editor' => array(
        'editor_instances' => array(),
      ),
    );
    $add_js_settings = array();
    foreach ($match[0] as $key => $value) {
      $element_id = 'ace-editor-inline' . $key;
      $content = trim($match[1][$key], "\n\r\0\v");
      $replace = '<pre id="' . $element_id . '"></pre>';

      // Override settings with attributes on the tag.
      $settings = $filter->settings;
      $tag_attributes = tag_attributes('ace', $value);
      if ($tag_attributes) {
        foreach ($tag_attributes as $attribute_key => $attribute_value) {
          $settings[$attribute_key] = $attribute_value;
        }
      }
      $settings = ace_editor_make_js_friendly_settings($settings);
      $add_js_settings[] = $settings;
      $js_settings['ace_editor']['editor_instances'][] = array(
        'id' => $element_id,
        'content' => $content,
        'settings' => $settings,
      );
      $text = str_replace_once($value, $replace, $text);
    }
    drupal_add_js($js_settings, 'setting');
    ace_editor_add_js($add_js_settings, FALSE);
    drupal_add_js(drupal_get_path('module', 'ace_editor') . '/js/ace_editor.js');
    drupal_add_css(drupal_get_path('module', 'ace_editor') . '/styles/ace_editor.css');
  }
  return $text;
}

/**
 * Custom function to replace the code only once.
 *
 * Probably not the most efficient way, but at least it works.
 */
function str_replace_once($needle, $replace, $haystack) {

  // Looks for the first occurence of $needle in $haystack
  // and replaces it with $replace.
  $pos = strpos($haystack, $needle);
  if ($pos === FALSE) {

    // Nothing found.
    return $haystack;
  }
  return substr_replace($haystack, $replace, $pos, strlen($needle));
}

/**
 * Get all attributes of an <ace> tag in key/value pairs.
 */
function tag_attributes($element_name, $xml) {

  // Grab the string of attributes inside the editor tag.
  $found = preg_match('#<' . $element_name . '\\s+([^>]+(?:"|\'))\\s?/?>#', $xml, $matches);
  if ($found == 1) {
    $attribute_array = array();
    $attribute_string = $matches[1];

    // Match attribute-name attribute-value pairs.
    $found = preg_match_all('#([^\\s=]+)\\s*=\\s*(\'[^<\']*\'|"[^<"]*")#', $attribute_string, $matches, PREG_SET_ORDER);
    if ($found != 0) {

      // Create an associative array that matches attribute names
      // with their values.
      foreach ($matches as $attribute) {
        $value = substr($attribute[2], 1, -1);
        if ($value == "1" || $value == "0" || $value == "true" || $value == "false") {
          $value = intval($value);
        }
        $attribute_array[$attribute[1]] = $value;
      }
      return $attribute_array;
    }
  }

  // Attributes either weren't found, or couldn't be extracted
  // by the regular expression.
  return FALSE;
}

/**
 * Field formatter settings form.
 */
function get_setting_form_elements($settings) {
  return array(
    'theme' => array(
      '#type' => 'select',
      '#title' => t('Theme'),
      '#options' => ace_editor_get_themes(),
      '#default_value' => $settings['theme'],
      '#attributes' => array(
        'style' => 'width: 150px;',
      ),
    ),
    'syntax' => array(
      '#type' => 'select',
      '#title' => t('Syntax'),
      '#description' => t('The syntax that will be highlighted.'),
      '#options' => ace_editor_get_modes(),
      '#default_value' => $settings['syntax'],
      '#attributes' => array(
        'style' => 'width: 150px;',
      ),
    ),
    'height' => array(
      '#type' => 'textfield',
      '#title' => t('Height'),
      '#description' => t('The height of the editor in either pixels or percents.
        You can use "auto" to let the editor calculate the adequate height.'),
      '#default_value' => $settings['height'],
      '#attributes' => array(
        'style' => 'width: 100px;',
      ),
    ),
    'width' => array(
      '#type' => 'textfield',
      '#title' => t('Width'),
      '#description' => t('The width of the editor in either pixels or percents.'),
      '#default_value' => $settings['width'],
      '#attributes' => array(
        'style' => 'width: 100px;',
      ),
    ),
    'font-size' => array(
      '#type' => 'textfield',
      '#title' => t('Font size'),
      '#description' => t('The the font size of the editor.'),
      '#default_value' => $settings['font-size'],
      '#attributes' => array(
        'style' => 'width: 100px;',
      ),
    ),
    // Wrapmode breaks the read-only editor if activated.
    // 'autowrap' => array(
    // '#type' => 'checkbox',
    // '#title' => t('Autowrap lines'),
    // '#default_value' => $settings['autowrap'],
    // ),
    'linehighlighting' => array(
      '#type' => 'checkbox',
      '#title' => t('Line highlighting'),
      '#default_value' => $settings['linehighlighting'],
    ),
    'line-numbers' => array(
      '#type' => 'checkbox',
      '#title' => t('Show line numbers'),
      '#default_value' => $settings['line-numbers'],
    ),
  );
}

/**
 * The default settings used with the Ace field formatter.
 */
function ace_editor_field_formatter_default_settings() {
  return array(
    'autocomplete' => FALSE,
    'autowrap' => TRUE,
    'font-size' => variable_get('ace_editor_fontsize', '12pt'),
    'height' => '200px',
    'invisibles' => FALSE,
    'line-numbers' => FALSE,
    'linehighlighting' => TRUE,
    'print-margin' => FALSE,
    'syntax' => variable_get('ace_editor_default_syntax', 'html'),
    'tabsize' => variable_get('ace_editor_tabsize', 2),
    'theme' => variable_get('ace_editor_theme', 'cobalt'),
    'width' => '100%',
  );
}

/**
 * Add all JavaScript needed for the editor to work on the next page.
 */
function ace_editor_add_js($add_js_settings, $for_admin_pages) {
  drupal_add_library('ace_editor', 'ace', FALSE);
  global $base_path;
  $ace_lib_path = libraries_get_path('ace');
  if (isset($add_js_settings[0]) && is_array($add_js_settings[0])) {
    foreach ($add_js_settings as $key => $settings) {
      drupal_add_js($ace_lib_path . '/src/theme-' . $settings['theme'] . '.js', array(
        'preprocess' => FALSE,
      ));
      drupal_add_js($ace_lib_path . '/src/mode-' . $settings['syntax'] . '.js', array(
        'preprocess' => FALSE,
      ));
    }
  }
  else {
    drupal_add_js($ace_lib_path . '/src/theme-' . $add_js_settings['theme'] . '.js', array(
      'preprocess' => FALSE,
    ));
    drupal_add_js($ace_lib_path . '/src/mode-' . $add_js_settings['syntax'] . '.js', array(
      'preprocess' => FALSE,
    ));
  }
  if ($for_admin_pages) {
    drupal_add_library('system', 'ui.resizable');
  }
}

/**
 * Replaces dashes with underscores in keys for use with JS.
 */
function ace_editor_make_js_friendly_settings($settings) {
  $js_friendly = array();
  foreach ($settings as $key => $value) {
    $js_friendly[str_replace('-', '_', $key)] = $value;
  }
  return $js_friendly;
}

/**
 * Returns the installed themes.
 */
function ace_editor_get_themes() {

  // Available themes are loaded on installation.
  $assets = variable_get('ace_editor_assets', array(
    'theme' => array(
      'cobalt' => 'Cobalt',
    ),
  ));
  $themes = $assets['theme'];
  asort($themes);
  return $themes;
}

/**
 * Returns all of the modes.
 */
function ace_editor_get_modes() {

  // Available modeses are loaded on installation.
  $assets = variable_get('ace_editor_assets', array(
    'mode' => array(
      'html' => 'HTML',
    ),
  ));
  asort($assets['mode']);
  $modes = array_merge($assets['mode'], array(
    // Translate some most used languages to their common name.
    'c_cpp' => 'C/C++',
    'coffee' => 'CoffeeScript',
    'csharp' => 'C#',
    'css' => 'CSS',
    'html' => 'HTML',
    'json' => 'JSON',
    'less' => 'LESS',
    'php' => 'PHP',
    'scss' => 'SCSS',
    'xml' => 'XML',
    'yaml' => 'YAML',
  ));
  return $modes;
}

/**
 * Implements hook_library().
 *
 * For using drupal_add_library()
 */
function ace_editor_library() {
  $libraries['ace'] = array(
    'title' => 'Ace Editor',
    'website' => 'http://ace.c9.io/',
    'version' => '',
    'js' => array(
      libraries_get_path('ace') . '/src/ace.js' => array(),
      libraries_get_path('ace') . '/src/ext-language_tools.js' => array(),
    ),
  );
  return $libraries;
}

/**
 * Implements hook_libraries_info().
 */
function ace_editor_libraries_info() {
  $libraries['ace'] = array(
    'title' => 'Ace Editor',
    'vendor url' => 'http://ace.c9.io/',
    'download url' => 'https://github.com/ajaxorg/ace-builds/archive/master.zip',
    'version arguments' => array(
      'file' => 'package.json',
      'pattern' => '/"version": "(\\d+\\.+\\d+.+\\d+)"/',
    ),
    'files' => array(
      'js' => array(
        'src/ace.js',
        // Required for autocompletion.
        'src/ext-language_tools.js',
      ),
    ),
  );
  return $libraries;
}

/**
 * Returns if the library is installed.
 */
function ace_editor_library_installed() {
  $library = libraries_detect('ace');
  return isset($library['installed']) && $library['installed'];
}

/**
 * Fills the global var 'ace_editor_assets'.
 */
function ace_editor_get_assets() {

  // Find out available themes and modes in the library.
  $libraries = libraries_get_libraries();
  if (isset($libraries['ace'])) {
    $files = file_scan_directory($libraries['ace'] . '/src', '/(mode|theme)-(.+)\\.js$/', array(
      'recurse' => FALSE,
    ));
    $assets = array();
    foreach ($files as $file_info) {
      $asset = explode('-', $file_info->name);
      $assets[$asset[0]][$asset[1]] = ucwords(str_replace('_', ' ', $asset[1]));
    }
    if (!empty($assets)) {
      variable_set('ace_editor_assets', $assets);
    }
  }
}

/**
 * Implements hook_init().
 */
function ace_editor_init() {

  // Load the library assets if it was not installed before enabling the module.
  if (!variable_get('ace_editor_assets') && ace_editor_library_installed()) {
    ace_editor_get_assets();
  }

  // Load PHP consoles support for specific modules.
  if (module_exists('devel')) {
    $module_path = drupal_get_path('module', 'ace_editor');

    // Load module includes.
    module_load_include('inc', 'ace_editor', 'ace_editor.devel');
  }
}

Functions

Namesort descending Description
ace_editor_add An API function to embed read-only editors into template files.
ace_editor_add_js Add all JavaScript needed for the editor to work on the next page.
ace_editor_field_formatter_default_settings The default settings used with the Ace field formatter.
ace_editor_field_formatter_info Implements hook_field_formatter_info().
ace_editor_field_formatter_settings_form Implements hook_field_formatter_settings_form().
ace_editor_field_formatter_settings_summary Implements hook_field_formatter_settings_summary().
ace_editor_field_formatter_view Implements hook_field_formatter_view().
ace_editor_filter_info Implements hook_filter_info().
ace_editor_filter_process Implements hook_filter_FILTER_prepare().
ace_editor_filter_settings Returns the settings form for the text filter.
ace_editor_form_alter Implements hook_form_alter().
ace_editor_get_assets Fills the global var 'ace_editor_assets'.
ace_editor_get_modes Returns all of the modes.
ace_editor_get_themes Returns the installed themes.
ace_editor_init Implements hook_init().
ace_editor_libraries_info Implements hook_libraries_info().
ace_editor_library Implements hook_library().
ace_editor_library_installed Returns if the library is installed.
ace_editor_make_js_friendly_settings Replaces dashes with underscores in keys for use with JS.
ace_editor_menu Implements hook_menu().
ace_editor_node_block_edit_form_attach_js Add JS to the page containing the affected forms.
ace_editor_permission Implements hook_permission().
get_setting_form_elements Field formatter settings form.
str_replace_once Custom function to replace the code only once.
tag_attributes Get all attributes of an <ace> tag in key/value pairs.