/** * Returns the entity_form_display object used to build an entity form. * * Depending on the configuration of the form mode for the entity bundle, this * can be either the display object associated to the form mode, or the * 'default' display. * * This method should only be used internally when rendering an entity form. * When assigning suggested display options for a component in a given form * mode, entity_get_form_display() should be used instead, in order to avoid * inadvertently modifying the output of other form modes that might happen to * use the 'default' display too. Those options will then be effectively * applied only if the form mode is configured to use them. * * hook_entity_form_display_alter() is invoked on each display, allowing 3rd * party code to alter the display options held in the display before they are * used to generate render arrays. * * @param \Drupal\Core\Entity\FieldableEntityInterface $entity * The entity for which the form is being built. * @param string $form_mode * The form mode. * * @return \Drupal\Core\Entity\Display\EntityFormDisplayInterface * The display object that should be used to build the entity form. * * @see entity_get_form_display() * @see hook_entity_form_display_alter() */ public static function collectRenderDisplay(FieldableEntityInterface $entity, $form_mode) { $entity_type = $entity->getEntityTypeId(); $bundle = $entity->bundle(); // Check the existence and status of: // - the display for the form mode, // - the 'default' display. if ($form_mode != 'default') { $candidate_ids[] = $entity_type . '.' . $bundle . '.' . $form_mode; } $candidate_ids[] = $entity_type . '.' . $bundle . '.default'; $results = \Drupal::entityQuery('entity_form_display')->condition('id', $candidate_ids)->condition('status', TRUE)->execute(); // Load the first valid candidate display, if any. $storage = \Drupal::entityManager()->getStorage('entity_form_display'); foreach ($candidate_ids as $candidate_id) { if (isset($results[$candidate_id])) { $display = $storage->load($candidate_id); break; } } // Else create a fresh runtime object. if (empty($display)) { $display = $storage->create(array('targetEntityType' => $entity_type, 'bundle' => $bundle, 'mode' => $form_mode, 'status' => TRUE)); } // Let the display know which form mode was originally requested. $display->originalMode = $form_mode; // Let modules alter the display. $display_context = array('entity_type' => $entity_type, 'bundle' => $bundle, 'form_mode' => $form_mode); \Drupal::moduleHandler()->alter('entity_form_display', $display, $display_context); return $display; }
/** * {@inheritdoc} */ public function create(FieldableEntityInterface $entity, $fields) { $query = $this->database->insert('comment_entity_statistics')->fields(array('entity_id', 'entity_type', 'field_name', 'cid', 'last_comment_timestamp', 'last_comment_name', 'last_comment_uid', 'comment_count')); foreach ($fields as $field_name => $detail) { // Skip fields that entity does not have. if (!$entity->hasField($field_name)) { continue; } // Get the user ID from the entity if it's set, or default to the // currently logged in user. $last_comment_uid = 0; if ($entity instanceof EntityOwnerInterface) { $last_comment_uid = $entity->getOwnerId(); } if (!isset($last_comment_uid)) { // Default to current user when entity does not implement // EntityOwnerInterface or author is not set. $last_comment_uid = $this->currentUser->id(); } // Default to REQUEST_TIME when entity does not have a changed property. $last_comment_timestamp = REQUEST_TIME; // @todo Make comment statistics language aware and add some tests. See // https://www.drupal.org/node/2318875 if ($entity instanceof EntityChangedInterface) { $last_comment_timestamp = $entity->getChangedTimeAcrossTranslations(); } $query->values(array('entity_id' => $entity->id(), 'entity_type' => $entity->getEntityTypeId(), 'field_name' => $field_name, 'cid' => 0, 'last_comment_timestamp' => $last_comment_timestamp, 'last_comment_name' => NULL, 'last_comment_uid' => $last_comment_uid, 'comment_count' => 0)); } $query->execute(); }
/** * Provide the allowed values for a 'list_*' field. * * Callback for options_allowed_values(). * * 'list_*' fields can specify a callback to define the set of their allowed * values using the 'allowed_values_function' storage setting. * * That function will be called: * - either in the context of a specific entity, which is then provided as the * $entity parameter, * - or for the field generally without the context of any specific entity or * entity bundle (typically, Views needing a list of values for an exposed * filter), in which case the $entity parameter is NULL. * This lets the callback restrict the set of allowed values or adjust the * labels depending on some conditions on the containing entity. * * For consistency, the set of values returned when an $entity is provided * should be a subset of the values returned when no $entity is provided. * * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $definition * The field storage definition. * @param \Drupal\Core\Entity\FieldableEntityInterface|null $entity * (optional) The entity context if known, or NULL if the allowed values are * being collected without the context of a specific entity. * @param bool &$cacheable * (optional) If an $entity is provided, the $cacheable parameter should be * modified by reference and set to FALSE if the set of allowed values * returned was specifically adjusted for that entity and cannot not be reused * for other entities. Defaults to TRUE. * * @return array * The array of allowed values. Keys of the array are the raw stored values * (number or text), values of the array are the display labels. If $entity * is NULL, you should return the list of all the possible allowed values in * any context so that other code (e.g. Views filters) can support the allowed * values for all possible entities and bundles. * * @ingroup callbacks * @see options_allowed_values() * @see options_test_allowed_values_callback() * @see options_test_dynamic_values_callback() */ function callback_allowed_values_function(FieldStorageDefinitionInterface $definition, FieldableEntityInterface $entity = NULL, &$cacheable = TRUE) { if (isset($entity) && $entity->bundle() == 'not_a_programmer') { $values = array(1 => 'One', 2 => 'Two'); } else { $values = array('Group 1' => array(0 => 'Zero', 1 => 'One'), 'Group 2' => array(2 => 'Two')); } return $values; }
/** * {@inheritdoc} */ public function filterByFieldAccess(AccountInterface $account = NULL) { $filtered_fields = array(); foreach ($this->getFieldNames() as $field_name) { if (!$this->entity->get($field_name)->access('edit', $account)) { $filtered_fields[] = $field_name; } } return $this->filterByFields($filtered_fields); }
/** * Checks whether field languages are correctly stored for the given entity. * * @param \Drupal\Core\Entity\FieldableEntityInterface $entity * The entity fields are attached to. * @param string $message * (optional) A message to display with the assertion. */ protected function assertFieldStorageLangcode(FieldableEntityInterface $entity, $message = '') { $status = TRUE; $entity_type = $entity->getEntityTypeId(); $id = $entity->id(); $langcode = $entity->getUntranslated()->language()->getId(); $fields = array($this->field_name, $this->untranslatable_field_name); /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ $table_mapping = \Drupal::entityManager()->getStorage($entity_type)->getTableMapping(); foreach ($fields as $field_name) { $field_storage = FieldStorageConfig::loadByName($entity_type, $field_name); $table = $table_mapping->getDedicatedDataTableName($field_storage); $record = \Drupal::database()->select($table, 'f')->fields('f')->condition('f.entity_id', $id)->condition('f.revision_id', $id)->execute()->fetchObject(); if ($record->langcode != $langcode) { $status = FALSE; break; } } return $this->assertTrue($status, $message); }
/** * {@inheritdoc} */ public function getOptionsProvider($property_name, FieldableEntityInterface $entity) { // If the field item class implements the interface, create an orphaned // runtime item object, so that it can be used as the options provider // without modifying the entity being worked on. if (is_subclass_of($this->getFieldItemClass(), '\\Drupal\\Core\\TypedData\\OptionsProviderInterface')) { $items = $entity->get($this->getName()); return \Drupal::service('plugin.manager.field.field_type')->createFieldItem($items, 0); } // @todo: Allow setting custom options provider, see // https://www.drupal.org/node/2002138. }
/** * {@inheritdoc} */ public function setChangedTime($timestamp) { $field_name = $this->translation->hasField('content_translation_changed') ? 'content_translation_changed' : 'changed'; $this->translation->set($field_name, $timestamp); return $this; }
/** * {@inheritdoc} */ public function getOptionsProvider($property_name, FieldableEntityInterface $entity) { // If the field item class implements the interface, proxy it through. $item = $entity->get($this->getName())->first(); if ($item instanceof OptionsProviderInterface) { return $item; } // @todo: Allow setting custom options provider, see // https://www.drupal.org/node/2002138. }
/** * Returns the display object used to render an entity. * * See the collectRenderDisplays() method for details. * * @param \Drupal\Core\Entity\FieldableEntityInterface $entity * The entity being rendered. * @param string $view_mode * The view mode. * * @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface * The display object that should be used to render the entity. * * @see \Drupal\entity\Entity\EntityDisplay::collectRenderDisplays() */ public static function collectRenderDisplay(FieldableEntityInterface $entity, $view_mode) { $displays = static::collectRenderDisplays(array($entity), $view_mode); return $displays[$entity->bundle()]; }
/** * Finds the field name for the field carrying the changed timestamp, if any. * * @param \Drupal\Core\Entity\FieldableEntityInterface $entity * The entity. * * @return string|null * The name of the field found or NULL if not found. */ protected function getChangedFieldName(FieldableEntityInterface $entity) { foreach ($entity->getFieldDefinitions() as $field) { if ($field->getType() == 'changed') { return $field->getName(); } } }
/** * {@inheritdoc} */ public function createFieldItemList(FieldableEntityInterface $entity, $field_name, $values = NULL) { // Leverage prototyping of the Typed Data API for fast instantiation. return $this->typedDataManager->getPropertyInstance($entity->getTypedData(), $field_name, $values); }
/** * {@inheritdoc} */ public function getNewCommentPageNumber($total_comments, $new_comments, FieldableEntityInterface $entity, $field_name) { $field = $entity->getFieldDefinition($field_name); $comments_per_page = $field->getSetting('per_page'); if ($total_comments <= $comments_per_page) { // Only one page of comments. $count = 0; } elseif ($field->getSetting('default_mode') == CommentManagerInterface::COMMENT_MODE_FLAT) { // Flat comments. $count = $total_comments - $new_comments; } else { // Threaded comments. // 1. Find all the threads with a new comment. $unread_threads_query = $this->database->select('comment_field_data', 'comment')->fields('comment', array('thread'))->condition('entity_id', $entity->id())->condition('entity_type', $entity->getEntityTypeId())->condition('field_name', $field_name)->condition('status', CommentInterface::PUBLISHED)->condition('default_langcode', 1)->orderBy('created', 'DESC')->orderBy('cid', 'DESC')->range(0, $new_comments); // 2. Find the first thread. $first_thread_query = $this->database->select($unread_threads_query, 'thread'); $first_thread_query->addExpression('SUBSTRING(thread, 1, (LENGTH(thread) - 1))', 'torder'); $first_thread = $first_thread_query->fields('thread', array('thread'))->orderBy('torder')->range(0, 1)->execute()->fetchField(); // Remove the final '/'. $first_thread = substr($first_thread, 0, -1); // Find the number of the first comment of the first unread thread. $count = $this->database->query('SELECT COUNT(*) FROM {comment_field_data} WHERE entity_id = :entity_id AND entity_type = :entity_type AND field_name = :field_name AND status = :status AND SUBSTRING(thread, 1, (LENGTH(thread) - 1)) < :thread AND default_langcode = 1', array(':status' => CommentInterface::PUBLISHED, ':entity_id' => $entity->id(), ':field_name' => $field_name, ':entity_type' => $entity->getEntityTypeId(), ':thread' => $first_thread))->fetchField(); } return $comments_per_page > 0 ? (int) ($count / $comments_per_page) : 0; }
/** * {@inheritdoc} */ public function buildForm(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state) { // Set #parents to 'top-level' by default. $form += array('#parents' => array()); // Let each widget generate the form elements. foreach ($this->getComponents() as $name => $options) { if ($widget = $this->getRenderer($name)) { $items = $entity->get($name); $items->filterEmptyItems(); $form[$name] = $widget->form($items, $form, $form_state); $form[$name]['#access'] = $items->access('edit'); // Assign the correct weight. This duplicates the reordering done in // processForm(), but is needed for other forms calling this method // directly. $form[$name]['#weight'] = $options['weight']; // Associate the cache tags for the field definition & field storage // definition. $field_definition = $this->getFieldDefinition($name); $this->renderer->addCacheableDependency($form[$name], $field_definition); $this->renderer->addCacheableDependency($form[$name], $field_definition->getFieldStorageDefinition()); } } // Associate the cache tags for the form display. $this->renderer->addCacheableDependency($form, $this); // Add a process callback so we can assign weights and hide extra fields. $form['#process'][] = array($this, 'processForm'); }
/** * Checks if a given entity has a given field. * * @param \Drupal\Core\Entity\FieldableEntityInterface $entity * The entity to check for the provided field. * @param string $field * The field to check for on the entity. * * @return bool * TRUE if the provided entity has the provided field. */ protected function doEvaluate(FieldableEntityInterface $entity, $field) { return $entity->hasField($field); }
/** * {@inheritdoc} */ public function validateFormValues(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state) { $violations = $entity->validate(); $violations->filterByFieldAccess(); // Flag entity level violations. foreach ($violations->getEntityViolations() as $violation) { /** @var \Symfony\Component\Validator\ConstraintViolationInterface $violation */ $form_state->setErrorByName('', $violation->getMessage()); } $this->flagWidgetsErrorsFromViolations($violations, $form, $form_state); }
/** * Updates a field value, only if the field is translatable. * * @param string $field_name * The name of the field. * @param mixed $value * The field value to be set. */ protected function setFieldOnlyIfTranslatable($field_name, $value) { if ($this->translation->getFieldDefinition($field_name)->isTranslatable()) { $this->translation->set($field_name, $value); } }
/** * {@inheritdoc} */ public function buildCommentedEntityLinks(FieldableEntityInterface $entity, array &$context) { $entity_links = array(); $view_mode = $context['view_mode']; if ($view_mode == 'search_index' || $view_mode == 'search_result' || $view_mode == 'print' || $view_mode == 'rss') { // Do not add any links if the entity is displayed for: // - search indexing. // - constructing a search result excerpt. // - print. // - rss. return array(); } $fields = $this->commentManager->getFields($entity->getEntityTypeId()); foreach ($fields as $field_name => $detail) { // Skip fields that the entity does not have. if (!$entity->hasField($field_name)) { continue; } $links = array(); $commenting_status = $entity->get($field_name)->status; if ($commenting_status != CommentItemInterface::HIDDEN) { // Entity has commenting status open or closed. $field_definition = $entity->getFieldDefinition($field_name); if ($view_mode == 'teaser') { // Teaser view: display the number of comments that have been posted, // or a link to add new comments if the user has permission, the // entity is open to new comments, and there currently are none. if ($this->currentUser->hasPermission('access comments')) { if (!empty($entity->get($field_name)->comment_count)) { $links['comment-comments'] = array('title' => $this->formatPlural($entity->get($field_name)->comment_count, '1 comment', '@count comments'), 'attributes' => array('title' => $this->t('Jump to the first comment.')), 'fragment' => 'comments', 'url' => $entity->urlInfo()); if ($this->moduleHandler->moduleExists('history')) { $links['comment-new-comments'] = array('title' => '', 'url' => Url::fromRoute('<current>'), 'attributes' => array('class' => 'hidden', 'title' => $this->t('Jump to the first new comment.'), 'data-history-node-last-comment-timestamp' => $entity->get($field_name)->last_comment_timestamp, 'data-history-node-field-name' => $field_name)); } } } // Provide a link to new comment form. if ($commenting_status == CommentItemInterface::OPEN) { $comment_form_location = $field_definition->getSetting('form_location'); if ($this->currentUser->hasPermission('post comments')) { $links['comment-add'] = array('title' => $this->t('Add new comments'), 'language' => $entity->language(), 'attributes' => array('title' => $this->t('Share your thoughts and opinions.')), 'fragment' => 'comment-form'); if ($comment_form_location == CommentItemInterface::FORM_SEPARATE_PAGE) { $links['comment-add']['url'] = Url::fromRoute('comment.reply', ['entity_type' => $entity->getEntityTypeId(), 'entity' => $entity->id(), 'field_name' => $field_name]); } else { $links['comment-add'] += ['url' => $entity->urlInfo()]; } } elseif ($this->currentUser->isAnonymous()) { $links['comment-forbidden'] = array('title' => $this->commentManager->forbiddenMessage($entity, $field_name)); } } } else { // Entity in other view modes: add a "post comment" link if the user // is allowed to post comments and if this entity is allowing new // comments. if ($commenting_status == CommentItemInterface::OPEN) { $comment_form_location = $field_definition->getSetting('form_location'); if ($this->currentUser->hasPermission('post comments')) { // Show the "post comment" link if the form is on another page, or // if there are existing comments that the link will skip past. if ($comment_form_location == CommentItemInterface::FORM_SEPARATE_PAGE || !empty($entity->get($field_name)->comment_count) && $this->currentUser->hasPermission('access comments')) { $links['comment-add'] = array('title' => $this->t('Add new comment'), 'attributes' => array('title' => $this->t('Share your thoughts and opinions.')), 'fragment' => 'comment-form'); if ($comment_form_location == CommentItemInterface::FORM_SEPARATE_PAGE) { $links['comment-add']['url'] = Url::fromRoute('comment.reply', ['entity_type' => $entity->getEntityTypeId(), 'entity' => $entity->id(), 'field_name' => $field_name]); } else { $links['comment-add']['url'] = $entity->urlInfo(); } } } elseif ($this->currentUser->isAnonymous()) { $links['comment-forbidden'] = array('title' => $this->commentManager->forbiddenMessage($entity, $field_name)); } } } } if (!empty($links)) { $entity_links['comment__' . $field_name] = array('#theme' => 'links__entity__comment__' . $field_name, '#links' => $links, '#attributes' => array('class' => array('links', 'inline'))); if ($view_mode == 'teaser' && $this->moduleHandler->moduleExists('history') && $this->currentUser->isAuthenticated()) { $entity_links['comment__' . $field_name]['#cache']['contexts'][] = 'user'; $entity_links['comment__' . $field_name]['#attached']['library'][] = 'comment/drupal.node-new-comments-link'; // Embed the metadata for the "X new comments" link (if any) on this // entity. $entity_links['comment__' . $field_name]['#attached']['drupalSettings']['history']['lastReadTimestamps'][$entity->id()] = (int) history_read($entity->id()); $new_comments = $this->commentManager->getCountNewComments($entity); if ($new_comments > 0) { $page_number = $this->entityManager->getStorage('comment')->getNewCommentPageNumber($entity->{$field_name}->comment_count, $new_comments, $entity, $field_name); $query = $page_number ? ['page' => $page_number] : NULL; $value = ['new_comment_count' => (int) $new_comments, 'first_new_comment_link' => $entity->url('canonical', ['query' => $query, 'fragment' => 'new'])]; $parents = ['comment', 'newCommentsLinks', $entity->getEntityTypeId(), $field_name, $entity->id()]; NestedArray::setValue($entity_links['comment__' . $field_name]['#attached']['drupalSettings'], $parents, $value); } } } } return $entity_links; }
/** * @inheritdoc */ public static function getDefaultAllValue(FieldableEntityInterface $entity, FieldDefinitionInterface $definition) { // @TODO: This may become configurable. $item = FALSE; switch ($entity->getEntityType()) { case 'user': case 'node': $item = FALSE; break; default: break; } return $item; }