<?php

/**
 * @file
 * A list of orphaned Islandora objects.
 */

/**
 * Builds the Orphaned Islandora Objects management form.
 *
 * @param array $form
 *   An array representing a form within Drupal.
 * @param array $form_state
 *   An array containing the Drupal form state.
 *
 * @return array
 *   An array containing the form to be rendered.
 */
function islandora_manage_orphaned_objects_form($form, $form_state) {
  if (isset($form_state['show_confirm'])) {
    $pids = $form_state['pids_to_delete'];
    $form['confirm_message'] = array(
      '#type' => 'item',
      '#markup' => format_plural(count($form_state['pids_to_delete']),
      'Are you sure you want to delete this object? This action cannot be reversed.',
      'Are you sure you want to delete these @count objects? This action cannot be reversed.'),
    );
    if (count($pids) <= 10) {
      $form['pids_to_delete'] = array(
        '#type' => 'markup',
        '#theme' => 'item_list',
        '#list_type' => 'ol',
      );
      $options = array('attributes' => array('target' => '_blank'));
      foreach ($pids as $pid) {
        $form['pids_to_delete']['#items'][] = l($pid, "/islandora/object/{$pid}", $options);
      }
    }
    $form['confirm_submit'] = array(
      '#type' => 'submit',
      '#value' => t('Confirm'),
      '#weight' => 2,
      '#submit' => array('islandora_manage_orphaned_objects_confirm_submit'),
    );
    $form['cancel_submit'] = array(
      '#type' => 'submit',
      '#value' => t('Cancel'),
      '#weight' => 3,
    );
  }
  else {
    drupal_set_message(t('This page lists objects that have at least one parent, according to their RELS-EXT, that does not
    exist in the Fedora repository. These orphans might exist due to a failed batch ingest, their parents being deleted,
    or a variety of other reasons. Some of these orphans may exist intentionally.
    Please be cautious when deleting, as this action is irreversible.'), 'warning');
    $orphaned_objects = islandora_get_orphaned_objects();
    $rows = array();
    foreach ($orphaned_objects as $orphaned_object) {
      $pid = $orphaned_object['object']['value'];
      if (islandora_namespace_accessible($pid)) {
        $rows[$pid] = array(l($orphaned_object['title']['value'] . " (" . $pid . ")", "islandora/object/$pid"));
      }
    }
    ksort($rows);
    $form['management_table'] = array(
      '#type' => 'tableselect',
      '#header' => array(t('Object')),
      '#options' => $rows,
      '#attributes' => array(),
      '#empty' => t('No orphaned objects were found.'),
    );
    if (!empty($rows)) {
      $form['submit_selected'] = array(
        '#type' => 'submit',
        '#name' => 'islandora-orphaned-objects-submit-selected',
        '#validate' => array('islandora_delete_selected_orphaned_objects_validate'),
        '#submit' => array('islandora_delete_orphaned_objects_submit'),
        '#value' => t('Delete Selected'),
      );
      $form['submit_all'] = array(
        '#type' => 'submit',
        '#name' => 'islandora-orphaned-objects-submit-all',
        '#submit' => array('islandora_delete_orphaned_objects_submit'),
        '#value' => t('Delete All'),
      );
    }
  }
  return $form;
}

/**
 * Validation for the Islandora Orphaned Objects management form.
 *
 * @param array $form
 *   An array representing a form within Drupal.
 * @param array $form_state
 *   An array containing the Drupal form state.
 */
function islandora_delete_selected_orphaned_objects_validate($form, $form_state) {
  $selected = array_filter($form_state['values']['management_table']);
  if (empty($selected)) {
    form_error($form['management_table'], t('At least one object must be selected to delete!'));
  }
}
/**
 * Submit handler for the delete buttons in the workflow management form.
 *
 * @param array $form
 *   An array representing a form within Drupal.
 * @param array $form_state
 *   An array containing the Drupal form state.
 */
function islandora_delete_orphaned_objects_submit(&$form, &$form_state) {
  if ($form_state['triggering_element']['#name'] == 'islandora-orphaned-objects-submit-selected') {
    $selected = array_keys(array_filter($form_state['values']['management_table']));
  }
  else {
    $selected = array_keys($form_state['values']['management_table']);
  }
  $form_state['pids_to_delete'] = $selected;
  // Rebuild to show the confirm form.
  $form_state['rebuild'] = TRUE;
  $form_state['show_confirm'] = TRUE;
}
/**
 * Submit handler for the workflow management confirm form.
 *
 * @param array $form
 *   An array representing a form within Drupal.
 * @param array $form_state
 *   An array containing the Drupal form state.
 */
function islandora_manage_orphaned_objects_confirm_submit($form, &$form_state) {
  $batch = islandora_delete_orphaned_objects_create_batch($form_state['pids_to_delete']);
  batch_set($batch);
}

/**
 * Query for orphaned objects.
 *
 * @return array
 *   An array containing the results of the orphaned objects queries.
 */
function islandora_get_orphaned_objects() {
  $connection = islandora_get_tuque_connection();
  // SPARQL: get orphaned objects, exclude any with a living parent.
  $object_query = <<<EOQ
!prefix
SELECT DISTINCT ?object ?title 
WHERE {
  ?object <fedora-model:hasModel> <info:fedora/fedora-system:FedoraObject-3.0> ;
               ?p ?otherobject .
   ?object <fedora-model:label> ?title;
  OPTIONAL {
    ?otherobject <fedora-model:hasModel> ?model .
  } .
  FILTER (!bound(?model))

  # Filter by "parent" relationships
  FILTER (!dead_parent_relationships)
 
  # Exclude objects with live parents
  OPTIONAL {
    !live_parent_relationships .
    ?liveparent <fedora-model:hasModel> <info:fedora/fedora-system:FedoraObject-3.0> .
  }
  !optionals
  !filters
  FILTER (!bound(?liveparent))
} ORDER BY ?object
EOQ;
  $parent_relationships = module_invoke_all('islandora_solution_pack_child_relationships', 'all');
  $parent_relationships['prefix'] = array_unique($parent_relationships['prefix']);
  $parent_relationships['predicate'] = array_unique($parent_relationships['predicate']);
  if (count($parent_relationships['predicate']) == 0) {
    // No predicates to search for. Exit early.
    return array();
  }
  $optionals = (array) module_invoke('islandora_xacml_api', 'islandora_basic_collection_get_query_optionals', 'view');
  $filter_modules = array(
    'islandora_xacml_api',
    'islandora',
  );
  $filters = array();
  foreach ($filter_modules as $module) {
    $filters = array_merge_recursive($filters, (array) module_invoke($module, 'islandora_basic_collection_get_query_filters', 'view'));
  }
  $filter_map = function ($filter) {
    return "FILTER($filter)";
  };
  $parent_map = function ($parent) {
    return "?object $parent ?liveparent";
  };
  // Use separate queries for different object types.
  $sparql_query_objects = format_string($object_query, array(
    '!optionals' => !empty($optionals) ? ('OPTIONAL {{' . implode('} UNION {', $optionals) . '}}') : '',
    '!filters' => !empty($filters) ? implode(' ', array_map($filter_map, $filters)) : '',
    '!dead_parent_relationships' => '?p = ' . implode(' || ?p = ', $parent_relationships['predicate']),
    '!live_parent_relationships' => '{' . implode(' } UNION { ', array_map($parent_map, $parent_relationships['predicate'])) . '}',
    '!prefix' => implode("\n", $parent_relationships['prefix']),
  ));
  $results = $connection->repository->ri->sparqlQuery($sparql_query_objects);
  return $results;
}

/**
 * Constructs the batch that will go out and delete objects.
 *
 * @param array $pids
 *   The array of pids to be deleted.
 *
 * @return array
 *   An array detailing the batch that is about to be run.
 */
function islandora_delete_orphaned_objects_create_batch($pids) {
  // Set up a batch operation.
  $batch = array(
    'operations' => array(
      array('islandora_delete_orphaned_objects_batch_operation', array($pids)),
    ),
    'title' => t('Deleting the selected objects...'),
    'init_message' => t('Preparing to delete objects.'),
    'progress_message' => t('Time elapsed: @elapsed <br/>Estimated time remaining @estimate.'),
    'error_message' => t('An error has occurred.'),
    'finished' => 'islandora_delete_orphaned_objects_batch_finished',
    'file' => drupal_get_path('module', 'islandora') . '/includes/orphaned_objects.inc',
  );
  return $batch;
}
/**
 * Constructs and performs the deleting batch operation.
 *
 * @param array $pids
 *   An array of pids to be deleted.
 * @param array $context
 *   The context of the Drupal batch.
 */
function islandora_delete_orphaned_objects_batch_operation($pids, &$context) {
  if (empty($context['sandbox'])) {
    $context['sandbox'] = array();
    $context['sandbox']['progress'] = 0;
    $context['sandbox']['pids'] = $pids;
    $context['sandbox']['total'] = count($pids);
    $context['results']['success'] = array();
  }
  if (!empty($context['sandbox']['pids'])) {
    $target_pid = array_pop($context['sandbox']['pids']);
    $target_object = islandora_object_load($target_pid);
    $context['message'] = t('Deleting @label (@pid) (@current of @total)...', array(
        '@label' => $target_object->label,
        '@pid' => $target_pid,
        '@current' => $context['sandbox']['progress'],
        '@total' => $context['sandbox']['total'],
    ));
    islandora_delete_object($target_object);
    $object_check = islandora_object_load($target_pid);
    if ($object_check) {
      drupal_set_message(t('Could not delete %pid. You may not have permission to manage this object.',
        array(
          '%pid' => $target_pid,
        )), 'error');
    }
    else {
      $context['results']['success'][] = $target_pid;
    }
    $context['sandbox']['progress']++;
  }
  $context['finished'] = ($context['sandbox']['total'] == 0) ? 1 : ($context['sandbox']['progress'] / $context['sandbox']['total']);
}
/**
 * Finished function for the orphaned objects delete batch.
 *
 * @param bool $success
 *   Whether the batch was successful or not.
 * @param array $results
 *   An array containing the results of the batch operations.
 * @param array $operations
 *   The operations array that was used in the batch.
 */
function islandora_delete_orphaned_objects_batch_finished($success, $results, $operations) {
  if ($success) {
    $message = format_plural(count($results['success']), 'One object deleted.', '@count objects deleted.');
  }
  else {
    $message = t('Finished with an error.');
  }
  drupal_set_message($message);
}