You are here

bulk_photo_nodes.module in Bulk File Nodes 7

hooks and helper functions for bulk photo node.

File

bulk_photo_nodes.module
View source
<?php

/**
 * @file
 * hooks and helper functions for bulk photo node.
 */

/**
 * Implements hook_permission().
 */
function bulk_photo_nodes_permission() {
  return array(
    'create bulk photo nodes' => array(
      'title' => t('Create bulk photo nodes'),
    ),
  );
}

/**
 * Implements hook_menu().
 */
function bulk_photo_nodes_menu() {
  $items = array();
  $bpn_var = variable_get('bulk_photo_node_types');
  if (!empty($bpn_var)) {

    // Taken from node.module
    node_type_cache_reset();
    foreach (node_type_get_types() as $type) {
      if (!empty($bpn_var[$type->type]) && $bpn_var[$type->type] !== 'none') {
        $type_url_str = str_replace('_', '-', $type->type);
        $items['node/add/' . $type_url_str . '/bulk'] = array(
          'title' => 'Upload Images',
          'title callback' => 'check_plain',
          'page callback' => 'bulk_photo_nodes_page_get',
          'page arguments' => array(
            $type->type,
          ),
          'access callback' => 'user_access',
          'access arguments' => array(
            'create bulk photo nodes',
          ),
          'description' => $type->description,
          'file' => 'node.pages.inc',
          'file path' => drupal_get_path('module', 'node'),
        );
      }
    }
  }
  return $items;
}

/**
 * Implements hook_menu_alter().
 *
 * Replaces standard node add pages with bulk pages.
 */
function bulk_photo_nodes_menu_alter(&$items) {
  $bpn_var = variable_get('bulk_photo_node_types');
  if (!empty($bpn_var)) {
    foreach (entity_get_info() as $entity_type => $entity_info) {
      if ($entity_type !== 'node') {
        continue;
      }
      foreach (array_keys($entity_info['bundles']) as $bundle) {
        if (isset($bpn_var[$bundle]) && !empty($bpn_var[$bundle . '_override']) && (!empty($bpn_var[$bundle]) || $bpn_var[$bundle] !== 'none')) {
          $node_add_path = 'node/add/' . strtr($bundle, '_', '-');
          $items[$node_add_path]['page callback'] = 'bulk_photo_nodes_page';
          $items[$node_add_path]['page arguments'] = array(
            $bundle,
          );
          $items[$node_add_path]['access callback'] = 'user_access';
          $items[$node_add_path]['access arguments'] = array(
            'create bulk photo nodes',
          );
        }
      }
    }
  }
}

/**
 * Implements hook_form_node_type_form_alter().
 */
function bulk_photo_nodes_form_node_type_form_alter(&$form, &$form_state, $form_id) {
  $form['bulk_photo_nodes'] = array(
    '#tree' => TRUE,
    '#type' => 'fieldset',
    '#group' => 'additional_settings',
    '#title' => 'Bulk Photo Node Settings',
  );
  $form['bulk_photo_nodes']['image_field'] = array(
    '#type' => 'select',
    '#title' => 'Choose a field for the image',
    '#default_value' => bulk_photo_nodes_get_image_field($form['#node_type']->type),
    '#options' => bulk_photo_nodes_get_image_fields($form['#node_type']->type),
  );

  // Build path for use in description.
  $path = 'node/add/' . strtr($form['#node_type']->type, '_', '-') . '/bulk';

  // Get setting node option.
  $option = bulk_photo_nodes_get_option($form['#node_type']->type);

  // If never set before, default to on.
  $option = $option === 'none' ? 1 : $option;
  $bpn_var = variable_get('bulk_photo_node_types');
  $form['bulk_photo_nodes']['override_add_form'] = array(
    '#type' => 'checkbox',
    '#title' => t('Override add form'),
    '#description' => t('By default the Bulk options will override the normal node creation form. Separate bulk path !link is always available.', array(
      '!link' => l($path, $path),
    )),
    '#default_value' => $option,
  );
  $form['#submit'][] = 'bulk_photo_nodes_submit';
}

/**
 * Form submission handler for bulk_photo_nodes_form_node_type_form_alter().
 */
function bulk_photo_nodes_submit($form, &$form_state) {
  $image_field = $form_state['values']['bulk_photo_nodes']['image_field'];
  $override_add_form = $form_state['values']['bulk_photo_nodes']['override_add_form'];
  $node_type = $form['#node_type']->type;
  $orig_bpn_var = variable_get('bulk_photo_node_types', array());
  $new_bpn_var = $orig_bpn_var;

  // We need $bpn_var[$node_type] to be set, as we check it later on.
  if (!isset($bpn_var[$node_type])) {
    $bpn_var[$node_type] = NULL;
  }

  // Always save field even if set to none.
  if ($image_field == 'none') {
    if (array_key_exists($node_type, $new_bpn_var)) {
      unset($new_bpn_var[$node_type]);
    }
  }
  else {
    $new_bpn_var[$node_type] = $image_field;
  }

  // Override the form?
  $new_bpn_var[$node_type . '_override'] = $override_add_form;

  // Only save if something has changed.
  foreach ($new_bpn_var as $key => $val) {
    if (!array_key_exists($key, $orig_bpn_var) || $orig_bpn_var[$key] != $val) {

      // Save as soon as anything's different.
      variable_set('bulk_photo_node_types', $new_bpn_var);
      menu_rebuild();
      drupal_set_message("Bulk photo node settings saved.");

      // No need to keep checking.
      continue;
    }
  }
}

/**
 * Returns a list of image fields for a given content type.
 *
 * @param string $node_type
 *   The name of the content type
 *
 * @return array
 *   An associative array where key = machine name, value = field label
 */
function bulk_photo_nodes_get_image_fields($node_type) {
  $image_fields = array(
    'none' => '- None -',
  );
  $fields_info = field_info_instances('node', $node_type);
  foreach ($fields_info as $field_name => $field_value) {
    $field_info = field_info_field($field_name);
    $type = $field_info['type'];
    if ($type == 'image') {
      $image_fields[$field_name] = $field_value['label'];
    }
  }
  return $image_fields;
}

/**
 * Indicates if a given content type is being used as bulk photo nodes.
 *
 * @param string $node_type
 *   The machine name of a content_type.
 *
 * @return string
 *   The machine name of the image field or 'none' if the content type isn't
 *   used.
 */
function bulk_photo_nodes_get_image_field($node_type) {
  $bpn_var = variable_get('bulk_photo_node_types');
  if (is_array($bpn_var)) {
    return array_key_exists($node_type, $bpn_var) ? $bpn_var[$node_type] : 'none';
  }
  return 'none';
}

/**
 * Get a named option for a given node type.
 *
 * Currently defaults to 'override' as this is the only available option.
 */
function bulk_photo_nodes_get_option($node_type, $option = 'override') {
  $bpn_var = variable_get('bulk_photo_node_types');
  if (is_array($bpn_var)) {
    $key = $node_type . '_' . $option;
    return array_key_exists($key, $bpn_var) ? $bpn_var[$key] : 'none';
  }
  return 'none';
}

/**
 * Page callback: Displays the first step of the bulk photo node upload form.
 *
 * In case the user would like to see the normal node/add form, simply supply
 * a query string of ?override=1
 *
 * @param string $node_type
 *   The content type of the node, used in case of an override.
 *
 * @return array
 *   A render array for the page.
 *
 * @see bulk_photo_nodes_menu_alter()
 */
function bulk_photo_nodes_page($node_type) {
  if (bulk_photo_nodes_is_bpn_add_page($node_type)) {
    return bulk_photo_nodes_page_get($node_type);
  }
  else {
    $form = node_add($node_type);
    return $form;
  }
}

/**
 * Return bulk node add form.
 */
function bulk_photo_nodes_page_get($node_type) {
  drupal_add_css(drupal_get_path('module', 'bulk_photo_nodes') . '/css/bulk_photo_nodes_default.css');
  $content = array();
  $form_names = module_invoke_all('bulk_photo_nodes_method');
  $content_forms = array();
  $chosen_form = FALSE;
  foreach ($form_names as $form_name) {
    $form = drupal_get_form($form_name, $node_type);
    $content_forms[] = $form;
    if (array_key_exists('#bpn_chosen_form', $form)) {
      $chosen_form = $form;
      break;
    }
  }

  // If we've chosen a form, only include that one.
  if ($chosen_form) {
    $content['forms'] = array(
      $chosen_form,
    );
  }
  else {
    $content['forms'] = $content_forms;
  }
  return $content;
}

/**
 * Determines if a given node type's add form should be displayed as a BPN form.
 *
 * @return bool
 *   TRUE if the current node add form  is used by bpn, FALSE otherwise.
 */
function bulk_photo_nodes_is_bpn_add_page($node_type) {
  $query = drupal_get_query_parameters();
  if (!is_array($query) || !array_key_exists('override', $query)) {
    $bpn_var = variable_get('bulk_photo_node_types');
    $has_bpn_field = !empty($bpn_var) && isset($bpn_var[$node_type]) && $bpn_var[$node_type] !== 'none';
    $is_overridden = isset($bpn_var[$node_type . '_override']) && $bpn_var[$node_type . '_override'] != 1;
    if ($has_bpn_field && !$is_overridden) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Form constructor for final step of bpn_multistep_form().
 *
 * @ingroup forms
 */
function bulk_photo_nodes_add_form($form, &$form_state) {
  bulk_photo_nodes_chosen_form('bulk_photo_nodes_add_form');
  drupal_set_title(t('Add Description(s)'));
  $form = array();
  $form['#attached']['js'] = array(
    drupal_get_path('module', 'bulk_photo_nodes') . '/js/bulk_photo_nodes.js',
  );
  $form['#attributes'] = array(
    'class' => array(
      'bpn-enabled',
    ),
  );
  $form['nodes'] = array(
    '#tree' => TRUE,
    '#type' => 'fieldset',
    '#collapsed' => FALSE,
    '#prefix' => '<div id="bpn-nodes">' . '<p class="bpn-inline-help"><em>' . t('To apply the same descriptions to all photos, use Batch Settings on the right') . '<p></em>',
    '#suffix' => '</div>',
    '#attributes' => array(
      'class' => array(
        'bpn-left bpn-info clearfix',
      ),
    ),
  );

  // Attach files to form.
  bulk_photo_nodes_get_session_files($form_state);

  // Generate node subforms from files.
  bulk_photo_nodes_create_subform($form, $form_state);
  bulk_photo_nodes_create_overrides($form, $form_state);

  // Handle required/optional logic for fields in file subforms.
  bulk_photo_nodes_required_optional($form, $form_state);
  drupal_alter('bulk_photo_nodes_overrides', $form, $form_state);
  return $form;
}

/**
 * Generate batch override form for bulk_photo_nodes_add_form().
 *
 * This attaches a form that allows a user to fill out a given field one time
 * and have it apply to all bulk_photo_nodes.
 */
function bulk_photo_nodes_create_overrides(&$form, &$form_state) {
  $node = bulk_photo_nodes_create_node($form_state['node_type']);
  $form['override_fields'] = array(
    '#type' => 'container',
    '#tree' => TRUE,
    '#attributes' => array(
      'class' => array(
        'bpn-right clearfix',
      ),
    ),
  );
  $form['override_fields']['fields'] = array(
    '#parents' => array(
      'override_fields',
      'fields',
    ),
  );
  $form['override_fields']['fields']['title_display'] = array(
    '#markup' => '<h2>Batch Settings</h2><p>(applies to all photos, unless overridden)</p>',
    '#weight' => -11,
  );
  field_attach_form('node', $node, $form['override_fields']['fields'], $form_state);
  $form['override_fields']['fields']['title'] = array(
    '#type' => 'textfield',
    '#title' => 'Title',
    '#required' => FALSE,
    '#weight' => -10,
  );
  $image_field = bulk_photo_nodes_get_image_field($node->type);
  unset($form['override_fields']['fields'][$image_field]);
  $form['override_fields']['finish'] = array(
    '#type' => 'submit',
    '#value' => t('Complete Upload'),
    '#submit' => array(
      'bulk_photo_nodes_add_form_submit',
    ),
  );

  // Set all batch fields as optional.
  bulk_photo_nodes_recursive_set_optional($form['override_fields']['fields']);

  // Attach AJAX handlers to all batch override form inputs.
  bulk_photo_nodes_recursive_ajax($form['override_fields']['fields']);
}

/**
 * Creates a dummy node to use with field_attach_form().
 */
function bulk_photo_nodes_create_node($bundle) {
  $node = new stdClass();
  $node->type = $bundle;
  node_object_prepare($node);
  return $node;
}

/**
 * Generates all node subforms in bulk_photo_nodes_add_form().
 *
 * This iterates through all the saved files from previous steps, and creates
 * subforms out of each file.
 */
function bulk_photo_nodes_create_subform(&$form, &$form_state) {
  foreach ($form_state['saved_files'] as $key => $file) {
    $extra_info = !empty($file['extra_info']) ? $file['extra_info'] : array();
    $vars = array(
      'style_name' => 'bulk_photo_nodes',
      'path' => $file['file_object']->uri,
      'attributes' => array(
        'class' => 'bpn-info-node-image',
      ),
    );
    $markup = theme('image_style', $vars);
    $node = bulk_photo_nodes_create_node($form_state['node_type']);
    $form['nodes'][$key] = array(
      '#type' => 'fieldset',
      '#tree' => TRUE,
      '#collapsed' => FALSE,
      '#attributes' => array(
        'class' => array(
          'bpn-info-node',
        ),
      ),
    );
    $form['nodes'][$key]['left'] = array(
      '#type' => 'container',
      '#collapsed' => FALSE,
      '#attributes' => array(
        'class' => array(
          'bpn-info-node-left clearfix',
        ),
      ),
    );
    $form['nodes'][$key]['left']['image'] = array(
      '#type' => 'markup',
      '#markup' => $markup,
    );
    $form['nodes'][$key]['left']["delete_{$key}"] = array(
      '#type' => 'submit',
      '#value' => 'Delete',
      '#name' => $key,
      '#limit_validation_errors' => array(),
      '#submit' => array(
        'bulk_photo_nodes_delete_node',
      ),
      '#attributes' => array(
        'class' => array(
          'bpn-info-node-delete',
        ),
      ),
    );
    $form['nodes'][$key]['right'] = array(
      '#type' => 'fieldset',
      '#parents' => array(
        'nodes',
        $key,
        'right',
      ),
      '#attributes' => array(
        'class' => array(
          'bpn-info-node-right clearfix',
        ),
      ),
    );
    $form['nodes'][$key]['right']['node'] = array(
      '#type' => 'fieldset',
      '#title' => t('Edit Additional Information'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#parents' => array(
        'nodes',
        $key,
        'right',
        'node',
      ),
      '#attributes' => array(
        'class' => array(
          'bpn-info-node-additional',
        ),
      ),
      '#weight' => 10,
    );

    // Manually add a title form item since it's not a field.
    $default_title = array_key_exists('title', $extra_info) ? $extra_info['title'] : _bulk_photo_nodes_title_from_filename($file['file_object']->filename);
    $form['nodes'][$key]['right']['title'] = array(
      '#type' => 'textfield',
      '#title' => 'Title',
      '#default_value' => $default_title,
      '#required' => !empty($form_state['values']['override_fields']['fields']['title']) ? FALSE : TRUE,
      '#weight' => -10,
      '#attributes' => array(
        'class' => array(
          'bpn-info-node-title',
        ),
      ),
    );

    // Attach all fields found in the node form.
    field_attach_form('node', $node, $form['nodes'][$key]['right']['node'], $form_state);

    // Manually attach file from plupload.
    $image_field = bulk_photo_nodes_get_image_field($node->type);
    unset($form['nodes'][$key]['right']['node'][$image_field]);
    $form['nodes'][$key]['right']['node'][$image_field]['und'][0]['#type'] = 'value';
    $form['nodes'][$key]['right']['node'][$image_field]['und'][0]['#value'] = (array) $file['file_object'];

    // Move body field around to proper fieldset.
    $form['nodes'][$key]['right']['body'] = $form['nodes'][$key]['right']['node']['body'];
    $form['nodes'][$key]['right']['node']['body']['#weight'] = 0;
    unset($form['nodes'][$key]['right']['node']['body']);
  }
}

/**
 * Insert batch saved files into bulk_photo_nodes_add_form state.
 */
function bulk_photo_nodes_get_session_files(&$form_state) {
  if (!empty($_SESSION['saved_files'])) {
    $form_state['saved_files'] = $_SESSION['saved_files'];
  }
  if (empty($form_state['saved_files'])) {
    drupal_goto("node/add/{$form_state['node_type']}");
  }
}

/**
 * Set required/optional bulk photo node fields.
 */
function bulk_photo_nodes_required_optional(&$form, &$form_state) {
  if (empty($form_state['values']['override_fields']['fields'])) {
    return;
  }
  $override_node = (object) $form_state['values']['override_fields']['fields'];
  $override_node->type = $form_state['node_type'];
  $parent_fields_all = element_children($form['nodes'][0]['right']);
  $parent_fields = array_diff($parent_fields_all, array(
    'node',
  ));
  foreach (array_keys($form_state['input']['override_fields']['fields']) as $field_name) {

    // Check if batch field has a value.
    $field_has_value = bulk_photo_nodes_check_field_empty($field_name, $override_node);

    // If the batch field has a value, set the respective node subform field
    // as optional.
    foreach (element_children($form['nodes']) as $key) {

      // Check for parent fields first, then fields in the collapsed fieldset.
      // @todo Find cleaner, DRY way to do this.
      if (in_array($field_name, $parent_fields)) {
        if (bulk_photo_nodes_is_required($form['nodes'][$key]['right'][$field_name])) {
          if ($field_has_value) {
            bulk_photo_nodes_recursive_set_optional($form['nodes'][$key]['right'][$field_name]);
          }
          else {
            bulk_photo_nodes_recursive_set_required($form['nodes'][$key]['right'][$field_name]);
          }
        }
      }
      else {
        if (bulk_photo_nodes_is_required($form['nodes'][$key]['right']['node'][$field_name])) {
          if ($field_has_value) {
            bulk_photo_nodes_recursive_set_optional($form['nodes'][$key]['right']['node'][$field_name]);
          }
          else {
            bulk_photo_nodes_recursive_set_required($form['nodes'][$key]['right']['node'][$field_name]);
          }
        }
      }
    }
  }
}

/**
 * AJAX callback for bulk_photo_nodes_add_form().
 */
function bulk_photo_nodes_add_form_ajax(&$form, &$form_state) {
  return $form['nodes'];
}

/**
 * Form submission handler for bulk_photo_nodes_add_form().
 */
function bulk_photo_nodes_add_form_submit($form, &$form_state) {

  // Convenience short aliases.
  $node_type = $form_state['node_type'];
  $nodes =& $form_state['values']['nodes'];
  $node_overrides =& $form_state['values']['override_fields']['fields'];
  $operations = array();
  foreach (array_values($nodes) as $outer_node) {
    $operations[] = array(
      'bulk_photo_nodes_save_node',
      array(
        $node_type,
        $outer_node,
        $node_overrides,
      ),
    );
  }
  $batch = array(
    'title' => t('Saving nodes'),
    'finished' => 'bulk_photo_nodes_batch_finished',
    'operations' => $operations,
  );
  batch_set($batch);
}

/**
 * Batch 'finished' callback used by bulk_photo_nodes_add_form_submit().
 */
function bulk_photo_nodes_batch_finished($success, $results, $operations) {
  if ($success) {
    drupal_set_message(t('@count Photos saved.', array(
      '@count' => count($results),
    )));

    // Delete old image style generated images.
    $image_style = image_style_load('bulk_photo_nodes');
    image_style_flush($image_style);
  }
  else {

    // An error occurred.
    // $operations contains the operations that remained unprocessed.
    $error_operation = reset($operations);
    drupal_set_message(t('An error occurred while processing @operation with arguments : @args', array(
      '@operation' => $error_operation[0],
      '@args' => print_r($error_operation[0], TRUE),
    )));
  }
}

/**
 * Deletes an individual bulk photo node.
 */
function bulk_photo_nodes_delete_node($form, &$form_state) {
  $key = $form_state['triggering_element']['#name'];
  file_delete($form_state['saved_files'][$key]['file_object']);
  unset($form_state['saved_files'][$key]);
  unset($form_state['values']['nodes'][$key]);
  $form_state['removal'] = $key;
  $form_state['rebuild'] = TRUE;
}

/**
 * Batch operation: Saves an individual bulk photo node.
 *
 * @param string $node_type
 *   The content type of the node.
 *
 * @param array $node_fields
 *   The fields of the given node.
 *
 * @param array $node_overrides
 *   Fields that will override $node_fields if they are empty.
 *
 * @return bool
 *   TRUE if the node was saved successfully, FALSE otherwise.
 */
function bulk_photo_nodes_save_node($node_type, $node_fields, $node_overrides, &$context) {

  // Move title and body back.
  $node_fields['right']['node']['body'] = $node_fields['right']['body'];
  $node_fields['right']['node']['title'] = $node_fields['right']['title'];

  // Update batch operation progressbar.
  $context['message'] = t('Saving node "@title"', array(
    '@title' => $node_fields['right']['node']['title'],
  ));
  $context['results'][] = $node_fields['right']['node']['title'];

  // Prepare a new node for saving.
  $node = (object) $node_fields['right']['node'];
  $node->type = $node_type;
  $node->language = LANGUAGE_NONE;
  node_object_prepare($node);

  // Move the file from public to field-defined destination.
  $options = bulk_photo_nodes_get_file_info($node_type);
  $image_field_name = $options['field_name'];
  $image_field_ref =& $node->{$image_field_name}[LANGUAGE_NONE][0];
  $image_field = (object) $node->{$image_field_name}[LANGUAGE_NONE][0];
  $extension = pathinfo($image_field->filename, PATHINFO_EXTENSION);
  $query_string = "SELECT COUNT(nid) FROM {node} WHERE type = :node_type AND uid = :uid";
  $images_count = db_query($query_string, array(
    ':node_type' => $node_type,
    ':uid' => $image_field->uid,
  ))
    ->fetchField();
  $images_count++;
  drupal_alter('bulk_photo_nodes_filename', $images_count);
  if ($options['directory'] == '/') {
    $options['directory'] = '';
  }
  $destination = $options['scheme'] . $image_field->uid . '-' . $images_count . '.' . $extension;
  $image_field->filename = $image_field->uid . '-' . $images_count . '.' . $extension;
  $image_field_ref = (array) file_move($image_field, $destination, FILE_EXISTS_REPLACE);

  // Correctly set all fields for the node.
  $node = bulk_photo_nodes_prepare_fields($node_fields, $node, $node_overrides, $image_field_name);
  drupal_alter('bulk_photo_nodes_node', $node, $node_fields, $node_overrides);
  if (!isset($node->path)) {
    $node->path = array();
  }
  $node->path['pathauto'] = TRUE;
  node_save($node);
  return !empty($node->nid) ? TRUE : FALSE;
}

/**
 * Saves all form field values as node properties.
 */
function bulk_photo_nodes_prepare_fields(&$node_fields, $node, &$node_overrides, $image_field_name) {
  foreach ($node_fields['right']['node'] as $field_name => $field_values) {

    // Iterate through fields. Convert form values to properties of the node.
    if ($field_name == $image_field_name) {
      continue;
    }
    if ($field_name != 'title') {
      $field_info = field_info_field($field_name);

      // Check if field is empty.
      $empty_function = $field_info['module'] . '_field_is_empty';
      $is_empty = TRUE;
      if (function_exists($empty_function)) {
        $items = field_get_items('node', $node, $field_name);
        $lang = entity_language('node', $node);
        if (is_array($items)) {

          // Multi-value fields.
          foreach ($items as $key => $item) {
            if ($empty_function($item, $field_info)) {

              // Need to make sure to unset any empty values before passing
              // these to the final node, or they will break things.
              unset($field_values[$lang][$key]);
            }
            else {
              $is_empty = FALSE;
            }
          }
        }
        elseif (!is_array($items) && !empty($items)) {
          if (!$empty_function($items, $field_info)) {
            $is_empty = FALSE;
          }
        }
      }
    }
    else {
      $is_empty = empty($field_values);
    }

    // This field is empty for this node, so populate it with batch fields if
    // available; otherwise unset it.
    if ($is_empty) {
      if (is_array($node_overrides[$field_name])) {

        // It is necessary to remove fields which have effectively empty arrays
        // (null leaves). The empty() language construct cannot accept function
        // return values.
        $check_empty = _bulk_photo_nodes_array_filter($node_overrides[$field_name]);
        if (!empty($check_empty)) {
          $node->{$field_name} = $node_overrides[$field_name];
        }
        else {
          unset($node->{$field_name});
        }
      }
      else {
        if ($node_overrides[$field_name]) {
          $node->{$field_name} = $node_overrides[$field_name];
        }
        else {
          unset($node->{$field_name});
        }
      }
    }
    else {
      $node->{$field_name} = $field_values;
    }
  }
  return $node;
}

/**
 * Gets configuration options of the image field used by bulk_photo_nodes.
 *
 * @param string $node_type
 *   The machine of the content type used by bulk_photo_nodes.
 *
 * @return array
 *   Various configuration options for the image field used.
 */
function bulk_photo_nodes_get_file_info($node_type) {
  $options = array();
  $options['field_name'] = bulk_photo_nodes_get_image_field($node_type);
  $options['field_info'] = field_info_field($options['field_name']);
  $options['instance_info'] = field_info_instance('node', $options['field_name'], $node_type);
  $options['scheme'] = $options['field_info']['settings']['uri_scheme'] . '://';
  $options['directory'] = $options['instance_info']['settings']['file_directory'] . '/';
  return $options;
}

/**
 * Adds an #ajax property recursively to all elements of a form.
 *
 * @param array $element
 *   The first element to recursively apply #ajax to.
 */
function bulk_photo_nodes_recursive_ajax(&$element) {
  if (element_children($element)) {
    foreach (element_children($element) as $child) {
      bulk_photo_nodes_recursive_ajax($element[$child]);
    }
    if (empty($element['#ajax'])) {
      $element['#ajax'] = array(
        'event' => 'change',
        'wrapper' => 'bpn-nodes',
        'callback' => 'bulk_photo_nodes_add_form_ajax',
        'method' => 'replace',
        'progress' => array(
          'type' => 'none',
        ),
      );
    }
    elseif ($element['#ajax']['callback'] == 'field_add_more_js') {
      $element['#element_validate'] = array(
        'bulk_photo_nodes_add_more_button_validate',
      );
      $element['#ajax']['callback'] = 'bulk_photo_nodes_field_add_more_js';
    }
    if (!empty($element['#autocomplete_path'])) {
      $field_info = field_info_field($element['#field_name']);
      if ($field_info['type'] == 'entityreference') {
        $element['#element_validate'] = array(
          'bulk_photo_nodes_entityreference_autocomplete_validate',
        );
      }
    }
    elseif (!empty($element['#type']) && $element['#type'] == 'select') {

      // @todo: Since applying the patch at https://www.drupal.org/node/2278141#comment-9299441
      // (commit id 3c17363b01e6b4cff64544bb85bd9418c73c7ccd) 'tid' was changed to 'target_id'
      // here, so bulk_photo_nodes_taxonomy_validate() is now acting on entity reference
      // fields, and NOT on taxonomy reference fields. Need to figure out which reference
      // fields this should actually be acting on, and whether the function should be renamed.
      // Note also that select list validation issues may be related to https://www.drupal.org/node/2454031.
      if (!empty($element['#value_key']) && $element['#value_key'] == 'target_id') {
        $element['#element_validate'] = array(
          'bulk_photo_nodes_taxonomy_validate',
        );
      }
    }
  }
  else {
    if (empty($element['#ajax'])) {
      $element['#ajax'] = array(
        'event' => 'change',
        'wrapper' => 'bpn-nodes',
        'callback' => 'bulk_photo_nodes_add_form_ajax',
        'method' => 'replace',
        'progress' => array(
          'type' => 'none',
        ),
      );
    }
    elseif ($element['#ajax']['callback'] == 'field_add_more_js') {
      $element['#element_validate'] = array(
        'bulk_photo_nodes_add_more_button_validate',
      );
      $element['#ajax']['callback'] = 'bulk_photo_nodes_field_add_more_js';
    }
    if (!empty($element['#autocomplete_path'])) {

      // Add custom validation for autocomplete taxonomy textfields.
      $field_info = field_info_field($element['#field_name']);
      if ($field_info['type'] == 'entityreference') {
        $element['#element_validate'] = array(
          'bulk_photo_nodes_entityreference_autocomplete_validate',
        );
      }
    }
    elseif (!empty($element['#type']) && $element['#type'] == 'select') {

      // Add custom validation for taxonomy selects.
      if (!empty($element['#value_key']) && $element['#value_key'] == 'target_id') {
        $element['#element_validate'] = array(
          'bulk_photo_nodes_taxonomy_validate',
        );
      }
    }
  }
}

/**
 * Sets the #required to FALSE recurvisely to all elements of a form.
 *
 * @param array $element
 *   The first element to recursively set #required = FALSE.
 */
function bulk_photo_nodes_recursive_set_optional(&$element) {
  if (!$element) {
    return;
  }
  if (isset($element['#required'])) {
    $element['#required'] = FALSE;

    // @todo: Unsetting #element_validate seems to cause problems, at least
    // for taxonomy reference fields. Is there a reason to unset it?
    // See https://www.drupal.org/node/2446787.
    // $element['#element_validate'] = array();
  }
  $children = element_children($element);
  if ($children) {
    foreach ($children as $child) {
      bulk_photo_nodes_recursive_set_optional($element[$child]);
    }
  }
}

/**
 * Sets the #required to FALSE recurvisely to all elements of a form.
 *
 * @param array $element
 *   The first element to recursively set #required = FALSE.
 */
function bulk_photo_nodes_recursive_set_required(&$element) {
  if (!$element) {
    return;
  }
  if (isset($element['#required'])) {
    $element['#required'] = TRUE;
  }
  $children = element_children($element);
  if ($children) {
    foreach ($children as $child) {
      bulk_photo_nodes_recursive_set_required($element[$child]);
    }
  }
}

/**
 * Checks if given element is required.
 *
 * @param array $element
 *   The first element to recursively check for #required = TRUE.
 */
function bulk_photo_nodes_is_required(&$element) {
  if (!$element) {
    return FALSE;
  }
  if (isset($element['#required']) && $element['#required']) {
    return TRUE;
  }
  $children = element_children($element);
  if ($children) {
    foreach ($children as $child) {
      if (bulk_photo_nodes_is_required($element[$child])) {
        return TRUE;
      }
    }
  }
  return FALSE;
}

/**
 * Validates selected taxonomy terms.
 *
 * @todo: Since applying the patch at https://www.drupal.org/node/2278141#comment-9299441
 * (commit id 3c17363b01e6b4cff64544bb85bd9418c73c7ccd) 'tid' was changed to 'target_id'
 * here, so bulk_photo_nodes_taxonomy_validate() is now acting on entity reference
 * fields, and NOT on taxonomy reference fields. Need to figure out which reference
 * fields this should actually be acting on, and whether the function should be renamed.
 * See bulk_photo_nodes_recursive_ajax().
 *
 * Note also that select list validation issues may be related to https://www.drupal.org/node/2454031.
 */
function bulk_photo_nodes_taxonomy_validate(&$element, &$form_state) {
  if ($element['#value'] !== '_none') {
    $term = array(
      0 => array(
        'target_id' => is_array($element['#value']) ? reset($element['#value']) : $element['#value'],
      ),
    );
    form_set_value($element, $term, $form_state);
  }
}

/**
 * Validates selected entityreference fields.
 */
function bulk_photo_nodes_entityreference_autocomplete_validate(&$element, &$form_state, $form) {
  if (!empty($element['#value'])) {
    $field = field_info_field($element['#field_name']);
    $field_name = $element['#field_name'];

    // Take "label (entity id)', match the id from parenthesis.
    if (preg_match("/.+\\((\\d+)\\)/", $element['#value'], $matches)) {
      $value = $matches[1];
    }
    else {

      // Try to get a match from the input string when the user didn't use the
      // autocomplete but filled in a value manually.
      $handler = entityreference_get_selection_handler($field);
      $instance = field_info_instance($element['#entity_type'], $field_name, $element['#bundle']);
      $handler = entityreference_get_selection_handler($field, $instance);
      $value = $handler
        ->validateAutocompleteInput($element['#value'], $element, $form_state, $form);
    }
    if ($field['settings']['target_type'] == 'taxonomy_term') {
      $value_array = array(
        0 => array(
          'target_id' => $value,
        ),
      );
      form_set_value($element, $value_array, $form_state);
    }
    else {

      // Update the value of this element so the field can validate the product IDs.
      form_set_value($element, $value, $form_state);
    }
  }
}

/**
 * Removes value of Add more button
 */
function bulk_photo_nodes_add_more_button_validate(&$element, &$form_state, $form) {
  $ref =& $form_state['values'];
  end($element['#parents']);
  $last_key = key($element['#parents']);
  foreach ($element['#parents'] as $key => $parent) {
    if ($key == $last_key) {
      unset($ref[$parent]);
    }
    else {
      $ref =& $ref[$parent];
    }
  }
}

/**
 * Checks if a given field form element has a value.
 *
 * @param string $field_name
 *   The name of the field to check.
 *
 * @param object $node
 *   The node object to check values against.
 *
 * @return bool
 *   TRUE if the field has a value, otherwise FALSE.
 */
function bulk_photo_nodes_check_field_empty($field_name, $node) {
  $field_info = field_info_field($field_name);
  $empty_function = $field_info['module'] . '_field_is_empty';
  $field_has_value = FALSE;
  if (function_exists($empty_function)) {
    $field_items = field_get_items('node', $node, $field_name);
    if (is_array($field_items)) {

      // Multi-value field.
      foreach ($field_items as $item) {
        if (!$empty_function($item, $field_info)) {
          $field_has_value = TRUE;
        }
      }
    }
    elseif (!is_array($field_items) && !empty($field_items)) {

      // String/numeric value.
      if (!$empty_function($field_items, $field_info)) {
        $field_has_value = TRUE;
      }
      else {
        $field_has_value = FALSE;
      }
    }
  }
  return $field_has_value;
}

/**
 * Sets and returns the current form step form id.
 *
 * @param string $chosen_form
 *   The form ID of the current step.
 *
 * @return string
 *   The form ID of the current step.
 */
function bulk_photo_nodes_chosen_form($chosen_form = NULL) {
  $form_id =& drupal_static(__FUNCTION__);
  if (isset($chosen_form)) {
    $form_id = $chosen_form;
  }
  return $form_id;
}

/**
 * Recursively remove empty values from an array.
 *
 * Helper function for saving nodes.
 *
 * @param array $input
 *   The array to filter.
 *
 * @return array
 *   The filtered array.
 */
function _bulk_photo_nodes_array_filter($input) {
  if (is_array($input)) {
    foreach ($input as &$value) {
      if (is_array($value)) {
        $value = _bulk_photo_nodes_array_filter($value);
      }
    }
    return array_filter($input, '_bulk_photo_nodes_empty_field_value');
  }
  else {
    return FALSE;
  }
}

/**
 * Check if field value is empty.
 *
 * '_none' is a reserved value that is used for select lists, etc. that have
 * no selection, so should be considered empty.
 */
function _bulk_photo_nodes_empty_field_value($field_value) {
  if (empty($field_value) || $field_value == '_none') {
    return FALSE;
  }
  return TRUE;
}

/**
 * Helper function to generate a default node title from a given filename.
 */
function _bulk_photo_nodes_title_from_filename($filename) {

  // Separate filename from extension/path/etc.
  $path_parts = pathinfo($filename);
  $title = $path_parts['filename'];

  // Replace underscores, dashes, etc. with spaces.
  $title = str_replace('-', ' ', $title);
  $title = str_replace('_', ' ', $title);
  $title = str_replace('.', ' ', $title);

  // Replace double spaces with single space.
  $title = preg_replace('!\\s+!', ' ', $title);

  // Trim spaces from front and back.
  $title = trim($title, ' ');

  // Capitalize.
  $title = ucwords($title);

  // Sanitize.
  $title = check_plain($title);

  // Return.
  return $title;
}

/**
 * Forked Ajax callback in response to a new empty widget being added to the form.
 *
 * This returns the new page content to replace the page content made obsolete
 * by the form submission.
 *
 * @see field_add_more_submit()
 */
function bulk_photo_nodes_field_add_more_js($form, $form_state) {
  $button = $form_state['triggering_element'];

  // Go one level up in the form, to the widgets container.
  $element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -1));
  $field_name = $element['#field_name'];
  $langcode = $element['#language'];
  $parents = $element['#field_parents'];
  if (empty($field_name)) {
    return;
  }
  $field = field_info_field($field_name);
  if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED) {
    return;
  }

  // Add a DIV around the delta receiving the Ajax effect.
  $delta = $element['#max_delta'];
  $element[$delta]['#prefix'] = '<div class="ajax-new-content">' . (isset($element[$delta]['#prefix']) ? $element[$delta]['#prefix'] : '');
  $element[$delta]['#suffix'] = (isset($element[$delta]['#suffix']) ? $element[$delta]['#suffix'] : '') . '</div>';
  return $element;
}

Functions

Namesort descending Description
bulk_photo_nodes_add_form Form constructor for final step of bpn_multistep_form().
bulk_photo_nodes_add_form_ajax AJAX callback for bulk_photo_nodes_add_form().
bulk_photo_nodes_add_form_submit Form submission handler for bulk_photo_nodes_add_form().
bulk_photo_nodes_add_more_button_validate Removes value of Add more button
bulk_photo_nodes_batch_finished Batch 'finished' callback used by bulk_photo_nodes_add_form_submit().
bulk_photo_nodes_check_field_empty Checks if a given field form element has a value.
bulk_photo_nodes_chosen_form Sets and returns the current form step form id.
bulk_photo_nodes_create_node Creates a dummy node to use with field_attach_form().
bulk_photo_nodes_create_overrides Generate batch override form for bulk_photo_nodes_add_form().
bulk_photo_nodes_create_subform Generates all node subforms in bulk_photo_nodes_add_form().
bulk_photo_nodes_delete_node Deletes an individual bulk photo node.
bulk_photo_nodes_entityreference_autocomplete_validate Validates selected entityreference fields.
bulk_photo_nodes_field_add_more_js Forked Ajax callback in response to a new empty widget being added to the form.
bulk_photo_nodes_form_node_type_form_alter Implements hook_form_node_type_form_alter().
bulk_photo_nodes_get_file_info Gets configuration options of the image field used by bulk_photo_nodes.
bulk_photo_nodes_get_image_field Indicates if a given content type is being used as bulk photo nodes.
bulk_photo_nodes_get_image_fields Returns a list of image fields for a given content type.
bulk_photo_nodes_get_option Get a named option for a given node type.
bulk_photo_nodes_get_session_files Insert batch saved files into bulk_photo_nodes_add_form state.
bulk_photo_nodes_is_bpn_add_page Determines if a given node type's add form should be displayed as a BPN form.
bulk_photo_nodes_is_required Checks if given element is required.
bulk_photo_nodes_menu Implements hook_menu().
bulk_photo_nodes_menu_alter Implements hook_menu_alter().
bulk_photo_nodes_page Page callback: Displays the first step of the bulk photo node upload form.
bulk_photo_nodes_page_get Return bulk node add form.
bulk_photo_nodes_permission Implements hook_permission().
bulk_photo_nodes_prepare_fields Saves all form field values as node properties.
bulk_photo_nodes_recursive_ajax Adds an #ajax property recursively to all elements of a form.
bulk_photo_nodes_recursive_set_optional Sets the #required to FALSE recurvisely to all elements of a form.
bulk_photo_nodes_recursive_set_required Sets the #required to FALSE recurvisely to all elements of a form.
bulk_photo_nodes_required_optional Set required/optional bulk photo node fields.
bulk_photo_nodes_save_node Batch operation: Saves an individual bulk photo node.
bulk_photo_nodes_submit Form submission handler for bulk_photo_nodes_form_node_type_form_alter().
bulk_photo_nodes_taxonomy_validate Validates selected taxonomy terms.
_bulk_photo_nodes_array_filter Recursively remove empty values from an array.
_bulk_photo_nodes_empty_field_value Check if field value is empty.
_bulk_photo_nodes_title_from_filename Helper function to generate a default node title from a given filename.