<?php

/**
 * @file
 * Install, update, and uninstall functions for the Archibald module.
 */

/**
 * Implements hook_install().
 */
function archibald_install() {
  // We need to execute our module hooks after Revisioning, which has a weight
  // of 6 by default, and after Views, which has a weight of 10 by default.
  db_update('system')
    ->fields(array('weight' => 20))
    ->condition('name', 'archibald')
    ->execute();

  // Set the default configuration, as most installs will use it like this
  // anyway.
  // @todo Set the production URL.
  variable_set('archibald_api_url', 'https://dev-dsb-api.educa.ch/v2');
  variable_set('archibald_api_version', 2);

  // Activate entity translation for nodes and taxonomy terms.
  variable_set('entity_translation_entity_types', array(
    'taxonomy_term' => 'taxonomy_term',
    'node' => 'node',
    'field_collection_item' => 'field_collection_item',
  ));

  // Create custom image styles, if needed.
  $style = image_style_load('archibald_partner_logo_summary');
  if (empty($style)) {
    $style = image_style_save(array(
      'name' => 'archibald_partner_logo_summary',
    ));
    $effect = array(
      'name' => 'image_scale',
      'data' => array(
        'width' => 100,
        'height' => 30,
        'upscale' => FALSE,
      ),
      'isid' => $style['isid'],
    );
    image_effect_save($effect);
  }

  $style = image_style_load('archibald_partner_logo_default');
  if (empty($style)) {
    $style = image_style_save(array(
      'name' => 'archibald_partner_logo_default',
    ));
    $effect = array(
      'name' => 'image_scale',
      'data' => array(
        'width' => 100,
        'height' => 100,
        'upscale' => FALSE,
      ),
      'isid' => $style['isid'],
    );
    image_effect_save($effect);
  }

  // Create taxonomy vocabularies.
  // Keywords and Coverage.
  foreach (array(
    'archibald_lomch_keywords' => "Keywords",
    'archibald_lomch_coverage' => "Coverages",
  ) as $vocabulary_machine_name => $vocabulary_name) {
    // Check if the vocabulary already exists. If not, create it.
    $vocabulary = taxonomy_vocabulary_machine_name_load($vocabulary_machine_name);
    if (empty($vocabulary)) {
      $vocabulary = (object) array(
        'name' => $vocabulary_name,
        'description' => "",
        'machine_name' => $vocabulary_machine_name,
      );
      taxonomy_vocabulary_save($vocabulary);
    }
  }

  // Resource languages.
  $vocabulary_machine_name = 'archibald_lomch_description_languages';
  $vocabulary_name = "Language(s) of the resource";

  // Check if the vocabulary already exists. If not, create it.
  $vocabulary = taxonomy_vocabulary_machine_name_load($vocabulary_machine_name);
  if (empty($vocabulary)) {
    $vocabulary = (object) array(
      'name' => $vocabulary_name,
      'description' => "Languages for the described resources.",
      'machine_name' => $vocabulary_machine_name,
      // If the Internationalization module is enabled, we set the localization
      // method to "Localize".
      'i18n_mode' => 1,
    );
    taxonomy_vocabulary_save($vocabulary);
  }

  // The vocabulary terms have an ISO code field. Does it already exist? If not,
  // create it.
  $field_name = 'archibald_lang_iso_code';
  $field = field_info_field($field_name);
  if (empty($field)) {
    $field = array(
      'field_name' => $field_name,
      'type' => 'text',
      'cardinality' => 1,
    );
    field_create_field($field);
  }

  $instance = field_info_instance('taxonomy_term', $field_name, $vocabulary_machine_name);
  if (empty($instance)) {
    $instance = array(
      'field_name' => $field_name,
      'entity_type' => 'taxonomy_term',
      'bundle' => $vocabulary_machine_name,
      'label' => "Language ISO code",
      'required' => TRUE,
      'widget' => array(
        'type' => 'text_textfield',
      ),
    );
    field_create_instance($instance);
  }

  // The vocabulary terms also have a "Status" field, which sets whether the
  // language is active or not.
  $field_name = 'archibald_term_status';
  $field = field_info_field($field_name);
  if (empty($field)) {
    $field = array(
      'field_name' => $field_name,
      'type' => 'list_boolean',
      'cardinality' => 1,
      'settings' => array(
        'allowed_values' => array(
          0 => "No",
          1 => "Yes",
        ),
      ),
    );
    field_create_field($field);
  }

  $instance = field_info_instance('taxonomy_term', $field_name, $vocabulary_machine_name);
  if (empty($instance)) {
    $instance = array(
      'field_name' => $field_name,
      'entity_type' => 'taxonomy_term',
      'bundle' => $vocabulary_machine_name,
      'label' => "Activate language on description form",
      'required' => TRUE,
      'widget' => array(
        'type' => 'options_buttons',
      ),
    );
    field_create_instance($instance);
  }

  // "Terms of use" terms.
  $vocabulary_machine_name = 'archibald_lomch_description_licenses';
  $vocabulary_name = "Terms of use";

  // Check if the vocabulary already exists. If not, create it.
  $vocabulary = taxonomy_vocabulary_machine_name_load($vocabulary_machine_name);
  if (empty($vocabulary)) {
    $vocabulary = (object) array(
      'name' => $vocabulary_name,
      'description' => "",
      'machine_name' => $vocabulary_machine_name,
      // If the Internationalization module is enabled, we set the localization
      // method to "disabled". We will use Entity Translation.
      'i18n_mode' => 0,
    );
    taxonomy_vocabulary_save($vocabulary);
  }

  // The vocabulary terms have an machine name field. Does it already exist? If
  // not, create it.
  $field_name = 'archibald_license_id';
  $field = field_info_field($field_name);
  if (empty($field)) {
    $field = array(
      'field_name' => $field_name,
      'type' => 'text',
      'cardinality' => 1,
    );
    field_create_field($field);
  }

  $instance = field_info_instance('taxonomy_term', $field_name, $vocabulary_machine_name);
  if (empty($instance)) {
    $instance = array(
      'field_name' => $field_name,
      'entity_type' => 'taxonomy_term',
      'bundle' => $vocabulary_machine_name,
      'label' => "Identifier",
      'description' => "In order to correctly synchronize data with the Ontology server, we need to provide each license with an <em>identifier</em> (think <em>machine name</em>). This is usually in English, lower case and uses no special characters. If this is a custom license, with no corresponding license on the Ontology server, this can be left blank.",
      'widget' => array(
        'type' => 'text_textfield',
      ),
    );
    field_create_instance($instance);
  }

  // The vocabulary terms have a URL field. Does it already exist? If not,
  // create it.
  $field_name = 'archibald_license_url';
  $field = field_info_field($field_name);
  if (empty($field)) {
    $field = array(
      'field_name' => $field_name,
      'type' => 'url',
      'cardinality' => FIELD_CARDINALITY_UNLIMITED,
    );
    field_create_field($field);
  }

  $instance = field_info_instance('taxonomy_term', $field_name, $vocabulary_machine_name);
  if (empty($instance)) {
    $instance = array(
      'field_name' => $field_name,
      'entity_type' => 'taxonomy_term',
      'bundle' => $vocabulary_machine_name,
      'label' => "URL",
      'description' => "A link to the license file, or to a resource with more information regarding the license.",
      'widget' => array(
        'type' => 'url_external',
      ),
    );
    field_create_instance($instance);
  }

  // The vocabulary terms also have a "Status" field, which sets whether the
  // license is active or not.
  $field_name = 'archibald_term_status';
  $instance = field_info_instance('taxonomy_term', $field_name, $vocabulary_machine_name);
  if (empty($instance)) {
    $instance = array(
      'field_name' => $field_name,
      'entity_type' => 'taxonomy_term',
      'bundle' => $vocabulary_machine_name,
      'label' => "Activate license on description form",
      'required' => TRUE,
      'widget' => array(
        'type' => 'options_buttons',
      ),
    );
    field_create_instance($instance);
  }

  // Generate an encryption key, and store it in the private file system.
  if (file_stream_wrapper_get_instance_by_uri('private://')) {
    archibald_install_generate_encryption_key();
  }
}

/**
 * Implements hook_uninstall().
 */
function archibald_uninstall() {
  // Remove custom variables.
  foreach (array(
    'archibald_api_version',
    'archibald_api_url',
    'archibald_entities_added_fields',
    'archibald_enhance_admin_themes',
    'archibald_activate_collapsible_vtabs',
    'archibald_activate_footer_links',
    'archibald_activate_sticky_vtabs',
    'archibald_licenses_vocabulary_created',
    'archibald_languages_vocabulary_created',
    'archibald_show_link_to_dsb_portal',
    'archibald_dsb_portal_url',
  ) as $variable) {
    variable_del($variable);
  }

  // Remove custom entities.
  foreach (array(
    'node' => 'archibald_lomch_description',
    'archibald_vcard' => 'archibald_vcard',
    'archibald_partner' => 'archibald_partner',
  ) as $entity_type => $bundle) {
    field_attach_delete_bundle($entity_type, $bundle);
  }

  // Remove custom content type.
  node_type_delete('archibald_lomch_description');

  // Remove custom vocabularies.
  foreach (array(
    'archibald_lomch_description_licenses',
    'archibald_lomch_description_languages',
    'archibald_lomch_keywords',
    'archibald_lomch_coverage',
  ) as $vocabulary_name) {
    $vocabulary = taxonomy_vocabulary_machine_name_load($vocabulary_name);
    taxonomy_vocabulary_delete($vocabulary->vid);
  }
}

/**
 * Implements hook_schema().
 */
function archibald_schema() {
  return array(
    'archibald_lom_id' => array(
      'description' => '1 to 1 relation table for mapping nodes to a LOM ID in the national catalog',
      'fields' => array(
        'nid' => array(
          'type' => 'int',
          'unsigned' => TRUE,
          'not null' => TRUE,
        ),
        'lom_id' => array(
          'type' => 'varchar',
          'length' => 255,
          'not null' => TRUE,
        ),
      ),
      'primary key' => array('nid'),
    ),
    'archibald_lom_hash' => array(
      'description' => 'MD5 hash of published nodes',
      'fields' => array(
        'nid' => array(
          'type' => 'int',
          'unsigned' => TRUE,
          'not null' => TRUE,
        ),
        'published_hash' => array(
          'type' => 'varchar',
          'length' => 255,
          'not null' => TRUE,
          'default' => '',
        ),
        'current_hash' => array(
          'type' => 'varchar',
          'length' => 255,
          'not null' => TRUE,
          'default' => '',
        ),
        'timestamp' => array(
          'type' => 'int',
          'unsigned' => TRUE,
          'not null' => TRUE,
        ),
      ),
      'primary key' => array('nid'),
    ),
    'archibald_vcard' => array(
      'description' => 'The base table for the Archibald VCard entity',
      'fields' => array(
        'id' => array(
          'type' => 'serial',
          'unsigned' => TRUE,
          'not null' => TRUE,
        ),
        'name' => array(
          'type' => 'varchar',
          'length' => 255,
          'not null' => FALSE,
        ),
        'changed' => array(
          'type' => 'int',
          'not null' => TRUE,
          'default' => 0,
        ),
      ),
      'primary key' => array('id'),
    ),
    'archibald_partner' => array(
      'description' => 'The base table for the Archibald Partner entity',
      'fields' => array(
        'id' => array(
          'type' => 'serial',
          'unsigned' => TRUE,
          'not null' => TRUE,
        ),
        'name' => array(
          'type' => 'varchar',
          'length' => 255,
          'not null' => FALSE,
        ),
        'passphrase' => array(
          'type' => 'varchar',
          'length' => 255,
          'not null' => FALSE,
        ),
        'is_default' => array(
          'type' => 'int',
          'default' => 0,
          'not null' => FALSE,
        ),
        'iv' => array(
          'type' => 'varchar',
          'length' => 255,
          'not null' => FALSE,
          'default' => '',
        ),
        'changed' => array(
          'type' => 'int',
          'not null' => TRUE,
          'default' => 0,
        ),
      ),
      'primary key' => array('id'),
    ),
  );
}

/**
 * Implements hook_field_schema().
 */
function archibald_field_schema($field) {
  switch ($field['type']) {
    case 'archibald_lomch_udi_identifier':
      return array(
        'columns' => array(
          'title' => array('type' => 'varchar', 'length' => 255, 'not null' => FALSE),
          'type' => array('type' => 'varchar', 'length' => 10, 'not null' => TRUE),
          'value' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE),
        ),
      );

    case 'archibald_lomch_ontology_vocabulary':
      return array(
        'columns' => array(
          'value' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE),
        ),
      );

    case 'archibald_lomch_filesize':
      return array(
        'columns' => array(
          'value' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE),
          'unit' => array('type' => 'varchar', 'length' => 2, 'not null' => TRUE),
        ),
      );
  }
}

/**
 * Implements hook_enable().
 */
function archibald_enable() {
  $t = get_t();

  drupal_set_message($t("In order to use Archibald, you will have to take several additional steps. Please see the <a href='!link'>status</a> page for more information.", array(
    '!link' => url('admin/reports/status'),
  )), 'status');

  if (!module_exists('actions_permissions')) {
    $info = system_rebuild_module_data();
    drupal_set_message($t("It is highly recommended to also enable the %name module when using Archibald (located inside the %package package).", array(
      '%name' => $info['actions_permissions']->info['name'],
      '%package' => $info['actions_permissions']->info['package'],
    )), 'status');
  }

  foreach (array(
    'field_collection' => '7.x-1.0-beta11',
    'revisioning' => '7.x-1.9',
  ) as $module_name => $module_version) {
    $info = system_get_info('module', $module_name);
    if (!drupal_check_incompatibility(
      drupal_parse_dependency("archibald:{$module_name} (>{$module_version})"),
      $info['version']
    )) {
      drupal_set_message($t("Archibald requires the %name module to work correctly, but the installed version might have issues. Please see the <a href='!link'>status</a> page for more information.", array(
        '%name' => $info['name'],
        '!link' => url('admin/reports/status'),
      )), 'warning');
    }
  }
}

/**
 * Implements hook_requirements().
 */
function archibald_requirements($phase) {
  $t = get_t();
  $return = array();

  if ($phase == 'runtime') {
    if (!$key = archibald_get_encryption_key()) {
      // Try generating it again. Maybe the problem was solved, and we can now
      // create one.
      if ($key = archibald_install_generate_encryption_key()) {
        drupal_set_message($t("An encryption key was successfully generated and stored."));
      }
    }
    $return['archibald__encryption_key'] = array(
      'title' => $t("Archibald: encryption key"),
      'description' => $t("In order to securely store passphrases to the partner private keys, we need a unique and secure encryption key.") . ($key ? '' : ' ' . $t("Please make sure the private filesystem exists and is writable by Drupal. If it is, refresh this page to try and generate a key.")),
      'value' => $key ? $t("Encryption key found") : $t("No encryption key"),
      'severity' => $key ? REQUIREMENT_OK : REQUIREMENT_ERROR,
    );

    $time = variable_get('archibald_languages_vocabulary_created', FALSE);
    $return['archibald__language_vocabulary_imported'] = array(
      'title' => $t("Archibald: language vocabulary imported"),
      'description' => $t("Languages can be imported by visiting <a href='!link'>this link</a>.", array(
        '!link' => url('admin/archibald/import-vocabularies/languages'),
      )),
      'value' => $time ? $t("Imported on !time", array('!time' => format_date($time))) : $t("Not imported"),
      'severity' => $time ? REQUIREMENT_OK : REQUIREMENT_ERROR,
    );

    $time = variable_get('archibald_licenses_vocabulary_created', FALSE);
    $return['archibald__license_vocabulary_imported'] = array(
      'title' => $t("Archibald: Terms of use vocabulary imported"),
      'description' => $t("Terms of use taxonomy terms can be imported by visiting <a href='!link'>this link</a>.", array(
        '!link' => url('admin/archibald/import-vocabularies/licenses'),
      )),
      'value' => $time ? $t("Imported on !time", array('!time' => format_date($time))) : $t("Not imported"),
      'severity' => $time ? REQUIREMENT_OK : REQUIREMENT_ERROR,
    );

    $info = system_get_info('module', 'field_collection');
    $return['archibald__field_collection_version'] = array(
      'title' => $t("Archibald: Field Collection module version"),
      'description' => $t("The Field Collection module (version 7.x-1.0-beta11 and below) has an issue with Entity Translation. You can find more information here: !link.<br />Archibald ships with a patch file you can apply to Field Collection to solve the problem. See the README.txt file for more information.", array(
        '!link' => l("Field Collection: Field translation (entity_translation) support.", 'https://www.drupal.org/node/1344672'),
      )),
      'value' => $info['version'],
    );
    if (drupal_check_incompatibility(
      drupal_parse_dependency('archibald:field_collection (>7.x-1.0-beta11)'),
      str_replace('7.x-', '', $info['version'])
    )) {
      $return['archibald__field_collection_version']['severity'] = REQUIREMENT_WARNING;
    }
    else {
      $return['archibald__field_collection_version']['severity'] = REQUIREMENT_OK;
    }

    $info = system_get_info('module', 'entity_translation');
    $return['archibald__entity_translation_version'] = array(
      'title' => $t("Archibald: Entity Translation module version"),
      'description' => $t("The Entity Translation module (version 7.x-1.0-beta5 and below) has an issue. You can find more information here: !link.<br />Archibald ships with a patch file you can apply to Entity Translation to solve the problem. See the README.txt file for more information.", array(
        '!link' => l("Allow default translation handler to be completely overridden", 'https://www.drupal.org/node/2750179#comment-11300853'),
      )),
      'value' => $info['version'],
    );
    if (drupal_check_incompatibility(
      drupal_parse_dependency('archibald:entity_translation (>7.x-1.0-beta11)'),
      str_replace('7.x-', '', $info['version'])
    )) {
      $return['archibald__entity_translation_version']['severity'] = REQUIREMENT_WARNING;
    }
    else {
      $return['archibald__entity_translation_version']['severity'] = REQUIREMENT_OK;
    }

    $info = system_get_info('module', 'revisioning');
    $return['archibald__revisioning_version'] = array(
      'title' => $t("Archibald: Revisioning module version"),
      'description' => $t("The Revisioning module (version 7.x-1.9 and below) has an issue with Views, and another issue with Entity Translation. You can find more information here: !link1 and here: !link2.<br />Archibald ships with 2 patch files you can apply to Revisioning to solve the problem. See the README.txt file for more information.", array(
        '!link1' => l("Views 'Content revision: State' gives wrong results for Pending state", 'https://www.drupal.org/node/2070819#comment-10369059'),
        '!link2' => l("Work with Entity Translation", 'https://www.drupal.org/node/2750169#comment-11300871'),
      )),
      'value' => $info['version'],
    );
    if (drupal_check_incompatibility(
      drupal_parse_dependency('archibald:revisioning (>7.x-1.9)'),
      str_replace('7.x-', '', $info['version'])
    )) {
      $return['archibald__revisioning_version']['severity'] = REQUIREMENT_WARNING;
    }
    else {
      $return['archibald__revisioning_version']['severity'] = REQUIREMENT_OK;
    }

    $return['archibald__configuration'] = array(
      'title' => $t("Archibald: configuration"),
      'description' => $t("You can configure Archibald here: !link", array(
        '!link' => l('admin/config/services/archibald', 'admin/config/services/archibald'),
      )),
    );
    if (archibald_check_module_configuration()) {
      $return['archibald__configuration']['value'] = $t("Correctly configured");
      $return['archibald__configuration']['severity'] = REQUIREMENT_OK;
    }
    else {
      $return['archibald__configuration']['value'] = $t("Not correctly configured.");
      $return['archibald__configuration']['severity'] = REQUIREMENT_ERROR;
    }

    $return['archibald__partner_configuration'] = array(
      'title' => $t("Archibald: default partner"),
      'description' => $t("You can manage partners here: !link", array(
        '!link' => l('admin/archibald/partner', 'admin/archibald/partner'),
      )),
    );
    if ($partner = archibald_check_default_partner_configuration()) {
      $return['archibald__partner_configuration']['value'] = check_plain($partner->name);
      $return['archibald__partner_configuration']['severity'] = REQUIREMENT_OK;
    }
    else {
      $return['archibald__partner_configuration']['value'] = $t("No default partner");
      $return['archibald__partner_configuration']['severity'] = REQUIREMENT_ERROR;
    }

    $return['archibald__dependencies'] = array(
      'title' => $t("Archibald: dependencies"),
    );
    if ($module_dependency_loaded = archibald_check_module_dependency()) {
      $return['archibald__dependencies']['value'] = $module_dependency_loaded;
      $return['archibald__dependencies']['severity'] = REQUIREMENT_OK;
    }
    else {
      $return['archibald__dependencies']['value'] = $t("Required modules not found");
      $return['archibald__dependencies']['description'] = $t("To use Archibald, you either need the Composer Manager module, or the Libraries API and X Autoload modules together. Check README.txt for more information.");
      $return['archibald__dependencies']['severity'] = REQUIREMENT_ERROR;

      // We return directly. We cannot check the libraries anyway.
      return $return;
    }

    foreach (archibald_libraries_info() as $library_name => $library_info) {
      $library_installed = FALSE;
      if (module_exists('composer_manager')) {
        $library_installed = !empty($library_info['_archibald check class']) ?
          class_exists($library_info['_archibald check class']) :
          interface_exists($library_info['_archibald check interface']);
      }
      elseif (module_exists('libraries')) {
        $library = libraries_load($library_name);
        $library_installed = !empty($library['installed']);
      }

      if (!$library_installed) {
        $return['archibald__' . $library_name] = array(
          'title' => $t("Archibald: %name library", array('%name' => $library_info['name'])),
          'value' => $library_info['_archibald version'],
          'description' => $t("In order to use Archibald, you must first download the !library_name library (!version) at !link. Check the README.txt file for more information", array(
              '!library_name' => $library_info['name'],
              '!version' => $library_info['_archibald version'],
              '!link' => $library_info['download url'],
            )),
          'severity' => REQUIREMENT_ERROR,
        );
      }
      else {
        $return['archibald__' . $library_name] = array(
          'title' => $t("Archibald: %name library", array('%name' => $library_info['name'])),
          'value' => $t("!library_name @version available", array(
              '!library_name' => $library_info['name'],
              '@version' => isset($library['version']) ? $library['version'] : $library_info['_archibald version'],
            )
          ),
          'severity' => REQUIREMENT_OK,
        );
      }
    }
  }

  $return['archibald__openssl'] = array(
    'title' => $t("Archibald: OpenSSL PHP extension"),
  );
  if (!function_exists('openssl_get_privatekey')) {
    $return['archibald__openssl']['value'] = $t("Missing");
    $return['archibald__openssl']['description'] = $t("In order to use Archibald, you must install the PHP OpenSSL extension");
    $return['archibald__openssl']['severity'] = REQUIREMENT_ERROR;
  }
  else {
    $return['archibald__openssl']['value'] = $t("OpenSSL PHP extension is available");
    $return['archibald__openssl']['severity'] = REQUIREMENT_OK;
  }

  $return['archibald__php_version'] = array(
    'title' => $t("Archibald: PHP version"),
    'value' => PHP_VERSION,
    'severity' => version_compare(PHP_VERSION, '5.6', '>=') ? REQUIREMENT_OK : REQUIREMENT_ERROR,
  );

  return $return;
}

/**
 * Implements hook_modules_enabled().
 */
function archibald_modules_enabled($modules) {
  if (in_array('i18n_taxonomy', $modules)) {
    // When installing in context of a distribution, this part doesn't work.
    // It seems field_info_field() will return field information, but that
    // field_update_field() doesn't know about the field, which will trigger
    // an exception. Make sure the field cache is cleared before moving on.
    field_info_cache_clear();

    // i18n_taxonomy will replace the options list callback for our custom
    // fields with its own. We don't want that. Put our own callbacks in there
    // again.
    module_load_include('inc', 'archibald', 'includes/archibald.entities');
    foreach (archibald_archibald_lomch_description_fields() as $fieldname => $info) {
      if (
        $info['type'] == 'taxonomy_term_reference' &&
        isset($info['settings']['options_list_callback'])
      ) {
        if ($field = field_info_field($fieldname)) {
          $field['settings']['options_list_callback'] = $info['settings']['options_list_callback'];
          field_update_field($field);
        }
      }
    }
  }
}

/**
 * Implements hook_modules_disabled().
 */
function archibald_modules_disabled($modules) {
  // i18n_taxonomy will replace the options list callback for our custom
  // fields with its own on install, but with NULL on uninstall. We don't want
  // that. Put our own callbacks in there again, using the same approach as
  // in hook_modules_enabled().
  archibald_modules_enabled($modules);
}

/**
 * Helper function to generate an encryption key.
 *
 * In order to store the private key passphrases, we need an encryption key.
 * Generate one for this site and store it in a file outside the webroot. The
 * path to the key is stored in a system variable called
 * "archibald_encryption_key_path".
 *
 * @return bool
 *    True on success, false otherwise.
 */
function archibald_install_generate_encryption_key() {
  $filename = 'archibald-encryption-key-' . uniqid() . '.txt';
  $key = base64_encode(openssl_random_pseudo_bytes(32));
  if (file_put_contents('private://' . $filename, $key)) {
    variable_set('archibald_encryption_key_path', 'private://' . $filename);
    return TRUE;
  }
  else {
    return FALSE;
  }
}

/**
 * Implements hook_update_N().
 *
 * - Generate an encryption key to store passphrases.
 * - Add a "changed" field to the custom entities.
 * - Add a "iv" field to the partner table, to secure passphrases. Populate all
 *   partner entries with a randomly generated initialization vector.
 * - Encrypt passphrases. Each passphrase will get encrypted using the site's
 *   encryption key and the generated initialization vector.
 */
function archibald_update_7301() {
  if (
    !archibald_get_encryption_key() &&
    !archibald_install_generate_encryption_key()
  ) {
    throw new Exception(t("Could not generate, nor find, an encryption key! Please make sure the private:// file system exists, and is writable."));
  }

  foreach (array('archibald_partner', 'archibald_vcard') as $table) {
    db_add_field($table, 'changed', array(
      'type' => 'int',
      'not null' => TRUE,
      'default' => 0,
    ));
    // Give a default value corresponding to the current time.
    db_update($table)->fields(array('changed' => REQUEST_TIME))->execute();
  }

  db_add_field('archibald_partner', 'iv', array(
    'type' => 'varchar',
    'length' => 255,
    'not null' => FALSE,
    'default' => '',
  ));
  $result = db_query("SELECT * FROM {archibald_partner}");
  while($partner = $result->fetchAssoc()) {
    // Generate a random initialization vector. archibald_encrypt() expects the
    // vector to be base64 encoded, so encode it now.
    $iv = base64_encode(openssl_random_pseudo_bytes(16));

    // Encrypt the passphrase.
    if (!empty($partner['passphrase'])) {
      $passphrase = archibald_encrypt($partner['passphrase'], $iv);
    }
    else {
      $passphrase = '';
    }

    db_update('archibald_partner')
      ->fields(array(
        'iv' => $iv,
        'passphrase' => $passphrase,
      ))
      ->condition('id', $partner['id'])
      ->execute();
  }

  return array();
}

/**
 * Implements hook_update_N().
 *
 * - Change the label of the VCards to "Lastname Firstname (organization)".
 */
function archibald_update_7302() {
  $vcards = entity_load('archibald_vcard');

  if (empty($vcards)) {
    return array();
  }

  foreach ($vcards as $vcard) {
    // Prepare the name based on the field information. Either we have a last
    // name, in which case we use the format "lastname firstname (organization)",
    // or not, in case we only use the organization name.
    if (!empty($vcard->vcard_last_name[LANGUAGE_NONE][0]['value'])) {
      $name = "{$vcard->vcard_last_name[LANGUAGE_NONE][0]['value']} {$vcard->vcard_first_name[LANGUAGE_NONE][0]['value']}";

      if (!empty($vcard->vcard_organization[LANGUAGE_NONE][0]['value'])) {
        $name .= " ({$vcard->vcard_organization[LANGUAGE_NONE][0]['value']})";
      }
    }
    else {
      $name = $vcard->vcard_organization[LANGUAGE_NONE][0]['value'];
    }

    $vcard->name = $name;
    $vcard->changed = REQUEST_TIME;
    $vcard->save();
  }

  return array();
}

/**
 * Implements hook_update_N().
 *
 * - Migrate "pre-school" items to "compulsory education" for
 *   "education_context" ontology items. "pre-school" has officially been
 *   deprecated.
 */
function archibald_update_7303() {
  $query = new EntityFieldQuery();
  $query->entityCondition('entity_type', 'node')
        ->entityCondition('bundle', 'archibald_lomch_description')
        ->fieldCondition('lomch_context', 'value', 'pre-school', '=');

  $result = $query->execute();
  $nodes = $result['node'];

  foreach ($nodes as $nid => $node) {
    // Load it fully.
    $node = node_load($nid, NULL, TRUE);

    // Find the field delta with the "pre-school" value, and replace it with
    // "compulsory education", unless "compulsory education" was already
    // present, in which case it must simply be removed.
    foreach ($node->lomch_context as $langcode => $values) {
      foreach ($values as $delta => $item) {
        if ($item['value'] == 'pre-school') {
          // Do we already have "compulsory education"? If so, remove our
          // "pre-school" entry. Otherwise, replace it with "compulsory
          // education".
          if (array_reduce($values, function($carry, $value) {
            if (!$carry) {
              $carry = ($value['value'] == 'compulsory education');
            }
            return $carry;
          }, FALSE)) {
            // Remove it.
            unset($node->lomch_context[$langcode][$delta]);
          }
          else {
            // Replace it.
            $node->lomch_context[$langcode][$delta]['value'] = 'compulsory education';
          }
        }
      }
    }

    // Save it again.
    node_save($node);

    if ($node->status == NODE_PUBLISHED) {
      // Re-computing the hash right now is not feasible if we have a very
      // large amount of nodes. Instead, add all nodes to the queue,
      // flagging them as needing to be rehashed. Make sure we don't queue
      // the same node twice by clearing existing queues.
      archibald_clear_queue("node:{$nid}:rehash");
      archibald_add_to_queue(
        "node:{$nid}:rehash",
        '_archibald_recompute_hash',
        array($nid, $node->vid, 'current')
      );
    }
  }

  if (!empty($nodes)) {
    drupal_set_message(t("Some nodes have been updated, and may need to be re-published. They contained a deprecated ontology term, 'pre-school'. All values have been removed, and replaced with 'compulsory education'."), 'warning');
  }

  return array();
}

/**
 * Implements hook_update_N().
 *
 * - Set "Format" and "Location" fields as "recommended".
 * - Make taxonomies translatable.
 */
function archibald_update_7304() {
  foreach (array(
    'lomch_technical_format',
    'lomch_location',
  ) as $field_name) {
    $instance = field_info_instance(
      'node',
      $field_name,
      'archibald_lomch_description'
    );

    if (!empty($instance)) {
      $instance['settings']['archibald_lom_recommended'] = 1;
      field_update_instance($instance);
    }
  }

  // Enable the translation.
  module_load_include('inc', 'archibald', 'includes/archibald.entities');
  archibald_taxonomy_entity_insert();

  // We need to make sure all terms have at least one translation, otherwise
  // they will appear with empty names.
  foreach (array(
    'archibald_lomch_coverage',
    'archibald_lomch_keywords',
  ) as $bundle) {
    if ($vocabulary = taxonomy_vocabulary_machine_name_load($bundle)) {
      $query = new EntityFieldQuery();
      $result = $query
        ->entityCondition('entity_type', 'taxonomy_term')
        ->propertyCondition('vid', $vocabulary->vid, '=')
        ->execute();
      if (!empty($result['taxonomy_term'])) {
        foreach ($result['taxonomy_term'] as $term) {
          // Load fully, and save again. This will trigger the Title module
          // logic.
          $term = taxonomy_term_load($term->tid);
          taxonomy_term_save($term);
        }
      }
    }
  }
  drupal_set_message(t("All previously untranslated Coverage and Keyword items have now been assigned to the default language (%language).", array(
    '%language' => language_default('native'),
  )), 'warning');

  return array();
}
