flag.module in Flag 8.4
Same filename and directory in other branches
The Flag module.
File
flag.moduleView 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();
}
}