You are here

syntaxhighlighter.module in Syntax Highlighter 7.2

Syntax highlight code using the Syntaxhighlighter javascript library. See http://alexgorbatchev.com/wiki/SyntaxHighlighter

@author: Matthew Young <www.hddigitalworks.com/contact>

File

syntaxhighlighter.module
View source
<?php

/**
 * @file
 * Syntax highlight code using the Syntaxhighlighter javascript library.
 * See http://alexgorbatchev.com/wiki/SyntaxHighlighter
 *
 * @author: Matthew Young <www.hddigitalworks.com/contact>
 */

/**
 * Inject syntaxhighlighter on every page except the listed pages.
 */
define('SYNTAXHIGHLIGHTER_INJECT_EXCEPT_LISTED', 0);

/**
 * Inject syntaxhighlighter on only the listed pages.
 */
define('SYNTAXHIGHLIGHTER_INJECT_IF_LISTED', 1);

/**
 * Inject syntaxhighlighter if the associated PHP code returns TRUE.
 */
define('SYNTAXHIGHLIGHTER_INJECT_PHP', 2);
define('SYNTAXHIGHLIGHTER_PHP_PERMISSION', 'use PHP for syntaxhighlighter js/css code inject control');

/**
 * Use a completely none sense word for replacement when filtering so there is
 * absolutely no chance this will conflict with something in content text
 */
define('SYNTAXHIGHLGHTER_TAG_STRING', '-_sYnTaXhIgHlIgHtEr_-');

/**
 * Implements hook_permission().
 */
function syntaxhighlighter_permission() {
  return array(
    SYNTAXHIGHLIGHTER_PHP_PERMISSION => array(
      'title' => t('Use PHP for syntaxhighlighter js/css code inject control'),
      'restrict access' => TRUE,
    ),
  );
}

/**
 * Menu for admin settings page
 */
function syntaxhighlighter_menu() {
  $items['admin/config/content/syntaxhighlighter'] = array(
    'title' => 'Syntax highlighter',
    'description' => 'Configure syntax highlighter',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'syntaxhighlighter_settings_form',
    ),
    'access arguments' => array(
      'administer modules',
    ),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'syntaxhighlighter.admin.inc',
  );
  return $items;
}
function syntaxhighlighter_help($path, $arg) {
  switch ($path) {
    case 'admin/help#syntaxhighlighter':
      return _syntaxhighlighter_filter_tips(0, 0, TRUE);
  }
}
function syntaxhighlighter_init() {
  if (!_syntaxhighlighter_page_match()) {
    return;
  }
  $lib_location = _syntaxhighlighter_get_lib_location();
  $styles_path = $lib_location . '/styles/';
  $scripts_path = $lib_location . '/scripts/';
  $js_options = array(
    'type' => 'file',
    'group' => JS_DEFAULT,
    'every_page' => TRUE,
  );
  drupal_add_css($styles_path . 'shCore.css');
  $theme = variable_get('syntaxhighlighter_theme', 'shThemeDefault.css');
  drupal_add_css($styles_path . $theme);
  drupal_add_js($scripts_path . 'shCore.js', $js_options);
  if (variable_get('syntaxhighlighter_legacy_mode', 0)) {
    drupal_add_js($scripts_path . 'shLegacy.js', $js_options);
  }
  if (variable_get('syntaxhighlighter_use_autoloader', FALSE)) {
    drupal_add_js($scripts_path . 'shAutoloader.js', $js_options);
    drupal_add_js(_syntaxhighlighter_file_directory_path() . '/syntaxhighlighter.autoloader.js', $js_options);
    $settings['useAutoloader'] = TRUE;
  }
  else {
    $enabled_languages = variable_get('syntaxhighlighter_enabled_languages', array(
      'shBrushPhp.js',
    ));
    foreach ($enabled_languages as $lang) {
      if (!empty($lang)) {
        drupal_add_js($scripts_path . $lang, $js_options);
      }
    }
  }
  $tag_name = variable_get('syntaxhighlighter_tagname', 'pre');
  if ($tag_name !== 'pre') {
    $settings['tagName'] = $tag_name;
  }
  if (file_exists($scripts_path . 'clipboard.swf')) {
    $settings['clipboard'] = base_path() . $scripts_path . 'clipboard.swf';
  }
  if (variable_get('syntaxhighlighter_legacy_mode', 0)) {
    $settings['legacyMode'] = TRUE;
  }
  if (isset($settings)) {
    drupal_add_js(array(
      'syntaxhighlighter' => $settings,
    ), 'setting');
  }
  if ($defaultExpression = variable_get('syntaxhighlighter_default_expressions', '')) {
    drupal_add_js($defaultExpression, 'inline');
  }
  drupal_add_js(drupal_get_path('module', 'syntaxhighlighter') . '/syntaxhighlighter.min.js', array(
    'type' => 'file',
    'scope' => 'footer',
    'group' => JS_DEFAULT,
    'every_page' => TRUE,
  ));
}
function _syntaxhighlighter_page_match() {
  $inject = variable_get('syntaxhighlighter_inject', SYNTAXHIGHLIGHTER_INJECT_EXCEPT_LISTED);
  $pages = variable_get('syntaxhighlighter_pages', "admin\nadmin/*\nuser\nuser/*\nimce\nimce/*\n");
  if ($inject != SYNTAXHIGHLIGHTER_INJECT_PHP) {
    $path = drupal_get_path_alias($_GET['q']);

    // Compare with the internal and path alias (if any).
    $page_match = drupal_match_path($path, $pages);
    if ($path != $_GET['q']) {
      $page_match = $page_match || drupal_match_path($_GET['q'], $pages);
    }
    return !($inject xor $page_match);
  }
  else {

    // if the PHP module is not enabled, we just return FALSE
    // which just ends up disabling the syntaxhighlighter
    return function_exists('php_eval') && php_eval($pages);
  }
}
function syntaxhighlighter_modules_disabled($modules) {
  if (in_array('php', $modules) && variable_get('syntaxhighlighter_inject', SYNTAXHIGHLIGHTER_INJECT_EXCEPT_LISTED) == SYNTAXHIGHLIGHTER_INJECT_PHP) {
    drupal_set_message(t('The "%syntaxhighlighter" module is currently configured to use PHP for js/css code inject control, disabling the "%module_name" module will effectively turn off syntax highlighting on all pages.', array(
      '%syntaxhighlighter' => t('Syntax highlighter'),
      '%module_name' => t('PHP filter'),
    )), 'warning');
  }
}

/**
 * Implements hook_filter_info()
 * 
 * declare the syntaxhighlighter filter
 */
function syntaxhighlighter_filter_info() {
  $filters['syntaxhighlighter'] = array(
    'title' => t('Syntax highlighter'),
    'description' => t('Process syntax highlighter filter code block'),
    'tips callback' => '_syntaxhighlighter_filter_tips',
    'prepare callback' => '_syntaxhighlighter_do_filter_prepare',
    'process callback' => '_syntaxhighlighter_do_filter_process',
    'cache' => TRUE,
  );
  return $filters;
}
function _syntaxhighlighter_filter_tips($filter, $format, $long = FALSE) {
  $tag_name = variable_get('syntaxhighlighter_tagname', 'pre');
  $tip = t('Syntax highlight code surrounded by the <code>!ex0</code> tags, where !lang is one of the following language brushes: %brushes.', array(
    '!ex0' => "&lt;{$tag_name} class=\"brush: <i>lang</i>\"&gt;...&lt;/{$tag_name}&gt;",
    '!lang' => '<i>lang</i>',
    '%brushes' => implode(', ', _syntaxhighlighter_get_enabled_language_brushes()),
  ));
  if ($long) {
    $tip .= ' ' . t('See <a href="!url0">the SyntaxHighlighter javascript library site</a> for additional helps.', array(
      '!url0' => 'http://alexgorbatchev.com/',
    ));
  }
  return $tip;
}

/**
 * @return an array of all enabled language brushes
 */
function _syntaxhighlighter_get_enabled_language_brushes() {
  $brushes =& drupal_static(__FUNCTION__);
  if (!isset($brushes)) {
    $brushes = array();
    foreach (variable_get('syntaxhighlighter_enabled_languages', array(
      'shBrushPhp.js',
    )) as $val) {
      if ($val) {
        $brushes[] = strtolower(substr(substr($val, 7), 0, -3));
      }
    }
  }
  return $brushes;
}

/**
 * Escape the content text in preparation for filtering:
 * 
 *  - change all syntaxhighlighter <pre> tag pairs to {-_sYnTaXhIgHlIgHtEr_-}
 *    {/-_sYnTaXhIgHlIgHtEr_-} pair (so other filters would not mess with them
 *
 * Precondition: all the open/close tags much match because search is done on
 * pair by pair basis. If match is not even, do nothing.
 * 
 * All HTML tags and entities inside the SyntaxHighlighter must be properly
 * escape. For example, if you show HTML code, change
 * 
 *  - '<' to '&lt;': e.g.  <pre> -> &lt;pre>, <html> -> &lt;html>
 *  - neutralize & in entity: e.g.: &gt; -> &amp;gt;
 * 
 * @param string $text
 *   the content text to be filtered
 * @return
 *   the escape content text
 */
function _syntaxhighlighter_do_filter_prepare($text) {
  $tag_name = variable_get('syntaxhighlighter_tagname', 'pre');
  $pattern = "#<{$tag_name}\\s*([^>]*)>|</\\s*{$tag_name}>#";
  preg_match_all($pattern, $text, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
  $output = '';
  $at = 0;
  $n = count($matches);

  // do nothing if open/close tag match is not even
  if ($n % 2) {
    return $text;
  }
  for ($i = 0; $i < $n;) {
    $open_tag = $matches[$i++];
    $close_tag = $matches[$i++];
    if (strpos($open_tag[1][0], 'brush:')) {
      $output .= substr($text, $at, $open_tag[0][1] - $at);
      $begin = $open_tag[0][1] + strlen($open_tag[0][0]);
      $length = $close_tag[0][1] - $begin;
      $output .= '{' . SYNTAXHIGHLGHTER_TAG_STRING . ' ' . $open_tag[1][0] . '}' . substr($text, $begin, $length) . '{/' . SYNTAXHIGHLGHTER_TAG_STRING . '}';
      $at = $close_tag[0][1] + strlen($close_tag[0][0]);
    }
  }
  $output .= substr($text, $at);
  return $output;
}

/**
 * Revert back to <pre> tag
 */
function _syntaxhighlighter_do_filter_process($text) {
  $patterns = array(
    '#{' . SYNTAXHIGHLGHTER_TAG_STRING . ' ([^}]+)}#',
    '#{/' . SYNTAXHIGHLGHTER_TAG_STRING . '}#',
  );
  $tag_name = variable_get('syntaxhighlighter_tagname', 'pre');
  $replacements = array(
    "<{$tag_name} \$1>",
    "</{$tag_name}>",
  );
  return preg_replace($patterns, $replacements, $text);
}

/**
 * Validate on the node input text to be sure there is no bad
 * {syntaxhighlighter} tags
 */
function syntaxhighlighter_node_validate($node, $form, &$form_state) {
  if (isset($node->body)) {
    foreach ($node->body as $lang => $v) {
      if (_syntaxhighlighter_format_has_syntaxhighlighter_filter(isset($v[0]['format']) ? $v[0]['format'] : filter_fallback_format())) {
        if (!empty($v[0]['summary'])) {
          _syntaxhighlighter_validate_input("body][{$lang}][0][summary", $v[0]['summary']);
        }
        _syntaxhighlighter_validate_input("body][{$lang}][0][value", $v[0]['value']);
      }
    }
  }
}

/**
 * Install custom comment validate function
 */
function syntaxhighlighter_form_comment_form_alter(&$form, &$form_state, $form_id) {
  $form['#validate'][] = '_syntaxhighlighter_comment_validate';
}

/**
 * Validate on comment input text to be sure there is no bad
 * {syntaxhighlighter} tags
 */
function _syntaxhighlighter_comment_validate($form, &$form_state) {
  if (isset($form_state['values']['comment_body'])) {
    foreach ($form_state['values']['comment_body'] as $lang => $v) {
      if (_syntaxhighlighter_format_has_syntaxhighlighter_filter(isset($v[0]['format']) ? $v[0]['format'] : filter_fallback_format())) {
        _syntaxhighlighter_validate_input("comment_body][{$lang}][0][value", $v[0]['value']);
      }
    }
  }
}

/**
 * Check for error with syntaxhighlighter input
 *
 * @param string $field
 *   what input field are we checking? We do form_set_error on this if
 *   any error is found
 * @param string $text
 *   the input text to check for
 */
function _syntaxhighlighter_validate_input($field, $text) {
  $errors = array();
  $tag_name = variable_get('syntaxhighlighter_tagname', 'pre');

  // check for balance open/close tags
  preg_match_all("#<{$tag_name}\\s*[^>]*>#", $text, $matches_open);
  preg_match_all("#</\\s*{$tag_name}\\s*>#", $text, $matches_close);
  if (count($matches_open[0]) != count($matches_close[0])) {
    $errors[] = t('Unbalanced !tag tags.', array(
      '!tag' => "&lt;{$tag_name}&gt;",
    ));
  }

  // make sure no nesting
  preg_match_all("#<{$tag_name}\\s*[^>]*>.*</\\s*{$tag_name}\\s*>#sU", $text, $matches_pair);
  if (count($matches_pair[0]) != count($matches_open[0]) || count($matches_pair[0]) != count($matches_close[0])) {
    $errors[] = t('!tag tags cannot be nested.', array(
      '!tag' => "&lt;{$tag_name}&gt;",
    ));
  }
  if (!empty($errors)) {
    form_set_error($field, implode(' ', $errors));
  }
}

/**
 * @return the directory path where the syntaxhighlighter js lib is installed, NULL if not found
 */
function _syntaxhighlighter_get_lib_location() {
  $result = variable_get('syntaxhighlighter_lib_location', NULL);
  if (!$result) {
    $result = _syntaxhighlighter_scan_lib_location();

    // library location may have changed, recreate the setup script if the lib
    // is found
    if ($result) {
      variable_set('syntaxhighlighter_lib_location', $result);
      _syntaxhighlighter_setup_autoloader_script();
    }
  }
  return $result;
}

/**
 * Locate the syntaxhighlighter js library.  The library must be placed in a
 * directory named "syntaxhighlighter" in one of the following locations:
 *   1) standard library paths provided by the "libraries" module (if installed)
 *   2) the syntaxhighlighter module
 *   3) sites/all/libraries/syntaxhighlighter
 *   4) sites/[sitename]/libraries/syntaxhighlighter
 *   5) under the install profile libraries directory
 * @return the file location of the js lib or NULL if not found
 */
function _syntaxhighlighter_scan_lib_location() {
  $directories = array();
  if (module_exists('libraries')) {
    $directories[] = libraries_get_path('syntaxhighlighter');
  }
  $directories[] = drupal_get_path('module', 'syntaxhighlighter') . "/syntaxhighlighter";
  $directories[] = 'sites/all/libraries/syntaxhighlighter';

  // Mult-site specific location.
  $directories[] = conf_path() . '/libraries/syntaxhighlighter';

  // When this function is called during Drupal's initial installation process,
  // the name of the profile that is about to be installed is stored in the
  // global $profile variable. At all other times, the regular system variable
  // contains the name of the current profile, and we can call variable_get()
  // to determine the profile.
  global $profile;
  if (!isset($profile)) {
    $profile = variable_get('install_profile', 'default');
  }
  $directories[] = "profiles/{$profile}/libraries/syntaxhighlighter";
  foreach ($directories as $d) {
    if (is_file($d . "/scripts/shCore.js")) {
      return $d;
    }
  }
  return NULL;
}

/**
 * Create the autoload setup script file. Must call this whenever lib
 * location  and/or the enable brushes change.  Make sure never call this
 * if the js lib is not found
 */
function _syntaxhighlighter_setup_autoloader_script() {
  $path = 'public://syntaxhighlighter.autoloader.js';
  if (variable_get('syntaxhighlighter_use_autoloader', FALSE)) {

    // use variable_get() instead of _syntaxhighlighter_get_lib_location()
    // because this function is called only if the lib location is found
    $script_path = base_path() . variable_get('syntaxhighlighter_lib_location', NULL) . '/scripts/';
    $script_data = <<<__HERE__
/*
 * This file is generated by the Syntaxhighlighter module
 */
__HERE__;
    $script_data .= "\nfunction syntaxhighlighterAutoloaderSetup() {\n  SyntaxHighlighter.autoloader(\n";
    $need_ending = FALSE;
    $brushes = variable_get('syntaxhighlighter_enabled_languages', array(
      'shBrushPhp.js',
    ));
    foreach ($brushes as $b) {
      if ($b) {
        if ($need_ending) {
          $script_data .= ",\n";
        }
        $alias = strtolower(substr(substr($b, 7), 0, -3));
        $script_data .= "    '{$alias} {$script_path}{$b}'";
        $need_ending = TRUE;
      }
    }
    $script_data .= "\n);\n}\n";
    file_unmanaged_save_data($script_data, $path, FILE_EXISTS_REPLACE);
  }
  else {
    if (file_exists($path)) {
      file_unmanaged_delete($path);
    }
  }
}

/**
 * This does the same thing as the old D6 file_directory_path() which
 * is to give the default 'files' directory path relative the the web root
 * 
 * @return a string containing the path to the site's 'files' directory
 */
function _syntaxhighlighter_file_directory_path() {
  return variable_get('file_public_path', conf_path() . '/files');
}
function _syntaxhighlighter_format_has_syntaxhighlighter_filter($format_id) {
  return array_key_exists('syntaxhighlighter', filter_list_format($format_id));
}

/**
 * Implements hook_libraries_info().
 */
function syntaxhighlighter_libraries_info() {
  $libraries['syntaxhighlighter'] = array(
    // Only used in administrative UI of Libraries API.
    'name' => 'Syntax Highlighter',
    'vendor url' => 'http://alexgorbatchev.com/SyntaxHighlighter/',
    'download url' => 'http://alexgorbatchev.com/SyntaxHighlighter/download/',
    'download' => array(
      'type' => 'file',
      'url' => 'http://alexgorbatchev.com/SyntaxHighlighter/download/download.php?sh_current',
    ),
    'path' => 'scripts',
    'version arguments' => array(
      'file' => 'scripts/shCore.js',
      'pattern' => '@([0-9]+.[0-9]+.[0-9]+)@',
      'lines' => 15,
      'cols' => 30,
    ),
  );
  return $libraries;
}

Functions

Namesort descending Description
syntaxhighlighter_filter_info Implements hook_filter_info()
syntaxhighlighter_form_comment_form_alter Install custom comment validate function
syntaxhighlighter_help
syntaxhighlighter_init
syntaxhighlighter_libraries_info Implements hook_libraries_info().
syntaxhighlighter_menu Menu for admin settings page
syntaxhighlighter_modules_disabled
syntaxhighlighter_node_validate Validate on the node input text to be sure there is no bad {syntaxhighlighter} tags
syntaxhighlighter_permission Implements hook_permission().
_syntaxhighlighter_comment_validate Validate on comment input text to be sure there is no bad {syntaxhighlighter} tags
_syntaxhighlighter_do_filter_prepare Escape the content text in preparation for filtering:
_syntaxhighlighter_do_filter_process Revert back to <pre> tag
_syntaxhighlighter_file_directory_path This does the same thing as the old D6 file_directory_path() which is to give the default 'files' directory path relative the the web root
_syntaxhighlighter_filter_tips
_syntaxhighlighter_format_has_syntaxhighlighter_filter
_syntaxhighlighter_get_enabled_language_brushes
_syntaxhighlighter_get_lib_location
_syntaxhighlighter_page_match
_syntaxhighlighter_scan_lib_location Locate the syntaxhighlighter js library. The library must be placed in a directory named "syntaxhighlighter" in one of the following locations: 1) standard library paths provided by the "libraries" module (if installed) 2) the…
_syntaxhighlighter_setup_autoloader_script Create the autoload setup script file. Must call this whenever lib location and/or the enable brushes change. Make sure never call this if the js lib is not found
_syntaxhighlighter_validate_input Check for error with syntaxhighlighter input

Constants

Namesort descending Description
SYNTAXHIGHLGHTER_TAG_STRING Use a completely none sense word for replacement when filtering so there is absolutely no chance this will conflict with something in content text
SYNTAXHIGHLIGHTER_INJECT_EXCEPT_LISTED Inject syntaxhighlighter on every page except the listed pages.
SYNTAXHIGHLIGHTER_INJECT_IF_LISTED Inject syntaxhighlighter on only the listed pages.
SYNTAXHIGHLIGHTER_INJECT_PHP Inject syntaxhighlighter if the associated PHP code returns TRUE.
SYNTAXHIGHLIGHTER_PHP_PERMISSION