You are here

editablefields.module in Editable Fields 6.3

Editable fields module.

File

editablefields.module
View source
<?php

/**
 * @file
 * Editable fields module.
 */

/**
 * Implementation of hook_perm.
 *
 * @return array
 * An array of permissions that this module provides.
 */
function editablefields_perm() {
  return array(
    'administer editablefields',
  );
}

/**
 * Implementation of hook_ctools_plugin_directory().
 *
 * @param $module
 * @param $plugin_type
 * @return string
 */
function editablefields_ctools_plugin_directory($module, $plugin_type) {
  if ($plugin_type == 'export_ui') {
    return "plugins/export_ui";
  }
  if ($module == 'editablefields' && !empty($plugin_type)) {
    return "plugins/{$plugin_type}";
  }
}

/**
 * Implementation of hook_ctools_plugin_api().
 *
 * Tell Ctools that we support the default_editablefields_presets API.
 */
function editablefields_ctools_plugin_api($owner, $api) {
  if ($owner == 'editablefields' && $api == 'default_editablefields_presets') {
    return array(
      'version' => 1,
    );
  }
}

/**
 * Implementation of hook_default_editablefields_preset().
 *
 * Provide a couple of default presets.
 */
function editablefields_default_editablefields_preset() {
  $export = array();
  $export['modal'] = (object) array(
    'api_version' => 1,
    'name' => 'modal',
    'description' => 'Display the field form in a Modal Dialog',
    'config' => array(
      'editable_type' => 'modal',
    ),
  );
  $export['html'] = (object) array(
    'api_version' => 1,
    'name' => 'html',
    'description' => 'Display the field form inline',
    'config' => array(
      'editable_type' => 'html',
    ),
  );
  return $export;
}

/**
 * Implementation of hook_menu_alter().
 */
function editablefields_menu_alter(&$items) {

  // takeover the filefield ahah callback. This is a temporary hackish solution.
  if (isset($items['filefield/ahah/%/%/%'])) {
    $items['filefield/ahah/%/%/%'] = array(
      'page callback' => 'editablefields_filefield_js',
    ) + $items['filefield/ahah/%/%/%'];
  }
}

/**
 * Implementation of hook_menu().
 */
function editablefields_menu() {
  $items = array();

  // Admin pages:
  $items['editablefields_view'] = array(
    'page callback' => 'editablefields_view',
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
    'title' => 'ajax view',
  );
  $items['editablefields_html'] = array(
    'page callback' => 'editablefields_html',
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
    'title' => 'ajax form',
  );
  $items['editablefields_submit'] = array(
    'page callback' => 'editablefields_submit',
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
    'title' => 'ajax submit',
  );
  $plugins = editablefields_get_responders();
  foreach ($plugins as $plugin_name => $plugin) {
    foreach ($plugin['hook_menu'] as $plugin_item_path => $plugin_item) {
      $items[$plugin_item_path] = $plugin_item + array(
        'title' => $plugin['title'],
        'file' => $plugin['file'],
        'file path' => $plugin['path'],
        'type' => MENU_CALLBACK,
      );
    }
  }
  return $items;
}

/**
 * Implementation of hook_theme().
 */
function editablefields_theme() {
  $items = array();
  $plugins = editablefields_get_editables();
  foreach ($plugins as $plugin_name => $plugin) {
    if (!empty($plugin['theme'])) {
      $items = array_merge($items, $plugin['theme']);
    }
  }
  return $items;
}

/*
function editablefields_theme_registry_alter(&$items) {
  $items['views_view_field']['function'] = 'editablefields_views_view_field';
}
*/

/**
 * Implementation of hook_field_formatter_info().
 */
function editablefields_field_formatter_info() {
  $items = array();
  $plugins = editablefields_get_editables();
  foreach ($plugins as $plugin_name => $plugin) {
    if (!empty($plugin['formatter_info'])) {
      $items = array_merge($items, $plugin['formatter_info']);
    }
  }
  return $items;
}

/**
 * Implementation of hook_content_build_modes().
 *
 * @return array
 */
function editablefields_content_build_modes() {

  // Tell CCK about our "build" modes so that users can specify
  // how they want the text in their modal/other link to be formatted
  // on the Display Fields tab of the CCK admin.
  $build_modes = array();
  $plugins = editablefields_get_responders();
  if (!empty($plugins)) {
    $build_modes = array(
      'editablefields' => array(
        'title' => t('Editable'),
        'build modes' => array(),
      ),
    );
    foreach ($plugins as $plugin_name => $plugin) {
      foreach ($plugin['build modes'] as $mode_name => $mode_info) {
        $build_modes['editablefields']['build modes'][$mode_name] = $mode_info;
      }
    }
  }
  return $build_modes;
}

/**
 * Implementation of hook_form_alter().
 */
function editablefields_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'content_display_overview_form') {

    // Remove editable field formatters from the options.
    if ($form['#contexts'] == 'editablefields') {
      $formatters = editablefields_field_formatter_info();
      foreach ($form as $key => &$element) {
        if (substr($key, 0, 6) == 'field_') {
          foreach ($element as $setting_name => &$setting) {
            if (!empty($setting['format'])) {
              foreach ($formatters as $formatter_name => $formatter) {
                unset($setting['format']['#options'][$formatter_name]);
                unset($setting['format']['base']['#options'][$formatter_name]);
              }
            }
          }
        }
      }
    }
  }
}

/**
 * Editablefields version of replace CTools command.
 * It uses the
 *
 * The replace command will replace a portion of the current document
 * with the specified HTML.
 *
 * @param $html
 *   The data to use with the jquery replace() function.
 *
 * @return array
 */
function ctools_ajax_command_replace_active_element($index, $html) {
  return array(
    'command' => 'replace_active_element',
    'active_elem_index' => $index,
    'data' => $html,
  );
}

/**
 * Theme the editable field.
 */
function theme_editablefields_formatter_editable($element) {
  static $js_ready;
  $field_name = $element['#field_name'];
  $field = content_fields($field_name);
  $revision = !empty($element['#node']->vid) ? $element['#node']->vid : NULL;
  $node = node_load($element['#node']->nid, $revision);
  $delta = $element['#item']['#delta'];

  // to disable overriding textareas just set this variable to 0
  if (variable_get('editablefields_textareas_resizable_threshold', 25)) {
    static $textareas_count = 0;
    if ($field['type'] == 'text' && $field['widget']['type'] == 'text_textarea') {
      if ($textareas_count++ >= variable_get('editablefields_textareas_resizable_threshold', 25)) {
        drupal_add_js(drupal_get_path('module', 'editablefields') . '/editablefields_overrides.js');
      }
    }
  }

  // See if access to this form element is restricted,
  // if so, skip widget processing and just set the value.
  if (!node_access('update', $node) || !content_access('edit', $field)) {

    // can't edit
    $formatter_name = 'default';
    if ($formatter = _content_get_formatter($formatter_name, $field['type'])) {
      $theme = $formatter['module'] . '_formatter_' . $formatter_name;
      return theme($theme, $element);
    }
  }
  else {
    $formatter_name = 'default';
    if ($formatter = _content_get_formatter($formatter_name, $field['type'])) {
      if (!isset($js_ready)) {
        $js_ready = TRUE;
        drupal_add_js('misc/jquery.form.js');
        drupal_add_js(drupal_get_path('module', 'editablefields') . '/editablefields.js');
        drupal_add_css(drupal_get_path('module', 'editablefields') . '/editablefields.css');
        $settings = array(
          'url_html' => url('editablefields_html', array(
            'absolute' => TRUE,
          )),
          'url_submit' => url('editablefields_submit', array(
            'absolute' => TRUE,
          )),
          'url_view' => url('editablefields_view', array(
            'absolute' => TRUE,
          )),
          'clicktoedit_message' => theme('editablefields_clicktoedit_message'),
          'clicktoedit_message_empty' => theme('editablefields_clicktoedit_message_empty'),
        );
        drupal_add_js(array(
          'editablefields' => $settings,
        ), 'setting');
      }

      // [andreiashu] not sure how this works exactly but it does...
      if (content_handle('widget', 'multiple values', $field) != CONTENT_HANDLE_CORE) {
        if ($delta != 0) {
          return;
        }
      }
      $theme = $formatter['module'] . '_formatter_' . $formatter_name;
      $class = "editablefields";
      if ($element['#formatter'] == 'editable') {
        $class .= " ajax-editable";
      }
      elseif ($element['#formatter'] == 'clicktoedit') {
        $class .= " clicktoedit";
      }
      elseif ($element['#formatter'] == 'editable_html') {
        $class .= " editablefields-html-load";
      }
      $pre = '<div class="' . $class . '" nid="' . $node->nid . '" field="' . $field_name . '" delta="' . $delta . '">';
      $post = '</div>';
      if ($element['#formatter'] != 'editable_html') {
        $themed_element = theme($theme, $element);

        // add the edit link for clicktoedit formatters
        if ($element['#formatter'] == 'clicktoedit') {
          if (!empty($themed_element)) {
            $pre .= theme('editablefields_clicktoedit_message');
          }
          else {
            $pre .= theme('editablefields_clicktoedit_message_empty');
          }
        }
        return $pre . $themed_element . $post;
      }
      else {

        // $node seems to be incomplete, so we reload it
        $node = node_load($node->nid, $revision);
        return $pre . drupal_get_form('editablefields_form', $node, $field_name, $delta) . $post;
      }
    }
  }
}
function theme_editablefields_clicktoedit_message() {
  return '<span class="editablefields_clicktoedit_message editablefields-hide">' . t('[edit]') . '</span>';
}
function theme_editablefields_clicktoedit_message_empty() {
  return '<span class="editablefields_clicktoedit_message">' . t('[edit]') . '</span>';
}

/**
 * Implementation of hook_forms().
 */
function editablefields_forms() {
  $forms = array();
  $forms['editablefields_form'] = array(
    'callback' => 'editablefields_form_builder',
  );
  return $forms;
}

/**
 * Form builder callback.
 */
function editablefields_form_builder(&$form_state, $node, $field_name, $delta) {
  $field = content_fields($field_name);
  $form = array(
    '#node' => $node,
  );

  //  $form_state = array('values' => array($field['field_name'] => $default_value));
  module_load_include('inc', 'content', 'includes/content.node_form');
  $form['#field_info'] = array(
    $field['field_name'] => $field,
  );
  $form = content_field_form($form, $form_state, $field, $delta);
  unset($form[$field_name]['#title']);
  if (is_array($form[$field_name][0]) && !is_array($form[$field_name][1])) {
    unset($form[$field_name][0]['#title']);
  }
  $form['editablefields_node_nid'] = array(
    '#type' => 'hidden',
    '#value' => $node->nid,
  );
  $form['#field_info'] = array(
    $field['field_name'] => $field,
  );
  $form['#pre_render'] = array(
    '_editablefields_pre_render',
  );
  return $form;
}

/**
 * Resizable Textarea has an issue with setting focus.
 * To circumvent this we have to set the forms fields #resizable field to FALSE.
 * We have to use pre_render because the here the field's #type is still textarea.
 * TODO Keep editable_HTML and editable_JAVA fields resizable, they don't have the focus issue.
 */
function _editablefields_pre_render(&$form) {
  if ($form['field_name'][0]['value']['#type'] == 'textarea') {
    $form['field_name'][0]['value']['#resizable'] = FALSE;
  }
  return $form;
}

/**
 * Menu callback: ajax view.
 */
function editablefields_view() {
  $output = '';
  $nid = arg(1);
  $field_name = arg(2);
  $delta = arg(3);
  $node = node_load($nid);
  drupal_set_header('Content-Type: text; charset=utf-8');

  //  $html = node_view($node, FALSE, FALSE, FALSE);
  // this required traversing the entire node to get the field (and doesn't work
  // for checkboxes anyway)
  $field = content_fields($field_name, $node->type);
  $field['display_settings']['label']['format'] = 'hidden';

  // We have 2 reasonable choices here. We COULD use 'clicktoedit' and end up
  // with the same HTML - then we could strip that HTML down to remove the
  // surrounding 'click to edit' div (which we dont want, as we'll be replacing
  // the html inside one of those div's)
  // or we can simply use the 'defualt' formatter - which wont have the click to
  // edit inside it  - and is hard coded into this module (for now) anyway!
  $field['display_settings']['full']['format'] = 'default';
  $html = content_view_field($field, $node);
  $messages = drupal_get_messages('status', TRUE);
  if (count($messages) > 0) {
    foreach ($messages as $type => $messages) {
      foreach ($messages as $message) {
        $output .= '<div class="messages ' . $type . '">' . $message . "</div>";
      }
    }
  }
  $object = new stdClass();
  $object->content = $output . $html . drupal_render($node->content[$field_name]);
  drupal_json($object);
  exit;
}

/**
 * Menu callback: ajax form.
 */
function editablefields_html() {
  $nid = arg(1);
  $field_name = arg(2);
  $delta = arg(3);
  $node = node_load($nid);
  if (node_access('update', $node)) {

    //  $html = _editablefields_create_form($node, $field_name);
    $html = drupal_get_form('editablefields_form', $node, $field_name, $delta);
    $object = new stdClass();
    $object->content = $html;

    // Register the JavaScript callback for this module.
    $object->__callbacks = array();

    // Allow other modules to extend the data returned.
    drupal_alter('ajax_data', $object, 'editablefields', $html);
    drupal_json($object);
  }
  else {
    drupal_not_found();
  }
  exit;
}

/**
 * Menu callback: ajax submit.
 */
function editablefields_submit() {
  $nid = $_POST['nid'];
  $field_name = $_POST['field'];
  $delta = $_POST['delta'];
  $node = node_load($nid);
  $node_options = variable_get('node_options_' . $node->type, array(
    'status',
    'promote',
  ));
  $node->revision = in_array('revision', $node_options);
  if ($node->revision) {
    $node->log = t('%field_name updated by editablefields.', array(
      '%field_name' => $field_name,
    ));
  }
  if (node_access('update', $node)) {
    if (!isset($_POST[$field_name])) {
      $_POST[$field_name] = array();
    }
    $form_state = array(
      'values' => $_POST,
    );

    /* it seems that the serializer does not serialize e.g. un-checked
     * checkboxes. Leaving them as empty arrays. This FILTHY hack fills in the
     * array with 'something' so that when the form is executed, it fills in the
     * right value - I dislike this code - JMB */
    if (is_array($node->{$field_name})) {
      $field = content_fields($field_name, $node->type);
      $items =& $form_state['values'][$field_name];
      if (empty($items)) {
        foreach (array_keys($field['columns']) as $column) {
          if ($field['multiple']) {
            $items[$delta][$column][] = NULL;
          }
          else {
            $items[$column] = NULL;
          }
        }
      }
      if (isset($items['value'])) {
        if (!($field['widget']['type'] == 'optionwidgets_buttons' && $field['multiple'])) {
          $items = array(
            $items,
          );
        }
      }

      // go through content_set_empty if this is NOT a checkbox multi valued element
      if (!($field['widget']['type'] == 'optionwidgets_buttons' && $field['multiple'])) {
        $items = content_set_empty($field, $items);
      }
      switch ($field['type']) {
        case 'nodereference':
        case 'userreference':
          if ($field['multiple']) {
            reset($field['columns']);
            $items[key($field['columns'])] = array_pop($items);
          }
          break;
      }
      drupal_execute('editablefields_form', $form_state, $node, $field_name, $delta);
      $err = drupal_get_messages();
      if (count($err) > 0) {
        drupal_set_header('HTTP/1.1 404 Not Found');

        // format the error message suitable for a popup window in simple text.
        foreach ($err as $type => $messages) {
          foreach ($messages as $message) {
            print $type . ' : ' . $message . "\n";
          }
        }
        exit;
      }

      // the matrix field identifies itself as being multivalue, but in fact, it is not.
      if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE && $field['type'] != matrix) {
        if ($node->{$field_name}[$delta] != $form_state['values'][$field_name][0]) {
          $node->{$field_name}[$delta] = $form_state['values'][$field_name][0];
          node_save($node);
        }
      }
      else {
        if ($node->{$field_name} != $form_state['values'][$field_name]) {
          $node->{$field_name} = $form_state['values'][$field_name];
          node_save($node);
        }
      }

      // make sure sensible headers etc are sent...
      drupal_set_header('Content-Type: text; charset=utf-8');
    }
    else {
      drupal_set_header('HTTP/1.1 404 Not Found');
      print t('No field found, of name: %field', array(
        '%field' => $field_name,
      ));
    }
  }
  else {
    drupal_set_header('HTTP/1.1 404 Not Found');
    print t('No write permissions for %field', array(
      '%field' => $field_name,
    ));
  }
  exit;
}

/**
 * If the Remove button was clicked remove the file from the node.
 */
function editablefields_filefield_js($type_name, $field_name, $delta) {
  if (!empty($_POST['editablefields_node_nid']) && ($node = node_load($_POST['editablefields_node_nid']))) {
    $remove_button_clicked = FALSE;
    foreach ($_POST as $key => $value) {

      // check if the remove button was clicked
      if (stripos($key, $field_name) === 0 && stripos($key, '_filefield_remove') !== FALSE) {
        $remove_button_clicked = TRUE;
        break;
      }
    }
    if ($remove_button_clicked) {
      $field = $_POST[$field_name];
      $field = array_pop($field);
      $file = (object) field_file_load($field['fid']);

      // respect revision settings
      $node_options = variable_get('node_options_' . $node->type, array(
        'status',
        'promote',
      ));
      $node->revision = in_array('revision', $node_options);
      if ($node->revision) {
        $node->log = t('%field_name updated by editablefields.', array(
          '%field_name' => $field_name,
        ));
      }
      $file->field_name = $field_name;
      $references = field_file_delete($file);
      $node->{$field_name}[$delta] = array();
      node_save($node);
      drupal_set_message(t('File %filename was removed from node.', array(
        '%filename' => $file->filename,
      )));
    }
  }
  filefield_js($type_name, $field_name, $delta);
}

/**
 * Implementation of hook_file_insert().
 */
function editablefields_file_insert($file) {
  if (stripos($_GET['q'], 'filefield/ahah/') === 0 && !empty($_POST['editablefields_node_nid']) && ($node = node_load($_POST['editablefields_node_nid']))) {
    $file_clone = (array) $file;
    field_file_save($node, $file_clone);
    $q_arr = explode('/', $_GET['q']);
    $field_name = $q_arr[3];
    $delta = $q_arr[4];
    $node->{$field_name}[$delta] = array(
      'fid' => $file->fid,
    );

    // respect revision settings
    $node_options = variable_get('node_options_' . $node->type, array(
      'status',
      'promote',
    ));
    $node->revision = in_array('revision', $node_options);
    if ($node->revision) {
      $node->log = t('%field_name updated by editablefields.', array(
        '%field_name' => $field_name,
      ));
    }
    node_save($node);
  }
}
function editablefields_formatter_get_settings($field_name, $type_name, $context) {
  $options = array();
  $value = 'editablefields:' . $type_name . ':' . $context . ':' . $field_name;
  $options['editablefields'] = variable_get($value . '_text_formatter', 'default');

  /*
  $options['repeat']['show_repeat_rule'] = variable_get($value .'_show_repeat_rule', 'show');
  $options['multiple']['multiple_number'] = variable_get($value .'_multiple_number', '');
  $options['multiple']['multiple_from'] = variable_get($value .'_multiple_from', '');
  $options['multiple']['multiple_to'] = variable_get($value .'_multiple_to', '');
  $options['fromto']['fromto'] = variable_get($value .'_fromto', 'both')
  */
  return $options;
}

/**
 * Returns all 'editables' plugins.
 *
 * @return array
 */
function editablefields_get_responders() {
  ctools_include('plugins');
  $plugins = ctools_get_plugins('editablefields', 'responders');
  foreach ($plugins as &$plugin) {

    // add defaults
    $plugin += array(
      'hook_menu' => array(),
      'formatter_info' => array(),
    );
  }
  return $plugins;
}
function editablefields_get_editables() {
  ctools_include('plugins');
  $plugins = ctools_get_plugins('editablefields', 'editables');
  return $plugins;
}

/**
 * Menu wildcard argument callback.
 */
function editablefields_object_load($object_id, $editable) {
  $editables = editablefields_get_editables();
  $function = $editables[$editable]['entity callback'];
  return $function($object_id);
}

/**
 * Implementation of hook_views_api().
 */
function editablefields_views_api() {
  return array(
    'api' => 2,
    'path' => drupal_get_path('module', 'editablefields') . '/includes/views',
  );
}
function editablefields_views_option_form(&$form, &$form_state, $editable) {
  $form['editablefields_allowed'] = array(
    '#title' => t('Make field editable'),
    '#description' => t('This will override any other link you have set.'),
    '#type' => 'checkbox',
    '#default_value' => '',
  );
  $responders = editablefields_get_responders();
  $options = array();
  foreach ($responders as $responder) {
    $options[$responder['name']] = $responder['title'];
  }
  $form['editablefields_responder'] = array(
    '#title' => t('Editable Behavior'),
    '#description' => t('How would you like to edit this field?'),
    '#type' => 'select',
    '#options' => $options,
    '#dependency' => array(
      'edit-options-editablefields-allowed' => array(
        '1',
      ),
    ),
    '#process' => array(
      'views_process_dependency',
    ),
  );
  $form['editablefields_editable'] = array(
    '#type' => 'hidden',
    '#value' => $editable,
  );
}

Functions

Namesort descending Description
ctools_ajax_command_replace_active_element Editablefields version of replace CTools command. It uses the
editablefields_content_build_modes Implementation of hook_content_build_modes().
editablefields_ctools_plugin_api Implementation of hook_ctools_plugin_api().
editablefields_ctools_plugin_directory Implementation of hook_ctools_plugin_directory().
editablefields_default_editablefields_preset Implementation of hook_default_editablefields_preset().
editablefields_field_formatter_info Implementation of hook_field_formatter_info().
editablefields_filefield_js If the Remove button was clicked remove the file from the node.
editablefields_file_insert Implementation of hook_file_insert().
editablefields_formatter_get_settings
editablefields_forms Implementation of hook_forms().
editablefields_form_alter Implementation of hook_form_alter().
editablefields_form_builder Form builder callback.
editablefields_get_editables
editablefields_get_responders Returns all 'editables' plugins.
editablefields_html Menu callback: ajax form.
editablefields_menu Implementation of hook_menu().
editablefields_menu_alter Implementation of hook_menu_alter().
editablefields_object_load Menu wildcard argument callback.
editablefields_perm Implementation of hook_perm.
editablefields_submit Menu callback: ajax submit.
editablefields_theme Implementation of hook_theme().
editablefields_view Menu callback: ajax view.
editablefields_views_api Implementation of hook_views_api().
editablefields_views_option_form
theme_editablefields_clicktoedit_message
theme_editablefields_clicktoedit_message_empty
theme_editablefields_formatter_editable Theme the editable field.
_editablefields_pre_render Resizable Textarea has an issue with setting focus. To circumvent this we have to set the forms fields #resizable field to FALSE. We have to use pre_render because the here the field's #type is still textarea. TODO Keep editable_HTML and…