You are here

mee.module in Scald: Media Management made easy 7

Defines a special textarea, with drag and drop media driven by Scald and dnd.module.

File

modules/fields/mee/mee.module
View source
<?php

/**
 * @file
 * Defines a special textarea, with drag and drop media driven by Scald and
 * dnd.module.
 */
define('MEE_RENDERED_COPYRIGHT_PATTERN', '/<!--\\s*copyright=(\\d+)\\s*-->(.*)<!--\\s*END copyright=\\1\\s*-->/sU');

/**
 * Implements hook_menu().
 */
function mee_menu() {

  // AJAX callback used to render atoms in the widget-based plugin for
  // CKEditor4.
  $items['atom/ajax-widget-expand/%scald_atom_fallback'] = array(
    'page callback' => 'mee_ajax_widget_expand',
    'page arguments' => array(
      2,
    ),
    'access callback' => TRUE,
    'delivery callback' => 'ajax_deliver',
    'theme callback' => 'ajax_base_page_theme',
  );
  return $items;
}

/**
 * Implements hook_theme().
 */
function mee_theme($existing, $type, $theme, $path) {
  return array(
    'mee_resource_manager' => array(
      'render element' => 'resource_manager',
    ),
    'mee_widget_embed' => array(
      'variables' => array(
        'atom' => NULL,
        'context' => NULL,
        'options' => NULL,
        'align' => NULL,
        'caption' => NULL,
        'content' => NULL,
        'wysiwyg' => FALSE,
      ),
    ),
  );
}

/**
 * Implements hook_library().
 */
function mee_library() {
  $path = drupal_get_path('module', 'mee');
  $libraries['library'] = array(
    'title' => 'MEE Library',
    'website' => 'http://drupal.org/project/scald',
    'version' => '1.x',
    'js' => array(
      $path . '/mee.js' => array(),
      array(
        'type' => 'setting',
        'data' => array(
          'mee' => array(
            'sas' => mee_store_format() == 'sas',
            'editors' => array(),
          ),
        ),
      ),
    ),
    'css' => array(
      $path . '/css/mee.css' => array(),
    ),
  );

  // This file is included automatically if the CKEditor plugin is enabled.
  // However we need to load it directly in Drupal so that strings can be
  // translated.
  $plugin = mee_store_format() !== 'embed_div' ? 'ckeditor' : 'dndck4';
  $libraries['library']['js'][$path . '/plugins/' . $plugin . '/lang/en.js'] = array();
  return $libraries;
}

/**
 * Implements hook_wywiwyg_plugin().
 */
function mee_wysiwyg_plugin($editor, $version) {
  $plugins = array();
  $mee_store_format = mee_store_format();
  switch ($editor) {
    case 'ckeditor':
      if ($mee_store_format == 'sas') {
        $plugins['dnd'] = array(
          'path' => drupal_get_path('module', 'mee') . '/plugins/ckeditor',
          'filename' => 'plugin.js',
          'buttons' => array(
            'dnd' => t('Scald DnD integration'),
          ),
          'load' => TRUE,
        );
      }
      elseif ($mee_store_format == 'embed_div') {
        $plugins['dndck4'] = array(
          'path' => drupal_get_path('module', 'mee') . '/plugins/dndck4',
          'filename' => 'plugin.js',
          'buttons' => array(
            'dndck4' => t('Scald DnD integration - CKEditor 4 widgets'),
          ),
          'load' => TRUE,
        );
      }
      break;
  }
  return $plugins;
}

/**
 * Implements hook_ckeditor_plugin().
 */
function mee_ckeditor_plugin() {
  $plugins = array();
  $mee_store_format = mee_store_format();
  if ($mee_store_format == 'sas') {
    $plugins['dnd'] = array(
      'name' => 'dnd',
      'desc' => t('Scald Drag and Drop integration'),
      'path' => drupal_get_path('module', 'mee') . '/plugins/ckeditor/',
      'buttons' => array(
        'ScaldAtom' => array(
          'icon' => 'icons/atom.png',
          'label' => t('Edit atom properties'),
        ),
      ),
    );
  }
  elseif ($mee_store_format == 'embed_div' && version_compare(ckeditor_get_version(), '4.3.0') >= 0) {
    $plugins['dndck4'] = array(
      'name' => 'dndck4',
      'desc' => t('Scald Drag and Drop integration - CKEditor 4 widgets'),
      'path' => drupal_get_path('module', 'mee') . '/plugins/dndck4/',
      'buttons' => array(
        'ScaldAtom' => array(
          'icon' => 'icons/atom.png',
          'label' => t('Edit atom properties'),
        ),
      ),
    );
  }
  return $plugins;
}

/**
 * Implements hook_editor_ckeditor_plugins().
 */
function mee_editor_ckeditor_plugins() {
  $plugins = array();
  $mee_store_format = mee_store_format();
  if ($mee_store_format == 'sas') {
    $plugins['dnd'] = array(
      'name' => 'dnd',
      'desc' => t('Scald Drag and Drop integration'),
      'path' => drupal_get_path('module', 'mee') . '/plugins/ckeditor',
      'buttons' => array(
        'ScaldAtom' => array(
          'label' => t('Edit atom properties'),
          'image' => drupal_get_path('module', 'mee') . '/plugins/ckeditor/icons/atom.png',
        ),
      ),
      'file' => 'plugin.js',
      'enabled callback' => TRUE,
    );
  }
  elseif ($mee_store_format == 'embed_div') {
    $plugins['dndck4'] = array(
      'name' => 'dndck4',
      'desc' => t('Scald Drag and Drop integration - CKEditor 4 widgets'),
      'path' => drupal_get_path('module', 'mee') . '/plugins/dndck4',
      'buttons' => array(
        'ScaldAtom' => array(
          'label' => t('Edit atom properties'),
          'image' => drupal_get_path('module', 'mee') . '/plugins/dndck4/icons/atom.png',
        ),
      ),
      'file' => 'plugin.js',
      'enabled callback' => TRUE,
    );
  }
  return $plugins;
}

/**
 * Implements hook_views_api().
 */
function mee_views_api($module = NULL, $api = NULL) {
  return array(
    "api" => "3.0",
  );
}

/**
 * Implements hook_field_info_alter().
 */
function mee_field_info_alter(&$info) {
  foreach (mee_field_types() as $name) {
    $info[$name]['instance_settings']['dnd_enabled'] = 0;
    $info[$name]['instance_settings']['mee_enabled'] = 0;
    $info[$name]['instance_settings']['context'] = '';
  }
}

/**
 * Returns the current storage format for embedded atoms.
 *
 * @return string
 *   - 'sas' when using the legacy CKEditor plugin,
 *   - 'embed_div' when using the CKEditor 4 widget plugin.
 */
function mee_store_format() {
  return variable_get('mee_store_format', 'embed_div');
}

/**
 * Implements hook_form_alter().
 *
 * Normally this should go in a hook_field_instance_settings_form() if the field
 * belongs to the module. But it is not the case, so we implement in a form
 * alter.
 */
function mee_form_alter(&$form, &$form_state, $form_id) {

  // Verify if we are in the instance settings form.
  if ($form_id !== 'field_ui_field_edit_form' || !in_array($form['#field']['type'], mee_field_types())) {
    return;
  }
  $settings = $form['#instance']['settings'];
  $context_options = array();
  foreach (scald_contexts_public() as $name => $context) {
    $context_options[$name] = $context['title'];
  }
  $form['instance']['settings']['dnd_enabled'] = array(
    '#type' => 'checkbox',
    '#title' => t('Drag\'n\'Drop Enabled'),
    '#description' => t('Enable DnD for this field will show the Atom library and will allow you to drag and drop atoms to this field.'),
    '#default_value' => $settings['dnd_enabled'],
  );
  $form['instance']['settings']['mee_enabled'] = array(
    '#type' => 'checkbox',
    '#title' => t('MEE Enabled'),
    '#description' => t('Enable MEE for this field to get access to an advance resource management interface. MEE will automatically detect the resources embedded in this field, and allow you to define a few metadata properties on them, e.g. choose if the node should be unpublished if at some point in the future the resource became unavailable.'),
    '#default_value' => $settings['mee_enabled'],
  );
  $form['instance']['settings']['context_default'] = array(
    '#type' => 'select',
    '#title' => t('Scald default context'),
    '#description' => t('You can customize field level default context for drag and drop atoms.'),
    '#default_value' => isset($settings['context_default']) ? $settings['context_default'] : variable_get('dnd_context_default', 'sdl_editor_representation'),
    '#options' => $context_options,
  );
  $form['instance']['settings']['context'] = array(
    '#type' => 'select',
    '#title' => t('Scald fallback context'),
    '#description' => t('The fallback context is only used when the specified context for embedded atom is not available (e.g. deleted).'),
    '#default_value' => $settings['context'],
    '#options' => $context_options,
  );
}

/**
 * Implements hook_field_presave() on behalf of Text module.
 *
 * @todo Find a better approach to avoid possible collision with other "tricky"
 * modules. However we should be safe with Drupal 7 core.
 */
function text_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {

  // Convert rendered atom back to SAS for on the fly render if required.
  if (mee_store_format() != 'sas') {
    return;
  }
  foreach ($items as $delta => &$item) {
    if (!empty($item['value'])) {
      $item['value'] = scald_rendered_to_sas($item['value']);
    }
  }
}

/**
 * Implements hook_field_insert() on behalf of Text module.
 *
 * @see text_field_presave()
 */
function text_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
  if (!_mee_field_instance_enabled($instance, 'mee')) {
    return;
  }
  list($id, $revision_id) = _mee_extract_id($entity_type, $entity);
  foreach ($items as $delta => $item) {
    list($sids, $copyrights) = _mee_process_item_value($item, $entity_type, $entity, $field, $delta);

    // Normalize the weight, putting our separator at 0.
    $separator = $item['mee']['resource_manager'][0]['weight'];
    foreach ($sids as $sid) {
      $resource = $item['mee']['resource_manager'][$sid];
      db_insert('mee_resource')
        ->fields(array(
        'entity_type' => $entity_type,
        'entity_id' => $id,
        'revision_id' => $revision_id,
        'atom_sid' => $sid,
        'field' => $field['field_name'],
        'delta' => $delta,
        'weight' => $resource['weight'] - $separator,
        'required' => (int) $resource['required'],
        'copyright' => isset($copyrights[$sid]) ? $copyrights[$sid] : '',
      ))
        ->execute();
    }
  }
}

/**
 * Implements hook_field_update() on behalf of Text module.
 *
 * @see text_field_presave()
 */
function text_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
  if (!_mee_field_instance_enabled($instance, 'mee')) {
    return;
  }
  list($id, $revision_id) = _mee_extract_id($entity_type, $entity);
  foreach ($items as $delta => $item) {
    list($sids, $copyrights) = _mee_process_item_value($item, $entity_type, $entity, $field, $delta);

    // In fact, we'll delete all the associations and recreate afterwards
    // the needed one, to be sure that new resources are correctly
    // registered, and that no longer used one are removed.
    db_delete('mee_resource')
      ->condition('entity_type', $entity_type)
      ->condition('entity_id', $id)
      ->condition('revision_id', $revision_id)
      ->condition('field', $field['field_name'])
      ->condition('delta', $delta)
      ->execute();

    // Normalize the weight, putting our separator at 0.
    $separator = $item['mee']['resource_manager'][0]['weight'];
    foreach ($sids as $sid) {
      $resource = $item['mee']['resource_manager'][$sid];
      db_insert('mee_resource')
        ->fields(array(
        'entity_type' => $entity_type,
        'entity_id' => $id,
        'revision_id' => $revision_id,
        'atom_sid' => $sid,
        'field' => $field['field_name'],
        'delta' => $delta,
        'weight' => $resource['weight'] - $separator,
        'required' => isset($resource['required']) ? (int) $resource['required'] : 0,
        'copyright' => isset($copyrights[$sid]) ? $copyrights[$sid] : '',
      ))
        ->execute();
    }
  }
}

/**
 * Implements hook_field_delete() on behalf of Text module.
 *
 * @see text_field_presave()
 */
function text_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) {
  if (!_mee_field_instance_enabled($instance, 'mee')) {
    return;
  }
  list($id, ) = _mee_extract_id($entity_type, $entity);

  // Delete all resource associations for this field.
  db_delete('mee_resource')
    ->condition('entity_type', $entity_type)
    ->condition('entity_id', $id)
    ->condition('field', $field['field_name'])
    ->execute();
}

/**
 * Implements hook_field_attach_view_alter.
 *
 * Converts the SAS representation to the rendered representation.
 */
function mee_field_attach_view_alter(&$output, $context) {
  $store_format = mee_store_format();
  if ($store_format == 'sas' || $store_format == 'embed_div') {
    list($id, $revision_id, $bundle) = entity_extract_ids($context['entity_type'], $context['entity']);
    $fields = field_info_instances($context['entity_type'], $bundle);
    foreach ($fields as $name => $field) {
      if (!empty($field['settings']['dnd_enabled']) && isset($output[$name])) {
        foreach (element_children($output[$name]) as $key) {
          if ($store_format == 'embed_div') {
            $input_format = $output[$name]['#items'][$key]['format'];
            $list = filter_list_format($input_format);
            if (empty($list['mee_scald_widgets']) || $list['mee_scald_widgets']->status != 1) {
              $output[$name][$key]['#markup'] = mee_filter_process($output[$name][$key]['#markup']);
            }
          }
          $output[$name][$key]['#markup'] = scald_sas_to_rendered($output[$name][$key]['#markup'], $field['settings']['context'], FALSE, dnd_scald_wysiwyg_context_slugs());
        }
      }
    }
  }
}

/**
 * Implements hook_panels_pane_content_alter().
 *
 * Converts the SAS representation to the rendered representation
 * in custom content panes.
 */
function mee_panels_pane_content_alter($content, $pane, $args, $context) {
  if ($pane->type === 'custom' && $pane->subtype === 'custom' && $content->type === 'custom' && is_string($content->content)) {
    $store_format = mee_store_format();
    if ($store_format == 'embed_div') {
      $input_format = $pane->configuration['format'];
      $list = filter_list_format($input_format);
      if (empty($list['mee_scald_widgets']) || $list['mee_scald_widgets']->status != 1) {
        $content->content = mee_filter_process($content->content);
      }
    }
    $content->content = scald_sas_to_rendered($content->content, NULL, FALSE, dnd_scald_wysiwyg_context_slugs());
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Add dnd for ALL panels 'custom content' ctools forms.
 */
function mee_form_ctools_custom_content_type_edit_form_alter(&$form, &$form_state) {
  $form['body']['#attached']['library'] = array(
    array(
      'dnd',
      'library',
    ),
  );
}

/**
 * Implements hook_field_widget_form_alter().
 */
function mee_field_widget_form_alter(&$element, &$form_state, $context) {
  $enables = _mee_field_instance_enabled($context['instance']);

  // In any case, convert SAS into rendered for format textarea.
  if (in_array($context['field']['type'], mee_field_types()) && isset($element['#default_value'])) {
    if (mee_store_format() == 'sas') {
      $element['#default_value'] = scald_sas_to_rendered($element['#default_value'], $context['instance']['settings']['context'], FALSE, dnd_scald_wysiwyg_context_slugs());
    }
  }

  // Activate DnD Library for this element if enabled.
  if (!empty($enables['dnd'])) {
    $settings = $context['instance']['settings'];
    $context_default = isset($settings['context_default']) ? $settings['context_default'] : variable_get('dnd_context_default', 'sdl_editor_representation');
    $element['#attributes']['data-dnd-context'][] = $context_default;
    $element['#attached']['library'][] = array(
      'dnd',
      'library',
    );
    if (isset($element['summary'])) {
      $element['summary']['#attributes']['data-dnd-context'][] = $context_default;
    }
  }

  // Add our custom form element into MEE enabled textarea only.
  if (empty($enables['mee'])) {
    return;
  }
  $element['mee'] = array(
    '#prefix' => '<div class="mee-wrap-editor-library">',
    '#suffix' => '</div>',
    '#attached' => array(
      'library' => array(
        array(
          'mee',
          'library',
        ),
      ),
    ),
    '#element_validate' => array(
      'mee_field_text_validate',
    ),
    '#weight' => 0.5,
    'resource_manager' => array(
      '#theme' => 'mee_resource_manager',
    ),
  );
  $resource_manager = array();

  // 'input' is used instead of 'values' because we need extra items inserted
  // using JavaScript on the client side.
  if (isset($form_state['input'][$context['field']['field_name']][$context['langcode']])) {
    $resource_manager = $form_state['input'][$context['field']['field_name']][$context['langcode']][$context['delta']]['mee']['resource_manager'];
  }
  elseif (isset($element['#entity'])) {
    $item = array();
    _mee_load_resources($element['#entity_type'], $element['#entity'], $context['field'], $context['delta'], $item);
    $resource_manager = $item['mee']['resource_manager'];
  }
  foreach ($resource_manager as $sid => $item) {
    $atom = scald_fetch($sid);
    if (!is_object($atom)) {
      continue;
    }

    // Render the atom to get sanitized values.
    $title = scald_render($atom, 'title');
    $element['mee']['resource_manager'][$sid] = array(
      'title' => array(
        '#markup' => $title,
      ),
      'required' => array(
        '#type' => 'select',
        '#options' => array(
          t('Optional'),
          t('Required'),
        ),
        '#default_value' => $item['required'],
      ),
      'weight' => array(
        '#type' => 'weight',
        '#default_value' => $item['weight'],
      ),
      '#weight' => $item['weight'],
    );
  }

  // And now we add the separator.
  $element['mee']['resource_manager'][0] = array(
    'title' => array(
      '#markup' => t('< Primary / Secondary >'),
    ),
    'required' => array(
      '#markup' => '-',
    ),
    'weight' => array(
      '#type' => 'weight',
      '#prefix' => '<div class="mee-rm-separator">',
      '#suffix' => '</div>',
    ),
    '#weight' => isset($resource_manager[0]['weight']) ? $resource_manager[0]['weight'] : 0,
  );
}

/**
 * Validate callback for mee_field_widget_form.
 */
function mee_field_text_validate($element, &$form_state) {
  foreach ($form_state['field'] as $field_name => $values) {
    foreach ($values as $langcode => $data) {
      if (isset($form_state['values'][$field_name][$langcode]) && is_array($form_state['values'][$field_name][$langcode]) && isset($form_state['values'][$field_name][$langcode][0]['mee']) && isset($form_state['input'][$field_name][$langcode][0]['mee'])) {
        $form_state['values'][$field_name][$langcode][0]['mee'] = $form_state['input'][$field_name][$langcode][0]['mee'];
      }
    }
  }
}

/**
 * Helper function to return a list of supported field.
 *
 * Note that only fields defined in the core Text module (text, text_long,
 * text_with_summary) are eligible due to the actual implementation.
 */
function mee_field_types() {
  return variable_get('mee_field_types', array(
    'text',
    'text_long',
    'text_with_summary',
  ));
}

/**
 * Implements hook_scald_atom_delete().
 */
function mee_scald_atom_delete($atom) {

  // @todo Verify if the deleted atom is required for some nodes, they will be
  // unpublished.
  // Then delete all links in the Resource manager.
  db_delete('mee_resource')
    ->condition('atom_sid', $atom->sid)
    ->execute();
}

/**
 * Implements hook_node_revision_delete().
 */
function mee_node_revision_delete($revision) {

  // Delete all resource associations for this revision
  db_delete('mee_resource')
    ->condition('entity_type', 'node')
    ->condition('entity_id', $revision->nid)
    ->condition('revision_id', $revision->vid)
    ->execute();
}

/**
 * Implements hook_ckeditor_filter_xss_allowed_tags().
 */
function mee_ckeditor_filter_xss_allowed_tags() {

  // The comment "tag" is used to mark parseable atom. It is currently required
  // for the dnd plugin to work. Add it to the CKEditor XSS allowed tags.
  return array(
    '!--',
  );
}

/**
 * Returns HTML for the MEE resource list.
 *
 * @param $variables
 *   An associative array containing:
 *   - resource_manager: A render element representing the MEE resource list.
 */
function theme_mee_resource_manager($variables) {
  $form = $variables['resource_manager'];
  static $count = 0;
  $id = 'mee-resource-manager-' . $count;
  drupal_add_tabledrag($id, 'order', 'sibling', 'mee-rm-weight');
  $count++;
  $header = array(
    t('Title'),
    t('Required'),
    t('Weight'),
  );
  $rows = array();
  foreach (element_children($form) as $key) {
    $form[$key]['weight']['#attributes']['class'] = array(
      'mee-rm-weight',
    );
    $row = array();
    $row[] = drupal_render($form[$key]['title']);
    $row[] = drupal_render($form[$key]['required']);
    $row[] = drupal_render($form[$key]['weight']);
    $rows[] = array(
      'data' => $row,
      'class' => array(
        'draggable',
      ),
    );
  }
  $output = theme('table', array(
    'header' => $header,
    'rows' => $rows,
    'attributes' => array(
      'id' => $id,
      'class' => array(
        'mee-resource-manager',
      ),
    ),
    'caption' => t('Resource Manager'),
  ));
  $output .= drupal_render_children($form);
  return $output;
}

/**
 * Returns the HTML for an atom embedded through the CK widget plugin.
 *
 * This is used by the mee_scald_widgets filter, and leaves a placeholder SAS
 * code for later rendering of the atom.
 *
 * @param $vars
 *   An array with the following key/value pairs:
 *   - atom: the atom,
 *   - context: the rendering context,
 *   - options: the options as a JSON string,
 *   - align: the alignment (left, right, center, none),
 *   - caption: the caption HTML,
 *   - content: the atom content. Note: to account for filter cache, the
 *     'mee_scald_widgets' filter only passes a SAS code here, that gets
 *     replaced with the HTML for the rendered atom in
 *     mee_field_attach_view_alter().
 *   - wysiwyg: TRUE when the atom is displayed within a CKEditor. Defaults to
 *     FALSE.
 *
 * @return string
 *   The HTML for the atom embed.
 */
function theme_mee_widget_embed($vars) {
  $options = array();
  if (isset($vars['options'])) {
    $options = drupal_json_decode($vars['options']);
  }
  $classes = array(
    'dnd-widget-wrapper',
    'context-' . $vars['context'],
    'type-' . $vars['atom']->type,
  );
  if ($vars['align'] != 'none') {
    $classes[] = 'atom-align-' . $vars['align'];
  }
  if (!empty($options['additionalClasses'])) {
    foreach (explode(' ', $options['additionalClasses']) as $class) {
      $classes[] = $class;
    }
  }
  $output = '<div class="' . implode(' ', $classes) . '">';
  $output .= '<div class="dnd-atom-rendered">' . $vars['content'] . '</div>';

  // When displaying a widget within a CKEditor, always include a container div
  // for the editable. Otherwise, only display the caption container if there
  // is a caption.
  if (!empty($vars['caption']) || $vars['wysiwyg']) {

    // Note: The 'dnd-caption-wrapper' class is used by the CKEditor plugin to
    // identify the editable zone and should not be modified by theme overrides.
    $output .= '<div class="dnd-caption-wrapper">' . $vars['caption'] . '</div>';
  }
  $output .= '</div>';
  return $output;
}

/**
 * Ajax callback: returns the expanded HTML atom widget.
 *
 * This URL is used by the "CKEditor 4" widget plugin.
 *
 * @param $atom
 *   The atom.
 *
 * Other parameters, such as context, could also be passed via the querystring.
 */
function mee_ajax_widget_expand($atom) {
  $context = isset($_GET['context']) && in_array($_GET['context'], dnd_scald_wysiwyg_context_slugs()) ? $_GET['context'] : NULL;
  if ($atom->type == 'scald_atom_fallback') {
    $context = 'invalid-id';
  }
  $options = isset($_GET['options']) ? urldecode($_GET['options']) : '';
  $align = isset($_GET['align']) && in_array($_GET['align'], array(
    'left',
    'right',
    'center',
  )) ? $_GET['align'] : 'none';

  // The legend call needs at least the basic atom meta-data
  // to be pre-rendered, so ensure they are present by doing
  // an early render, eventually in the lightweight 'title'
  // context if no explicit context is given.
  $output = $context ? scald_render($atom, $context, $options) : scald_render($atom, 'title');
  if (empty($atom->omit_legend)) {
    $legend = theme('sdl_editor_legend', array(
      'atom' => $atom,
    ));
  }
  else {
    $legend = '';
  }
  $commands = array();
  $commands[] = array(
    'command' => 'dndck4_cache_atom_metadatadata',
    'data' => array(
      'sid' => $atom->sid,
      'meta' => array(
        'title' => $atom->title,
        'type' => $atom->type,
        'data' => !empty($atom->data) ? $atom->data : array(),
        'provider' => $atom->provider,
        'legend' => $legend,
      ),
      'actions' => array_keys(scald_atom_actions_available($atom)),
    ),
  );
  if ($context) {
    $commands[] = array(
      'command' => 'dndck4_expand_widget',
      'data' => theme('mee_widget_embed', array(
        'atom' => $atom,
        'context' => $context,
        'options' => $options,
        'align' => $align,
        'content' => $output,
        'wysiwyg' => TRUE,
      )),
    );
  }
  return array(
    '#type' => 'ajax',
    '#commands' => $commands,
  );
}

/**
 * Tests if MEE is supported and enabled for this field instance.
 */
function _mee_field_instance_enabled($instance, $key = NULL) {
  $enables = array(
    'mee' => FALSE,
    'dnd' => FALSE,
  );
  if (!empty($instance['settings']['mee_enabled'])) {
    $enables['mee'] = TRUE;
  }
  if (!empty($instance['settings']['dnd_enabled'])) {
    $enables['dnd'] = TRUE;
  }
  return $key ? $enables[$key] : $enables;
}

/**
 * Extract entity id, sanitizing revision_id if necessary.
 */
function _mee_extract_id($entity_type, $entity) {
  list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity);

  // The revision_id is part of the primary key, and thus
  // can't be NULL in some databases. Follow Field SQL Storage
  // pattern and use the entity_id as a revision_id.
  if (!isset($revision_id)) {
    $revision_id = $entity_id;
  }
  return array(
    $entity_id,
    $revision_id,
  );
}

/**
 * Extracts sids and copyright from $item. Updates $item if necessary.
 */
function _mee_process_item_value(&$item, $entity_type, $entity, $field, $delta) {
  if (mee_store_format() == 'embed_div') {
    $sids = array();
    $copyrights = array();

    // Collect the emebed data.
    foreach (_mee_extract_widget_embed_info(filter_dom_load($item['value'])) as $info) {
      $sids[] = $info['sid'];
      $copyrights[$info['sid']] = $info['caption'];
    }
    if (!empty($item['summary'])) {
      foreach (_mee_extract_widget_embed_info(filter_dom_load($item['summary'])) as $info) {
        $sids[] = $info['sid'];
        $copyrights[$info['sid']] = $info['caption'];
      }
    }
    $sids = array_unique($sids);
  }
  else {

    // $sids contains the list of atom sid actually used in the item.
    $sas = scald_rendered_to_sas($item['value']);
    $scald_included = scald_included($sas);
    $sids = array_unique($scald_included);

    // Parse copyright informations.
    $copyrights = mee_extract_copyrights($item['value']);
  }

  // If $item['mee'] does not hold anything, load default data into it.
  if (empty($item['mee']) || !is_array($item['mee']['resource_manager'])) {
    _mee_load_resources($entity_type, $entity, $field, $delta, $item);
  }

  // Finally, if there was unknown client-side problem, we might not have new
  // inserted resources. We set default value for them.
  foreach ($sids as $sid) {
    if (!isset($item['mee']['resource_manager'][$sid])) {
      $item['mee']['resource_manager'][$sid] = array(
        'required' => FALSE,
        'weight' => 0,
      );
    }
  }
  return array(
    $sids,
    $copyrights,
  );
}

/**
 * Load used resource in an entity into an array.
 *
 * @param $item renderable array to render the field
 */
function _mee_load_resources($entity_type, $entity, $field, $delta, &$item) {
  list($id, $revision_id) = _mee_extract_id($entity_type, $entity);
  $result = db_select('mee_resource', 'r')
    ->fields('r', array(
    'atom_sid',
    'weight',
    'required',
  ))
    ->condition('entity_type', $entity_type)
    ->condition('entity_id', $id)
    ->condition('revision_id', $revision_id)
    ->condition('field', $field['field_name'])
    ->condition('delta', $delta)
    ->execute();
  $item['mee']['resource_manager'] = array();
  $item['mee']['resource_manager'] = $result
    ->fetchAllAssoc('atom_sid', PDO::FETCH_ASSOC);
  $item['mee']['resource_manager'][0] = array(
    'weight' => 0,
    'required' => FALSE,
  );
}

/**
 * Extract all copyright informations from a string.
 */
function mee_extract_copyrights($string) {
  $copyrights = array();
  if (preg_match_all(MEE_RENDERED_COPYRIGHT_PATTERN, $string, $matches)) {
    foreach ($matches[1] as $key => $sid) {
      $copyrights[$sid] = $matches[2][$key];
    }
  }
  return $copyrights;
}

/**
 * Implements hook_wysiwyg_include_directory().
 */
function mee_wysiwyg_include_directory($type) {
  switch ($type) {
    case 'plugins':
      return $type;
  }
}

/**
 * Implements hook_filter_info().
 */
function mee_filter_info() {
  $filters['mee_scald_widgets'] = array(
    'title' => t('Embedded atoms'),
    'description' => t('This is only needed when using the Scald plugin for CKEditor 4'),
    'process callback' => 'mee_filter_process',
  );
  return $filters;
}

/**
 * Process callback for the 'mee_scald_widgets' filter.
 *
 * This expends the embed marker into the themed "atom embed" markup. The atom
 * itself is not rendered yet, since that is not cacheable. Instead, a
 * placeholder SAS code is left, that will be replaced in
 * mee_field_attach_view_alter().
 *
 * @param string $text
 *   The text to process.
 *
 * @return string
 *   The processed text.
 */
function mee_filter_process($text) {

  // Work on the string as a DOM structure.
  $dom = filter_dom_load($text);

  // Collect the DOM nodes and the corresponding embed data.
  if ($embed_info = _mee_extract_widget_embed_info($dom)) {

    // Collect the corresponding atom ids and load them upfront to benefit from
    // multiple-loading.
    $sids = array();
    foreach ($embed_info as $info) {
      $sids[] = $info['sid'];
    }
    $atoms = scald_atom_load_multiple(array_unique($sids));

    // Replace each DOM node with the themed embed.
    foreach ($embed_info as $info) {
      $html = '';
      if (isset($atoms[$info['sid']])) {
        $html = theme('mee_widget_embed', array(
          'atom' => $atoms[$info['sid']],
          'context' => $info['context'],
          'options' => $info['options'],
          'align' => $info['align'],
          'caption' => $info['caption'],
          // Only store a SAS code in the filter cache, that will get replaced
          // with the HTML for the rendered atom in
          // mee_field_attach_view_alter().
          'content' => '[scald=' . $info['sid'] . ':' . $info['context'] . ($info['options'] ? ' ' . $info['options'] : '') . ']',
        ));
      }
      $node = $info['node'];
      $fragment = $dom
        ->createDocumentFragment();
      $fragment
        ->appendXML($html);
      $node->parentNode
        ->replaceChild($fragment, $node);
    }
    $text = filter_dom_serialize($dom);
  }
  return $text;
}

/**
 * Extract information about atom embeds found in a DOMDocument.
 *
 * @param DOMDocument $dom
 *   The DOMDocument.
 *
 * @return array
 *   Information about atom embeds found in $dom. Each value is an array with
 *   the following key/value pairs:
 *   - node: the DOMNode containing the embed.
 *   - sid: the atom id.
 *   - align: the embed alignment (left, right, center, none).
 *   - context: the atom render context.
 *   - options: the atom render options.
 *   - caption: the embed caption.
 */
function _mee_extract_widget_embed_info($dom) {
  $embed_info = array();

  // Collect the DOM nodes and the corresponding embed data.
  $xpath = new DOMXPath($dom);
  $nodes = $xpath
    ->query("//div[@class='dnd-atom-wrapper']|//figure[@class='dnd-atom-wrapper']|//span[@class='dnd-atom-wrapper']");
  foreach ($nodes as $node) {
    $info = array(
      'node' => $node,
      'sid' => $node
        ->getAttribute('data-scald-sid'),
      'align' => $node
        ->getAttribute('data-scald-align'),
      'context' => $node
        ->getAttribute('data-scald-context'),
      'options' => urldecode($node
        ->getAttribute('data-scald-options')),
      'caption' => '',
    );

    // Extract the caption if present.
    $result = $xpath
      ->query("div[@class='dnd-caption-wrapper']|figcaption[@class='dnd-caption-wrapper']", $node);
    if ($result->length) {
      foreach ($result
        ->item(0)->childNodes as $child) {
        $info['caption'] .= $dom
          ->saveXML($child);
      }
    }
    $embed_info[] = $info;
  }
  return $embed_info;
}

Functions

Namesort descending Description
mee_ajax_widget_expand Ajax callback: returns the expanded HTML atom widget.
mee_ckeditor_filter_xss_allowed_tags Implements hook_ckeditor_filter_xss_allowed_tags().
mee_ckeditor_plugin Implements hook_ckeditor_plugin().
mee_editor_ckeditor_plugins Implements hook_editor_ckeditor_plugins().
mee_extract_copyrights Extract all copyright informations from a string.
mee_field_attach_view_alter Implements hook_field_attach_view_alter.
mee_field_info_alter Implements hook_field_info_alter().
mee_field_text_validate Validate callback for mee_field_widget_form.
mee_field_types Helper function to return a list of supported field.
mee_field_widget_form_alter Implements hook_field_widget_form_alter().
mee_filter_info Implements hook_filter_info().
mee_filter_process Process callback for the 'mee_scald_widgets' filter.
mee_form_alter Implements hook_form_alter().
mee_form_ctools_custom_content_type_edit_form_alter Implements hook_form_FORM_ID_alter().
mee_library Implements hook_library().
mee_menu Implements hook_menu().
mee_node_revision_delete Implements hook_node_revision_delete().
mee_panels_pane_content_alter Implements hook_panels_pane_content_alter().
mee_scald_atom_delete Implements hook_scald_atom_delete().
mee_store_format Returns the current storage format for embedded atoms.
mee_theme Implements hook_theme().
mee_views_api Implements hook_views_api().
mee_wysiwyg_include_directory Implements hook_wysiwyg_include_directory().
mee_wysiwyg_plugin Implements hook_wywiwyg_plugin().
text_field_delete Implements hook_field_delete() on behalf of Text module.
text_field_insert Implements hook_field_insert() on behalf of Text module.
text_field_presave Implements hook_field_presave() on behalf of Text module.
text_field_update Implements hook_field_update() on behalf of Text module.
theme_mee_resource_manager Returns HTML for the MEE resource list.
theme_mee_widget_embed Returns the HTML for an atom embedded through the CK widget plugin.
_mee_extract_id Extract entity id, sanitizing revision_id if necessary.
_mee_extract_widget_embed_info Extract information about atom embeds found in a DOMDocument.
_mee_field_instance_enabled Tests if MEE is supported and enabled for this field instance.
_mee_load_resources Load used resource in an entity into an array.
_mee_process_item_value Extracts sids and copyright from $item. Updates $item if necessary.

Constants

Namesort descending Description
MEE_RENDERED_COPYRIGHT_PATTERN @file Defines a special textarea, with drag and drop media driven by Scald and dnd.module.