' . t('This page lists all the flags that are currently defined on this system.') . '
'; if (\Drupal::moduleHandler()->moduleExists('views')) { $output .= ''; $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 bookmarks flag. You can use these as templates by cloning these views and then customizing as desired.'); } $output .= ' ' . t('The Flag module handbook contains extensive documentation on creating customized views using flags.', ['@flag-handbook-url' => 'http://drupal.org/handbook/modules/flag', '@customize-url' => 'http://drupal.org/node/296954']); $output .= '
'; } if (\Drupal::moduleHandler()->moduleExists('rules')) { $output .= '' . t('Flagging an item may trigger rules.', ['@rules-url' => Url::fromRoute('entity.rules_reaction_rule.collection')->toString()]) . '
'; } else { $output .= '' . t('Flagging an item may trigger rules. However, you don\'t have the Rules 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()]) . '
'; } $output .= '' . t('To learn about the various ways to use flags, please check out the Flag module handbook.', ['@handbook-url' => 'http://drupal.org/handbook/modules/flag']) . '
'; return $output; case 'flag.add_page': $output = '' . t('Select the type of flag to create.') . '
'; return $output; case 'field_ui.overview_flagging': // @todo: Doesn't make sense at the moment, implement when form // functionality is available. /* // Get the existing link types that provide a flagging form. $link_types = flag_get_link_types(); $form_link_types = array(); foreach (flag_get_link_types() as $link_type) { if ($link_type['provides form']) { $form_link_types[] = '' . $link_type['title'] . ''; } } // Get the flag for which we're managing fields. $flag = menu_get_object('flag', FLAG_ADMIN_PATH_START + 1); // Common text. $output = '' . t('Flags can have fields added to them. For example, a "Spam" flag could have a Reason field where a user could type in why he believes the item flagged is spam. A "Bookmarks" flag could have a Folder field into which a user could arrange her bookmarks.') . '
'; $output .= '' . t('On this page you can add fields to flags, delete them, and otherwise manage them.') . '
'; // Three cases: if ($flag->link_type == 'form') { // Case 1: the current link type is the flagging form. Don't tell the // user anything extra, all is fine. } elseif ($link_types[$flag->link_type]['provides form']) { // Case 2: the current link type shows the form for creation of the // flagging, but it not the flagging form. Tell the user they can't edit // existing flagging fields. $output .= t("Field values may be edited when flaggings are created because this flag's link type shows a form for the flagging. However, to edit field values on existing flaggings, you will need to set your flag to use the Flagging form link type. This is provided by the Flagging Form module.", array( '!flagging-form-url' => 'http://drupal.org/project/flagging_form', )); if (!\Drupal::moduleHandler()->moduleExists('flagging_form')) { $output .= ' ' . t("You do not currently have this module enabled.") . ''; } $output .= ''; } else { // Case 3: the current link type does not allow access to the flagging // form. Tell the user they should change it. $output .= '' . t("To allow users to enter values for fields you will need to set your flag to use one of the following link types which allow users to access the flagging form: !link-types-list. (In case a form isn't used, the fields are assigned their default values.)", array( '!form-link-type-url' => url('admin/structure/flags/manage/' . $flag->name, array('fragment' => 'edit-link-type')), // The list of labels from link types. These are all defined in code // in hook_flag_link_type_info() and therefore safe to output raw. '!link-types-list' => implode(', ', $form_link_types), )) . '
'; $output .= '' . t("Additionally, to edit field values on existing flaggings, you will need to set your flag to use the Flagging form link type. This is provided by the Flagging Form module.", array( '!flagging-form-url' => 'http://drupal.org/project/flagging_form', )); if (!\Drupal::moduleHandler()->moduleExists('flagging_form')) { $output .= ' ' . t("You do not currently have this module enabled.") . ''; } $output .= '
'; } return $output; */ } } /** * 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(); } }