auto_entitylabel-8.x-3.x-dev/auto_entitylabel.module
auto_entitylabel.module
<?php /** * @file * Allows hiding of entity label fields and automatic label creation. */ use Drupal\auto_entitylabel\AutoEntityLabelManager; use Drupal\Component\Render\MarkupInterface; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityFormInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Markup; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Url; /** * Implements hook_help(). */ function auto_entitylabel_help($route_name, RouteMatchInterface $route_match) { switch ($route_name) { case 'help.page.auto_entitylabel': $output = '<h3>' . t('About') . '</h3>'; $output .= '<p>' . t('This is a small and efficient module that allows hiding of entity label fields. To prevent empty labels it can be configured to generate the label automatically by a given pattern. The module can be used for any entity type that has a label, including e.g. for node titles, comment subjects, taxonomy term names and profile2 labels.') . '</p>'; $output .= '<p>' . t('Patterns for automatic labels are constructed with the help of tokens. Drupal core provides a basic set of <a href="@url_tokens" target="blank">tokens</a>. For a token selection widget install the token. Some entity types (e.g. profile2) provide tokens via the entity_token, which is part of the entity module.', ['@url_tokens' => 'https://www.drupal.org/project/token']) . '</p>'; $output .= '<p>' . t('Watch the <a href="@url_daily_dose_of_drupal" target="blank">Daily Dose of Drupal</a> screencast by <a href="@url_shane_thomas" target="blank">Shane Thomas</a> for a short introduction and demonstration of the module and some of its features. Demonstration made in D7 but can help a lot.', [ '@url_daily_dose_of_drupal' => 'http://codekarate.com/daily-dose-of-drupal/drupal-7-automatic-entity-label-module', '@url_shane_thomas' => 'https://www.drupal.org/user/506260', ]) . '</p>'; $output .= '<h3>' . t('Usage') . '</h3>'; $output .= '<p>' . t('The configuration can be accessed with the <i>Manage automatic entity labels</i> operation or the <i>Automatic label</i> tab when editing entity types. For example, when configuring a node type, visit <i>Administration</i> » <i>Structure</i> » <i>Content types</i> (/admin/structure/types). You can also configure automatic labels for other entity types such as <i>Media<i>, in which case you would visit <i>Administration</i> » <i>Structure</i> » <i>Media</i> (/admin/structure/media).') . '</p>'; return $output; } } /** * Implements hook_entity_type_alter(). * * Adds the Auto Label tab to the entity configuration page. */ function auto_entitylabel_entity_type_alter(array &$entity_types) { /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */ foreach ($entity_types as $entity_type) { if ($entity_type->getBundleOf() && $entity_type->hasLinkTemplate('edit-form')) { $entity_type->setLinkTemplate('auto-label', $entity_type->getLinkTemplate('edit-form') . "/auto-label"); } } } /** * Implements hook_form_alter(). */ function auto_entitylabel_form_alter(&$form, FormStateInterface $form_state) { /** @var \Drupal\Core\Entity\EntityFormInterface $formObject */ $formObject = $form_state->getFormObject(); if ($formObject instanceof EntityFormInterface) { $entity = $formObject->getEntity(); if ($entity instanceof ContentEntityInterface) { auto_entitylabel_prepare_entityform($form, $entity); } } } /** * Implements hook_inline_entity_form_entity_form_alter(). */ function auto_entitylabel_inline_entity_form_entity_form_alter(&$form, &$form_state) { $entity = $form['#entity']; auto_entitylabel_prepare_entityform($form, $entity); } /** * Implements hook_inline_entity_form_table_fields_alter(). */ function auto_entitylabel_inline_entity_form_table_fields_alter(&$fields, $context) { // Replace label field behavior. if (isset($fields['label'])) { $fields['label']['type'] = 'callback'; $fields['label']['callback'] = 'auto_entitylabel_inline_entity_label_callback'; } } /** * A callback function to provide autoentitylabel for inline entity form. */ function auto_entitylabel_inline_entity_label_callback($entity, $variables) { $autolabel = $entity->label(); if ($entity instanceof ContentEntityInterface) { $decorator = \Drupal::service('auto_entitylabel.entity_decorator'); /** @var \Drupal\auto_entitylabel\AutoEntityLabelManager $decorated_entity */ $decorated_entity = $decorator->decorate($entity); if ($decorated_entity->hasLabel() && $decorated_entity->autoLabelNeeded()) { $autolabel = $decorated_entity->setLabel(); } } return $autolabel; } /** * Prepares the label replacement in the entity form. * * @param array $form * Form array. * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity which title will be replaced. */ function auto_entitylabel_prepare_entityform(array &$form, ContentEntityInterface $entity) { if (empty($form['#auto_entitylabel_processed'])) { $decorator = \Drupal::service('auto_entitylabel.entity_decorator'); /** @var \Drupal\auto_entitylabel\AutoEntityLabelManager $entity */ $entity = $decorator->decorate($entity); $label = $entity->getLabelName(); $widget = &$form[$label]['widget'][0]; switch ($entity->getStatus()) { case AutoEntityLabelManager::ENABLED: // Hide the label field. It will be automatically generated in // hook_entity_presave(). $widget['value']['#type'] = 'hidden'; $widget['value']['#required'] = FALSE; if (empty($widget['value']['#default_value'])) { $widget['value']['#default_value'] = '%AutoEntityLabel%'; } break; case AutoEntityLabelManager::OPTIONAL: // Allow label field to be empty. It will be automatically generated // in hook_entity_presave(). $widget['value']['#required'] = FALSE; break; case AutoEntityLabelManager::PREFILLED: if (empty($widget['value']['#default_value'])) { $widget['value']['#default_value'] = $entity->setLabel(); } break; } $form['#auto_entitylabel_processed'] = TRUE; } } /** * Implements hook_entity_prepare_view(). */ function auto_entitylabel_entity_prepare_view($entity_type_id, array $entities, array $displays, $view_mode) { foreach ($entities as $entity) { if (isset($entity->in_preview) && $entity->in_preview === TRUE && $entity instanceof ContentEntityInterface) { $decorator = \Drupal::service('auto_entitylabel.entity_decorator'); /** @var \Drupal\auto_entitylabel\AutoEntityLabelManager $decorated_entity */ $decorated_entity = $decorator->decorate($entity); if ($decorated_entity->hasLabel() && $decorated_entity->autoLabelNeeded()) { $decorated_entity->setLabel(); } } } } /** * Implements hook_entity_presave(). */ function auto_entitylabel_entity_presave(EntityInterface $entity) { if ($entity instanceof ContentEntityInterface) { $decorator = \Drupal::service('auto_entitylabel.entity_decorator'); /** @var \Drupal\auto_entitylabel\AutoEntityLabelManager $decorated_entity */ $decorated_entity = $decorator->decorate($entity); if ($decorated_entity->hasLabel() && $decorated_entity->autoLabelNeeded()) { // Need to generate a label for this entity. // // There's a few cases we need to handle. // I'm going to lay these out explicitly so that the logic is // clear. I expect we can optimize these at a later point. // // Two of the scenarios need the placeholder label, so // always generate it. // Make the placeholder unique for this entity. Handles the case // where both a paragraph on a node and a node have an auto label. $placeholder = strtr('%AutoEntityLabel: @entityId%', ['@entityId' => $entity->uuid()]); // Check to see if this is a new entity. if ($entity->isNew()) { // Handle the case where the automatic label is optional. // Check to see if isTitlePreserved is set. If the autolabel is // optional AND the user has filled this title in then the // the autolabel should not be set. if (!$decorated_entity->isTitlePreserved() || $entity->label() == '%AutoEntityLabel%') { // For a new entity check to see whether the automatic label // is configured to be generated before we've saved the node // to the database or after. While the entity is flagged as // new this presave is running before the entity has been saved // to the database. if ($decorated_entity->getNewContentBehavior() === AutoEntityLabelManager::BEFORE_SAVE) { // Generate the automatic label during this run of the presave // hook. Note that not all tokens are available during the // first presave hook run for new entities (most notably the // entity id token). $decorated_entity->setLabel(); } else { // Generate the label after the first save, when the entity has // been written to the database and all tokens are available. // When the after first save option is selected code in the // autoentity_entity_insert function will trigger a resave of the // entity, which will in turn trigger another run of this presave // hook (but during the second run the entity will not be new). // To allow the entity to save we'll set a placeholder title, // one that will be replaced once the entity is resaved. // Using Drupal functions so as not to trigger two runs of // setLabel(). $label_field = $entity->getEntityType()->getKey('label'); $entity->set($label_field, $placeholder); } } } else { // This is an update of an existing entity. // Check to see whether the existing title needs to be // preserved. if ($decorated_entity->isTitlePreserved()) { // The existing title needs to be preserved. // Now check whether or not the title is the new entity // placeholder. Because if it is we want to ignore // the isTitlePreserved flag and update the label anyway // (the code assumes that the only way the entity will // have the placeholder title is if we've configured this entity // to set its auto label during the second run of the presave // hook and this is that second run). $oldLabel = $entity->label(); if ($oldLabel == $placeholder) { $decorated_entity->setLabel(); } } else { // Don't preserve the exiting title. Update the label. $decorated_entity->setLabel(); } } } } } /** * Implements hook_entity_insert(). */ function auto_entitylabel_entity_insert(EntityInterface $entity) { // AutoEntityLabel only supports content entities. if ($entity instanceof ContentEntityInterface) { // To support tokens that are only available after the entity has // been created (like id tokens) trigger a second save. // To do this without corrupting the entity run the // save operation at the end of the entity insert transaction. // To run code at the entity of the entity insert transaction // we need to register a transaction shutdown function. // Check the autolabel settings for the entity to see if we // need to register the shutdown function. $decorator = \Drupal::service('auto_entitylabel.entity_decorator'); /** @var \Drupal\auto_entitylabel\AutoEntityLabelManager $decorated_entity */ $decorated_entity = $decorator->decorate($entity); if ($decorated_entity->hasLabel() && $decorated_entity->autoLabelNeeded() && $decorated_entity->getNewContentBehavior() === AutoEntityLabelManager::AFTER_SAVE ) { // This new entity has an autolabel and it needs to be generated // after the entity has been saved in the database. Register the // shutdown function. drupal_register_shutdown_function('_auto_entitylabel_post_insert', $entity); // Set entity label in memory so messages and such can use what will be // saved during shutdown. $decorator = \Drupal::service('auto_entitylabel.entity_decorator'); /** @var \Drupal\auto_entitylabel\AutoEntityLabelManager $decorated_entity */ $decorated_entity = $decorator->decorate($entity); if ($decorated_entity->hasLabel() && $decorated_entity->autoLabelNeeded()) { $placeholder = strtr('%AutoEntityLabel: @entityId%', ['@entityId' => $entity->uuid()]); if (!$decorated_entity->isTitlePreserved() || $entity->label() == $placeholder) { if ($decorated_entity->getNewContentBehavior() === AutoEntityLabelManager::AFTER_SAVE) { // Update entity's label in memory for anything running after us. $label = $decorated_entity->setLabel(); // Update messages that were using the placeholder label. $messenger = Drupal::messenger(); $all_messages = $messenger->all(); $messenger->deleteAll(); foreach ($all_messages as $type => $messages) { foreach ($messages as $message) { if (strpos($message, $placeholder) >= 0) { if ($message instanceof MarkupInterface) { $message = Markup::create(str_replace($placeholder, $label, $message)); } else { $message = str_replace($placeholder, $label, $message); } } $messenger->addMessage($message, $type); } } } } } } } } /** * Re-save the entity to trigger creation of the automatic label if necessary. */ function _auto_entitylabel_post_insert(EntityInterface $entityArg) { if ($entityArg instanceof ContentEntityInterface) { // Because of the way PHP shutdown functions work this operation may // be called during an entity delete operation (as demonstrated by // the Kernel test). Reload the entity from the database to check that // it hasn't been deleted. if ($entity = \Drupal::entityTypeManager()->getStorage($entityArg->getEntityTypeId())->loadUnchanged($entityArg->id())) { // The entity hasn't been deleted, continue processing. // Again because of the way shutdown functions work this // function may be called for entities that don't have an // autolabel or ones that do but don't need to be saved // again. Run the same checks that were run during the // insert hook to be sure that this entity really needs the // second save. $decorator = \Drupal::service('auto_entitylabel.entity_decorator'); /** @var \Drupal\auto_entitylabel\AutoEntityLabelManager $decorated_entity */ $decorated_entity = $decorator->decorate($entity); if ($decorated_entity->hasLabel() && $decorated_entity->autoLabelNeeded() && $decorated_entity->getNewContentBehavior() === AutoEntityLabelManager::AFTER_SAVE) { if ($entity->getEntityType()->isRevisionable()) { $entity->setNewRevision(FALSE); } $entity->save(); } } } } /** * Implements hook_validation_constraint_alter(). * * Override core NotNull constraint to allow entities that use Auto Entity * Labels to validate when their label is empty before being set automatically. */ function auto_entitylabel_validation_constraint_alter(array &$definitions) { $definitions['NotNull']['class'] = 'Drupal\auto_entitylabel\Plugin\Validation\EntityLabelNotNullConstraint'; } /** * Implements hook_entity_operation(). */ function auto_entitylabel_entity_operation(EntityInterface $entity) { $operations = []; $entity_type = $entity->getEntityType(); $entity_type_id = $entity_type->id(); $entity_id = $entity->id(); if ($entity->hasLinkTemplate('auto-label') && \Drupal::currentUser() ->hasPermission('administer ' . $entity_type_id . ' labels')) { $operations['auto-label'] = [ 'title' => t('Manage automatic entity labels'), 'weight' => 100, 'url' => Url::fromRoute("entity.{$entity_type_id}.auto_label", [$entity_type_id => $entity_id]), ]; } return $operations; }