You are here

freelinking.module in Freelinking 7.3

File

freelinking.module
View source
<?php

/**
 * @file
 * Provides flexible, extensible, wiki-style linking of content.
 */
require DRUPAL_ROOT . '/' . (drupal_get_path('module', 'freelinking') . '/freelinking.utilities.inc');
require DRUPAL_ROOT . '/' . (drupal_get_path('module', 'freelinking') . '/freelinking.forms.inc');

/**
* Implements hook_help().
*/
function freelinking_help($path, $arg) {
  if ($path == 'admin/help#freelinking') {
    $output = '<p>' . t('This module is a filter to turn wiki-style links into HTML links.') . '</p>';
    if (function_exists('advanced_help_hint_docs')) {
      $output .= '<p>' . advanced_help_hint_docs('freelinking', 'https://www.drupal.org/docs/7/modules/freelinking', TRUE) . '</p>';
    }
    else {
      $output .= t('If you install and enable the module <strong>!url</strong>, you will get more help for <strong>Freelinking</strong>.', array(
        '!url' => l('Advanced help hint', 'https://www.drupal.org/project/advanced_help_hint'),
      ));
    }
    return $output;
  }
}

/**
 * Implements hook_menu().
 */
function freelinking_menu() {
  $items['admin/config/content/freelinking'] = array(
    'title' => 'Freelinking settings',
    'description' => 'Configure settings for the freelinking input filter',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'freelinking_settings',
    ),
    'access arguments' => array(
      'administer freelinking',
    ),
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

/**
 * Implements hook_permission().
 */
function freelinking_permission() {
  return array(
    'administer freelinking' => array(
      'title' => t('administer freelinking'),
      'description' => t('Allows configuring module and per plugin settings.'),
    ),
  );
}

/**
 * Implements hook_filter_info().
 */
function freelinking_filter_info() {

  // Caching is not compatible with node access modules.
  $cache = variable_get('freelinking_enablecache', FALSE) && !count(module_implements('node_grants'));
  $filters = array();
  $filters['freelinking'] = array(
    'title' => t('Freelinking'),
    'description' => t('Allows for a flexible format for linking content'),
    'process callback' => '_freelinking_process',
    'tips callback' => '_freelinking_tips',
    'cache' => $cache,
  );
  return $filters;
}

/**
 * Implements filter process callback
 */
function _freelinking_process($text, $filter, $format, $langcode) {
  $freelinking = freelinking_get_plugins();
  $defaultplugin = variable_get('freelinking_default', 'nodetitle');
  $global_options = variable_get('freelinking_options', array());
  $start = 0;
  $remain = $text;
  $delim = '[[';
  $newtext = '';
  while (TRUE) {
    $offset = 0;
    if (empty($remain)) {
      break;
    }
    if ('[' == $remain[0] && '[' == $remain[1]) {
      $infreelinkp = TRUE;
      $delim = ']]';
    }
    else {
      $infreelinkp = FALSE;
      $delim = '[[';
    }
    $pos = strpos($remain, $delim);
    if (FALSE === $pos) {
      break;
    }
    $chunk_all = substr($remain, $start, $pos);
    if ($infreelinkp) {
      $current_plugin = '';
      $chunk_stripped = substr($chunk_all, 2);
      $delim = strpos($chunk_stripped, ':');
      if (FALSE === $delim) {
        $indicator = $defaultplugin;
        $target = $chunk_stripped;
      }
      else {
        $indicator = substr($chunk_stripped, 0, $delim);
        $target = substr($chunk_stripped, $delim + 1);
      }

      // find a plugin for the match
      if ('showtext' == $indicator || 'nowiki' == $indicator || 'redact' == $indicator) {
        $current_plugin = $indicator;
      }
      else {
        foreach (array_keys($freelinking) as $plugin) {
          if ($freelinking[$plugin]['enabled'] && preg_match($freelinking[$plugin]['indicator'], $indicator)) {
            $current_plugin = $plugin;
          }
        }

        // end looping through plugins
      }
      if (empty($global_options['ignore_upi']) && $freelinking['nodetitle']['enabled'] && 'nodetitle' == $defaultplugin && empty($current_plugin)) {
        $target = $chunk_stripped;
        $current_plugin = 'nodetitle';
      }
      if (empty($global_options['ignore_upi']) || !empty($current_plugin)) {
        if (empty($current_plugin)) {
          $message = 'NONE' == $indicator ? t('Missing plugin indicator') : t('unknown plugin indicator "%indicator"', array(
            '%indicator' => $indicator,
          ));
          $link = theme('freelink_error', array(
            'plugin' => $indicator,
            'message' => $message,
          ));
        }
        else {
          $target_array = _freelinking_parse_target($target, $langcode);
          $link = freelinking_get_freelink($current_plugin, $target_array);
        }
        if ($link) {
          $chunk_all = $link;
          $offset = 2;
        }
      }
      $remain = substr($remain, $pos + $offset);
    }
    else {
      $remain = substr($remain, $pos);
    }
    $newtext .= $chunk_all;
  }
  $newtext .= $remain;
  return $newtext;
}

/**
 * Implements filter tips callback
 */
function _freelinking_tips($delta, $format, $long = FALSE) {
  $syntax = variable_get('freelinking_syntax_mode', 'double_bracket');
  if ($syntax == 'double_bracket') {
    $pattern = '<tt>[[indicator:target|Title]]</tt>';
  }
  elseif ($syntax == 'markdown') {
    $pattern = '<tt>[Title](indicator:target)</tt>';
  }
  else {
    $pattern = '<tt>[indicator:target|Title]</tt>';
  }
  $text = t('Freelinking helps you easily create HTML links. Links take the form of !pattern.', array(
    '!pattern' => $pattern,
  ));
  $plugins = freelinking_get_plugins();
  if ($long == FALSE) {
    $default = variable_get('freelinking_default', 'nodetitle');
    if ('NONE' != $default) {
      $default_tip = $plugins[$default]['tip'];
      if ($default_tip) {
        $text .= ' ' . t('By default (no indicator): !default_tip', array(
          '!default_tip' => $default_tip,
        ));
      }
    }
    return $text;
  }
  $text = '<h4>' . t('Freelinking') . '</h4>' . $text;
  $text .= '<br />';
  $text .= t('Below is a list of available types of freelinks you may use, organized as <strong>Plugin Name</strong>: <em>[indicator]</em>.');
  $tips = array();
  foreach ($plugins as $name => $plugin) {
    $tips[$name] = '<strong>' . drupal_ucfirst($name) . '</strong> <em>[' . $plugin['indicator'] . ']</em>';
    if (isset($plugin['tip'])) {
      $tips[$name] .= ' — ' . $plugin['tip'];
    }
  }
  $text .= theme('item_list', array(
    'items' => $tips,
  ));
  return $text;
}

/**
 * Implements hook_freelinking().
 *
 * Include plugins/*.inc plugins
 */
function freelinking_freelinking() {
  static $included;
  if (empty($included)) {
    $included = file_scan_directory(drupal_get_path('module', 'freelinking') . '/plugins/', '/.inc$/');
    foreach ($included as $absolute => $file) {
      require_once DRUPAL_ROOT . '/' . $absolute;
    }
  }

  // The freelinking variable is specified by included files.
  foreach ($freelinking as $plugin => $definition) {
    if (!isset($definition['settings'])) {
      $freelinking[$plugin]['settings'] = 'freelinking_' . $plugin . '_settings';
    }
  }
  return $freelinking;
}

/**
 * Implements hook_freelink_alter().
 * Used here to clean up and standardize links.
 */
function freelinking_freelink_alter(&$link, $context) {
  $target = $context['target'];
  $plugin_name = $context['plugin_name'];
  $plugin = $context['plugin'];

  // not a valid link
  if (!array_key_exists(1, $link)) {
    $link['error'] = t('Invalid Link');
    return;
  }

  // title text is empty, insert from target or use URL
  if (empty($link[0])) {
    $link[0] = $target['text'] ? $target['text'] : $target['dest'];
  }

  // support html link text unless plugin overrides
  if (isset($plugin['html']) && $plugin['html'] == TRUE) {
    $link[2]['html'] = TRUE;
  }

  // Set an empty tooltip
  if (!isset($link[2]['attributes']['title'])) {
    if (isset($context['plugin']['tip'])) {
      $link[2]['attributes']['title'] = $context['plugin']['tip'];
    }
    else {
      $link[2]['attributes']['title'] = $link[1];
    }
  }

  // standard set of CSS classes
  $link[2]['attributes']['class'][] = 'freelink';
  $link[2]['attributes']['class'][] = 'freelink-' . strtr($plugin_name, ' ', '-');

  // There was more than one effort to generate the link
  if (isset($target['other']['trace'])) {
    $link[2]['attributes']['class'][] = 'notfound';
  }

  // Is this an internal or external link?
  $parts = parse_url($link[1]);
  if (isset($parts['host']) && $parts['host'] != $_SERVER['SERVER_NAME']) {
    $link[2]['attributes']['class'][] = 'freelink-external';
  }
  else {
    $link[2]['attributes']['class'][] = 'freelink-internal';
  }
}

/**
 * Construct a link out of the $target with the specified plugin
 *
 * There are three built-in indicators that will not generate links:
 * - nowiki: will strip the nowiki-indicator and show the rest;
 * - showtext: as nowiki, but will also strip double brackets;
 * - redact: as showtext for logged in users, will redact for anon.
 */
function _freelinking_build_freelink($freelinking, $plugin_name, $target) {
  if ('showtext' == $plugin_name) {
    $text = $target['text'] ? $target['text'] : $target['dest'];
    return $text;
  }
  if ('nowiki' == $plugin_name) {
    $text = $target['text'] ? $target['text'] : $target['dest'];
    return '[[' . $text . ']]';
  }
  if ('redact' == $plugin_name) {
    if (user_is_logged_in()) {
      $text = $target['dest'];
    }
    else {
      $text = $target['text'] ? $target['text'] : '******';
    }
    return $text;
  }
  $plugin = $freelinking[$plugin_name];

  // if a plugin does not exist, go to failure.
  if (!$plugin) {
    return array(
      'error' => t('Plugin %plugin not found', array(
        '%plugin' => $plugin_name,
      )),
    );
  }

  // run the text through translation
  if (isset($plugin['translate'])) {
    $target['dest'] = strtr($target['dest'], $plugin['translate']);
  }

  // process simple replacement plugins if no callback exists
  if (isset($plugin['replacement']) && !isset($plugin['callback'])) {

    // %1 is the token all freelinking replacement strings must include
    $url = preg_replace('/%1/', $target['dest'], $plugin['replacement']);
    $link = array(
      '',
      $url,
    );
  }

  // process replacement callback
  if (isset($plugin['callback']) && function_exists($plugin['callback'])) {
    $link = call_user_func_array($plugin['callback'], array(
      $target,
      $plugin,
    ));
  }

  // Standardize link, grab authoritative "structured" version
  // designate the rendered text for display
  if (is_array($link)) {
    if (isset($link['failover']) && $link['failover'] != 'error' && $link['failover'] != 'NONE') {
      $target = isset($link['target']) ? $link['target'] : $target;
      $target['other']['trace'][] = $plugin_name;
      unset($freelinking[$plugin_name]);
      return _freelinking_build_freelink($freelinking, $link['failover'], $target);
    }
    if (is_array($link) && !isset($link['error'])) {
      $data = array(
        'target' => $target,
        'plugin_name' => $plugin_name,
        'plugin' => $plugin,
      );
      drupal_alter('freelink', $link, $data);
    }
  }

  // if empty/false, nothing will happen
  return $link;
}

/**
 * Parse target for secondary link arguments.
 *
 * $target is raw user input and needs to be checked by check_plain.
 * before rendered.
 */
function _freelinking_parse_target($target, $language) {
  $args = array();
  $args['target'] = $target;
  $items = explode('|', $target);

  // first three unnamed args are dest, text, tooltip
  $index = 0;
  foreach ($items as $key => $item) {
    if (strpos($item, '=')) {
      list($name, $value) = explode('=', $item);
      $args[$name] = $value;
    }
    elseif ($index < 3) {
      switch ($index) {
        case '0':
          $args['dest'] = $item;
          break;
        case '1':
          $args['text'] = $item;
          break;
        case '2':
          $args['tooltip'] = $item;
          break;
      }
      $index++;
    }
    else {
      $args['other'][] = $item;
    }
  }

  // Convert URL-encoded text into something readable for link text & tooltip.
  $args['text'] = isset($args['text']) ? urldecode($args['text']) : NULL;
  $args['tooltip'] = isset($args['tooltip']) ? urldecode($args['tooltip']) : NULL;
  $args['language'] = $language;
  return $args;
}

/**
 * Theme Functions
 */

/**
 * Implements hook_theme().
 */
function freelinking_theme() {
  return array(
    'freelink' => array(
      'variables' => array(
        'plugin' => NULL,
        'link' => NULL,
      ),
    ),
    'freelink_error' => array(
      'variables' => array(
        'plugin' => NULL,
        'message' => NULL,
      ),
    ),
  );
}

/**
 * Theme the Freelink
 */
function theme_freelink($variables) {
  $prefix = '';
  $suffix = '';
  $plugin = $variables['plugin'];
  $link = $variables['link'];
  if (isset($link['extra'])) {
    $prefix = $link['extra']['prefix'];
    $suffix = $link['extra']['suffix'];
    unset($link['extra']);
  }

  // Tweak the language to be an object if it exists.
  $options = $link[2];
  if (isset($link[2]['language']) && !isset($link[2]['language']->language)) {
    $language = $link[2]['language'];
    $link[2]['language'] = new StdClass();
    $link[2]['language']->language = $language;
  }
  $rendered = $prefix . call_user_func_array('l', $link) . $suffix;
  return $rendered;
}

/**
 * Theme the error message
 */
function theme_freelink_error($variables) {
  $plugin = $variables['plugin'];
  $message = $variables['message'];
  if ($message) {
    $message = ': ' . $message;
  }
  return '<code class="freelink freelink-error freelink-' . $plugin . ' freelink-error-' . $plugin . '">[' . t('Freelinking') . $message . ']</code>';
}

Functions

Namesort descending Description
freelinking_filter_info Implements hook_filter_info().
freelinking_freelinking Implements hook_freelinking().
freelinking_freelink_alter Implements hook_freelink_alter(). Used here to clean up and standardize links.
freelinking_help Implements hook_help().
freelinking_menu Implements hook_menu().
freelinking_permission Implements hook_permission().
freelinking_theme Implements hook_theme().
theme_freelink Theme the Freelink
theme_freelink_error Theme the error message
_freelinking_build_freelink Construct a link out of the $target with the specified plugin
_freelinking_parse_target Parse target for secondary link arguments.
_freelinking_process Implements filter process callback
_freelinking_tips Implements filter tips callback