You are here

js_injector.module in JS injector 7

Allows administrators to inject JS into the page output based on configurable rules. Useful for adding simple JS tweaks without modifying a site's official theme.

File

js_injector.module
View source
<?php

/**
 * @file
 * Allows administrators to inject JS into the page output based on
 * configurable rules. Useful for adding simple JS tweaks without modifying
 * a site's official theme.
 */

/**
 * Deploy this JS snippet on every page except the listed pages.
 */
define('JS_INJECTOR_PAGES_NOTLISTED', 0);

/**
 * Deploy this JS snippet on only the listed pages.
 */
define('JS_INJECTOR_PAGES_LISTED', 1);

/**
 * Deploy this JS snippet only if the associated PHP code returns TRUE.
 */
define('JS_INJECTOR_PHP', 2);

/**
 * Implements hook_help().
 */
function js_injector_help($path, $arg) {
  $output = '';
  switch ($path) {
    case 'admin/config/modules#description':
      $output .= t('Allows administrators to inject JS into the page output based on configurable rules.');
      break;
    case 'admin/config/development/js-injector':
      $output .= '<p>' . t('Use JS injection rules to add small snippets of JS to the page output when specific criteria are met. For example, a simple rule could change the page background color at night or float a particular div to the right on node editing pages.') . '</p>';
      break;
  }
  return $output;
}

/**
 * Implements hook_init().
 * Checks to see whether any JS files should be added to the current page,
 * based on rules configured by the site administrator.
 */
function js_injector_init() {
  $js_rules = _js_injector_load_rule();
  foreach ($js_rules as $js_rule) {
    if (_js_injector_evaluate_rule($js_rule)) {
      $filepath = _js_injector_rule_path($js_rule['crid']);
      drupal_add_js($filepath, array(
        'type' => 'file',
        'group' => JS_THEME,
        'media' => $js_rule['media'],
        'preprocess' => $js_rule['preprocess'],
      ));
    }
  }
}

/**
 * Implements hook_js_alter().
 * Since we're trying to give the administrator complete control, we'll move
 * JS that this module has added to a high weight, higher even than the theme's
 * JS files. Currently the weight is 200 + the crid, which is currently higher
 * than Bartik's JS.
 *
 * @param $js
 *   The array of JS files.
 */
function js_injector_js_alter(&$js) {
  $js_rules = _js_injector_load_rule();
  foreach ($js_rules as $js_rule) {
    $filepath = _js_injector_rule_path($js_rule['crid']);
    if (!empty($js[$filepath])) {
      $js[$filepath]['weight'] = 200 + $js_rule['crid'];
    }
  }
}

/**
 * Implements hook_menu().
 * Defines menu callbacks for JS Injector's configuration pages.
 */
function js_injector_menu() {
  $items = array(
    'admin/config/development/js-injector' => array(
      'title' => 'JS injector',
      'description' => 'Add JS to the page output based on configurable rules.',
      'page callback' => 'drupal_get_form',
      'page arguments' => array(
        'js_injector_admin_form',
      ),
      'access arguments' => array(
        'administer js injection',
      ),
      'file' => 'js_injector.admin.inc',
    ),
    'admin/config/development/js-injector/edit' => array(
      'title' => 'Edit JS injector rule',
      'page callback' => 'drupal_get_form',
      'page arguments' => array(
        'js_injector_edit',
      ),
      'access arguments' => array(
        'administer js injection',
      ),
      'file' => 'js_injector.admin.inc',
      'type' => MENU_CALLBACK,
    ),
    'admin/config/development/js-injector/add' => array(
      'title' => 'Add JS injector rule',
      'page callback' => 'drupal_get_form',
      'page arguments' => array(
        'js_injector_edit',
      ),
      'access arguments' => array(
        'administer js injection',
      ),
      'file' => 'js_injector.admin.inc',
      'type' => MENU_CALLBACK,
    ),
    'admin/config/development/js-injector/delete' => array(
      'title' => 'Delete JS injector rule',
      'page callback' => 'drupal_get_form',
      'page arguments' => array(
        'js_injector_delete_confirm',
      ),
      'access arguments' => array(
        'administer js injection',
      ),
      'file' => 'js_injector.admin.inc',
      'type' => MENU_CALLBACK,
    ),
  );
  return $items;
}

/**
 * Implements hook_theme().
 */
function js_injector_theme() {
  $items['js_injector_admin_form'] = array(
    'render element' => 'form',
    'file' => 'js_injector.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_permission().
 */
function js_injector_permission() {
  return array(
    'administer js injection' => array(
      'title' => t('Administer JS Injection'),
    ),
  );
}

/**
 * Helper function to load all JS injection rules.
 */
function _js_injector_load_rule($crid = NULL, $reset = FALSE) {
  static $rules;

  // TODO: Change to drupal_static_fast pattern.
  if (!isset($rules) || $reset) {
    if (!$reset && ($cache = cache_get('js_injector:rules')) && !empty($cache->data)) {
      $rules = $cache->data;
    }
    else {
      $rules = array();
      $results = db_query("SELECT * FROM {js_injector_rule}", array(), array(
        'fetch' => PDO::FETCH_ASSOC,
      ))
        ->fetchAllAssoc('crid');
      foreach ($results as $id => $rule) {
        $rules[$id] = $rule;
      }
      cache_set('js_injector:rules', $rules);
    }
  }
  if (is_numeric($crid)) {
    return $rules[$crid];
  }
  else {
    return $rules;
  }
}

/**
 * Helper function to delete an existing rule and its accompanying file.
 */
function _js_injector_delete_rule($crid) {
  if ($rule = _js_injector_load_rule($crid)) {
    file_unmanaged_delete(_js_injector_rule_path($crid));
    db_delete('js_injector_rule')
      ->condition('crid', $crid)
      ->execute();
    drupal_set_message(t('The JS rule %title has been deleted.', array(
      '%title' => $rule['title'],
    )));
  }
}

/**
 * Helper function to determine whether a rule's conditions are met.
 *
 * @param $js_rule
 *   Array describing the rule.
 */
function _js_injector_evaluate_rule($js_rule = array()) {

  // Match path if necessary.
  if (!empty($js_rule['rule_conditions'])) {
    if ($js_rule['rule_type'] < JS_INJECTOR_PHP) {
      $path = drupal_get_path_alias($_GET['q']);

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

      // When $js_rule['rule_type'] has a value of
      // JS_INJECTOR_PAGES_NOTLISTED, the rule is matched on
      // all pages except those listed in $js_rule['rule_conditions'].
      // When set to JS_INJECTOR_PAGES_LISTED, it is displayed only on those
      // pages listed in $js_rule['rule_type'].
      $page_match = !($js_rule['rule_type'] xor $page_match);
    }
    else {
      if (module_exists('php')) {
        $page_match = php_eval($js_rule['rule_conditions']);
      }
      else {
        $page_match = FALSE;
      }
    }
  }
  else {
    $page_match = TRUE;
  }
  return $page_match;
}

/**
 * Helper function to get file path for a rule.
 * This will get the path relative to DRUPAL_ROOT,
 * as in 'sites/default/files/js_injector/js_injector_99.js'.
 *
 * @param $crid
 *   The integer identifying the JS Rule ID (crid)
 */
function _js_injector_rule_path($crid) {
  if (!empty($crid)) {
    $local_path = drupal_realpath(_js_injector_rule_uri($crid));

    // Now remove the part before the drupal root.
    // The +1 gets rid of the leading '/'.
    $local_path = substr_replace($local_path, '', 0, strlen(DRUPAL_ROOT) + 1);
    return $local_path;
  }
  return NULL;
}

/**
 * Return the URI for a crid.
 * @param $crid
 *   The integer identifying the JS Rule ID (crid)
 */
function _js_injector_rule_uri($crid) {
  if (!empty($crid)) {
    $uri = 'public://js_injector/js_injector_' . $crid . '.js';
    return $uri;
  }
}

Functions

Namesort descending Description
js_injector_help Implements hook_help().
js_injector_init Implements hook_init(). Checks to see whether any JS files should be added to the current page, based on rules configured by the site administrator.
js_injector_js_alter Implements hook_js_alter(). Since we're trying to give the administrator complete control, we'll move JS that this module has added to a high weight, higher even than the theme's JS files. Currently the weight is 200 + the crid, which…
js_injector_menu Implements hook_menu(). Defines menu callbacks for JS Injector's configuration pages.
js_injector_permission Implements hook_permission().
js_injector_theme Implements hook_theme().
_js_injector_delete_rule Helper function to delete an existing rule and its accompanying file.
_js_injector_evaluate_rule Helper function to determine whether a rule's conditions are met.
_js_injector_load_rule Helper function to load all JS injection rules.
_js_injector_rule_path Helper function to get file path for a rule. This will get the path relative to DRUPAL_ROOT, as in 'sites/default/files/js_injector/js_injector_99.js'.
_js_injector_rule_uri Return the URI for a crid.

Constants

Namesort descending Description
JS_INJECTOR_PAGES_LISTED Deploy this JS snippet on only the listed pages.
JS_INJECTOR_PAGES_NOTLISTED Deploy this JS snippet on every page except the listed pages.
JS_INJECTOR_PHP Deploy this JS snippet only if the associated PHP code returns TRUE.