You are here

quickedit.module in Quick Edit 7

Provides in-place content editing functionality for fields.

The Quick Edit module makes content editable in-place. Rather than having to visit a separate page to edit content, it may be edited in-place.

Technically, this module adds classes and data- attributes to fields and entities, enabling them for in-place editing.

File

quickedit.module
View source
<?php

/**
 * @file
 * Provides in-place content editing functionality for fields.
 *
 * The Quick Edit module makes content editable in-place. Rather than having to
 * visit a separate page to edit content, it may be edited in-place.
 *
 * Technically, this module adds classes and data- attributes to fields and
 * entities, enabling them for in-place editing.
 */

// Load filter system metadata additions.
module_load_include('inc', 'quickedit', 'includes/filter');

// Load node module "extra fields" support.
module_load_include('inc', 'quickedit', 'includes/node');

// Load Panelizer support.
module_load_include('inc', 'quickedit', 'includes/panelizer');

/**
 * Implements hook_hook_info().
 */
function quickedit_hook_info() {
  $hooks = array(
    'quickedit_editor_info',
    'quickedit_editor_info_alter',
    'quickedit_editor_metadata_alter',
    'quickedit_editor_attachments_alter',
    'quickedit_render_field',
  );
  return array_fill_keys($hooks, array(
    'group' => 'quickedit',
  ));
}

/**
 * Implements hook_menu().
 */
function quickedit_menu() {
  $items = array();
  $items['quickedit/metadata'] = array(
    'access arguments' => array(
      'access in-place editing',
    ),
    'page callback' => 'quickedit_metadata',
    'file' => 'includes/pages.inc',
    'type' => MENU_CALLBACK,
  );
  $items['quickedit/attachments'] = array(
    'access arguments' => array(
      'access in-place editing',
    ),
    'page callback' => 'quickedit_attachments',
    'file' => 'includes/pages.inc',
    'delivery callback' => 'ajax_deliver',
    'theme callback' => 'ajax_base_page_theme',
    'type' => MENU_CALLBACK,
  );
  $items['quickedit/form/%/%/%/%/%'] = array(
    'access arguments' => array(
      'access in-place editing',
    ),
    'page callback' => 'quickedit_field_edit',
    'page arguments' => array(
      2,
      3,
      4,
      5,
      6,
    ),
    'file' => 'includes/pages.inc',
    'delivery callback' => 'ajax_deliver',
    'theme callback' => 'ajax_base_page_theme',
    'type' => MENU_CALLBACK,
  );
  $items['quickedit/entity/%/%'] = array(
    'access arguments' => array(
      'access in-place editing',
    ),
    'page callback' => 'quickedit_entity_save',
    'page arguments' => array(
      2,
      3,
    ),
    'file' => 'includes/pages.inc',
    'delivery callback' => 'ajax_deliver',
    'theme callback' => 'ajax_base_page_theme',
    'type' => MENU_CALLBACK,
  );
  $items['quickedit/ckeditor/%/%/%/%/%'] = array(
    'access arguments' => array(
      'access in-place editing',
    ),
    'page callback' => 'quickedit_ckeditor_get_untransformed_text',
    'page arguments' => array(
      2,
      3,
      4,
      5,
      6,
    ),
    'file' => 'includes/pages.inc',
    'delivery callback' => 'ajax_deliver',
    'theme callback' => 'ajax_base_page_theme',
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_help().
 */
function quickedit_help($path, $arg) {
  switch ($path) {
    case 'admin/help#quickedit':
      $output = '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('The Quick Edit module allows users with the <a href="!quickedit_permission">Access in-place editing</a> and <a href="!contextual_permission">Use contextual links</a> permissions to edit field content without visiting a separate page. For more information, see the <a href="!handbook_url">online documentation for the Quick Edit module</a>.', array(
        '!handbook_url' => 'https://drupal.org/documentation/modules/edit',
        '!quickedit_permission' => url('admin/people/permissions', array(
          'fragment' => 'module-quickedit',
        )),
        '!contextual_permission' => url('admin/people/permissions', array(
          'fragment' => 'module-contextual',
        )),
      )) . '</p>';
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Editing content in-place') . '</dt>';
      $output .= '<dd>';
      $output .= '<p>' . t('To edit content in place, you need to activate quick edit mode for a content item. Activate quick edit mode by choosing Quick edit from the contextual links for an area displaying the content (see the <a href="!contextual">Contextual Links module help</a> for more information about how to use contextual links).', array(
        '!contextual' => url('admin/help/contextual'),
      )) . '</p>';
      $output .= '<p>' . t('Once quick edit mode is activated, you will be able to edit the individual fields of your content. In the default theme, with a JavaScript-enabled browser and a mouse, the output of different fields in your content is outlined in blue, a pop-up gives the field name as you hover over the field output, and clicking on a field activates the editor. Closing the pop-up window ends quick edit mode.') . '</p>';
      $output .= '</dd>';
      $output .= '</dl>';
      return $output;
  }
}

/**
 * Implements hook_permission().
 */
function quickedit_permission() {
  return array(
    'access in-place editing' => array(
      'title' => t('Access in-place editing'),
    ),
  );
}

/**
 * Implements hook_page_build().
 *
 * Adds the quickedit library to the page for any user who has the 'access
 * in-place editing' permission.
 */
function quickedit_page_build(&$page) {

  // If the user lacks the appropriate permission, return early to avoid
  // processing.
  if (!user_access('access in-place editing')) {
    return;
  }

  // In-place editing is only supported on the front-end.
  if (path_is_admin(current_path())) {
    return;
  }
  elseif (!empty($page['content']['system_main']['#node_edit_form'])) {
    return;
  }

  // Incredibly ugly hack to support Panelizer overrides using Page Manager.
  // @see includes/panelizer.inc/quickedit_ctools_render_alter()
  global $quickedit_workaround_for_fundamentally_broken_page_manager;
  if ($quickedit_workaround_for_fundamentally_broken_page_manager) {

    // Change from
    //   <div class="contextual-links-wrapper">
    // to
    //   <div class="contextual-links-wrapper" data-quickedit-is-contextual-region-for-entity>
    $page['content']['system_main']['#attributes']['data-quickedit-is-contextual-region-for-entity'] = '';
  }

  // Abuse the 'page_top' region for attaching our libraries.
  $page['page_top']['#attached']['library'][] = array(
    'quickedit',
    'quickedit',
  );

  // Certain themes don't add region wrappers, so we can't assume region
  // wrappers are present. Therefore, Quick Edit must inject its own
  // alternative: start and end markers for the "content" region.
  $page['content']['#theme_wrappers'][] = 'quickedit_wrap_content_region';

  // Provide the user ID and permissions hash in Drupal.settings to allow
  // JavaScript code to maintain client-side caches.
  // @see Drupal 8's user_page_build()
  // @see Drupal 8's \Drupal\user\PermissionsHash
  // @see https://drupal.org/node/2005644
  global $user;
  $roles = array_keys($user->roles);
  $serialized_roles = implode(',', $roles);
  if ($cache = cache_get("quickedit_user_permissions_hash:{$serialized_roles}", 'cache_page')) {
    $permissions_hash = $cache->data;
  }
  else {
    $permissions_hash = hash('sha256', drupal_get_hash_salt() . serialize(user_role_permissions($user->roles)));

    // Use the 'cache_page' bin along with CACHE_TEMPORARY, because the submit
    // callback for the form responsible for changing user permissions calls
    // cache_clear_all() without arguments, which clears the page cache, and
    // hence it will also clear this cache entry.
    cache_set("quickedit_user_permissions_hash:{$serialized_roles}", $permissions_hash, 'cache_page', CACHE_TEMPORARY);
  }
  $page['page_top']['#attached']['js'][] = array(
    'type' => 'setting',
    'data' => array(
      'quickedit' => array(
        'user' => array(
          'uid' => $user->uid,
          'permissionsHash' => $permissions_hash,
        ),
      ),
    ),
  );
}

/**
 * Implements hook_theme().
 */
function quickedit_theme() {
  return array(
    'quickedit_wrap_content_region' => array(
      'render element' => 'element',
    ),
    'quickedit_wrap_field' => array(
      'variables' => array(
        'value' => NULL,
        'quickedit_field_id' => NULL,
      ),
    ),
  );
}

/**
 * Implements hook_library().
 */
function quickedit_library() {
  $path = drupal_get_path('module', 'quickedit');
  $options = array(
    'scope' => 'footer',
  );
  $libraries['quickedit'] = array(
    'title' => 'Quick Edit: in-place editing',
    'website' => 'http://drupal.org/project/quickedit',
    'version' => VERSION,
    'js' => array(
      // Core.
      $path . '/js/quickedit.js' => $options,
      $path . '/js/util.js' => $options,
      // Models.
      $path . '/js/models/BaseModel.js' => $options,
      $path . '/js/models/AppModel.js' => $options,
      $path . '/js/models/EntityModel.js' => $options,
      $path . '/js/models/FieldModel.js' => $options,
      $path . '/js/models/EditorModel.js' => $options,
      // Views.
      $path . '/js/views/AppView.js' => $options,
      $path . '/js/views/FieldDecorationView.js' => $options,
      $path . '/js/views/EntityDecorationView.js' => $options,
      $path . '/js/views/EntityToolbarView.js' => $options,
      $path . '/js/views/ContextualLinkView.js' => $options,
      $path . '/js/views/FieldToolbarView.js' => $options,
      $path . '/js/views/EditorView.js' => $options,
      $path . '/js/views/ModalView.js' => $options,
      // Other.
      $path . '/js/theme.js' => $options,
      // Monkey-patch in jQuery UI 1.10 Position at $.fn.position_quickedit.
      $path . '/js/jquery/ducktape.position.js' => $options,
      // Basic settings.
      array(
        'data' => array(
          'quickedit' => array(
            'metadataURL' => url('quickedit/metadata'),
            'attachmentsURL' => url('quickedit/attachments'),
            'fieldFormURL' => url('quickedit/form/!entity_type/!id/!field_name/!langcode/!view_mode'),
            'entitySaveURL' => url('quickedit/entity/!entity_type/!id'),
            'rerenderProcessedTextURL' => url('quickedit/text/!entity_type/!id/!field_name/!langcode/!view_mode'),
            'context' => 'body',
          ),
        ),
        'type' => 'setting',
      ),
    ),
    'css' => array(
      $path . '/css/contextual-zindex-fix-1102156.css' => array(),
      $path . '/css/quickedit.module.css' => array(),
      $path . '/css/quickedit.theme.css' => array(),
      $path . '/css/quickedit.icons.css' => array(),
    ),
    'dependencies' => array(
      array(
        'system',
        'jquery',
      ),
      array(
        'system',
        'jquery.once',
      ),
      array(
        'quickedit',
        'underscore',
      ),
      array(
        'quickedit',
        'backbone',
      ),
      array(
        'system',
        'jquery.form',
      ),
      array(
        'system',
        'ui.position',
      ),
      array(
        'system',
        'drupal.form',
      ),
      array(
        'system',
        'drupal.ajax',
      ),
    ),
  );
  $libraries['quickedit.inPlaceEditor.form'] = array(
    'title' => 'Form in-place editor',
    'version' => VERSION,
    'js' => array(
      $path . '/js/editors/formEditor.js' => $options,
    ),
    'dependencies' => array(
      array(
        'quickedit',
        'quickedit',
      ),
    ),
  );
  $libraries['quickedit.inPlaceEditor.plainText'] = array(
    'title' => 'Plain text in-place editor',
    'version' => VERSION,
    'js' => array(
      $path . '/js/editors/plainTextEditor.js' => $options,
    ),
    'dependencies' => array(
      array(
        'quickedit',
        'quickedit',
      ),
    ),
  );
  if (module_exists('ckeditor')) {
    $libraries['quickedit.inPlaceEditor.ckeditor'] = array(
      'title' => 'CKEditor formatted text in-place editor',
      'version' => VERSION,
      'js' => array(
        $path . '/js/editors/formattedTextEditor.js' => array(
          'weight' => 3,
        ) + $options,
        array(
          'type' => 'setting',
          'data' => array(
            'quickedit' => array(
              'ckeditor' => array(
                'getUntransformedTextURL' => url('quickedit/ckeditor/!entity_type/!id/!field_name/!langcode/!view_mode'),
              ),
            ),
          ),
        ),
        // CKEditor.module sadly does not implement hook_library(), so we must
        // indicate here that we want it to be loaded.
        ckeditor_library_path('url') . '/ckeditor/ckeditor.js' => array(
          'weight' => 4,
        ) + $options,
      ),
      'dependencies' => array(
        array(
          'quickedit',
          'quickedit',
        ),
        array(
          'system',
          'drupal.ajax',
        ),
      ),
    );
  }
  module_load_include('inc', 'quickedit', 'includes/libraries');
  $libraries['underscore'] = _quickedit_convert_libraries_to_library(libraries_detect('underscore'), array(
    'group' => JS_LIBRARY,
    'weight' => -20,
  ));
  $libraries['backbone'] = _quickedit_convert_libraries_to_library(libraries_detect('backbone'), array(
    'group' => JS_LIBRARY,
    'weight' => -19,
  ));
  return $libraries;
}

/**
 * Implements hook_field_attach_view_alter().
 */
function quickedit_field_attach_view_alter(&$output, $context) {

  // Special case for this special mode.
  if ($context['display'] == 'quickedit-render-without-transformation-filters') {
    $children = element_children($output);
    $field = reset($children);
    $langcode = $output[$field]['#language'];
    foreach (array_keys($output[$field]['#items']) as $item) {
      $text = $output[$field]['#items'][$item]['value'];
      $format_id = $output[$field]['#items'][$item]['format'];
      $wrapped_and_untransformed = check_markup2($text, $format_id, $langcode, FALSE, array(
        FILTER_TYPE_TRANSFORM_REVERSIBLE,
        FILTER_TYPE_TRANSFORM_IRREVERSIBLE,
      ));
      $output[$field][$item]['#markup'] = $wrapped_and_untransformed;
    }
  }
}

/**
 * Provides metadata of all registered "extra fields".
 *
 * All parameters are optional. You can use them to only retrieve metadata that
 * is relevant for your particular use case.
 *
 * @param $entity_type
 *   Retrieves all "extra fields" metadata for this entity type.
 * @param $field_name
 *   Retrieves the "extra fields" metadata for this entity type and field.
 * @param $key
 *   Retrieves one key of "extra fields" metadata for this entity type and field.
 * @param bool $reset
 *   Resets this function's internal cache when set to TRUE.
 *
 * @return array
 *
 * @see hook_quickedit_extra_fields_info()
 * @see hook_quickedit_extra_fields_info_alter()
 */
function quickedit_extra_field_info($entity_type = NULL, $field_name = NULL, $key = NULL, $reset = FALSE) {
  $extra_fields =& drupal_static(__FUNCTION__, NULL);
  if (!$extra_fields || $reset) {
    $extra_fields = module_invoke_all('quickedit_extra_fields_info');
    drupal_alter('quickedit_extra_fields_info', $editors);
  }
  if (isset($entity_type)) {
    if (isset($field_name)) {
      if (isset($key)) {
        return $extra_fields[$entity_type][$field_name][$key];
      }
      return $extra_fields[$entity_type][$field_name];
    }
    return $extra_fields[$entity_type];
  }
  return $extra_fields;
}

/**
 * Indicates whether a field is an "extra field".
 *
 * @param $entity_type
 *   Machine name of the entity.
 * @param $field_name
 *   Entity's field name that is being checked.
 *
 * @return bool
 *
 * @see hook_quickedit_extra_field_info()
 */
function _quickedit_is_extra_field($entity_type, $field_name) {
  $extra_fields = quickedit_extra_field_info();
  return isset($extra_fields[$entity_type][$field_name]);
}

/**
 * Implements hook_preprocess_field().
 *
 * This is the main entry point for marking up a field as in-place editable.
 */
function quickedit_preprocess_field(&$variables) {

  // If the user lacks the appropriate permission, return early to avoid
  // processing.
  if (!user_access('access in-place editing')) {
    return;
  }
  $element = $variables['element'];

  // Quick Edit module only supports view modes, not dynamically defined "display
  // options" (which field_view_field() always names the "_custom_display" view
  // mode).
  // @see field_view_field()
  // @see https://drupal.org/node/2120335
  // However, we do support Panelizer, and Panelizer always uses a custom view
  // mode when rendering field panes, in all of its own "view modes".
  // @see includes/panelizer.inc
  if ($element['#view_mode'] === '_custom_display' && !isset($element['#object']->panelizer)) {
    return;
  }

  // Some fields might be rendered through theme_field()
  // but are not Field API fields, e.g. Display Suite fields.
  if (!empty($element['#skip_quickedit'])) {
    return;
  }
  $entity_type = $element['#entity_type'];

  // The 'comment' entity type will never support in-place editing, since it
  // doesn't get Contextual Links.
  if ($entity_type === 'comment') {
    return;
  }

  // For now, Quick Edit only supports 'node' entities. Therefor, don't annotate
  // fields of other entity types with Quick Edit's metadata.
  // @todo https://drupal.org/node/2168725
  if ($entity_type != 'node') {
    return;
  }
  $field_name = $element['#field_name'];
  $language = $element['#language'];
  $view_mode = $element['#view_mode'];
  $entity = $element['#object'];
  list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);

  // Support for field-collection fields.
  if ($entity_type === 'field_collection_item') {
    $host_entity = field_collection_item_get_host_entity($element['#object']);

    // When this field_collection_item entity is rendered as a field inside its
    // host entity, we should not mark up its fields; the field_collection_item
    // entity as a whole will already be in-place editable (since the entity is
    // considered a field in this contect); it does not make sense to make
    // fields within a field editable.
    if (isset($host_entity
      ->value()->entity_view_prepared)) {
      return;
    }
  }

  // Panelizer support; see explanation at the top of this function.
  // @see includes/panelizer.inc
  if ($element['#view_mode'] === '_custom_display' && isset($element['#object']->panelizer)) {
    $view_mode = _panelizer_generate_quickedit_viewmode($element['#object']);
  }

  // Provide metadata through data- attributes.
  $variables['attributes_array']['data-quickedit-field-id'] = "{$entity_type}/{$id}/{$field_name}/{$language}/{$view_mode}";
}

/**
 * Implements hook_preprocess_page().
 *
 * Wraps title field when viewing a node page to make it in-place editable.
 */
function quickedit_preprocess_page(&$variables) {

  // If the user lacks the appropriate permission, return early to avoid
  // processing.
  if (!user_access('access in-place editing')) {
    return;
  }

  // If we don't have a node object to work with, return early to avoid
  // processing.
  if (empty($variables['node'])) {
    return;
  }

  // On full node pages the title of the node becomes the page title so we
  // must handle it differently. In this case, we add a wrapper around the
  // title with the required attributes to enable in-place editability.
  $node = $variables['node'];
  $node_type = node_type_get_type($node->type);
  if ($node_type->has_title) {
    $language = !empty($node->language) ? $node->language : LANGUAGE_NONE;
    $variables['title'] = quickedit_wrap_pseudofield(drupal_get_title(), "node/{$node->nid}/title/{$language}/full");
  }
}

/**
 * Implements hook_preprocess_node().
 *
 * Sets data-quickedit-entity-id attribute.
 * Takes care of wrapping title, author, and created date for in-place
 * editability.
 */
function quickedit_preprocess_node(&$variables) {

  // If the user lacks the appropriate permission, return early to avoid
  // processing.
  if (!user_access('access in-place editing')) {
    return;
  }
  $entity_type = $variables['elements']['#entity_type'];
  $entity = $variables['elements']['#node'];
  $view_mode = $variables['elements']['#view_mode'];
  list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
  $language = !empty($entity->language) ? $entity->language : LANGUAGE_NONE;
  $quickedit_id_suffix = "{$language}/{$view_mode}";

  // Set data-quickedit-entity-id attribute.
  $node = $variables['elements']['#node'];
  $variables['attributes_array']['data-quickedit-entity-id'] = 'node/' . $node->nid;

  // Pseudo-field: title.
  $node_type = node_type_get_type($bundle);
  if ($node_type->has_title && !empty($variables['title'])) {
    $variables['title'] = quickedit_wrap_pseudofield($variables['title'], "node/{$id}/title/{$quickedit_id_suffix}");
  }

  // Pseudo-fields: author (name) and created date (authoring date).
  if ($variables['display_submitted']) {
    $variables['name'] = quickedit_wrap_pseudofield($variables['name'], "node/{$id}/author/{$quickedit_id_suffix}", TRUE);
    $variables['date'] = quickedit_wrap_pseudofield($variables['date'], "node/{$id}/created/{$quickedit_id_suffix}", TRUE);
    $variables['submitted'] = t('Submitted by !username on !datetime', array(
      '!username' => $variables['name'],
      '!datetime' => $variables['date'],
    ));
  }
}

/**
 * Implements hook_node_view_alter().
 *
 * For in-place editing, it's necessary for contextual links to always exist,
 * even when the node is being displayed on its own page.
 */
function quickedit_node_view_alter(&$build) {
  if (!isset($build['#contextual_links']['node'])) {
    $build['#contextual_links']['node'] = array(
      'node',
      array(
        $build['#node']->nid,
      ),
    );
  }
}

/**
 * Implements hook_preprocess_panels_pane().
 *
 * When a node is added as a pane, get it's view mode to build
 * data-quickedit-field-id properly.
 */
function quickedit_preprocess_panels_pane(&$variables) {

  // If the user lacks the appropriate permission, return early to avoid
  // processing.
  if (!user_access('access in-place editing')) {
    return;
  }

  // If we don't have a node object to work with, return early to avoid
  // processing.
  // Note: This convoluted check is required because the expression
  // $variables['content']['#node'] is being interpreted as "the first character
  // of the string in $variables['content']" in panes that contain 'content' as
  // a string, rather than an array. Bleh.
  if (!isset($variables['content']['#node']) || !is_object($variables['content']['#node'])) {
    return;
  }
  $node = $variables['content']['#node'];
  $language = !empty($node->language) ? $node->language : LANGUAGE_NONE;
  $view_mode = !empty($variables['pane']->configuration['build_mode']) ? $variables['pane']->configuration['build_mode'] : 'default';
  $quickedit_id_suffix = "{$language}/{$view_mode}";

  // Set data-quickedit-is-contextual-region-for-entity attribute, to indicate
  // that this Panels pane contains an in-place editable entity, yet the
  // contextual region is not the DOM element of that entity itself, but this
  // Panels pane. The data-quickedit-entity-id attribute is already being set on
  // the node's DOM element, since Panels just uses the standard render
  // pipeline.
  $variables['attributes_array']['data-quickedit-is-contextual-region-for-entity'] = '';
  $node_type = node_type_get_type($node->type);
  if ($node_type->has_title) {

    // Title needs some special handling. Only wraps it when it hasn't been
    // overridden. There is no way to update the panels configuration in Quick
    // Edit module currently.
    $configuration = $variables['pane']->configuration;
    if (!$configuration['override_title']) {
      $variables['title'] = quickedit_wrap_pseudofield($variables['title'], "node/{$node->nid}/title/{$quickedit_id_suffix}");
    }
  }
}

/**
 * Discovers all available editors by invoking hook_quickedit_editor_info().
 *
 * @param bool $reset
 *   Reset the editor info static cache.
 *
 * @return array
 *   An associative array keyed on editor ID.
 *
 * @see Drupal 8's Quick Edit's EditorManager
 */
function quickedit_editor_list($reset = FALSE) {
  $editors =& drupal_static(__FUNCTION__, NULL);
  if (!$editors || $reset) {
    $editors = module_invoke_all('quickedit_editor_info');
    drupal_alter('quickedit_editor_info', $editors);
  }
  return $editors;
}

/**
 * Helper to get a single editor info array.
 *
 * @param $editor
 *   Machine name of the editor we return the editor.
 *
 * @return mixed
 *   False if the editor is not found.
 *   Info array for the editor.
 */
function quickedit_editor_get($editor) {
  $list = quickedit_editor_list();
  return !empty($list[$editor]) ? $list[$editor] : FALSE;
}

/**
 * Helper to get a get an instance of an in-place editor plugin class.
 *
 * @param string $editor_id
 *   ID of the in-place editor plugin.
 * @param bool $reset
 *   Whether to reset the static cache of in-place editor plugin objects.
 *
 * @return QuickEditInPlaceEditorInterface
 *   An in-place editor plugin object.
 */
function _quickedit_get_editor_plugin($editor_id, $reset = FALSE) {
  $editors = quickedit_editor_list();
  $plugins =& drupal_static(__FUNCTION__, NULL);
  if (!$plugins || $reset) {
    foreach ($editors as $editor_plugin_id => $editor) {
      require_once $editor['file'];
      $plugins[$editor_plugin_id] = new $editor['class']();
    }
  }
  return $plugins[$editor_id];
}

/**
 * Implements hook_module_implements_alter().
 *
 * Make sure our alter hook is run after jquery update (and after all the others
 * for that matter).
 */
function quickedit_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'library_alter') {

    // Move our hook implementation to the bottom.
    $group = $implementations['quickedit'];
    unset($implementations['quickedit']);
    $implementations['quickedit'] = $group;
  }
}

/**
 * Implements hook_library_alter().
 *
 * Backport a couple of things from jQuery that are required by CreateJS/VIE.
 */
function quickedit_library_alter(&$libraries, $module) {
  $jquery_version =& drupal_static(__FUNCTION__, NULL);
  if ($module == 'system') {
    $jquery_version = $libraries['jquery']['version'];
  }
  if ($jquery_version && $module == 'quickedit') {
    $path = drupal_get_path('module', 'quickedit');

    // If the version of jQuery is old, we need to add `addBack`.
    if ($jquery_version < '1.8') {
      $libraries['quickedit']['js'][$path . '/js/jquery/ducktape.addback.js'] = array(
        'group' => JS_LIBRARY,
      );
    }

    // If the version of jQuery is old, we need to add `on` and `off`.
    if ($jquery_version < '1.7') {
      $libraries['quickedit']['js'][$path . '/js/jquery/ducktape.events.js'] = array(
        'group' => JS_LIBRARY,
      );
    }

    // If the version of jQuery is old, we need to add `prop`.
    if ($jquery_version < '1.6') {
      $libraries['quickedit']['js'][$path . '/js/jquery/ducktape.prop.js'] = array(
        'group' => JS_LIBRARY,
      );
    }

    // If the version of jQuery is old, we need to add `deferred`.
    if ($jquery_version < '1.5') {
      $libraries['quickedit']['js'][$path . '/js/jquery/ducktape.deferred.js'] = array(
        'group' => JS_LIBRARY,
      );
    }
  }
}

/**
 * Implements hook_field_formatter_info_alter().
 *
 * Quick Edit extends the field formatter info hook metadata with the following
 * keys:
 * - quickedit: currently only contains one subkey 'editor' which indicates
 *   which in-place editor should be used. Possible values are 'form',
 *   'plain_text', 'disabled' or another in-place editor other than the ones
 *   Quick Edit module provides.
 *
 * @see Drupal 8's quickedit_field_formatter_info_alter()
 */
function quickedit_field_formatter_info_alter(&$info) {
  foreach ($info as $key => $settings) {

    // Set in-place editor to 'form' if none is supplied.
    if (empty($settings['settings']['quickedit'])) {
      $info[$key]['settings']['quickedit'] = array(
        'editor' => 'form',
      );
    }
  }

  // @see Drupal 8's \Drupal\text\Plugin\field\formatter\TextDefaultFormatter
  // @see Drupal 8's \Drupal\text\Plugin\field\formatter\TextPlainFormatter
  if (module_exists('text')) {
    $info['text_default']['settings']['quickedit']['editor'] = 'plain_text';
    $info['text_plain']['settings']['quickedit']['editor'] = 'plain_text';
  }
}

/**
 * Wraps the name pseudo-field attached to nodes.
 *
 * @param $name
 *   The existing name value.
 * @param $quickedit_field_id
 *   The in-place editing field ID.
 * @param $is_inline
 *   Whether this pseudofield should be rendered as display:inline or not.
 *
 * @return
 *   The fully-themed HTML output for the wrapped "name" pseudo-field.
 */
function quickedit_wrap_pseudofield($value, $quickedit_field_id, $is_inline = FALSE) {
  return theme('quickedit_wrap_field', array(
    'value' => $value,
    'quickedit_field_id' => $quickedit_field_id,
    'inline' => $is_inline,
  ));
}

/**
 * Formats a field in a wrapper with the required metadata.
 *
 * Default tag is div because inline CKEditor will refuse to work on a span that
 * is made contenteditable.
 */
function theme_quickedit_wrap_field($variables) {
  $variables['attributes']['data-quickedit-field-id'] = $variables['quickedit_field_id'];
  $el = 'div';
  if ($variables['inline']) {
    $el = 'span';
  }
  return '<' . $el . drupal_attributes($variables['attributes']) . '>' . $variables['value'] . '</' . $el . '>';
}

/**
 * Injects start and end markers before and after the content region.
 *
 * Certain themes don't add region wrappers, so we can't assume region
 * wrappers are present. Therefore, Quick Edit must inject its own alternative:
 * start and end markers for the "content" region.
 */
function theme_quickedit_wrap_content_region($variables) {
  $element = $variables['element'];
  return '<div data-quickedit-content-region-start></div>' . $element['#children'] . '<div data-quickedit-content-region-end></div>';
}

/**
 * Implements hook_libraries_info().
 *
 * @see Libraries module.
 */
function quickedit_libraries_info() {
  module_load_include('inc', 'quickedit', 'includes/libraries');
  $libraries = array();
  $common = array(
    'version callback' => '_quickedit_libraries_get_version',
    'variant order' => array(
      'minified',
      'source',
    ),
  );
  $libraries['underscore'] = array(
    'name' => 'Underscore',
    'vendor url' => 'http://documentcloud.github.io/backbone/',
    'download url' => 'https://github.com/jashkenas/underscore/archive/1.5.2.zip',
    'version arguments' => array(
      'variants' => array(
        'source' => array(
          'file' => 'underscore.js',
          // @todo Document an actual example version string.
          'pattern' => '#VERSION *\\W *[\'\\"]{1}(.*?)[\'\\"]{1}#',
          // In the unminified Underscore.js 1.5.2, the version is defined on
          // line 68.
          'lines' => 100,
        ),
        'minified' => array(
          'file' => 'underscore-min.js',
          'pattern' => '#VERSION *\\W *[\'\\"]{1}(.*?)[\'\\"]{1}#',
        ),
      ),
    ),
    'versions' => array(
      // Means ">=1.5.0": matches 1.5.0, 1.5.2, etc.
      '1.5.0' => array(
        'variants' => array(
          'source' => array(
            'files' => array(
              'js' => array(
                'underscore.js',
              ),
            ),
            // Without a variant callback, the variant is assumed to be
            // installed.
            'variant callback' => '_quickedit_libraries_variant_exists',
            'variant arguments' => array(
              'underscore.js',
            ),
          ),
          'minified' => array(
            'files' => array(
              'js' => array(
                'underscore-min.js',
              ),
            ),
            // Without a variant callback, the variant is assumed to be
            // installed.
            'variant callback' => '_quickedit_libraries_variant_exists',
            'variant arguments' => array(
              'underscore-min.js',
            ),
          ),
        ),
      ),
    ),
  );
  $libraries['underscore'] += $common;
  $libraries['backbone'] = array(
    'name' => 'Backbone',
    'vendor url' => 'http://documentcloud.github.io/backbone/',
    'download url' => 'https://github.com/jashkenas/backbone/archive/1.1.0.zip',
    'version arguments' => array(
      'variants' => array(
        'source' => array(
          'file' => 'backbone.js',
          // @todo Document an actual example version string.
          'pattern' => '#VERSION *\\W *[\'\\"]{1}(.*?)[\'\\"]{1}#',
          // In the unminified Backbone.js 1.1.0, the version is defined on line
          // 38.
          'lines' => 50,
        ),
        'minified' => array(
          'file' => 'backbone-min.js',
          'pattern' => '#VERSION *\\W *[\'\\"]{1}(.*?)[\'\\"]{1}#',
        ),
      ),
    ),
    'versions' => array(
      // Means ">=1.0.0": matches 1.0.0, 1.1.0, etc.
      '1.0.0' => array(
        'variants' => array(
          'source' => array(
            'name' => 'Backbone',
            'files' => array(
              'js' => array(
                'backbone.js',
              ),
            ),
            // Without a variant callback, the variant is assumed to be
            // installed.
            'variant callback' => '_quickedit_libraries_variant_exists',
            'variant arguments' => array(
              'backbone.js',
            ),
            'dependencies' => array(
              'underscore (>=1.5.0)',
            ),
          ),
          'minified' => array(
            'name' => 'Backbone',
            'files' => array(
              'js' => array(
                'backbone-min.js',
              ),
            ),
            // Without a variant callback, the variant is assumed to be
            // installed.
            'variant callback' => '_quickedit_libraries_variant_exists',
            'variant arguments' => array(
              'backbone-min.js',
            ),
            'dependencies' => array(
              'underscore (>=1.5.0)',
            ),
          ),
        ),
      ),
    ),
  );
  $libraries['backbone'] += $common;
  return $libraries;
}

/**
 * Implement hook_libraries_info_alter().
 */
function quickedit_libraries_info_alter(&$info) {

  // underscore.module also declares library information for the underscore
  // library, but does not include the 'variant order' key.
  $info['underscore']['variant order'] = array(
    'minified',
    'source',
  );
}

/**
 * Generates an identifier to store an entity in TempStore while editing.
 *
 * Must be user-specific.
 *
 * @param string $entity_type
 *   The type of the entity being in-place edited.
 * @param int $entity_id
 *   The ID of the entity being in-place edited.
 *
 * @return string
 *   The user- and entity-specific TempStore identifier.
 */
function _quickedit_entity_tempstore_id($entity_type, $entity_id) {
  global $user;
  return $user->uid . '/' . $entity_type . '/' . $entity_id;
}

/**
 * Saves an entity to TempStore.
 *
 * @param string $entity_type
 *   The type of the entity being saved to TempStore.
 * @param int $entity_id
 *   The ID of the entity being saved to TempStore.
 * @param stdClass $entity
 *   The entity object being saved to TempStore.
 */
function _quickedit_entity_save_to_tempstore($entity_type, $entity_id, $entity) {
  ctools_include('object-cache');
  $tempstore_id = _quickedit_entity_tempstore_id($entity_type, $entity_id);
  ctools_object_cache_set('quickedit', $tempstore_id, $entity);
}

/**
 * Implements hook_css_alter().
 *
 * @see css/bartik.css
 * @see https://drupal.org/node/2149261#comment-8290547
 */
function quickedit_css_alter(&$css) {
  $bartik_style_css = drupal_get_path('theme', 'bartik') . '/css/style.css';
  if (isset($css[$bartik_style_css])) {
    $bartik_fix_css = drupal_get_path('module', 'quickedit') . '/css/bartik.css';
    $css[$bartik_fix_css] = array(
      'data' => $bartik_fix_css,
      // Same properties as quickedit.(module|theme|icons).css, except for weight.
      'group' => 100,
      'type' => 'file',
      'weight' => 1.0,
      'every_page' => FALSE,
      'media' => 'all',
      'preprocess' => TRUE,
      'browsers' => array(
        'IE' => TRUE,
        '!IE' => TRUE,
      ),
    );
  }
}

Functions

Namesort descending Description
quickedit_css_alter Implements hook_css_alter().
quickedit_editor_get Helper to get a single editor info array.
quickedit_editor_list Discovers all available editors by invoking hook_quickedit_editor_info().
quickedit_extra_field_info Provides metadata of all registered "extra fields".
quickedit_field_attach_view_alter Implements hook_field_attach_view_alter().
quickedit_field_formatter_info_alter Implements hook_field_formatter_info_alter().
quickedit_help Implements hook_help().
quickedit_hook_info Implements hook_hook_info().
quickedit_libraries_info Implements hook_libraries_info().
quickedit_libraries_info_alter Implement hook_libraries_info_alter().
quickedit_library Implements hook_library().
quickedit_library_alter Implements hook_library_alter().
quickedit_menu Implements hook_menu().
quickedit_module_implements_alter Implements hook_module_implements_alter().
quickedit_node_view_alter Implements hook_node_view_alter().
quickedit_page_build Implements hook_page_build().
quickedit_permission Implements hook_permission().
quickedit_preprocess_field Implements hook_preprocess_field().
quickedit_preprocess_node Implements hook_preprocess_node().
quickedit_preprocess_page Implements hook_preprocess_page().
quickedit_preprocess_panels_pane Implements hook_preprocess_panels_pane().
quickedit_theme Implements hook_theme().
quickedit_wrap_pseudofield Wraps the name pseudo-field attached to nodes.
theme_quickedit_wrap_content_region Injects start and end markers before and after the content region.
theme_quickedit_wrap_field Formats a field in a wrapper with the required metadata.
_quickedit_entity_save_to_tempstore Saves an entity to TempStore.
_quickedit_entity_tempstore_id Generates an identifier to store an entity in TempStore while editing.
_quickedit_get_editor_plugin Helper to get a get an instance of an in-place editor plugin class.
_quickedit_is_extra_field Indicates whether a field is an "extra field".