You are here

flagging_form.module in Flagging Form 7.3

Same filename and directory in other branches
  1. 7 flagging_form.module

Provides forms for editing and deleting a flagging.

Random notes:

  • We use the prefix "flagging_form_flagging_" for the form functions.
  • We use CRUD terminology wherever possible (e.g., 'create' and 'delete' instead of 'flag' and 'unflag'), because we're dealing with entities. For flag labels (e.g., the setting names in flagging_form_flag_link_types()), we use the old 'flag'/'unflag' terminology.

File

flagging_form.module
View source
<?php

/**
 * @file
 * Provides forms for editing and deleting a flagging.
 *
 * Random notes:
 *
 * - We use the prefix "flagging_form_flagging_"
 *   for the form functions.
 *
 * - We use CRUD terminology wherever possible (e.g., 'create' and 'delete'
 *   instead of 'flag' and 'unflag'), because we're dealing with entities. For
 *   flag labels (e.g., the setting names in flagging_form_flag_link_types()), we
 *   use the old 'flag'/'unflag' terminology.
 */

/**
 * Implements hook_flag_link_type_info().
 */
function flagging_form_flag_link_type_info() {
  return array(
    'form' => array(
      'title' => t('Form'),
      'description' => t('The user will be taken to a form to confirm his decision and possibly edit fields attached to the flag.'),
      'options' => array(
        'form_flagging_help' => '',
        'form_flagging_button' => '',
        'form_flagging_delete_button' => '',
        'form_unflagging_help' => '',
        'form_unflagging_button' => '',
        // @todo: Discuss: should the following checkbox be on by default?
        'form_unflag_link_leads_to_edit' => FALSE,
        'form_interaction' => 'default',
      ),
    ),
  );
}

/**
 * Implements hook_flagging_form_interactions().
 */
function flagging_form_flagging_form_interactions() {
  return array(
    'default' => array(
      'title' => t('Default'),
      'description' => t('Forms are displayed on their own page.'),
      'weight' => -20,
    ),
  );
}

/**
 * Returns all the available interactions.
 */
function flagging_form_interactions() {
  $list =& drupal_static(__FUNCTION__, NULL);
  if (!isset($list)) {
    $list = module_invoke_all('flagging_form_interactions');
    uasort($list, 'drupal_sort_weight');
  }
  return $list;
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Injects out settings to the flag admin form.
 */
function flagging_form_form_flag_form_alter(&$form) {
  $flag = $form['#flag'];

  // In some help texts we embed a link to the 'manage fields' tab. But we
  // can only do this for flags which are already saved.
  if (isset($flag->fid) && module_exists('field_ui')) {
    $manage_fields_url = url('admin/structure/flags/manage/' . $flag->name . '/fields');
  }
  elseif (module_exists('help')) {
    $manage_fields_url = url('admin/help/field');
  }
  else {
    $manage_fields_url = url('admin/structure/flags');
  }
  $settings = array(
    '#type' => 'fieldset',
    '#title' => t('Options for the "Form" link type'),
    '#description' => t('There are two forms: one used for flagging an item and editing possible <a href="@manage-fields-url">attached fields</a>, and one used for unflagging the item. You may configure the two forms below.', array(
      '@manage-fields-url' => $manage_fields_url,
    )),
    '#id' => 'link-options-form',
    '#weight' => 22,
  );

  // To prevent typos, we do this mechanically.
  $labels = array(
    'form_flagging_help' => array(
      '#title' => t('Flagging guidelines'),
      '#description' => t('This short help text will be displayed at the top of the form. For example, you may use it to instruct the user in filling out the various fields possibly attached to the flag. Or you may form it as a question: "Are you sure you want to flag this content?"'),
      '#type' => 'textarea',
      '#rows' => 3,
    ),
    'form_flagging_button' => array(
      '#title' => t('Confirmation button'),
      '#description' => t('The label to appear on the submit button. Leave empty to use the "Flag link" text. You may change it here; e.g., to "Save", or "Yes" (the latter if your guidelines are worded as a question).'),
    ),
    'form_flagging_delete_button' => array(
      '#title' => t('Button leading to the unflagging form'),
      '#description' => t('The label to appear on the button leading to the unflagging form. Leave empty to use the "Unflag link" text. You may change it here; e.g., to "Delete".'),
    ),
    'form_unflagging_help' => array(
      '#title' => t('Unflagging guidelines'),
      '#description' => t('This short help text will be displayed at the top of the form. For example, you may form it as a question: "Are you sure you want to unflag this content?"'),
      '#type' => 'textarea',
      '#rows' => 3,
    ),
    'form_unflagging_button' => array(
      '#title' => t('Confirmation button'),
      '#description' => t('The label to appear on the submit button. Leave empty to use the "Unflag link" text. You may change it here; e.g., to "Delete", or "Yes" (the later if your guidelines are worded as a question).'),
    ),
  );
  foreach ($labels as $name => $element) {
    $fields[$name] = $element + array(
      '#type' => 'textfield',
      '#default_value' => isset($flag->{$name}) ? $flag->{$name} : '',
      '#access' => empty($flag->locked[$name]),
    );
  }
  $settings['forms'] = array(
    '#type' => 'vertical_tabs',
    'flagging_form' => array(
      '#type' => 'fieldset',
      '#title' => t('Flagging form'),
    ),
    'unflagging_form' => array(
      '#type' => 'fieldset',
      '#title' => t('Unflagging form'),
    ),
  );
  foreach (array(
    'form_flagging_help',
    'form_flagging_button',
    'form_flagging_delete_button',
  ) as $field) {
    $settings['forms']['flagging_form'][$field] = $fields[$field];
  }
  foreach (array(
    'form_unflagging_help',
    'form_unflagging_button',
  ) as $field) {
    $settings['forms']['unflagging_form'][$field] = $fields[$field];
  }
  $tokens_help = array(
    '#markup' => '<div class="description">' . t('You may embed in these texts any of the tokens listed above.') . '</div>',
  );
  $settings['forms']['flagging_form']['tokens_help'] = $settings['forms']['unflagging_form']['tokens_help'] = $tokens_help;
  $settings['form_unflag_link_leads_to_edit'] = array(
    '#type' => 'checkbox',
    '#title' => t('"Unflag this" link to lead to the flagging form.'),
    '#default_value' => isset($flag->form_unflag_link_leads_to_edit) ? $flag->form_unflag_link_leads_to_edit : FALSE,
    '#description' => t('Usually, the "unflag this" link leads to the unflagging form, where the user can hit a button to unflag the item. However, if we <a href="@manage-fields-url">attach fields</a> to the flag we also want some means to edit them at a later time. By ticking this checkbox the "unflag this" link will instead lead to the <em>flagging</em> form, where the user can either edit the fields or hit a button that leads to the <em>unflagging</em> form. In this case you\'ll also want to modify the "Unflag link text" to reflect the status of the flag (good example: "Bookmarked") instead of one possible action behind the link (bad example: "Unbookmark this item").', array(
      '@manage-fields-url' => $manage_fields_url,
    )),
    '#access' => empty($flag->locked['form_unflag_link_leads_to_edit']),
  );
  $interactions = array();

  // @todo: We have several places where we need descriptions for radios /
  // checkboxes. Ideally we should add a preprocess to the radios type to
  // process #descriptive_options. However, D6 doesn't have hook_hook_info()
  // so this will lead to code bloat right now.
  foreach (flagging_form_interactions() as $interaction => $info) {
    $interactions[$interaction] = $info['title'] . ' <div class="description">' . $info['description'] . '</div>';
  }
  $settings['form_interaction'] = array(
    '#type' => 'radios',
    '#title' => t('Forms interaction'),
    '#options' => $interactions,
    '#default_value' => isset($flag->form_interaction) && isset($interactions[$flag->form_interaction]) ? $flag->form_interaction : 'default',
    '#description' => t('How users are to interact with the forms. Additional interaction modes are available by installing additional modules.'),
    '#access' => empty($flag->locked['form_interaction']),
  );
  $form['display']['link_options_form'] = $settings;
}

/**
 * Implements hook_flag_link().
 *
 * Returns the URL for the flag links.
 */
function flagging_form_flag_link($flag, $action, $entity_id) {
  if ($action == 'flag') {
    $op = 'create';
  }
  else {
    $op = !empty($flag->form_unflag_link_leads_to_edit) ? 'edit' : 'delete';
  }
  $link = array(
    'href' => 'flag/flagging/' . $flag->name . '/' . $entity_id . '/' . $op,
    'query' => drupal_get_destination(),
  );

  // Let form interaction modules alter the link.
  drupal_alter('flagging_form_link', $link, $flag, $action, $entity_id);
  return $link;
}

/**
 * Implements hook_menu().
 *
 * We provide two sets of CRUD links. The first set is for casual use by
 * end-users:
 *
 *   flag/flagging/%flag/%content_id/edit
 *   flag/flagging/%flag/%content_id/create
 *   flag/flagging/%flag/%content_id/delete
 *
 * However, this set doesn't let us edit and delete specific flaggings.
 * Therefore we provide another set, for privileged users only, to operate on
 * specific flaggings:
 *
 *   flag/privileged-flagging/%flagging/edit
 *   flag/privileged-flagging/%flagging/delete
 *
 * (This last set is patterned after 'node/%node/edit', or 'user/%user/edit'.)
 *
 * Why do we need two sets? Why not follow the 'node/%node/edit' example, which,
 * after all, works for both end-users and admins? Answer: mainly because
 * flagging objects, in contrast to node objects (and users), have a transient
 * nature; having the flagging ID embedded in a link might invalidate the link
 * once a similar ajax link on that page is clicked. Another reason: admin
 * links usually require a different interaction mode (e.g., they don't need
 * ajax; e.g., could use the Overlay module) and having a separate namespace
 * for them can be useful.
 */
function flagging_form_menu() {

  // Links for end-users.
  // Editing a flagging.
  $items['flag/flagging/%flag/%content_id/edit'] = array(
    'page callback' => 'flagging_form_flagging_edit_page',
    'page arguments' => array(
      2,
      3,
    ),
    'title callback' => '_flag_menu_title',
    'title arguments' => array(
      2,
    ),
    'access callback' => '_flagging_form_flagging_access',
    'access arguments' => array(
      'update',
      2,
      3,
    ),
  );

  // Creating a flagging.
  $items['flag/flagging/%flag/%content_id/create'] = array(
    // It's effectively identical to the 'edit' callback.
    'page callback' => 'flagging_form_flagging_edit_page',
    'page arguments' => array(
      2,
      3,
    ),
    'title callback' => '_flag_menu_title',
    'title arguments' => array(
      2,
    ),
    'access callback' => '_flagging_form_flagging_access',
    'access arguments' => array(
      'create',
      2,
      3,
    ),
  );

  // Deleting a flagging.
  $items['flag/flagging/%flag/%content_id/delete'] = array(
    'page callback' => 'flagging_form_flagging_delete_page',
    'page arguments' => array(
      2,
      3,
    ),
    'title callback' => '_flag_menu_title',
    'title arguments' => array(
      2,
    ),
    'access callback' => '_flagging_form_flagging_access',
    'access arguments' => array(
      'delete',
      2,
      3,
    ),
  );

  // Links for privileged users.
  // Editing a flagging.
  $items['flag/privileged-flagging/%flagging/edit'] = array(
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'flagging_form_flagging_form',
      2,
      TRUE,
    ),
    'title callback' => '_flagging_form_flagging_menu_title',
    'title arguments' => array(
      2,
    ),
    // For now we simply check for the 'administer flags' permission, but it
    // could make sense to introduce a separate permission for this.
    // (Incidentally, this is why we avoid the word "admin" in discussing this
    // and instead use the more elastic one "privileged".)
    'access arguments' => array(
      'administer flags',
    ),
  );

  // Deleting a flagging.
  $items['flag/privileged-flagging/%flagging/delete'] = array(
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'flagging_form_flagging_delete_form',
      2,
      TRUE,
    ),
    'title callback' => '_flagging_form_flagging_menu_title',
    'title arguments' => array(
      2,
    ),
    'access arguments' => array(
      'administer flags',
    ),
  );
  return $items;
}

/**
 * Menu loader for '%content_id' arguments.
 *
 * It serves solely as documentation. Will be removed eventually.
 */
function content_id_load($content_id) {
  return $content_id;
}

/**
 * Menu access callback.
 *
 * param $op
 *   One of 'update', 'create', 'delete'.
 */
function _flagging_form_flagging_access($op, $flag, $entity_id) {
  return $flag
    ->access($entity_id, $op == 'delete' ? 'unflag' : 'flag');
}

/**
 * Menu title callback.
 */
function _flagging_form_flagging_menu_title($flagging) {
  return flag_get_flag($flagging->flag_name)
    ->get_title();
}

/**
 * Gets a label for use on a form.
 *
 * It's a wrapper around $flag->get_label() that handles defaults.
 */
function _flagging_form_label($flag, $label, $entity_id) {
  static $defaults = array(
    'form_flagging_button' => 'flag_short',
    'form_flagging_delete_button' => 'unflag_short',
    'form_unflagging_button' => 'unflag_short',
  );
  $label = 'form_' . $label;
  if (empty($flag->{$label}) && isset($defaults[$label])) {
    $label = $defaults[$label];
  }
  return $flag
    ->get_label($label, $entity_id);
}

/**
 * Menu callbacks for the forms.
 */
function flagging_form_flagging_edit_page($flag, $entity_id) {
  $flagging = $flag
    ->get_flagging($entity_id);
  if (!$flagging) {

    // New flagging.
    $flagging = $flag
      ->new_flagging($entity_id);
  }
  return drupal_get_form('flagging_form_flagging_form', $flagging);
}

/**
 * Documentation needed.
 */
function flagging_form_flagging_delete_page($flag, $entity_id) {
  $flagging = $flag
    ->get_flagging($entity_id);
  if (!$flagging) {

    // The item isn't flagged. The form function will deal with this error.
    $flagging = $flag
      ->new_flagging($entity_id);
  }
  return drupal_get_form('flagging_form_flagging_delete_form', $flagging);
}

/**
 * Returns a form for editing a flagging.
 *
 * @param object $form
 *   Form being used
 * @param object &$form_state
 *   The referenced form_state
 * @param string $flagging
 *   The flagging entity. Either loaded form disk or a blank one.
 * @param boolean $is_privileged
 *   Operate in "privileged" mode (to be expanded in the future).
 */
function flagging_form_flagging_form($form, &$form_state, $flagging, $is_privileged = FALSE) {
  $form_state['flagging'] = $flagging;
  $form_state['flag_is_privileged'] = $is_privileged;
  $flag = flag_get_flag($flagging->flag_name);
  $is_new = empty($flagging->flagging_id);
  if ($help = _flagging_form_label($flag, 'flagging_help', $flagging->entity_id)) {
    $form['help'] = array(
      '#markup' => '<p class="flag-help">' . $help . '</p>',
      '#weight' => -20,
    );
  }

  // Add the buttons.
  $form['actions'] = array(
    '#type' => 'actions',
  );
  $form['actions']['submit'] = array(
    '#type' => 'submit',
    '#value' => _flagging_form_label($flag, 'flagging_button', $flagging->entity_id),
    '#submit' => array(
      'flagging_form_flagging_form_submit',
    ),
  );
  if (!$is_new) {
    $form['actions']['delete'] = array(
      '#type' => 'submit',
      '#value' => _flagging_form_label($flag, 'flagging_delete_button', $flagging->entity_id),
      '#submit' => array(
        'flagging_form_flagging_form_delete_submit',
      ),
      '#access' => $flag
        ->access($flagging->entity_id, 'unflag'),
    );
  }
  $form['actions']['cancel'] = array(
    '#type' => 'submit',
    '#value' => t('Cancel'),
    '#submit' => array(
      'flagging_form_flagging_cancel_submit',
    ),
  );
  $form['actions']['#weight'] = 999;
  field_attach_form('flagging', $flagging, $form, $form_state);

  // Debugging message.
  if (0) {
    drupal_set_message($is_privileged ? t('You are operating in "privileged" mode.') : t('<a href="@url">Switch to privileged mode.</a>', array(
      '@url' => url('flag/privileged-flagging/' . $flagging->flagging_id . '/edit'),
    )));
  }
  return $form;
}

/**
 * Documentation needed.
 */
function flagging_form_flagging_form_validate($form, &$form_state) {
  field_attach_form_validate('flagging', $form_state['flagging'], $form, $form_state);
}

/**
 * Documentation needed.
 */
function flagging_form_flagging_form_submit($form, &$form_state) {
  $flagging = $form_state['flagging'];
  $flag = flag_get_flag($flagging->flag_name);
  $is_new = empty($flagging->flagging_id);

  // Add the fields from the form.
  field_attach_submit('flagging', $flagging, $form, $form_state);
  if ($flag
    ->flag('flag', $flagging->entity_id, empty($flagging->uid) ? NULL : user_load($flagging->uid), FALSE, $flagging)) {
    if ($is_new) {
      if (empty($form_state['flag_suppress_messages'])) {
        drupal_set_message($flag
          ->get_label('flag_message', $flagging->entity_id));
      }
      $form_state['flag_status_has_changed'] = TRUE;
    }
    else {

      // @todo: Note that we don't have a message to print when !$is_new
      // (that is, when updating an existing flagging). Should we have such
      // a message?
    }
  }
  else {

    // Flagging failed.
    // We're unlikely to arrive here, as there's an access check on the menu
    // router.
  }
}

/**
 * Button submit function: handle the 'Delete' button on the flagging form.
 */
function flagging_form_flagging_form_delete_submit($form, &$form_state) {
  $is_privileged = $form_state['flag_is_privileged'];
  $destination = array();
  if (isset($_GET['destination'])) {
    $destination = drupal_get_destination();
    unset($_GET['destination']);
  }
  if ($is_privileged) {
    $target = 'flag/privileged-flagging/' . $form_state['flagging']->flagging_id . '/delete';
  }
  else {
    $target = 'flag/flagging/' . $form_state['flagging']->flag_name . '/' . $form_state['flagging']->entity_id . '/delete';
  }
  $form_state['redirect'] = array(
    $target,
    array(
      'query' => $destination,
    ),
  );
}

/**
 * Returns a form for deleting a flagging.
 */
function flagging_form_flagging_delete_form($form, &$form_state, $flagging, $is_privileged = FALSE) {
  $form_state['flagging'] = $flagging;
  $form_state['flag_is_privileged'] = $is_privileged;
  $flag = flag_get_flag($flagging->flag_name);
  $form['actions'] = array(
    '#type' => 'actions',
  );
  if (!$flagging->flagging_id) {

    // This scenario might happen when there are several 'unflag!' links on the
    // page.
    $form['error'] = array(
      '#markup' => '<p>' . t('The item is not flagged.') . '</p>',
    );
  }
  else {
    if ($help = _flagging_form_label($flag, 'unflagging_help', $flagging->entity_id)) {
      $form['help'] = array(
        '#markup' => '<p class="flag-help">' . $help . '</p>',
        '#weight' => -20,
      );
    }
    $form['actions']['submit'] = array(
      '#type' => 'submit',
      '#value' => _flagging_form_label($flag, 'unflagging_button', $flagging->entity_id),
      '#submit' => array(
        'flagging_form_flagging_delete_form_submit',
      ),
    );
  }
  $form['actions']['cancel'] = array(
    '#type' => 'submit',
    '#value' => t('Cancel'),
    '#submit' => array(
      'flagging_form_flagging_cancel_submit',
    ),
  );
  $form['actions']['#weight'] = 999;

  // Debugging message.
  if (0) {
    drupal_set_message($is_privileged ? t('You are operating in "privileged" mode.') : '');
  }
  return $form;
}

/**
 *  Documentation needed.
 */
function flagging_form_flagging_delete_form_submit($form, &$form_state) {
  $flagging = $form_state['flagging'];
  $flag = flag_get_flag($flagging->flag_name);

  // @todo: Privileged users don't have a way to delete flaggings of anonymous
  // users because $flag->flag() doesn't accept a SID. Solution: make it look
  // inside $flagging.
  if ($flag
    ->flag('unflag', $flagging->entity_id, $flag->global ? NULL : user_load($flagging->uid))) {
    if (empty($form_state['flag_suppress_messages'])) {
      drupal_set_message($flag
        ->get_label('unflag_message', $flagging->entity_id));
    }
    $form_state['flag_status_has_changed'] = TRUE;
  }
  else {

    // Unflagging failed.
    // We're unlikely to arrive here, as there's an access check on the menu
    // router.
  }
  $form_state['redirect'] = '<front>';
}

/**
 *  Documentation needed.
 */
function flagging_form_flagging_cancel_submit($form, &$form_state) {
  $form_state['redirect'] = '<front>';
}

Functions

Namesort descending Description
content_id_load Menu loader for '%content_id' arguments.
flagging_form_flagging_cancel_submit Documentation needed.
flagging_form_flagging_delete_form Returns a form for deleting a flagging.
flagging_form_flagging_delete_form_submit Documentation needed.
flagging_form_flagging_delete_page Documentation needed.
flagging_form_flagging_edit_page Menu callbacks for the forms.
flagging_form_flagging_form Returns a form for editing a flagging.
flagging_form_flagging_form_delete_submit Button submit function: handle the 'Delete' button on the flagging form.
flagging_form_flagging_form_interactions Implements hook_flagging_form_interactions().
flagging_form_flagging_form_submit Documentation needed.
flagging_form_flagging_form_validate Documentation needed.
flagging_form_flag_link Implements hook_flag_link().
flagging_form_flag_link_type_info Implements hook_flag_link_type_info().
flagging_form_form_flag_form_alter Implements hook_form_FORM_ID_alter().
flagging_form_interactions Returns all the available interactions.
flagging_form_menu Implements hook_menu().
_flagging_form_flagging_access Menu access callback.
_flagging_form_flagging_menu_title Menu title callback.
_flagging_form_label Gets a label for use on a form.