<?php

/**
 * @file
 * Custom action callbacks and configuration forms.
 */

/**
 * Assign a node to a user.
 *
 * We need a custom action for this, as actions are not entirely compatible with
 * Revisioning. We need to tweak the node a bit to force a pending revision to
 * stay that way. node_assign_owner_action() does not allow this. See
 * _archibald_actions_reset_revision_operation() for an explanation on why we
 * need this.
 *
 * @see node_assign_owner_action()
 * @see node_action_info()
 */
function archibald_assign_to_user_action($node, $context = array()) {
  // Simply proxy to node_assign_owner_action().
  node_assign_owner_action($node, $context);

  // Make sure the revision_operation property is correctly set.
  _archibald_actions_reset_revision_operation($node);

  module_invoke_all('archibald_assign_to_user_action', $node, $context);
}

/**
 * Configuration form for archibald_assign_to_user_action().
 *
 * @see archibald_assign_to_user_action()
 * @see node_assign_owner_action_form()
 */
function archibald_assign_to_user_action_form($context = array()) {
  return node_assign_owner_action_form($context);
}

/**
 * Validation callback for archibald_assign_to_user_action_form().
 *
 * @see archibald_assign_to_user_action()
 * @see node_assign_owner_action_validate()
 */
function archibald_assign_to_user_action_validate($form, $form_state) {
  node_assign_owner_action_validate($form, $form_state);
}

/**
 * Submission callback for archibald_assign_to_user_action_form().
 *
 * @see archibald_assign_to_user_action()
 * @see node_assign_owner_action_submit()
 */
function archibald_assign_to_user_action_submit($form, $form_state) {
  return node_assign_owner_action_submit($form, $form_state);
}

/**
 * Assign a node to a partner.
 */
function archibald_assign_to_partner_action($node, $context = array()) {
  $node->archibald_publication_partner[LANGUAGE_NONE][0]['target_id'] = $context['archibald_partner'];

  // Make sure the revision_operation property is correctly set.
  _archibald_actions_reset_revision_operation($node);

  module_invoke_all('archibald_assign_to_partner_action', $node, $context);

  // Log the change.
  $partner = archibald_partner_load($context['archibald_partner']);
  watchdog('action', "Assign @type %title to %partner.", array(
    '@type' => node_type_get_name($node),
    '%title' => $node->title,
    '%partner' => $partner->name,
  ));
}

/**
 * Configuration form for archibald_assign_to_partner_action().
 *
 * @see archibald_assign_to_partner_action()
 * @see archibald_assign_to_partner_action_submit()
 */
function archibald_assign_to_partner_action_form($context = array()) {
  $partners = entity_load('archibald_partner');
  if (empty($partners)) {
    drupal_set_message(t("There are currently no partners available."), 'warning');
    return array();
  }

  $form = array();
  $options = array();
  foreach ($partners as $partner) {
    $options[$partner->id] = $partner->name;
  }

  $form['archibald_partner'] = array(
    '#type' => 'select',
    '#title' => t('Partner'),
    '#options' => $options,
    '#description' => t("Choose to which partner this content should belong."),
  );

  return $form;
}

/**
 * Submission callback for archibald_assign_to_partner_action_form().
 */
function archibald_assign_to_partner_action_submit($form, $form_state) {
  return array('archibald_partner' => $form_state['values']['archibald_partner']);
}

/**
 * Assign a node to a catalog.
 */
function archibald_assign_to_catalog_action($node, $context = array()) {
  $node->archibald_publication_catalogs[LANGUAGE_NONE][0]['value'] = $context['archibald_catalog'];

  // Make sure the revision_operation property is correctly set.
  _archibald_actions_reset_revision_operation($node);

  module_invoke_all('archibald_assign_to_catalog_action', $node, $context);

  // Log the change.
  watchdog('action', "Assign @type %title to '%catalog' catalog(s).", array(
    '@type' => node_type_get_name($node),
    '%title' => $node->title,
    '%catalog' => $context['archibald_catalog'],
  ));
}

/**
 * Configuration form for archibald_assign_to_catalog_action().
 *
 * @see archibald_assign_to_catalog_action()
 * @see archibald_assign_to_catalog_action_submit()
 */
function archibald_assign_to_catalog_action_form($context = array()) {
  $field = field_info_field('archibald_publication_catalogs');

  $form = array();

  $form['archibald_catalog'] = array(
    '#type' => 'select',
    '#title' => t('Catalog(s)'),
    '#options' => $field['settings']['allowed_values'],
    '#description' => t("Choose to which catalog(s) this content should be published."),
  );

  return $form;
}

/**
 * Submission callback for archibald_assign_to_catalog_action_form().
 */
function archibald_assign_to_catalog_action_submit($form, $form_state) {
  return array('archibald_catalog' => $form_state['values']['archibald_catalog']);
}

/**
 * Validate a node.
 */
function archibald_validate_action($node, $context = array()) {
  module_load_include('inc', 'archibald', 'includes/archibald.publish');

  if (empty($node->archibald_publication_partner[LANGUAGE_NONE][0]['target_id'])) {
    drupal_set_message(t("%title cannot be validated. You must first assign it to a partner.", array(
      '%title' => $node->title,
    )), 'error');

    return FALSE;
  }

  $result = archibald_validate_description($node);

  module_invoke_all('archibald_validate_action', $node, $result, $context);

  if (!empty($result['valid'])) {
    drupal_set_message(t("%title is valid.", array(
      '%title' => $node->title,
    )));

    return TRUE;
  }
  else {
    $errors = array();
    array_walk($result['errors'], function($value, $key) use (&$errors) {
      $errors[] = t("The field %field is %error.", array(
        '%field' => $key,
        '%error' => is_array($value) ? implode(', ', $value) : $value,
      ));
    });

    drupal_set_message(t("%title does not validate:<br>!errors<br />You can find more information <a href='!link'>here</a>.", array(
      '%title' => $node->title,
      '!errors' => theme('item_list', array('items' => $errors)),
      '!link' => url("node/{$node->nid}/validate"),
    )), 'warning');

    return FALSE;
  }
}

/**
 * Publish a node revision.
 */
function archibald_publish_action($node, $context = array()) {
  // We must first check if the description is valid. If it is not, we cannot
  // publish it. If it is, we simply proxy to the Revisioning publish action,
  // which will trigger the publication.
  if (archibald_validate_action($node, $context)) {
    module_load_include('inc', 'revisioning', 'revisioning_triggers_actions');
    revisioning_publish_latest_revision_action($node, $context);

    module_invoke_all('archibald_publish_action', $node, $context);

    return TRUE;
  }
  else {
    drupal_set_message(t("Publishing of %title failed due to validation errors.", array(
      '%title' => $node->title,
    )), 'error');
    return FALSE;
  }
}

/**
 * Republish a node.
 */
function archibald_republish_action($node, $context = array()) {
  module_load_include('inc', 'archibald', 'includes/archibald.publish');
  // We must first check if the description is valid. If it is not, we cannot
  // publish it. If it is, we cannot proxy to Revisioning, as we might not want
  // to publish a new revision (and re-publishing an existing one doesn't work).
  // We must call the publishing function directly. We don't want to display
  // a message "Description is valid", so we don't call the validation action.
  // Instead, we simply check if it is valid. If not, we can call the action
  // anyway to see the validation errors.
  $result = archibald_validate_description($node);
  if (!empty($result['valid'])) {
    if (archibald_publish_description($node)) {
      module_invoke_all('archibald_republish_action', $node, $context);
      return TRUE;
    }
    else {
      drupal_set_message(t("Republishing of %title failed.", array(
        '%title' => $node->title,
      )), 'error');
      return FALSE;
    }
  }
  else {
    drupal_set_message(t("Republishing of %title failed due to validation errors.", array(
      '%title' => $node->title,
    )), 'error');

    // Show the validation errors.
    archibald_validate_action($node, $context);

    return FALSE;
  }
}

/**
 * Set the correct node revision_operation property.
 *
 * When in context of a node form, Revisioning sets a revision_operation
 * property, which allows it to keep the status of a pending revision. This is
 * not the case when using actions, however. The way Revisioning works is as
 * follows:
 *
 * 1. The node gets saved by Drupal. Drupal doesn't care much about the status.
 *    If the node is published, it remains published. It is important to realize
 *    that, when using Revisioning, if a node has a published revision, for
 *    Drupal the node has a status of 1, regardless of the pending, unpublished
 *    revision. Upon saving, Drupal simply stores a new revision, and updates
 *    the node database row, pointing the revision ID to this new revision.
 * 2. Revisioning kicks in, with revisioning_node_update(). It checks the
 *    revision_operation property. Based on its value, it "reverts" what Drupal
 *    just did, setting the revision ID in the node table back to the previous
 *    revision. This is what keeps the published revision one step behind the
 *    pending revision.
 *
 * This flow is not possible if the revision_operation property is missing. This
 * is why certain actions, although performed on a pending revision, will
 * publish the revision, making it the current one. For example, calling the
 * node_assign_owner_action() action will not simply change the author for the
 * pending revision; it will make it the current one. This is why we need this
 * function. It checks the node settings, and puts the property on the node,
 * before it gets saved. This allows Revisioning's revisioning_node_update()
 * hook to do its job, and let's us perform actions correctly on pending
 * revisions.
 *
 * @param object $node
 *    The node on which the action is performed.
 */
function _archibald_actions_reset_revision_operation($node) {
  module_load_include('inc', 'revisioning', 'revisioning_api');

  $is_moderated_content = isset($node->revision_moderation) ?
    $node->revision_moderation :
    revisioning_content_is_moderated($node->type, $node);

  $node->revision_operation = (int) $node->is_pending + (int) $is_moderated_content;
}
