You are here

flag.module in Flag 8.4

The Flag module.

File

flag.module
View source
<?php

/**
 * @file
 * The Flag module.
 */
use Drupal\Core\Entity\ContentEntityFormInterface;
use Drupal\Core\Form\ConfirmFormInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\flag\Plugin\Flag\EntityFlagType;
use Drupal\Core\Cache\Cache;
use Drupal\node\NodeInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\system\Entity\Action;
use Drupal\user\UserInterface;
use Drupal\flag\FlagInterface;
use Drupal\Core\Url;

/**
 * Implements hook_help().
 */
function flag_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'entity.flag.collection':
      $output = '<p>' . t('This page lists all the <em>flags</em> that are currently defined on this system.') . '</p>';
      if (\Drupal::moduleHandler()
        ->moduleExists('views')) {
        $output .= '<p>';
        $output .= t('Lists of flagged content can be displayed using views. You can configure these in the Views administration section.');
        if (\Drupal::service('flag')
          ->getFlagById('bookmark')) {
          $output .= ' ' . t('Flag module automatically provides a few default views for the <em>bookmarks</em> flag. You can use these as templates by cloning these views and then customizing as desired.');
        }
        $output .= ' ' . t('The <a href="@flag-handbook-url">Flag module handbook</a> contains extensive <a href="@customize-url">documentation on creating customized views</a> using flags.', [
          '@flag-handbook-url' => 'http://drupal.org/handbook/modules/flag',
          '@customize-url' => 'http://drupal.org/node/296954',
        ]);
        $output .= '</p>';
      }
      if (\Drupal::moduleHandler()
        ->moduleExists('rules')) {
        $output .= '<p>' . t('Flagging an item may trigger <a href="@rules-url">rules</a>.', [
          '@rules-url' => Url::fromRoute('entity.rules_reaction_rule.collection')
            ->toString(),
        ]) . '</p>';
      }
      else {
        $output .= '<p>' . t('Flagging an item may trigger <em>rules</em>. However, you don\'t have the <a href="@rules-url">Rules</a> module enabled, so you won\'t be able to enjoy this feature. The Rules module is a more extensive solution than Flag actions.', [
          '@rules-url' => Url::fromUri('http://drupal.org/node/407070')
            ->toString(),
        ]) . '</p>';
      }
      $output .= '<p>' . t('To learn about the various ways to use flags, please check out the <a href="@handbook-url">Flag module handbook</a>.', [
        '@handbook-url' => 'http://drupal.org/handbook/modules/flag',
      ]) . '</p>';
      return $output;
    case 'flag.add_page':
      $output = '<p>' . t('Select the type of flag to create.') . '</p>';
      return $output;
    case 'field_ui.overview_flagging':
  }
}

/**
 * Implements hook_form_alter().
 */
function flag_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $object = $form_state
    ->getFormObject();

  // We only want to operate on content entity forms.
  if (!$object instanceof ContentEntityFormInterface || $object instanceof ConfirmFormInterface) {
    return;
  }

  // Get the flags for the entity being edited by the form.
  $flag_service = \Drupal::service('flag');
  $entity = $object
    ->getEntity();
  $flags = $flag_service
    ->getAllFlags($entity
    ->getEntityTypeId(), $entity
    ->bundle());

  // Check the first flag and return early if the form isn't considered to be
  // an edit form.
  if (!empty($flags) && isset($flags[0]) && $flags[0] instanceof FlagInterface) {
    if (!$flags[0]
      ->getFlagTypePlugin()
      ->isAddEditForm($object
      ->getOperation())) {
      return;
    }
  }

  // Filter the flags to those that apply here:
  //  - the flag uses the entity type plugin.
  //  - the plugin is configured to output the flag in the entity form.
  //  - the current user has access to the flag.
  $filtered_flags = array_filter($flags, function (FlagInterface $flag) use ($object) {
    $plugin = $flag
      ->getFlagTypePlugin();
    $entity = $object
      ->getEntity();
    $action = $flag
      ->isFlagged($entity) ? 'unflag' : 'flag';
    $access = $flag
      ->actionAccess($action, NULL, $entity);
    return $plugin instanceof EntityFlagType && $plugin
      ->showOnForm() && $access
      ->isAllowed();
  });

  // If we still have any flags...
  if (!empty($filtered_flags)) {

    // Add a container to the form.
    $form['flag'] = [
      '#type' => 'details',
      '#title' => t('Flags'),
      '#attached' => [
        'library' => [
          'flag/flag.admin',
        ],
      ],
      '#group' => 'advanced',
      '#tree' => TRUE,
    ];

    /** @var FlagInterface $flag */
    foreach ($filtered_flags as $flag) {

      // Add each flag to the form.
      $form['flag'][$flag
        ->id()] = [
        '#type' => 'checkbox',
        '#title' => $flag
          ->label(),
        '#description' => $flag
          ->getLongText('flag'),
        '#default_value' => $flag
          ->isFlagged($entity) ? 1 : 0,
        '#return_value' => 1,
        // Used by our drupalSetSummary() on vertical tabs.
        '#attributes' => [
          'title' => $flag
            ->label(),
        ],
      ];
    }
    foreach (array_keys($form['actions']) as $action) {
      if ($action != 'preview' && isset($form['actions'][$action]['#type']) && $form['actions'][$action]['#type'] === 'submit') {
        $form['actions'][$action]['#submit'][] = 'flag_form_submit';
      }
    }
  }
}

/**
 * Form submission handler for the flag module.
 *
 * @see flag_form_alter()
 */
function flag_form_submit($form, FormStateInterface $form_state) {
  $entity = $form_state
    ->getFormObject()
    ->getEntity();
  if (!$form_state
    ->isValueEmpty('flag')) {
    $values = $form_state
      ->getValue('flag');
    flag_form_save($entity, $values);
  }
}

/**
 * Performs flagging/unflagging for the entity edit form.
 *
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   The entity being saved.
 * @param $values
 *   The flag entity form values.
 *
 * @see flag_form_submit()
 */
function flag_form_save(EntityInterface $entity, $values) {
  $flag_service = \Drupal::service('flag');
  $account = \Drupal::currentUser();

  // For existing entities, get any existing flaggings per flag.
  $flagging_ids = [];
  if (!$entity
    ->isNew()) {
    $flaggings = $flag_service
      ->getAllEntityFlaggings($entity, $account);
    $flagging_ids = array_map(function ($flagging) {
      return $flagging
        ->getFlagId();
    }, $flaggings);
  }

  // Load all the flags for the entity.
  $flags = $flag_service
    ->getAllFlags($entity
    ->getEntityTypeId(), $entity
    ->bundle());

  /** @var FlagInterface $flag */
  foreach ($flags as $flag) {

    // Get the flag_id from the Flag.
    $flag_id = $flag
      ->id();

    // If the flag_id is not part of the form values, no need to do anything.
    if (!isset($values[$flag_id])) {
      continue;
    }

    // Get the form flag value.
    $value = $values[$flag_id];

    // Determine if the flagging exists.
    $flagging_exists = in_array($flag_id, $flagging_ids);

    // If the flag is checked in the form, and the flagging doesn't exist...
    if ($value && !$flagging_exists) {

      // ...flag the entity.
      $flag_service
        ->flag($flag, $entity);
    }

    // If the flag is not checked in the form, and the flagging exists..
    if (!$value && $flagging_exists) {

      // ...unflag the entity.
      $flag_service
        ->unflag($flag, $entity);
    }
  }
}

/**
 * Implements hook_entity_extra_field_info().
 */
function flag_entity_extra_field_info() {
  $extra = [];
  $flag_service = \Drupal::service('flag');
  $flags = $flag_service
    ->getAllFlags();

  /** @var FlagInterface $flag */
  foreach ($flags as $flag) {

    // Skip flags that aren't on entities.
    $flag_type_plugin = $flag
      ->getFlagTypePlugin();
    if (!$flag_type_plugin instanceof EntityFlagType) {
      continue;
    }
    $flaggable_bundles = $flag
      ->getApplicableBundles();
    foreach ($flaggable_bundles as $bundle_name) {
      if ($flag_type_plugin
        ->showOnForm()) {
        $extra[$flag
          ->getFlaggableEntityTypeId()][$bundle_name]['form']['flag'] = [
          'label' => t('Flags'),
          'description' => t('Checkboxes for toggling flags'),
          'weight' => 10,
        ];
      }
      if ($flag_type_plugin
        ->showAsField()) {
        $extra[$flag
          ->getFlaggableEntityTypeId()][$bundle_name]['display']['flag_' . $flag
          ->id()] = [
          'label' => t('Flag: %title', [
            '%title' => $flag
              ->label(),
          ]),
          'description' => t('Individual flag link'),
          'weight' => 10,
        ];
      }
    }
  }
  return $extra;
}

/**
 * Implements hook_theme().
 */
function flag_theme() {
  return [
    'flag' => [
      'variables' => [
        'attributes' => [],
        'title' => NULL,
        'action' => 'flag',
        'flag' => NULL,
        'flaggable' => NULL,
      ],
    ],
  ];
}

/**
 * Implements hook_theme_suggestions_HOOK().
 */
function flag_theme_suggestions_flag(array $variables) {
  $flag = $variables['flag'];
  $flaggable = $variables['flaggable'];
  return [
    'flag__' . $flag
      ->id(),
    'flag__' . $flag
      ->id() . '_' . $flaggable
      ->id(),
  ];
}

/**
 * Implements hook_node_links_alter().
 */
function flag_node_links_alter(array &$links, NodeInterface $entity, array &$context) {

  // @todo: Define this for handling the showOnLinks() flag mode.
  // see https://www.drupal.org/project/flag/issues/2703229
}

/**
 * Implements hook_entity_view().
 *
 * Handles the 'show_in_links' and 'show_as_field' flag options.
 */
function flag_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {

  // Don't show on previews.
  if ($entity
    ->isNew()) {
    return;
  }
  $build['#cache']['contexts'][] = 'user.permissions';
  if (empty($build['#cache']['tags'])) {
    $build['#cache']['tags'] = [];
  }

  // Get all possible flags for this entity type.
  $flag_service = \Drupal::service('flag');
  $flags = $flag_service
    ->getAllFlags($entity
    ->getEntityTypeID(), $entity
    ->bundle());
  foreach ($flags as $flag) {
    $build['#cache']['tags'] = Cache::mergeTags($build['#cache']['tags'], $flag
      ->getCacheTags());

    // Do not display the flag if disabled.
    if (!$flag
      ->status()) {
      continue;
    }
    $flag_type_plugin = $flag
      ->getFlagTypePlugin();

    // Only add cache key if flag link is displayed.
    if (!$flag_type_plugin
      ->showAsField() || !$display
      ->getComponent('flag_' . $flag
      ->id())) {
      continue;
    }
    $build['flag_' . $flag
      ->id()] = [
      '#lazy_builder' => [
        'flag.link_builder:build',
        [
          $entity
            ->getEntityTypeId(),
          $entity
            ->id(),
          $flag
            ->id(),
        ],
      ],
      '#create_placeholder' => TRUE,
    ];
  }
}

/**
 * Implements hook_entity_build_defaults_alter().
 */
function flag_entity_build_defaults_alter(array &$build, EntityInterface $entity, $view_mode = 'full', $langcode = NULL) {

  /** @var \Drupal\flag\FlagService $flag_service */
  $flag_service = \Drupal::service('flag');

  // Get all possible flags for this entity type.
  $flags = $flag_service
    ->getAllFlags($entity
    ->getEntityTypeId(), $entity
    ->bundle());
  $no_cache = FALSE;
  foreach ($flags as $flag) {
    $flag_type_plugin = $flag
      ->getFlagTypePlugin();

    // Make sure we're dealing with an entity flag type.
    if (!$flag_type_plugin instanceof EntityFlagType) {
      continue;
    }

    // Only add max-age to entity render array if contextual links flag
    // display is enabled.
    if (!$flag_type_plugin
      ->showContextualLink()) {
      continue;
    }
    $no_cache = TRUE;
  }
  if ($no_cache) {
    $build['#cache']['max-age'] = 0;
  }
  return $build;
}

/**
 * Implements hook_entity_view_alter().
 *
 * Alters node contextual links placeholder id to contain flag metadata, so that
 * contextual links cache considers flags granularity.
 */
function flag_entity_view_alter(&$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
  $entity_type = $entity
    ->getEntityTypeId();
  if (isset($build['#contextual_links'][$entity_type])) {

    /** @var \Drupal\flag\FlagService $flag_service */
    $flag_service = \Drupal::service('flag');

    // Get all possible flags for this entity type.
    $flags = $flag_service
      ->getAllFlags($entity_type, $entity
      ->bundle());
    foreach ($flags as $flag) {
      $flag_type_plugin = $flag
        ->getFlagTypePlugin();

      // Make sure we're dealing with an entity flag type.
      if (!$flag_type_plugin instanceof EntityFlagType) {
        continue;
      }

      // Only apply metadata to contextual links if plugin is enabled
      if (!$flag_type_plugin
        ->showContextualLink()) {
        continue;
      }
      $action = 'flag';
      if ($flag
        ->isFlagged($entity)) {
        $action = 'unflag';
      }
      $flag_keys[] = $flag
        ->id() . '-' . $action;
    }
    if (!empty($flag_keys)) {
      $build['#contextual_links'][$entity_type]['metadata']['flag_keys'] = implode(',', $flag_keys);
    }
  }
}

/**
 * Implements hook_contextual_links_alter().
 */
function flag_contextual_links_alter(array &$links, $group, array $route_parameters) {

  // Assume that $group is one of known entity types and try to load an entity
  // based on that.
  $entity_type = $group;
  if (isset($route_parameters[$entity_type]) && !is_null(\Drupal::entityTypeManager()
    ->getDefinition($entity_type, FALSE))) {
    $entity = \Drupal::entityTypeManager()
      ->getStorage($entity_type)
      ->load($route_parameters[$entity_type]);
  }
  if (!isset($entity)) {
    return;
  }

  // Get all possible flags for this entity type.
  $flag_service = \Drupal::service('flag');
  $flags = $flag_service
    ->getAllFlags($entity
    ->getEntityTypeID(), $entity
    ->bundle());
  foreach ($flags as $flag) {

    /** @var \Drupal\flag\FlagInterface $flag */

    // Do not display the flag if disabled.
    if (!$flag
      ->status()) {
      continue;
    }

    /** @var \Drupal\flag\Plugin\Flag\EntityFlagType $flag_type_plugin */
    $flag_type_plugin = $flag
      ->getFlagTypePlugin();

    // Make sure we're dealing with an entity flag type.
    if (!$flag_type_plugin instanceof EntityFlagType) {
      continue;
    }

    // Skip flags for which contextual links setting is disabled.
    if (!$flag_type_plugin
      ->showContextualLink()) {
      continue;
    }
    $flag_link = $flag
      ->getLinkTypePlugin()
      ->getAsLink($flag, $entity);
    $flag_url = $flag_link
      ->getUrl();
    $links["flag_{$flag->id()}"] = [
      'route_name' => $flag_url
        ->getRouteName(),
      'route_parameters' => $flag_url
        ->getRouteParameters(),
      'title' => $flag_link
        ->getText(),
      'localized_options' => [],
    ];
  }
}

/*
 * Implements hook_entity_predelete().
 */
function flag_entity_predelete(EntityInterface $entity) {

  // User flags handle things through user entity hooks.
  if ($entity
    ->getEntityTypeId() == 'user') {
    return;
  }
  \Drupal::service('flag')
    ->unflagAllByEntity($entity);
}

/**
 * Implements hook_user_cancel().
 */
function flag_user_cancel($edit, $account, $method) {
  \Drupal::service('flag')
    ->userFlagRemoval($account);
}

/**
 * Implements hook_user_predelete().
 */
function flag_user_predelete(UserInterface $account) {
  \Drupal::service('flag')
    ->userFlagRemoval($account);
}

/**
 * Implements hook_entity_operation().
 */
function flag_entity_operation(\Drupal\Core\Entity\EntityInterface $entity) {
  $operations = [];
  if ($entity instanceof \Drupal\flag\FlagInterface) {
    if (!$entity
      ->status()) {
      $operations['enable'] = [
        'title' => t('Enable'),
        'url' => $entity
          ->toUrl('enable'),
        'weight' => 50,
      ];
    }
    else {
      $operations['disable'] = [
        'title' => t('Disable'),
        'url' => $entity
          ->toUrl('disable'),
        'weight' => 50,
      ];
    }
    $operations['reset'] = [
      'title' => t('Reset'),
      'url' => $entity
        ->toUrl('reset'),
      'weight' => 100,
    ];
  }
  return $operations;
}

/**
 * Implements hook_hook_info().
 *
 * Flag alter hooks should be defined in a MODULE.flag.inc file.
 */
function flag_hook_info() {
  $hooks = [];
  $hooks['flag_type_info_alter'] = [
    'group' => 'flag',
  ];
  $hooks['flag_link_type_info_alter'] = [
    'group' => 'flag',
  ];
  return $hooks;
}

/**
 * Implements hook_ENTITY_TYPE_insert().
 */
function flag_flag_insert(FlagInterface $flag) {
  if ($flag
    ->isSyncing()) {

    // Do not create actions when config is progress of synchronization.
    return;
  }

  // The action plugin cache needs to detect the new flag.

  /** @var \Drupal\Core\Action\ActionManager $action_manager */
  $action_manager = \Drupal::service('plugin.manager.action');
  $action_manager
    ->clearCachedDefinitions();

  // Add the flag/unflag actions for this flag and entity combination.
  $flag_id = 'flag_action.' . $flag
    ->id() . '_flag';
  if (!Action::load($flag_id)) {
    $action = Action::create([
      'id' => $flag_id,
      'type' => $flag
        ->getFlaggableEntityTypeId(),
      'label' => $flag
        ->getShortText('flag'),
      'plugin' => 'flag_action:' . $flag
        ->id() . '_flag',
      'configuration' => [
        'flag_id' => $flag
          ->id(),
        'flag_action' => 'flag',
      ],
    ]);
    $action
      ->trustData()
      ->save();
  }
  $unflag_id = 'flag_action.' . $flag
    ->id() . '_unflag';
  if (!Action::load($unflag_id)) {
    $action = Action::create([
      'id' => $unflag_id,
      'type' => $flag
        ->getFlaggableEntityTypeId(),
      'label' => $flag
        ->getShortText('unflag'),
      'plugin' => 'flag_action:' . $flag
        ->id() . '_unflag',
      'configuration' => [
        'flag_id' => $flag
          ->id(),
        'flag_action' => 'unflag',
      ],
    ]);
    $action
      ->trustData()
      ->save();
  }
}

/**
 * Implements hook_ENTITY_TYPE_delete().
 */
function flag_flag_delete(FlagInterface $flag) {

  // Do not delete actions when config is progress of synchronization.
  if ($flag
    ->isSyncing()) {
    return;
  }
  $actions = Action::loadMultiple([
    'flag_action.' . $flag
      ->id() . '_flag',
    'flag_action.' . $flag
      ->id() . '_unflag',
  ]);

  // Remove the flag/unflag actions for this flag and entity combination.
  foreach ($actions as $action) {
    $action
      ->delete();
  }
}