<?php

/**
 * @file
 * Field API logic for fields defined by Archibald.
 */

/**
 * Field instance configuration callback for the archibald_lomch_udi_identifier field.
 *
 * @see hook_field_instance_settings_form()
 * @see archibald_field_instance_settings_form()
 */
function archibald_lomch_udi_identifier_field_instance_settings_form($field, $instance) {
  $settings = $instance['settings'];

  $form = array();
  $form['show_title'] = array(
    '#type' => 'checkbox',
    '#title' => t("Show title field"),
    '#description' => t("The field can have an optional title field."),
    '#default_value' => !empty($settings['show_title']) ? $settings['show_title'] : 0,
  );

  return $form;
}

/**
 * Field instance configuration callback for the archibald_lomch_ontology_vocabulary field.
 *
 * @see hook_field_instance_settings_form()
 * @see archibald_field_instance_settings_form()
 */
function archibald_lomch_ontology_vocabulary_field_instance_settings_form($field, $instance) {
  $settings = $instance['settings'];

  $form = array();
  $form['ontology_vocabulary_id'] = array(
    '#type' => 'textfield',
    '#title' => t("Ontology vocabulary ID"),
    '#description' => t("The vocabulary identifier that should be used for this vocabulary field. E.g., <em>intended_enduserrole</em>. Make sure the value is correct, otherwise the field will not show any values."),
    '#required' => TRUE,
    '#default_value' => !empty($settings['ontology_vocabulary_id']) ? $settings['ontology_vocabulary_id'] : '',
  );

  return $form;
}

/**
 * Validation callback for the archibald_lomch_udi_identifier field.
 *
 * @see hook_field_validate()
 * @see archibald_field_validate()
 */
function archibald_lomch_udi_identifier_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
  foreach ($items as $delta => $item) {
    if (!empty($item['value'])) {
      switch ($item['type']) {
        case 'URL':
          if (!valid_url($item['value'], TRUE)) {
            $errors[$field['field_name']][$langcode][$delta][] = array(
              'error' => 'archibald_udi_url_invalid',
              'message' => t("The identifier must be a valid URL if you choose the URL type (e.g., http://example.com)."),
            );
          }
          break;

        case 'ISBN':
          if (!preg_match('/(ISBN)?[ \-]*[0-9\-\ ]{6,}/i', $item['value'])) {
            $errors[$field['field_name']][$langcode][$delta][] = array(
              'error' => 'archibald_udi_isbn_invalid',
              'message' => t("The identifier must be a valid ISBN code if you choose the ISBN type (e.g., 123-4-56-789012-3)."),
            );
          }
          break;

        case 'DOI':
          if (!preg_match('/^(10\.\d{4})((?:[.][0-9]+)*)\/(.*)/i', $item['value'])) {
            $errors[$field['field_name']][$langcode][$delta][] = array(
              'error' => 'archibald_udi_doi_invalid',
              'message' => t("The identifier must be a valid DOI code if you choose the DOI type (e.g., 10.1000/xyz123)."),
            );
          }
          break;
      }
    }
  }
}

/**
 * Validation callback for the archibald_lomch_udi_identifier field.
 *
 * @see hook_field_validate()
 * @see archibald_field_validate()
 */
function archibald_lomch_filesize_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
  foreach ($items as $delta => $item) {
    if (!empty($item['value']) && !is_numeric($item['value'])) {
      $errors[$field['field_name']][$langcode][$delta][] = array(
        'error' => 'archibald_filesize_invalid',
        'message' => t("The size must be numerical (e.g., 10.5)."),
      );
    }
  }
}

/**
 * Empty check callback for the archibald_lomch_udi_identifier field.
 *
 * @see hook_field_is_empty()
 * @see archibald_field_is_empty()
 */
function archibald_lomch_udi_identifier_field_is_empty($item, $field) {
  return empty($item['value']);
}

/**
 * Empty check callback for the archibald_lomch_ontology_vocabulary field.
 *
 * @see hook_field_is_empty()
 * @see archibald_field_is_empty()
 */
function archibald_lomch_ontology_vocabulary_field_is_empty($item, $field) {
  return empty($item['value']);
}

/**
 * Empty check callback for the archibald_lomch_filesize field.
 *
 * @see hook_field_is_empty()
 * @see archibald_field_is_empty()
 */
function archibald_lomch_filesize_field_is_empty($item, $field) {
  return empty($item['value']);
}

/**
 * Options callback for the archibald_lomch_ontology_vocabulary field.
 *
 * @see hook_options_list()
 * @see archibald_options_list()
 */
function archibald_lomch_ontology_vocabulary_options_list($field, $instance, $entity_type, $entity) {
  module_load_include('inc', 'archibald', 'includes/archibald.ontology');

  $ontology_vocid = $instance['settings']['ontology_vocabulary_id'];
  $options = archibald_ontology_load($ontology_vocid);

  // This is a small hack we have to implement. When loading the
  // technical_format vocabulary from the Ontology server, the identifiers are
  // not valid MIME types. The reason is historical, and annoying. But, alas!,
  // we have to deal with it. Which is why we prepend the "parent" identifier,
  // which will allow us to have valid MIME types.
  if ($ontology_vocid == 'technical_format') {
    foreach ($options as $group => $child_options) {
      if (is_array($child_options)) {
        foreach ($child_options as $key => $child_option) {
          $options[$group]["$group/$key"] = $child_option;
          unset($options[$group][$key]);
        }
      }
    }
  }

  return $options;
}

/**
 * Formatter callback for the archibald_lomch_udi_identifier field formatter.
 *
 * @see hook_field_formatter_view()
 * @see archibald_field_formatter_view()
 */
function archibald_lomch_udi_identifier_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $rows = array();

  foreach ($items as $delta => $item) {
    switch ($item['type']) {
      case 'URL':
        $title = t("URL");
        $value = l(!empty($item['title']) ? $item['title'] : $item['value'], $item['value'], array(
          'attributes' => array('target' => '_blank'),
        ));
        break;

      case 'DOI':
        $title = t("DOI");
        $value = check_plain($item['value']);
        if (!empty($item['title'])) {
          $value .= '<br />' . check_plain($item['title']);
        }
        break;

      case 'ISBN':
        $title = t("ISBN");
        $value = check_plain($item['value']);
        if (!empty($item['title'])) {
          $value .= '<br />' . check_plain($item['title']);
        }
        break;
    }

    $rows[$delta] = array(
      $title,
      $value,
    );
  }

  return array(
    array(
      '#theme' => 'table',
      '#header' => array(),
      '#rows' => $rows,
    ),
  );
}

/**
 * Formatter callback for the archibald_lomch_ontology_vocabulary field formatter.
 *
 * @see hook_field_formatter_view()
 * @see archibald_field_formatter_view()
 */
function archibald_lomch_ontology_vocabulary_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();
  $is_grouped = TRUE;

  $allowed_values = archibald_lomch_ontology_vocabulary_options_list($field, $instance, $entity_type, $entity);

  // Some vocabularies have sub-groups. Take this into account when rendering.
  if (!is_array(end($allowed_values))) {
    $is_grouped = FALSE;
    $allowed_values = array($allowed_values);
  }

  foreach ($allowed_values as $group_name => $values) {
    $list = array();

    foreach ($items as $delta => $item) {
      $output = '';
      if (isset($values[$item['value']])) {
        $output .= field_filter_xss($values[$item['value']]);
      }
      elseif (!$is_grouped) {
        // If no match was found in allowed values, fall back to the key.
        $output = field_filter_xss($item['value']);
      }

      // If the cardinality is 1, we simply output it as is. We can even stop
      // here.
      if ((int) $field['cardinality'] === 1 && !empty($output)) {
        return array(
          array(
            '#prefix' => '<div class="archibald-lomch-ontology-vocabulary-field-single">',
            '#markup' => $output,
            '#suffix' => '</div>',
          ),
        );
      }
      elseif (!empty($output)) {
        // Otherwise, we want to render it as a list of items. Add the item to
        // the list.
        $list[] = $output;
      }
    }

    // Do we have any items? Add it to our element.
    if (!empty($list)) {
      $element[] = array(
        '#prefix' => '<div class="archibald-lomch-ontology-vocabulary-field-multiple">',
        '#markup' => theme('item_list', array(
          'items' => $list,
          'title' => !empty($group_name) ? $group_name : NULL,
        )),
        '#suffix' => '</div>',
      );
    }
  }

  return $element;
}

/**
 * Formatter callback for the archibald_lomch_filesize field formatter.
 *
 * @see hook_field_formatter_view()
 * @see archibald_field_formatter_view()
 */
function archibald_lomch_filesize_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();
  foreach ($items as $delta => $item) {
    $value = $item['value'];

    // Let it fall through at each step, so we get the correct multiplier.
    switch ($item['unit']) {
      case 'gb':
        $value *= DRUPAL_KILOBYTE;
      case 'mb':
        $value *= DRUPAL_KILOBYTE;
      case 'kb':
        $value *= DRUPAL_KILOBYTE;
    }

    $element[] = array(
      '#markup' => format_size($value, $langcode),
    );
  }

  return $element;
}

/**
 * Form definition for archibald_lomch_udi_identifier widget.
 *
 * @see hook_field_widget_form()
 * @see archibald_field_widget_form()
 */
function archibald_lomch_udi_identifier_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  module_load_include('inc', 'archibald', 'theme/theme');

  $item = isset($items[$delta]) ? $items[$delta] : NULL;

  // Turn our element into a fieldset.
  $element += array(
    '#type' => 'fieldset',
    '#delta' => $delta,
  );

  $element['type'] = array(
    '#type' => 'select',
    '#title' => t("Type"),
    '#options' => array(
      'URL' => 'URL',
      'DOI' => 'DOI',
      'ISBN' => 'ISBN',
    ),
    '#default_value' => isset($item['type']) ? $item['type'] : 'URL',
  );

  $element['value'] = array(
    '#type' => 'textfield',
    '#title' => t("Value"),
    '#required_type' => 'archibald_lom_required',
    '#default_value' => isset($item['value']) ? $item['value'] : '',
  );

  $element['title'] = array(
    '#type' => !empty($instance['settings']['show_title']) ? 'textfield' : 'hidden',
    '#title' => t("Title"),
    '#default_value' => isset($item['title']) ? $item['title'] : '',
  );

  if (
    !empty($instance['settings']['archibald_lom_recommended']) ||
    !empty($instance['settings']['archibald_lom_required'])
  ) {
    foreach (array('type', 'value') as $key) {
      $element[$key]['#title'] .= ' ' . theme_archibald_form_required_marker(array(
        'element' => array(
          '#required_type' => !empty($instance['settings']['archibald_lom_required']) ?
            'archibald_lom_required' :
            'archibald_lom_recommended',
        ),
      ));
    }
  }

  if (module_exists('i18n_field') && function_exists('i18n_string')) {
    // i18n_field has a small bug, in that a field with a "value" sub-field is
    // expected to have the title on this "value" field, not the parent
    // container. This is hard to fix in i18n_field, which is why we implement
    // this simple hack. If the module is active, we localize the field label
    // here, in code.
    $element['#title'] = i18n_string(
      "field:{$instance['field_name']}:{$instance['bundle']}:label",
      $element['#title']
    );
  }

  return $element;
}

/**
 * Form definition for archibald_lomch_udi_identifier widget.
 *
 * @see hook_field_widget_form()
 * @see archibald_field_widget_form()
 */
function archibald_lomch_filesize_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  $item = isset($items[$delta]) ? $items[$delta] : NULL;

  // Turn our element into a fieldset.
  $element += array(
    '#type' => 'fieldset',
    '#prefix' => '<div class="archibald-lomch-filesize-wrapper">',
    '#suffix' => '</div>',
    '#delta' => $delta,
  );

  $element['value'] = array(
    '#type' => 'textfield',
    '#default_value' => isset($item['value']) ? $item['value'] : '',
    '#required' => $element['#required'],
  );

  $element['unit'] = array(
    '#type' => 'select',
    '#options' => array(
      'b'  => 'B',
      'kb' => 'KB',
      'mb' => 'MB',
      'gb' => 'GB',
    ),
    '#default_value' => isset($item['unit']) ? $item['unit'] : 'b',
    '#required' => $element['#required'],
  );

  if (module_exists('i18n_field') && function_exists('locale')) {
    // i18n_field has a small bug, in that a field with a "value" sub-field is
    // expected to have the title on this "value" field, not the parent
    // container. This is hard to fix in i18n_field, which is why we implement
    // this simple hack. If the module is active, we localize the field label
    // here, in code.
    $element['#title'] = locale(
      $element['#title'],
      "{$instance['field_name']}:{$instance['bundle']}:label"
    );
  }

  return $element;
}

/**
 * Alter the widget forms for archibald_lomch_ontology_vocabulary fields.
 *
 * Some vocabularies are in fact a 2-level tree structure. If the field uses
 * a select field type, this is fine, as Drupal handles multi-level select
 * fields. However, if the elements are checkboxes, we have a problem. Drupal
 * will flatten the tree, not showing the grouping information. We alter the
 * widgets for archibald_lomch_ontology_vocabulary fields and check their
 * types and options. If we see this is a checkbox list with a tree structure,
 * we alter the widget and insert label elements to represent the grouping.
 * This also requires some tricky to copy the values from the new checkboxes
 * over to the actual widget element. See
 * _archibald_lomch_ontology_copy_checkbox_values() for more information.
 *
 * @see _archibald_lomch_ontology_copy_checkbox_values()
 */
function archibald_archibald_lomch_ontology_vocabulary_field_widget_form_alter(&$element, &$form_state, $context) {
  // First, check if we are dealing with checkboxes or not. Select fields handle
  // nested options already, so we don't need to do anything.
  if ($element['#type'] == 'checkboxes') {
    // Now, are we dealing with a multi-level options list? Because Drupal has
    // already flattened the options, we load the data again. Because the
    // data is statically cached, this has no performance cost, so it is safe
    // to do so.
    $options = archibald_ontology_load($context['instance']['settings']['ontology_vocabulary_id']);
    if (is_array(end($options))) {
      // We need to alter the form. Instead of a "checkboxes" type, we will turn
      // it into a list of multiple "stand-alone" checkboxes.
      $element['#type'] = 'fieldset';
      unset($element['#options']);

      // Unfortunately, Drupal won't be able to fetch the values from the
      // child elements and apply it to our widget. We need to "hack" the form
      // submission a bit. Drupal does not provide a way to hook into the
      // submission workflow, but we can hook into the validation workflow. The
      // trick is to alter the actual submitted values in the validation step,
      // and copy the new child checkboxes' values to our widget. We do this
      // in _archibald_lomch_ontology_copy_checkbox_values(). However, because
      // Drupal won't load our current file automatically, we set the
      // callback to archibald_lomch_ontology_copy_checkbox_values()
      // which will forward it to
      //  _archibald_lomch_ontology_copy_checkbox_values().
      $element['#element_validate'] = array_merge(array(
        'archibald_lomch_ontology_copy_checkbox_values',
      ), $element['#element_validate']);

      // This helper key will allow us to quickly find the child checkboxes to
      // copy their values over.
      $element['#child_checkboxes'] = array();

      // Prepare the required marker, if any.
      if ($element['#required']) {
        $vars['element'] = $element;
        $marker = ' ' . theme_form_required_marker($vars);
      }
      elseif (
        !empty($context['instance']['settings']['archibald_lom_required']) ||
        !empty($context['instance']['settings']['archibald_lom_recommended'])
      ) {
        module_load_include('inc', 'archibald', 'theme/theme');

        $vars = array(
          'element' => array(
            '#required_type' => !empty($context['instance']['settings']['archibald_lom_required']) ?
              'archibald_lom_required' :
              'archibald_lom_recommended',
          ),
        );
        $marker = ' ' . theme_archibald_form_required_marker($vars);
      }
      else {
        $marker = '';
      }

      $i = 0;
      foreach ($options as $group_name => $child_options) {
        $element["group_{$i}_wrapper"] = array(
          '#prefix' => '<div class="form-item form-checkboxes">',
          '#suffix' => '</div>',
        );

        // We don't add the required/recommended marker yet. See below.
        $element["group_{$i}_wrapper"]["group_{$i}_label"] = array(
          '#theme' => 'html_tag',
          '#tag' => 'label',
          '#value' => check_plain($group_name),
        );

        // This is a really dirty hack, but we don't have much choice in the
        // matter. Usually, this widget is used by the
        // education.learningResourceType field. The thing is, in LOM-CH (as
        // opposed to LOM), this field is split into 2 sub-categories,
        // documentary and pedagogical. The Ontology server does not provide
        // IDs for these sub-groups; we only have their human-readable, and
        // localized, names. Furthermore, the pedagogical category is
        // "recommended", whereas the documentary category is "required". This
        // is not compatible with our field settings, which applies to both
        // categories (because it's a single field in reality). Now, we have to
        // respect the standard. Which is why we hard-code this tiny thing in
        // here, as hideous as it may be. We check the child options, as they
        // at least contain machine readable names. If we find 2 options that
        // belong to pedagogical, it is safe to assume we are in that context.
        // If we did already compute a marker, it means the "parent" field is
        // either required or recommended, so we hard-code our asterisk for
        // the pedagogical checkboxes. See the LOM-CHv1.3 standard and the
        // official Ontology documentation for more information.
        if (
          !empty($marker) &&
          in_array('exercise', array_keys($child_options)) &&
          in_array('experiment', array_keys($child_options))
        ) {
          // Re-load the theme file, just in case. It may not have been loaded
          // before.
          module_load_include('inc', 'archibald', 'theme/theme');

          // Create the recommended marker.
          $vars = array(
            'element' => array(
              '#required_type' => 'archibald_lom_recommended',
            ),
          );
          $tmp = ' ' . theme_archibald_form_required_marker($vars);
          $element["group_{$i}_wrapper"]["group_{$i}_label"]['#value'] .= $tmp;
        }
        else {
          // Simply add the regular marker.
          $element["group_{$i}_wrapper"]["group_{$i}_label"]['#value'] .= $marker;
        }

        foreach ($child_options as $key => $child_option) {
          $element["group_{$i}_wrapper"][$key] = array(
            '#type' => 'checkbox',
            '#title' => check_plain($child_option),
            '#default_value' => in_array($key, $element['#default_value']),
            '#return_value' => $key,
          );
          $element['#child_checkboxes']["group_{$i}_wrapper"][] = $key;
        }

        $i++;
      }
    }
  }
}

/**
 * Copy over the values of child checkboxes to the parent container.
 *
 * See the archibald_lomch_ontology_vocabulary_field_widget_form_alter()
 * function for an explanation of this callback.
 *
 * @see archibald_lomch_ontology_vocabulary_field_widget_form_alter()
 */
function _archibald_lomch_ontology_copy_checkbox_values(&$element, $form_state) {
  $element['#value'] = array();
  foreach ($element['#child_checkboxes'] as $group => $checkboxes) {
    foreach ($checkboxes as $key) {
      if ($element[$group][$key]['#value']) {
        $element['#value'][$key] = $key;
      }
    }
  }
}

/**
 * Alter the widget form for the lomch_preview_image field.
 *
 * We use the Image field "title" field as our "copyright" field. We want to
 * change the widget form to change the field title and description. This
 * must happen after the File and Image modules have finished processing the
 * widget. See _archibald_lomch_preview_image_title_to_copyright().
 *
 * @see _archibald_lomch_preview_image_title_to_copyright()
 */
function archibald_lomch_preview_image_field_widget_form_alter(&$element, $form_state, $context) {
  foreach ($element as $i => $value) {
    if (is_numeric($i)) {
      // We do the processing in _archibald_lomch_preview_image_title_to_copyright().
      // However, because Drupal won't load our current file automatically, we
      // set the callback to archibald_lomch_preview_image_title_to_copyright(), which
      // will forward it to _archibald_lomch_preview_image_title_to_copyright().
      $element[$i]['#process'][] = 'archibald_lomch_preview_image_title_to_copyright';
    }
  }
}

/**
 * Change the title field information for an image widget to copyright.
 *
 * See lomch_preview_image_field_widget_form_alter() for more information.
 *
 * @see lomch_preview_image_field_widget_form_alter()
 */
function _archibald_lomch_preview_image_title_to_copyright($element, &$form_state, $form) {
  if (isset($element['title'])) {
    if ($element['title']['#access']) {
      module_load_include('inc', 'archibald', 'theme/theme');

      $element['title']['#title'] = t("Copyright information", array(), array('context' => 'archibald:node_form')) . ' ' . theme_archibald_form_required_marker(array(
        'element' => array(
          '#required_type' => 'archibald_lom_recommended',
        ),
      ));
      $element['title']['#description'] = t("Add any copyright information here.");
    }
  }
  return $element;
}

/**
 * Add a description attribute to the lomch_resource_language field.
 *
 * Because it is not easily possible to insert tokens in a field description
 * string on install, we add it here.
 *
 * @see archibald_generate_taxonomy_help_text()
 */
function archibald_lomch_resource_language_field_widget_form_alter(&$element, $form_state, $context) {
  global $user;
  $vocabulary = taxonomy_vocabulary_machine_name_load('archibald_lomch_description_languages');
  $element['#description'] = archibald_generate_taxonomy_help_text($vocabulary, $user);
}

/**
 * Add a description attribute to the lomch_keywords field.
 *
 * Because it is not easily possible to insert tokens in a field description
 * string on install, we add it here.
 *
 * @see archibald_generate_taxonomy_help_text()
 */
function archibald_lomch_keywords_field_widget_form_alter(&$element, $form_state, $context) {
  global $user;
  $vocabulary = taxonomy_vocabulary_machine_name_load('archibald_lomch_keywords');
  $element['#description'] = archibald_generate_taxonomy_help_text($vocabulary, $user);
}

/**
 * Add a description attribute to the lomch_coverage field.
 *
 * Because it is not easily possible to insert tokens in a field description
 * string on install, we add it here.
 *
 * @see archibald_generate_taxonomy_help_text()
 */
function archibald_lomch_coverage_field_widget_form_alter(&$element, $form_state, $context) {
  global $user;
  $vocabulary = taxonomy_vocabulary_machine_name_load('archibald_lomch_coverage');
  $element['#description'] = archibald_generate_taxonomy_help_text($vocabulary, $user);
}

/**
 * Add a description attribute to the lomch_rights_description field.
 *
 * Because it is not easily possible to insert tokens in a field description
 * string on install, we add it here.
 *
 * @see archibald_generate_taxonomy_help_text()
 */
function archibald_lomch_rights_description_field_widget_form_alter(&$element, $form_state, $context) {
  global $user;
  $vocabulary = taxonomy_vocabulary_machine_name_load('archibald_lomch_description_licenses');
  $element['#description'] = archibald_generate_taxonomy_help_text($vocabulary, $user);
}

/**
 * Add a required marker to the field container.
 */
function archibald_lomch_typical_age_range_field_widget_form_alter(&$element, $form_state, $context) {
  if (module_exists('i18n_field') && function_exists('locale')) {
    // i18n_field has a small bug,:if a field has multiple sub-fields with
    // labels, but the parent field ALSO has a label, only the parent label is
    // translated (although the subfields are marked for translation). Don't
    // bother patching i18n, it probably won't get a new release any time soon.
    // Instead, fix it here.
    foreach (array('from', 'to') as $key) {
      $element[$key]['#title'] = locale(
        $element[$key]['#title'],
        "{$context['instance']['field_name']}:{$context['instance']['bundle']}:{$key}_label"
      );
    }
  }

  $type = !empty($context['instance']['settings']['archibald_lom_required']) ?
    'archibald_lom_required' :
    (
      !empty($context['instance']['settings']['archibald_lom_recommended']) ?
        'archibald_lom_recommended' :
        'required'
    );
  archibald_add_required_marker_to_element_children($element, $type);
}

/**
 * Helper function to generate a description text for a taxonomy field.
 *
 * For the taxonomy fields, we want to provide a help text with a link, so users
 * can quickly understand where to manage taxonomy terms. This helper function
 * also checks if the user has access. If not, the text is different.
 *
 * @param object $vocabulary_id
 *    The vocabulary for which the description text should be generated.
 * @param object $account
 *    The user for which the description text should be shown.
 *
 * @return string
 *    The generated description text.
 */
function archibald_generate_taxonomy_help_text($vocabulary, $account) {
  if (
    user_access('administer taxonomy', $account) ||
    user_access('edit terms in ' . $vocabulary->vid, $account)
  ) {
    return t("You can manage the %vocabulary terms <a href='!link' target='_blank'>here</a>.", array(
      '%vocabulary' => function_exists('i18n_taxonomy_vocabulary_name') ?
        i18n_taxonomy_vocabulary_name($vocabulary) :
        $vocabulary->name,
      '!link' => url('admin/structure/taxonomy/' . $vocabulary->machine_name),
    ));
  }
  else {
    return '';
  }
}

/**
 * Alter the archibald_publication_partner field widget.
 *
 * Add a description attribute to the archibald_publication_partner field.
 * Because this description depends on user permissions, as well as contains a
 * dynamic token, we add it here.
 *
 * If a default partner was selected, set it as the default value.
 *
 * @see archibald_generate_archibald_partner_entityreference_text()
 */
function archibald_archibald_publication_partner_field_widget_form_alter(&$element, $form_state, $context) {
  global $user;
  $element['#description'] = archibald_generate_archibald_partner_entityreference_text($user);

  $partner = archibald_load_default_partner();
  if (isset($partner->id) && empty($element['#default_value'])) {
    $element['#default_value'] = (int) $partner->id;
  }
}

/**
 * Helper function to generate a description text for an archibald_partner
 * entityreference field.
 *
 * For these entityreference fields, we want to provide a help text with a link,
 * so users can quickly understand where to manage archibald_partner entities.
 * This helper function also checks if the user has access. If not, the text is
 * different.
 *
 * @param object $account
 *    The user for which the description text should be shown.
 *
 * @return string
 *    The generated description text.
 */
function archibald_generate_archibald_partner_entityreference_text($account) {
  if (user_access('administer archibald_partners', $account)) {
    return t("You can manage the <em>Partners</em> <a href='!link' target='_blank'>here</a>.", array(
      '!link' => url('admin/archibald/partner'),
    ));
  }
  else {
    return '';
  }
}

/**
 * Helper function to add a required field marker to a container children.
 *
 * @param array &$element
 *    The element to modify.
 * @param string $type
 *    The type of required. Can be "required", "archibald_lom_required" or
 *    "archibald_lom_recommended". Defaults to "required".
 */
function archibald_add_required_marker_to_element_children(&$element, $type = 'required') {
  module_load_include('inc', 'archibald', 'theme/theme');

  $vars = array('element' => array('#required_type' => $type));
  $marker = ' ' . theme_archibald_form_required_marker($vars);

  foreach ($element as $key => $value) {
    if (!preg_match('/^#/', $key)) {
      $element[$key]['#title'] .= $marker;
    }
  }
}
