You are here

linkit.module in Linkit 7.3

Main file for Linkit module.

File

linkit.module
View source
<?php

/**
 * @file
 * Main file for Linkit module.
 */

/**
 * Define that urls should be constructed as it is (raw).
 */
define('LINKIT_URL_METHOD_RAW', 1);

/**
 * Define that urls should be constructed as it is (raw) with a prepending
 * slash.
 */
define('LINKIT_URL_METHOD_RAW_SLASH', 2);

/**
 * Define that paths should be constructed as an alias.
 */
define('LINKIT_URL_METHOD_ALIAS', 3);

/**
 * Define which query string key Better autocomplete will just.
 */
define('LINKIT_BAC_QUERY_KEY', 's');

/**
 * Define the minimum number of characters to perform server polling.
 */
define('LINKIT_CHAR_LIMIT', 3);

/**
 * Define the time from last key press to poll the server.
 */
define('LINKIT_WAIT', 350);

/**
 * Define the client side timeout for a request to the server.
 */
define('LINKIT_REMOTE_TIMEOUT', 10000);

/**
 * Define the profile type editor.
 */
define('LINKIT_PROFILE_TYPE_EDITOR', 1);

/**
 * Define the profile type field.
 */
define('LINKIT_PROFILE_TYPE_FIELD', 2);

/**
 * Define the profile type menu.
 */
define('LINKIT_PROFILE_TYPE_MENU', 3);

// Include the file with the field functions.
require_once dirname(__FILE__) . '/linkit.field.inc';

/**
 * Implements hook_permission().
 */
function linkit_permission() {
  return array(
    'administer linkit' => array(
      'title' => t('Administer Linkit'),
      'description' => t('Perform administration tasks for Linkit.'),
    ),
  );
}

/**
 * Implements hook_ctools_plugin_directory().
 */
function linkit_ctools_plugin_directory($module, $plugin) {
  if ($module == 'ctools' && !empty($plugin)) {
    return "plugins/" . $plugin;
  }
  if ($module == 'linkit' && !empty($plugin)) {
    return "plugins/" . $plugin;
  }
}

/**
 * Implements hook_ctools_plugin_TYPE() to inform the plugin system that Linkit
 * owns Linkit plugin types.
 */
function linkit_ctools_plugin_type() {
  $plugins['linkit_search'] = array(
    'child plugins' => TRUE,
    'classes' => array(
      'handler',
    ),
  );
  $plugins['linkit_insert'] = array(
    'process' => array(
      'function' => 'linkit_insert_plugin_process',
    ),
  );
  $plugins['linkit_attribute'] = array();
  return $plugins;
}

/**
 * Provide defaults for insert plugins.
 */
function linkit_insert_plugin_process(&$plugin, $info) {

  // The plugin javascript can be just a filename or a full path,
  // in case it's just a filename, add the plugin path.
  if (!file_exists($plugin['javascript'])) {
    $plugin['javascript'] = $plugin['path'] . '/' . $plugin['javascript'];
  }
}

/**
 * Load a single Linkit profile.
 *
 * @param $name
 *   A string with the name of the profile to load.
 *
 * @return
 *   A LinkitProfile object or FALSE if no profile is found.
 */
function linkit_profile_load($name) {
  ctools_include('export');
  $result = ctools_export_load_object('linkit_profiles', 'names', array(
    $name,
  ));
  if (isset($result[$name])) {
    return $result[$name];
  }
  return FALSE;
}

/**
 * Load all Linkit profiles.
 *
 * @return
 *   An array with LinkitProfile objects.
 */
function linkit_profile_load_all() {
  ctools_include('export');
  $profiles = ctools_export_load_object('linkit_profiles');
  uasort($profiles, 'linkit_profile_sort_weight');
  return $profiles;
}

/**
 * Sort profiles by their weight.
 */
function linkit_profile_sort_weight($a, $b) {
  $a_weight = isset($a->weight) ? $a->weight : 0;
  $b_weight = isset($b->weight) ? $b->weight : 0;
  if ($a_weight == $b_weight) {

    // If the weight is the same, sort by name.
    return strcmp($a->name, $b->name);
  }
  return $a_weight < $b_weight ? -1 : 1;
}

/**
 * Load all Linkit profiles that is for fields.
 *
 * @return
 *   An array with LinkitProfile objects.
 */
function linkit_profile_field_load_all() {
  $profiles = linkit_profile_load_all();
  foreach ($profiles as &$profile) {
    if ($profile->profile_type != LINKIT_PROFILE_TYPE_FIELD) {
      $profile = FALSE;
    }
  }
  return array_filter($profiles);
}

/**
 * Load all Linkit profiles that is for editors.
 *
 * @return
 *   An array with LinkitProfile objects.
 */
function linkit_profile_editor_load_all() {
  $profiles = linkit_profile_load_all();
  foreach ($profiles as &$profile) {
    if ($profile->profile_type != LINKIT_PROFILE_TYPE_EDITOR) {
      $profile = FALSE;
    }
  }
  return array_filter($profiles);
}

/**
 * Load all Linkit profiles that is for menu links.
 *
 * @return
 *   An array with LinkitProfile objects.
 */
function linkit_profile_menu_load_all() {
  $profiles = linkit_profile_load_all();
  foreach ($profiles as &$profile) {
    if ($profile->profile_type != LINKIT_PROFILE_TYPE_MENU) {
      $profile = FALSE;
    }
  }
  return array_filter($profiles);
}

/**
 * Temporary saves the active profile. Active means that the user is working
 * with the profile in the dialog.
 *
 * @param string LinkitProfile $profile
 *   A LinkitProfile object.
 */
function linkit_set_active_profile(LinkitProfile $profile) {
  $active_profile =& drupal_static('linkit_active_profile');
  $active_profile = $profile;
}

/**
 * Get the currently active profile.
 *
 * @return
 *   A LinkitProfile object if there is one set.
 *
 * @see linkit_set_active_profile()
 */
function linkit_get_active_profile() {
  return drupal_static('linkit_active_profile');
}

/**
 * Fetch metadata for all Linkit search plugins.
 *
 * @return
 *   An array of arrays with information about all available Linkit search
 *   plugins.
 */
function linkit_search_plugin_load_all() {
  ctools_include('plugins');
  $plugins = ctools_get_plugins('linkit', 'linkit_search');

  // If you alter the plugin handler, be sure the new handler is registerd or
  // you include it in some other way.
  drupal_alter('linkit_search_plugins', $plugins);
  return $plugins;
}

/**
 * Function used by uasort to sort plugins by weight.
 */
function linkit_sort_plugins_by_weight($a, $b) {
  return $a["weight"] >= $b["weight"];
}

/**
 * Fetch metadata for one Linkit search plugin by the given name.
 *
 * @param $plugin_name
 *   A string with the name of the plugin to load.
 *
 * @return
 *   An array with information about the search plugin.
 */
function linkit_search_plugin_load($plugin_name) {
  ctools_include('plugins');
  $plugin = ctools_get_plugins('linkit', 'linkit_search', $plugin_name);

  // If you alter the plugin handler, be sure the new handler is registerd or
  // you include it in some other way.
  drupal_alter('linkit_search_plugin', $plugin);
  return $plugin;
}

/**
 * Fetch metadata for all Linkit insert plugins.
 *
 * @return
 *   An array of arrays with information about all available insert plugins.
 */
function linkit_insert_plugin_load_all() {
  ctools_include('plugins');

  // Load all insert plugins.
  $plugins = ctools_get_plugins('linkit', 'linkit_insert');
  return $plugins;
}

/**
 * Fetch metadata for one Linkit insert plugin by the given name.
 *
 * @param $plugin_name
 *   A string with the name of the plugin to load.
 *
 * @return
 *   An array with information about the insert plugin.
 */
function linkit_insert_plugin_load($plugin_name) {
  ctools_include('plugins');

  // Load all insert plugins.
  $plugins = ctools_get_plugins('linkit', 'linkit_insert', $plugin_name);
  return $plugins;
}

/**
 * Fetch metadata for all Linkit attribute plugins.
 *
 * @return
 *   An array of arrays with information about all available attribute plugins.
 */
function linkit_attribute_plugin_load_all() {
  ctools_include('plugins');

  // Load all attribute plugins.
  $attributes = ctools_get_plugins('linkit', 'linkit_attribute');
  return $attributes;
}
function linkit_attribute_plugin_load($plugin_name) {
  ctools_include('plugins');

  // Load all insert plugins.
  $plugins = ctools_get_plugins('linkit', 'linkit_attribute', $plugin_name);
  return $plugins;
}

/**
 * Implements hook_theme().
 */
function linkit_theme($existing, $type, $theme, $path) {
  return array(
    'linkit_plugin_form_table' => array(
      'render element' => 'form',
      'file' => 'includes/theme.inc',
    ),
  );
}

/**
 * Implements hook_menu_alter().
 */
function linkit_menu_alter(&$items) {

  // Override the default titles that ctools export_ui sets.
  // This way there is less code compared to define this in the plugin array.
  $items['admin/config/content/linkit/add']['title'] = 'Add new profile';
  $items['admin/config/content/linkit/import']['title'] = 'Import profiles';

  // Make tabs instead of action links.
  $items['admin/config/content/linkit/add']['type'] = MENU_LOCAL_TASK;
  $items['admin/config/content/linkit/import']['type'] = MENU_LOCAL_TASK;
}

/**
 * Implements hook_module_implements_alter().
 *
 * @TODO: Document why we are doing this.
 * @see linkit_element_info_alter()
 */
function linkit_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'element_info_alter') {
    $group = $implementations['linkit'];
    unset($implementations['linkit']);
    $implementations['linkit'] = $group;
  }
}

/**
 * Implements hook_element_info_alter().
 */
function linkit_element_info_alter(&$types) {

  // Append a after_build function for the field integration.
  foreach (linkit_get_allowed_field_elements() as $element) {
    if (isset($types[$element])) {
      $types[$element]['#after_build'][] = 'linkit_field_element_after_build';
    }
  }

  // Used when using ckeditor module.
  if (isset($types['text_format']['#pre_render']) && is_array($types['text_format']['#pre_render'])) {
    if (in_array('ckeditor_pre_render_text_format', $types['text_format']['#pre_render'])) {
      $types['text_format']['#pre_render'][] = 'linkit_pre_render_editor_element';
    }
  }

  // Used when using wysiwyg module.
  if (isset($types['text_format']['#pre_render']) && is_array($types['text_format']['#pre_render'])) {
    if (in_array('wysiwyg_pre_render_text_format', $types['text_format']['#pre_render'])) {
      $types['text_format']['#process'][] = 'linkit_pre_render_editor_element';
    }
  }
}

/**
 * Implements hook_library().
 */
function linkit_library() {
  $path = drupal_get_path('module', 'linkit');
  $common = array(
    'website' => 'http://drupal.org/project/linkit',
    'version' => '7.3',
  );

  // Linkit base
  $libraries['base'] = array(
    'title' => 'Linkit base',
    'js' => array(
      $path . '/js/linkit.js' => array(
        'group' => JS_DEFAULT,
        'weight' => -1,
      ),
      // Add global settings for Linkit.
      array(
        'type' => 'setting',
        // ___profile___ is just a placeholder.
        'data' => array(
          'linkit' => array(
            'autocompletePath' => url('linkit/autocomplete/___profile___', array(
              'query' => array(
                LINKIT_BAC_QUERY_KEY => '',
              ),
              'absolute' => TRUE,
            )),
            'dashboardPath' => url('linkit/dashboard/'),
            'currentInstance' => new stdClass(),
          ),
        ),
      ),
    ),
    'dependencies' => array(
      array(
        'system',
        'ui.dialog',
      ),
      array(
        'system',
        'drupal.ajax',
      ),
      array(
        'linkit',
        'dashboard',
      ),
      array(
        'linkit',
        'bac',
      ),
    ),
  );

  // Linkit dashboard.
  $libraries['dashboard'] = array(
    'title' => 'Linkit dashboard',
    'js' => array(
      $path . '/js/linkit.dashboard.js' => array(
        'group' => JS_DEFAULT,
        'weight' => 0,
      ),
    ),
  );

  // Linkit field ui script.
  $libraries['field'] = array(
    'title' => 'Linkit Field UI',
    'js' => array(
      $path . '/js/linkit.field.js' => array(
        'group' => JS_DEFAULT,
      ),
    ),
    'dependencies' => array(
      array(
        'linkit',
        'base',
      ),
    ),
  );

  // Linkit ckeditor dialog script.
  $libraries['ckeditor'] = array(
    'title' => 'Linkit CKeditor',
    'js' => array(
      $path . '/editors/ckeditor/linkitDialog.js' => array(
        'group' => JS_DEFAULT,
      ),
    ),
    'dependencies' => array(
      array(
        'linkit',
        'base',
      ),
    ),
  );

  // Linkit tinymce dialog script.
  $libraries['tinymce'] = array(
    'title' => 'Linkit TinyMCE',
    'js' => array(
      $path . '/editors/tinymce/linkitDialog.js' => array(
        'group' => JS_DEFAULT,
      ),
    ),
    'dependencies' => array(
      array(
        'linkit',
        'base',
      ),
    ),
  );
  foreach ($libraries as &$library) {
    $library += $common;
  }

  // Linkit BAC
  $libraries['bac'] = array(
    'website' => 'https://github.com/betamos/Better-Autocomplete',
    'version' => '1.0',
    'title' => 'Better autocomplete',
    'js' => array(
      $path . '/better-autocomplete/jquery.better-autocomplete.js' => array(
        'group' => JS_LIBRARY,
      ),
    ),
    'css' => array(
      $path . '/better-autocomplete/better-autocomplete.css' => array(
        'group' => CSS_DEFAULT,
        'preprocess' => FALSE,
      ),
    ),
  );
  return $libraries;
}

/**
 * Implements hook_menu().
 */
function linkit_menu() {
  $items = array();

  // This is the Linkit dashboard menu callback.
  $items['linkit/dashboard/%linkit_profile'] = array(
    'title' => 'Linkit',
    'description' => 'Dashboard',
    'delivery callback' => 'ajax_deliver',
    'page callback' => 'linkit_dashboard_page',
    'page arguments' => array(
      2,
    ),
    'access callback' => TRUE,
    'theme callback' => 'ajax_base_page_theme',
    'type' => MENU_CALLBACK,
    'file path' => 'includes',
    'file' => 'form.inc',
  );

  // The autocomplete callback, the search_string is found in the $_GET array
  // so dont pass that to the page callback.
  $items['linkit/autocomplete/%linkit_profile'] = array(
    'title' => 'Linkit autocomplete response function',
    'page callback' => 'linkit_autocomplete',
    'page arguments' => array(
      2,
    ),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Creates the dialog dashboard.
 *
 * We don't call drupal_get_form() in the menu callback as we don't want to
 * return the rendered form, we just want to print it as it is.
 *
 * @param $profile
 *   A LinkitProfile object.
 */
function linkit_dashboard_page(LinkitProfile $profile) {

  // Set the active Linkit profile.
  linkit_set_active_profile($profile);

  // The dashboard isn't really a form.
  // See comment in linkit_dashboard_form() for more info.
  $form = drupal_get_form('linkit_dashboard_form');

  //$change_profile = drupal_get_form('linkit_dashboad_profile_change_form');
  return array(
    '#type' => 'ajax',
    '#commands' => array(
      ajax_command_html('#linkit-modal', drupal_render($form)),
      ajax_command_prepend('#linkit-modal', theme('status_messages')),
    ),
  );
}

/**
 * Ajax Form callback;
 *
 * Returns the Linkit "form" when changing profile.
 */
function linkit_change_profile($form, $form_state) {
  return $form['linkit_container'];
}

/**
 * Create the dashboard page.
 */
function linkit_dashboard_form($form, &$form_state) {

  // The use of a form here is insignificant as we are not submitting any data.
  // A form here just cause trouble because it can be submitted and will do new
  // request to the action of the form.
  // In normal cases this isn't really a problem as the javascript is preventing
  // the form to be submitted, but in case of a javascript error, this event
  // can't be prevented.
  // We still have to use the FAPI to build this "form", as we want proper
  // classes and ids on the elements and take advantage of all the good parts of
  // the form generation.
  // So, just make sure the form tags dont get rendered.

  //$form['#theme_wrappers'] = array();

  // Get the active Linkit profile.
  if (!empty($form_state['values']['profile'])) {
    linkit_set_active_profile(linkit_profile_load($form_state['values']['profile']));
  }
  $active_profile = linkit_get_active_profile();
  $profiles = linkit_profile_editor_load_all();
  $_profiles = array();
  foreach ($profiles as $profile) {
    $_profiles[$profile->name] = check_plain($profile->admin_title);
  }

  // Attach css to the form.
  $form['#attached']['css'][drupal_get_path('module', 'linkit') . '/css/linkit.css'] = array(
    'preprocess' => FALSE,
  );

  // Attach js to the form.
  $form['#attached']['js'] = array(
    drupal_get_path('module', 'linkit') . '/js/linkit.dashboard.js',
  );
  if ($active_profile->profile_type == LINKIT_PROFILE_TYPE_EDITOR) {
    $form['profile'] = array(
      '#type' => 'radios',
      '#title' => t('Select profile to use'),
      '#default_value' => $active_profile->name,
      '#weight' => -100,
      '#options' => $_profiles,
      '#ajax' => array(
        'callback' => 'linkit_change_profile',
        'wrapper' => 'linkit-dashboard-ajax-wrapper',
        'method' => 'replaceWith',
        'effect' => 'fade',
        'event' => 'click',
      ),
      '#prefix' => '<div id="linkit-profile-changer">',
      '#suffix' => '</div>',
    );
    foreach ($profiles as $profile) {
      $form['profile'] += array(
        $profile->name => array(
          '#description' => check_markup($profile->admin_description),
        ),
      );
    }
  }
  $form['linkit_container'] = array(
    '#type' => 'container',
    '#weight' => 100,
    '#prefix' => '<div id="linkit-dashboard-ajax-wrapper">',
    '#suffix' => '</div>',
    '#attached' => array(
      'js' => array(
        array(
          'data' => array(
            'linkit' => array(
              'currentInstance' => array(
                'profile' => $active_profile->name,
                'autocomplete' => array_filter($active_profile->data['autocomplete']),
              ),
            ),
          ),
          'type' => 'setting',
        ),
      ),
    ),
  );
  $form['linkit_container']['linkit_search'] = array(
    '#type' => 'textfield',
    '#title' => t('Search for content.'),
    '#description' => t('Start typing to find content or paste a URL.'),
    '#maxlength' => 255,
    '#size' => 60,
    '#default_value' => '',
    '#weight' => -10,
    '#attributes' => array(
      'class' => array(
        'linkit-search-element',
      ),
    ),
    '#attached' => array(
      'library' => array(
        array(
          'linkit',
          'bac',
        ),
      ),
    ),
  );
  $form['linkit_container']['linkit_path'] = array(
    '#type' => 'textfield',
    '#title' => t('Link URL'),
    '#description' => t('This will be populated by the search, or you can fill it in yourself.'),
    '#required' => TRUE,
    '#maxlength' => NULL,
    '#size' => 60,
    '#default_value' => '',
    '#weight' => -1,
    '#attributes' => array(
      'class' => array(
        'linkit-path-element',
      ),
    ),
  );

  // If we have enabled attributes, lets put them inside a fieldset.
  if ($active_profile
    ->getEnabledAttributePlugins()) {

    // Create the container fieldset.
    $form['linkit_container']['linkit_attributes'] = array(
      '#type' => 'fieldset',
      '#title' => t('Options'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#weight' => 10,
      '#attributes' => array(
        'class' => array(
          'linkit-attributes',
        ),
      ),
    );

    // Append the attributes info the fieldset.
    foreach ($active_profile
      ->getEnabledAttributePlugins() as $name => $attribute) {

      // Add a class to all items.
      $attribute['#attributes']['class'][] = 'linkit-attribute-' . $name;

      // Add 'linkit_' prefix to ensure that is unique.
      $form['linkit_container']['linkit_attributes']['linkit_' . $name] = $attribute;
    }
  }
  $form['linkit_container']['linkit_insert'] = array(
    '#type' => 'button',
    '#value' => t('Insert link'),
    '#suffix' => '<a id="linkit-cancel" href="#">' . t('Cancel') . '</a>',
    '#weight' => 100,
    '#attributes' => array(
      'class' => array(
        'linkit-insert',
      ),
    ),
  );
  return $form;
}

/**
 * Autocomplete callback function.
 *
 * @param object $profile
 *   A LinkitProfile object.
 */
function linkit_autocomplete(LinkitProfile $profile) {

  // Set the active Linkit profile.
  linkit_set_active_profile($profile);

  // This is not sanitized until it is used within output.
  $search_string = $_GET[LINKIT_BAC_QUERY_KEY];
  $results = array();

  // Special for link to frontpage.
  if (strpos($search_string, 'front') !== FALSE) {
    $results[] = array(
      'title' => t('Frontpage'),
      'description' => 'The frontpage for this site.',
      'path' => url('<front>'),
      'group' => t('System'),
    );
  }

  // Let the Linkit search plugins do their job.
  $results = array_merge($results, linkit_autocomplete_search_plugins($search_string));

  // If there is results from the Linkit search plugins, don't care about
  // searching for absolute URL's results.
  if (empty($results)) {

    // Try to parse the string as an URL.
    // We will not use the drupal wrapper function drupal_pasre_url() as that
    // function should only be used for URL's that have been generated by the
    // system, and we can't be sure that this is the case here.
    // Check for an e-mail address then return an e-mail result and create a
    // mail-to link if appropriate.
    if (filter_var($search_string, FILTER_VALIDATE_EMAIL)) {
      $results = array(
        array(
          'title' => t('E-mail @email', array(
            '@email' => $search_string,
          )),
          'path' => 'mailto:' . check_plain($search_string),
          'description' => t('Open your mail client ready to e-mail @email', array(
            '@email' => $search_string,
          )),
          'addClass' => 'status-notice',
        ),
      );
    }
    else {
      $parts = parse_url(trim($search_string, '/'));

      // This seems to be an absolute URL.
      if (isset($parts['scheme']) || isset($parts['host'])) {
        $results = array_merge($results, linkit_autocomplete_absolute_url($search_string, $parts));
      }
    }
  }

  // If there is still no results, return a "no results" array.
  if (empty($results)) {
    $results = array(
      array(
        'title' => t('No results'),
        'addClass' => 'status-notice',
        'disabled' => TRUE,
      ),
    );
  }
  print drupal_json_output($results);
  drupal_exit();
}

/**
 * Perform autocomplete search with the Linkit search plugins.
 *
 * @param $search_string
 *   The search string.
 *
 * @return
 *   An array with the results objects, or an empty array if no results.
 */
function linkit_autocomplete_search_plugins($search_string) {
  $matches = array();
  $profile = linkit_get_active_profile();

  // Get matches from all search plugins.
  foreach ($profile
    ->getEnabledsearchPlugins() as $plugin_name => $plugin) {
    $matches = array_merge($matches, $plugin
      ->fetchResults($search_string));
  }
  return $matches;
}

/**
 * Retrieve relevant information about a URL. Specifically this function is
 * usable for internal (absolute) URL:s, but it also works for external URL:s.
 *
 * @param $url
 *   The search string (URL) that should be scanned.
 *
 * @param $parts
 *   An array of URL parts from parse_url().
 *
 * @return
 *   An associative array containing:
 *   - url: The same as the argument $url, untouched.
 *   - target: Either "internal" or "external".
 *   - requested_path: If internal, the path requested relative to Drupal root.
 *     The only exception is when frontpage is referred directly, then it will
 *     be whatever the frontpage is set to.
 *   - system_path: If internal and the path is valid, the Drupal system path,
 *     e.g. "node/23".
 *   - query_fragment: If internal, the query and fragment of the url.
 *     Typically it is not needed for searching and is just reappended back
 *     when processing of the path is done. It could e.g. look like
 *     "?foo=bar#anchor".
 */
function linkit_parse_url($url, $parts) {
  global $base_url;

  // Make a new array, this will hold the components from parse_url() and our
  // own "Linkit" components.
  $path_info = array();

  // Append the original components from parse_url() to our array.
  $path_info += $parts;

  // Save the whole URL.
  $path_info['url'] = $url;
  if (!isset($path_info['query'])) {
    $path_info['query'] = '';
  }

  // Convert the query string to an array as Drupal can only handle querys as
  // arrays.
  // @see http://api.drupal.org/drupal_http_build_query
  parse_str($path_info['query'], $path_info['query']);

  // The 'q' parameter contains the path of the current page if clean URLs are
  // disabled. It overrides the 'path' of the URL when present, even if clean
  // URLs are enabled, due to how Apache rewriting rules work.
  if (isset($path_info['query']['q'])) {
    $path_info['path'] = $path_info['query']['q'];
    unset($path_info['query']['q']);
  }

  // Load all local stream wrappers. The $path_info['scheme'] will be tested
  // against this later to ensure it is local.
  $local_stream_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);

  // Internal URL.
  // We can not use the url_is_external() as it treats all absolute links as
  // external and treats all stream wrappers as internal.
  $local_hosts = array(
    $base_url,
  );

  // Give other modules a chance to add/remove/alter the local hosts.
  drupal_alter('linkit_local_hosts', $local_hosts);
  $local_url = isset($path_info['scheme']) && isset($path_info['host']) && in_array(trim($path_info['scheme'] . '://' . $path_info['host'] . base_path(), '/'), $local_hosts);
  $local_stream_wrapper = isset($local_stream_wrappers[$path_info['scheme']]);

  //@TODO: maybe file_valid_uri() ?
  if ($local_url || $local_stream_wrapper) {

    // Set target as internal.
    $path_info['target'] = 'internal';

    // If this is seems to be a valid local stream wrapper string, force the
    // $path_info['path'] to be set to the file_url.
    if ($local_stream_wrapper) {
      $path_info = array_merge($path_info, parse_url(file_create_url($path_info['url'])));
    }

    // Trim the path from slashes.
    $path_info['path'] = trim($path_info['path'], '/');

    // If we have an empty path, and an internal target, we can assume that the
    // URL should go the the frontpage.
    if (empty($path_info['path'])) {
      $path_info['frontpage'] = TRUE;
      $path_info['path'] = variable_get('site_frontpage', 'node');
    }

    // Try converting the path to an internal Drupal path.
    $internal_url = drupal_get_normal_path($path_info['path']);

    // Add the "real" system path (not the alias) if the current user have
    // access to the URL.
    $path_info['system_path'] = drupal_valid_path($internal_url) ? $internal_url : FALSE;
    $menu_item = menu_get_item($path_info['system_path']);
    if ($menu_item) {
      $path_info['menu']['path'] = $path_info['system_path'];
      $path_info['menu']['description'] = check_plain($menu_item['description']);
      $path_info['menu']['title'] = check_plain($menu_item['title']);
    }

    // If we have a valid stream wrapper URL, find out the internal url.
    if ($local_stream_wrapper) {
      $path_info['system_path'] = $path_info['path'];
      $path_info['menu']['path'] = $path_info['path'];
      $path_info['menu']['description'] = 'This funciton is not fully integrated yet.';
      $path_info['menu']['title'] = 'This funciton is not fully integrated yet.';
    }
  }
  else {

    // Set target as external.
    $path_info['target'] = 'external';
  }
  return $path_info;
}

/**
 * Retrieve the result object from an absolute URL. Both internal and external
 * paths work.
 *
 * @param $url
 *   The search string which seems to be an URL.
 *
 * @param $parts
 *   An array of URL parts from parse_url().
 *
 * @return
 *   A result object. This is an associative array which consists of:
 *   - title: The title of the result.
 *   - description: The description of the result (may contain HTML).
 *   - path: The target path which will be inserted as the href in the link.
 *   - addClass: A CSS class that will be added to the DOM result item.
 */
function linkit_autocomplete_absolute_url($url, $parts) {
  $result = array();

  // Retrieve relevant information about the search string and if its a URL.
  $path_info = linkit_parse_url($url, $parts);

  // Set default options to pass with the url() function if the URL is internal.
  $url_options = array();

  // Add the URL frament.
  $url_options['fragment'] = isset($path_info['fragment']) ? $path_info['fragment'] : '';

  // Add the URL query.
  $url_options['query'] = isset($path_info['query']) ? $path_info['query'] : '';

  // The URL is registerd by Drupal (Internal).
  if (isset($path_info['menu']) && $path_info['system_path'] !== FALSE) {
    $result = array(
      'path' => linkit_get_insert_plugin_processed_path(linkit_get_active_profile(), $path_info['menu']['path'], $url_options),
      'title' => $path_info['menu']['title'] ? check_plain($path_info['menu']['title']) : check_plain($path_info['menu']['path']),
      'description' => check_plain($path_info['menu']['description']) . ' ' . t('This is an internal path.'),
      'addClass' => 'status-ok',
    );
  }
  elseif ($path_info['target'] == 'internal') {
    $result = array(
      'path' => linkit_get_insert_plugin_processed_path(linkit_get_active_profile(), $path_info['path'], $url_options),
      'title' => t('Page not found'),
      'description' => t('This page does not exist or you do not have access to it.'),
      'addClass' => 'status-warning',
    );
  }
  elseif ($path_info['target'] == 'external') {
    $result = array(
      'title' => t('No information available'),
      'description' => t('This is an external URL, but we don\'t know where it leads.'),
      'path' => $path_info['url'],
      'addClass' => 'status-notice',
    );
  }

  // Return the results in an array as BAC will need to have it that way.
  return array(
    $result,
  );
}

/**
 * Implements hook_image_default_styles().
 *
 * @return
 *   An array of image styles, keyed by the style name.
 */
function linkit_image_default_styles() {
  $styles = array();
  $styles['linkit_thumb'] = array(
    'effects' => array(
      array(
        'name' => 'image_scale',
        'data' => array(
          'width' => 50,
          'height' => 50,
          'upscale' => 0,
        ),
        'weight' => 0,
      ),
    ),
  );
  return $styles;
}

/**
 * Implements hook_wysiwyg_plugin().
 */
function linkit_wysiwyg_plugin($editor, $version) {
  $plugin = array();
  $plugin['linkit'] = array(
    'path' => drupal_get_path('module', 'linkit') . '/editors/' . $editor,
    'buttons' => array(
      'linkit' => t('Linkit'),
    ),
    'url' => 'http://drupal.org/project/linkit',
    'load' => TRUE,
  );

  // TinyMCE needs to know the filename.
  if ($editor == 'tinymce') {
    $plugin['linkit']['filename'] = 'editor_plugin.js';
  }

  // Add the linkit library for the editor.
  drupal_add_library('linkit', $editor);
  return $plugin;
}

/**
 * Implements hook_wysiwyg_editor_settings_alter().
 */
function linkit_wysiwyg_editor_settings_alter(&$settings, $context) {
  if ($context['profile']->editor == 'ckeditor') {
    $button_exists = isset($context['profile']->settings['buttons']['linkit']);
    if (!empty($context['profile']->settings['default_toolbar_grouping']) && $button_exists) {
      foreach ($settings['toolbar'] as &$group) {
        if ($group['name'] == 'links') {
          array_unshift($group['items'], 'linkit');
        }
        if ($group['name'] == 'other') {
          if (in_array('linkit', $group['items'])) {
            unset($group['items'][array_search('linkit', $group['items'])]);

            // Regenerate the keys.
            $group['items'] = array_values($group['items']);
          }
        }
      }
    }
  }
}

/**
 * Implements hook_ckeditor_plugin().
 */
function linkit_ckeditor_plugin() {
  return array(
    'linkit' => array(
      // Name of the plugin used to write it
      'name' => 'linkit',
      // Description of plugin - it would appear in plugins managment of profile settings
      'desc' => t('Support for Linkit module'),
      // The full path to the CKEditor plugin directory, with trailing slash.
      'path' => drupal_get_path('module', 'linkit') . '/editors/ckeditor/',
      // Buttons to show up in the toolbar config area.
      'buttons' => array(
        'linkit' => array(
          'label' => 'Linkit',
          'icon' => 'icons/linkit.png',
        ),
      ),
    ),
  );
}

/**
 * Add Linkit settings to pages where we have an editor element.
 */
function linkit_pre_render_editor_element($element) {
  global $user;

  // Attach the linkit_base library.
  $element['#attached']['library'][] = array(
    'linkit',
    'base',
  );
  $all_formats = array_keys(filter_formats($user));
  $all_profiles = linkit_profile_editor_load_all();
  $profiles_formats = array();
  $use_imce = FALSE;

  // Initialize the list of profiles formats.
  foreach ($all_formats as $format) {
    $profiles_formats[$format] = array(
      'profile' => NULL,
      'enabled_profiles' => array(),
    );
  }

  // First we need to figure out which formats that the current user can access
  // have active Linkit profiles for them.
  foreach ($all_profiles as $profile) {
    if (empty($profile->disabled) && is_array($profile->data['text_formats'])) {
      foreach (array_filter($profile->data['text_formats']) as $text_format) {
        if (in_array($text_format, $all_formats)) {
          if (empty($profiles_formats[$text_format]['profile'])) {
            $profiles_formats[$text_format]['profile'] = $profile->name;
          }
          $profiles_formats[$text_format]['enabled_profiles'][$profile->name] = TRUE;
          if (isset($profile->data['imce']) && $profile->data['imce'] == 1) {
            $use_imce = TRUE;
          }
        }
      }
    }
  }
  if (!empty($profiles_formats)) {
    $field_js = array(
      'data' => array(
        'linkit' => array(
          'formats' => $profiles_formats,
        ),
      ),
      'type' => 'setting',
    );

    // Configure Linkit to have an IMCE selector.
    if (module_invoke('imce', 'access') && $use_imce) {
      $field_js['data']['linkit']['IMCEurl'] = url('imce', array(
        'query' => array(
          'app' => 'Linkit|sendto@Drupal.linkit.IMCECallback',
          'absolute' => TRUE,
        ),
      ));

      // We will only serv public files with IMCE.
      $field_js['data']['linkit']['publicFilesDirectory'] = variable_get('file_public_path', conf_path() . '/files');
    }
    $element['#attached']['js'][] = $field_js;
    if (isset($element['#format']) && isset($element['#pre_render'])) {
      if (in_array('ckeditor_pre_render_text_format', $element['#pre_render'])) {
        foreach ($profiles_formats as $format => $profile) {
          if ($format == $element['#format']) {

            // Attach the linkit_ckeditor library.
            $element['#attached']['library'][] = array(
              'linkit',
              'ckeditor',
            );
            break;
          }
        }
      }
    }
  }
  return $element;
}

/**
 * Load the first profile assigned to a text format.
 *
 * @param $format
 *   A string with the format machine name.
 *
 * @return
 *   A LinkitProfile object or FALSE if no profile is found.
 */
function linkit_profile_load_by_format($format) {
  $profiles = linkit_profile_editor_load_all();
  foreach ($profiles as $profile) {
    if (is_array($profile->data['text_formats']) && in_array($format, array_filter($profile->data['text_formats']))) {
      return $profile;
    }
  }
  return FALSE;
}

/**
 * Get profile type.
 */
function linkit_get_profile_type($type) {
  switch ($type) {
    case LINKIT_PROFILE_TYPE_EDITOR:
      return t('Editor');
    case LINKIT_PROFILE_TYPE_FIELD:
      return t('Field');
    case LINKIT_PROFILE_TYPE_MENU:
      return t('Menu');
    default:
      return t('Can not find the profile type');
  }
}

/**
 * Gets the path processed by the inputfilter choice.
 *
 * @param LinkitProfile $profile
 *    The Linkit profile to use.
 *
 * @param $uri
 *    The uri to act on.
 *
 * @param $options
 *   An array of options to the URL function if its used.
 *
 * @return The processed uri.
 */
function linkit_get_insert_plugin_processed_path(LinkitProfile $profile, $uri, $options = array()) {
  switch ($profile->data['insert_plugin']['url_method']) {
    case LINKIT_URL_METHOD_RAW:
      $path = $uri;
      break;
    case LINKIT_URL_METHOD_RAW_SLASH:
      $options['alias'] = TRUE;
      $path = url($uri, $options);
      break;
    case LINKIT_URL_METHOD_ALIAS:
      $path = url($uri, $options);
      break;
  }
  return $path;
}

/**
 * Implements hook_form_FORM_ID_alter() for "menu_edit_item".
 */
function linkit_form_menu_edit_item_alter(&$form, &$form_state) {

  // If a 'menu' profile type exists, use it to add Linkit to menu links.
  $profiles = linkit_profile_menu_load_all();
  foreach ($profiles as $profile) {
    $form['link_path']['#linkit'] = array(
      'profile' => $profile->name,
      'button_text' => t($profile->data['button_text']),
    );
    break;
  }
}

Functions

Namesort descending Description
linkit_attribute_plugin_load
linkit_attribute_plugin_load_all Fetch metadata for all Linkit attribute plugins.
linkit_autocomplete Autocomplete callback function.
linkit_autocomplete_absolute_url Retrieve the result object from an absolute URL. Both internal and external paths work.
linkit_autocomplete_search_plugins Perform autocomplete search with the Linkit search plugins.
linkit_change_profile Ajax Form callback;
linkit_ckeditor_plugin Implements hook_ckeditor_plugin().
linkit_ctools_plugin_directory Implements hook_ctools_plugin_directory().
linkit_ctools_plugin_type Implements hook_ctools_plugin_TYPE() to inform the plugin system that Linkit owns Linkit plugin types.
linkit_dashboard_form Create the dashboard page.
linkit_dashboard_page Creates the dialog dashboard.
linkit_element_info_alter Implements hook_element_info_alter().
linkit_form_menu_edit_item_alter Implements hook_form_FORM_ID_alter() for "menu_edit_item".
linkit_get_active_profile Get the currently active profile.
linkit_get_insert_plugin_processed_path Gets the path processed by the inputfilter choice.
linkit_get_profile_type Get profile type.
linkit_image_default_styles Implements hook_image_default_styles().
linkit_insert_plugin_load Fetch metadata for one Linkit insert plugin by the given name.
linkit_insert_plugin_load_all Fetch metadata for all Linkit insert plugins.
linkit_insert_plugin_process Provide defaults for insert plugins.
linkit_library Implements hook_library().
linkit_menu Implements hook_menu().
linkit_menu_alter Implements hook_menu_alter().
linkit_module_implements_alter Implements hook_module_implements_alter().
linkit_parse_url Retrieve relevant information about a URL. Specifically this function is usable for internal (absolute) URL:s, but it also works for external URL:s.
linkit_permission Implements hook_permission().
linkit_pre_render_editor_element Add Linkit settings to pages where we have an editor element.
linkit_profile_editor_load_all Load all Linkit profiles that is for editors.
linkit_profile_field_load_all Load all Linkit profiles that is for fields.
linkit_profile_load Load a single Linkit profile.
linkit_profile_load_all Load all Linkit profiles.
linkit_profile_load_by_format Load the first profile assigned to a text format.
linkit_profile_menu_load_all Load all Linkit profiles that is for menu links.
linkit_profile_sort_weight Sort profiles by their weight.
linkit_search_plugin_load Fetch metadata for one Linkit search plugin by the given name.
linkit_search_plugin_load_all Fetch metadata for all Linkit search plugins.
linkit_set_active_profile Temporary saves the active profile. Active means that the user is working with the profile in the dialog.
linkit_sort_plugins_by_weight Function used by uasort to sort plugins by weight.
linkit_theme Implements hook_theme().
linkit_wysiwyg_editor_settings_alter Implements hook_wysiwyg_editor_settings_alter().
linkit_wysiwyg_plugin Implements hook_wysiwyg_plugin().

Constants

Namesort descending Description
LINKIT_BAC_QUERY_KEY Define which query string key Better autocomplete will just.
LINKIT_CHAR_LIMIT Define the minimum number of characters to perform server polling.
LINKIT_PROFILE_TYPE_EDITOR Define the profile type editor.
LINKIT_PROFILE_TYPE_FIELD Define the profile type field.
LINKIT_PROFILE_TYPE_MENU Define the profile type menu.
LINKIT_REMOTE_TIMEOUT Define the client side timeout for a request to the server.
LINKIT_URL_METHOD_ALIAS Define that paths should be constructed as an alias.
LINKIT_URL_METHOD_RAW Define that urls should be constructed as it is (raw).
LINKIT_URL_METHOD_RAW_SLASH Define that urls should be constructed as it is (raw) with a prepending slash.
LINKIT_WAIT Define the time from last key press to poll the server.