You are here

syntaxhighlighter.module in Syntax Highlighter 6.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_perm().
 */
function syntaxhighlighter_perm() {
  return array(
    'use PHP for syntaxhighlighter js/css code inject control',
  );
}

/**
 * Menu for admin settings page
 */
function syntaxhighlighter_menu() {
  $items['admin/settings/syntaxhighlighter'] = array(
    'title' => 'Syntax highlighter',
    'description' => 'Configure syntax highlighter',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'syntaxhighlighter_settings_form',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    '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/';
  drupal_add_css($styles_path . 'shCore.css', 'module');
  $theme = variable_get('syntaxhighlighter_theme', 'shThemeDefault.css');
  drupal_add_css($styles_path . $theme, 'module');
  drupal_add_js($scripts_path . 'shCore.js', 'module');
  if (variable_get('syntaxhighlighter_legacy_mode', 0)) {
    drupal_add_js($scripts_path . 'shLegacy.js', 'module');
  }
  if (variable_get('syntaxhighlighter_use_autoloader', FALSE)) {
    drupal_add_js($scripts_path . 'shAutoloader.js');
    drupal_add_js(file_directory_path() . '/syntaxhighlighter.autoloader.js');
    $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, 'module');
      }
    }
  }
  $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;
  }
  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', 'module', 'footer');
}
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 {
    return drupal_eval($pages);
  }
}

/**
 * Implements hook_filter()
 */
function syntaxhighlighter_filter($op, $delta = 0, $format = -1, $text = '') {
  switch ($op) {
    case 'list':
      return array(
        0 => t('Syntax highlighter'),
      );
    case 'description':
      return syntaxhighlighter_filter_tips(0, 0, FALSE);
    case 'no cache':
      return FALSE;
    case 'prepare':
      if ($delta == 0) {
        return _syntaxhighlighter_do_filter_prepare($text);
      }
      else {
        return $text;
      }
    case 'process':
      if ($delta == 0) {
        return _syntaxhighlighter_do_filter_process($text);
      }
      else {
        return $text;
      }

    // do nothing
    default:
      return $text;
  }
}
function syntaxhighlighter_filter_tips($delta, $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() {
  static $brushes;
  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;
}

/**
 * Validate on the node input text to be sure there is no bad
 * {syntaxhighlighter} tags
 */
function syntaxhighlighter_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  switch ($op) {
    case 'validate':
      if (isset($node->body)) {
        $teaser_break = strpos($node->body, '<!--break-->');
        if ($teaser_break === 0) {
          _syntaxhighlighter_validate_input('body', $node->body);
        }
        else {
          if (isset($node->teaser_js)) {
            _syntaxhighlighter_validate_input('teaser_js', $node->teaser_js);
          }
          _syntaxhighlighter_validate_input('body', substr($node->body, $teaser_break));
        }
      }
      break;
  }
}

/**
 * Validate on comment input text to be sure there is no bad
 * {syntaxhighlighter} tags
 */
function syntaxhighlighter_comment(&$a1, $op) {
  switch ($op) {
    case 'validate':
      _syntaxhighlighter_validate_input('comment', $a1['comment']);
      break;
  }
}

/**
 * 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));
  }
}

/**
 * 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);
}

/**
 * @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();
    variable_set('syntaxhighlighter_lib_location', $result);

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

/**
 * Do an exhaustive scan of file directories for the location of the syntaxhighlighter js lib,
 * Allow the syntaxhighlighter js library to be installed in any of the following
 * locations and under any sub-directory (except 'src'):
 *   1) syntaxhighlighter module directory
 *   2) sites/<site_domain>/files    (whereever the "public://" path is)
 *   3) sites/all/libraries
 *   4) 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(
    drupal_get_path('module', 'syntaxhighlighter'),
    file_directory_path(),
    'sites/all/libraries',
  );

  // 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";
  foreach ($directories as $d) {
    foreach (file_scan_directory($d, 'shCore\\.js$', array(
      '.',
      '..',
      'CVS',
      'src',
      'pupload',
      'plupload',
    )) as $filename => $file_info) {

      // the path to syntaxhighlighter lib, (-18 to chop off "/scripts/shCore.js"
      // part at the end
      return substr($filename, 0, -18);
    }
  }
  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 = file_directory_path() . '/syntaxhighlighter.autoloader.js';
  if (variable_get('syntaxhighlighter_use_autoloader', FALSE)) {

    // use variable_get() instead of _syntaxhighlighter_get_lib_location()
    // because this function is only if the lib location is found
    $script_path = base_path() . variable_get('syntaxhighlighter_lib_location', NULL) . '/scripts/';
    $script_data = <<<HEREHERE
/*
 * This file is generated by the Syntaxhighlighter module
 */
HEREHERE;
    $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_save_data($script_data, $path, FILE_EXISTS_REPLACE);
  }
  else {
    file_delete($path);
  }
}

Functions

Namesort descending Description
syntaxhighlighter_comment Validate on comment input text to be sure there is no bad {syntaxhighlighter} tags
syntaxhighlighter_filter Implements hook_filter()
syntaxhighlighter_filter_tips
syntaxhighlighter_help
syntaxhighlighter_init
syntaxhighlighter_menu Menu for admin settings page
syntaxhighlighter_nodeapi Validate on the node input text to be sure there is no bad {syntaxhighlighter} tags
syntaxhighlighter_perm Implements hook_perm().
_syntaxhighlighter_do_filter_prepare Escape the content text in preparation for filtering:
_syntaxhighlighter_do_filter_process Revert back to <pre> tag
_syntaxhighlighter_get_enabled_language_brushes
_syntaxhighlighter_get_lib_location
_syntaxhighlighter_page_match
_syntaxhighlighter_scan_lib_location Do an exhaustive scan of file directories for the location of the syntaxhighlighter js lib, Allow the syntaxhighlighter js library to be installed in any of the following locations and under any sub-directory (except 'src'): 1)…
_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