You are here

uuid_link.module in UUID Link 6

Same filename and directory in other branches
  1. 7 uuid_link.module

Provides a filter and UI for adding links to entities that are not affected by changes in URL alias.

File

uuid_link.module
View source
<?php

/**
 * @file
 * Provides a filter and UI for adding links to entities that are not affected
 * by changes in URL alias.
 */

/**
 * Implements hook_permission().
 */
function uuid_link_permission() {
  return array(
    'access uuid link',
  );
}

/**
 * Implements hook_menu().
 */
function uuid_link_menu() {
  $items = array();
  $items['uuid-link/autocomplete'] = array(
    'page callback' => 'uuid_link_autocomplete',
    'access arguments' => array(
      'access uuid link',
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_wysiwyg_plugin().
 */
function uuid_link_wysiwyg_plugin($editor, $version) {
  if ($editor == 'ckeditor') {
    return array(
      'uuid_link' => array(
        'path' => drupal_get_path('module', 'uuid_link') . '/plugins/link/',
        'load' => TRUE,
        'extensions' => array(
          'uuid_link' => t('Link to site content'),
        ),
      ),
    );
  }
}

/**
 * Implements hook_element_info_alter().
 */
function uuid_link_element_info_alter(&$type) {
  if (!empty($type['text_format'])) {
    $type['form']['#post_render'][] = 'uuid_link_form_post_render';
  }
}

/**
 * Implements hook_entity_info_alter().
 */
function uuid_link_entity_info_alter(&$entity_info) {

  // The username should be the label for users.
  $entity_info['user']['entity keys']['label'] = 'name';
}

/**
 * Post render callback. Load javascript files and set module settings.
 */
function uuid_link_form_alter(&$form, $form_state, $form_id) {
  drupal_set_message($form_id);
  if (substr($form_id, -9) == 'node_form') {
    $form['#after_build'][] = 'uuid_link_form_post_build';
  }
}
function uuid_link_form_post_build($form) {
  static $added;
  if (empty($added) && ($js = drupal_add_js()) && isset($js['setting'])) {
    $settings = call_user_func_array('array_merge_recursive', $js['setting']);
    if (isset($settings['ckeditor']) || isset($settings['wysiwyg']['configs']['ckeditor'])) {
      $added = TRUE;
      drupal_add_js('misc/autocomplete.js');
      $settings = array(
        'autocomplete_path' => url('uuid-link/autocomplete'),
        'type_name' => uuid_link_get_link_type_name(),
      );
      drupal_add_js(array(
        'uuid_link' => $settings,
      ), 'setting');
    }
  }
  return $form;
}

/**
 * Get a list of available link types. In this case, a list of entity types.
 */
function uuid_link_get_link_type_name() {
  $types = array(
    'node' => 'Node',
    'user' => 'User',
    'term' => 'Term',
    'vocabulary' => 'Vocabulary',
  );
  return $types;
}

/**
 * Get URL to an entity.
 *
 * @param $entity_type
 *   Entity type.
 * @param $entity
 *   Entity object.
 */
function uuid_link_entity_url($entity_type, $entity) {
  switch ($entity_type) {
    default:
      $uri = entity_uri($entity_type, $entity);
      if (is_array($uri)) {
        return _uuid_link_entity_url($uri, $entity);
      }
    case 'file':
      return file_create_url($entity->uri);
  }
}

/**
 * Implements hook_filter().
 */
function uuid_link_filter($op, $delta = 0, $format = -1, $text = '', $cache_id = 0) {
  switch ($op) {
    case 'list':
      return uuid_link_filter_info();
    case 'no cache':
      return FALSE;
    case 'description':
      return uuid_link_filter_info($delta);
    case 'prepare':
      return uuid_link_filter_prepare($format, $text, $cache_id);
    case 'process':
      return uuid_link_filter_process($format, $text, $cache_id);
  }
}

/**
 * Returns information about filters.
 *
 * @param int $delta
 *   optional $delta parameter to request info about a particular filter. If
 *   not set, the function will return the list of filter names.
 *
 * @return array|string
 *   An array of defined filter names or the description for the filter $delta
 *   if the parameter is set.
 */
function uuid_link_filter_info($delta = NULL) {
  $filters = array(
    0 => t('UUID Link filter'),
  );
  $filter_info = array(
    0 => t('Converts links added through UUID Link into actual link to content.'),
  );
  if ($delta === NULL) {
    return $filters;
  }
  elseif (isset($filter_info[$delta])) {
    return $filter_info[$delta];
  }
  return NULL;
}

/**
 * Implements hook_filter_tips().
 */
function uuid_link_filter_tips($delta, $format, $long = FALSE) {

  // Since we have only one filter, we can assume $delta is 0.
  if (!$long) {
    $output = uuid_link_filter_info($delta);
  }
  else {
    $output = '<p>' . t('Tokens of the form [uuid-link:xxxx] will be transformed to links to the node with uuid xxxx.');
  }
  return $output;
}

/**
 * Filter prepare callback. Replaces [uuid-link:xxxx] with links.
 */
function uuid_link_filter_prepare($format, $text, $cache_id) {
  $pattern = "@\\[uuid-link:([^:]+):([^:\\]]+)\\]@";
  return preg_replace_callback($pattern, 'uuid_link_replace_callback', $text);
}

/**
 * Replace callback for uuid link tokens.
 */
function uuid_link_replace_callback($matches) {
  $url = NULL;
  $type = $matches[1];
  $uuid = $matches[2];
  if ($type == 'node' && ($nid = uuid_link_get_nid($uuid))) {
    $url = url("node/{$nid}");
  }
  elseif ($type == 'user' && ($uid = uuid_link_get_uid($uuid))) {
    $url = url("user/{$uid}");
  }
  if (empty($url)) {
    $url = '#uuid-link-not-found';
  }
  return $url;
}

/**
 * Returns the nid for a node based on a given uuid.
 *
 * @param string $uuid
 *   The uuid that needs to be mapped.
 */
function uuid_link_get_nid($uuid) {
  static $static_cache = array();
  if (empty($static_cache['uuid'])) {
    $nid = db_result(db_query(db_rewrite_sql("SELECT n.nid\n       FROM {node} AS n\n       INNER JOIN {uuid_node} AS un ON n.nid = un.nid\n       WHERE un.uuid = '%s'"), $uuid));
    $static_cache[$uuid] = $nid;
  }
  return $static_cache[$uuid];
}

/**
 * Returns the uid for a user based on a given uuid.
 *
 * @param string $uuid
 *   The uuid that needs to be mapped.
 */
function uuid_link_get_uid($uuid) {
  static $static_cache = array();
  if (empty($static_cache['uuid'])) {
    $uid = db_result(db_query("SELECT u.uid\n       FROM {users} AS u\n       INNER JOIN {uuid_users} AS uu ON u.uid = uu.uid\n       WHERE uu.uuid = '%s'", $uuid));
    $static_cache[$uuid] = $uid;
  }
  return $static_cache[$uuid];
}

/**
 * Filter process callback.
 */
function uuid_link_filter_process($format, $text, $cache_id) {

  // Tokens are replaced on the prepare operation.
  // Remove tags for links to content that no longer exists. Check the existence
  // of the #uuid-link-not-found before doing this expensive job.
  if (strpos($text, '#uuid-link-not-found') !== FALSE) {
    $dom = filter_dom_load($text);
    $xpath = new DOMXPath($dom);
    foreach ($xpath
      ->query('//a[@href="#uuid-link-not-found"]') as $link) {

      // Move all link tag content to its parent node just before it.
      while ($link
        ->hasChildNodes()) {
        $child = $link
          ->removeChild($link->firstChild);
        $link->parentNode
          ->insertBefore($child, $link);
      }

      // Remove the link tag.
      $link->parentNode
        ->removeChild($link);
    }
    $text = filter_dom_serialize($dom);
  }
  return $text;
}

/**
 * Autocomplete callback for entities.
 */
function uuid_link_autocomplete($type, $string = '') {
  $matches = array();
  if ($type == 'node') {
    $matches = _uuid_link_autocomplete_node($string);
  }
  elseif ($type == 'term') {
    $matches = _uuid_link_autocomplete_term($string);
  }
  elseif ($type == 'vocabulary') {
    $matches = _uuid_link_autocomplete_vocabulary($string);
  }
  elseif ($type == 'user') {
    $matches = _uuid_link_autocomplete_user($string);
  }
  drupal_json($matches);
}

/**
 * Callback for auto completing node links.
 *
 * @param string $string
 *   The partial string to auto complete.
 *
 * @return array
 *   An associative array of uuid => title.
 */
function _uuid_link_autocomplete_node($string) {
  $matches = array();
  $query = "SELECT DISTINCT(uu.uuid), title, language FROM {node} n INNER JOIN {uuid_node} u ON n.nid = u.nid";
  $query .= " WHERE n.title LIKE '%s%%'";

  // Only search for published nodes.
  $query .= " AND n.status = 1";
  $query .= " ORDER BY n.title";
  $result = db_query_range(db_rewrite_sql($query), $string, 0, 15);
  while ($row = db_fetch_object($result)) {

    // If content is language specific show language code.
    if (!empty($row->language)) {
      $matches[$row->uuid] = t('[@language] @label', array(
        '@language' => $row->language,
        '@label' => $row->title,
      ));
    }
    else {
      $matches[$row->uuid] = check_plain($row->title);
    }
  }
  return $matches;
}

/**
 * Callback for auto completing user links.
 *
 * @param string $string
 *   The partial string to auto complete.
 *
 * @return array
 *   An associative array of uuid => username.
 */
function _uuid_link_autocomplete_user($string) {
  $matches = array();
  $query = "SELECT DISTINCT(uu.uuid), name, language FROM {users} u INNER JOIN {uuid_users} uu ON u.uid = uu.uid";
  $query .= " WHERE u.name LIKE '%s%%'";

  // Only search for published nodes.
  $query .= " AND u.status = 1";
  $query .= " ORDER BY u.name";
  $result = db_query_range(db_rewrite_sql($query), $string, 0, 15);
  while ($row = db_fetch_object($result)) {
    $matches[$row->uuid] = check_plain($row->name);
  }
  return $matches;
}
if (!function_exists('filter_dom_load')) {

  /**
   * Backport of filter_dom_load.
   *
   * @param string $text
   *   html text.
   *
   * @return DOMDocument
   *   The text parsed and structured as a DOMDocument.
   */
  function filter_dom_load($text) {
    $dom_document = new DOMDocument();

    // Ignore warnings during HTML soup loading.
    @$dom_document
      ->loadHTML('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>' . $text . '</body></html>');
    return $dom_document;
  }
}
if (!function_exists('filter_dom_serialize')) {

  /**
   * Backports filter_dom_serialize().
   */
  function filter_dom_serialize($dom_document) {
    $body_node = $dom_document
      ->getElementsByTagName('body')
      ->item(0);
    $body_content = '';
    foreach ($body_node
      ->getElementsByTagName('script') as $node) {
      filter_dom_serialize_escape_cdata_element($dom_document, $node);
    }
    foreach ($body_node
      ->getElementsByTagName('style') as $node) {
      filter_dom_serialize_escape_cdata_element($dom_document, $node, '/*', '*/');
    }
    foreach ($body_node->childNodes as $child_node) {
      $body_content .= $dom_document
        ->saveXML($child_node);
    }
    return preg_replace('|<([^> ]*)/>|i', '<$1 />', $body_content);
  }
}
if (!function_exists('filter_dom_serialize_escape_cdata_element')) {

  /**
   * Backports filter_dom_serialize_escape_cdata_element().
   */
  function filter_dom_serialize_escape_cdata_element($dom_document, $dom_element, $comment_start = '//', $comment_end = '') {
    foreach ($dom_element->childNodes as $node) {
      if (get_class($node) == 'DOMCdataSection') {

        // See drupal_get_js().  This code is more or less duplicated there.
        $embed_prefix = "\n<!--{$comment_start}--><![CDATA[{$comment_start} ><!--{$comment_end}\n";
        $embed_suffix = "\n{$comment_start}--><!]]>{$comment_end}\n";

        // Prevent invalid cdata escaping as this would throw a DOM error.
        // This is the same behavior as found in libxml2.
        // Related W3C standard: http://www.w3.org/TR/REC-xml/#dt-cdsection
        // Fix explanation: http://en.wikipedia.org/wiki/CDATA#Nesting
        $data = str_replace(']]>', ']]]]><![CDATA[>', $node->data);
        $fragment = $dom_document
          ->createDocumentFragment();
        $fragment
          ->appendXML($embed_prefix . $data . $embed_suffix);
        $dom_element
          ->appendChild($fragment);
        $dom_element
          ->removeChild($node);
      }
    }
  }
}

Functions

Namesort descending Description
uuid_link_autocomplete Autocomplete callback for entities.
uuid_link_element_info_alter Implements hook_element_info_alter().
uuid_link_entity_info_alter Implements hook_entity_info_alter().
uuid_link_entity_url Get URL to an entity.
uuid_link_filter Implements hook_filter().
uuid_link_filter_info Returns information about filters.
uuid_link_filter_prepare Filter prepare callback. Replaces [uuid-link:xxxx] with links.
uuid_link_filter_process Filter process callback.
uuid_link_filter_tips Implements hook_filter_tips().
uuid_link_form_alter Post render callback. Load javascript files and set module settings.
uuid_link_form_post_build
uuid_link_get_link_type_name Get a list of available link types. In this case, a list of entity types.
uuid_link_get_nid Returns the nid for a node based on a given uuid.
uuid_link_get_uid Returns the uid for a user based on a given uuid.
uuid_link_menu Implements hook_menu().
uuid_link_permission Implements hook_permission().
uuid_link_replace_callback Replace callback for uuid link tokens.
uuid_link_wysiwyg_plugin Implements hook_wysiwyg_plugin().
_uuid_link_autocomplete_node Callback for auto completing node links.
_uuid_link_autocomplete_user Callback for auto completing user links.