/** * {@inheritdoc} */ public function save(EntityInterface $entity) { // We return SAVED_UPDATED by default because the logic below might not // update the entity if its values haven't changed, so returning FALSE // would be confusing in that situation. $return = SAVED_UPDATED; $transaction = $this->database->startTransaction(); try { // Load the stored entity, if any. if (!$entity->isNew() && !isset($entity->original)) { $id = $entity->id(); if ($entity->getOriginalId() !== NULL) { $id = $entity->getOriginalId(); } $entity->original = $this->loadUnchanged($id); } if ($entity->isNew()) { $entity->mlid = $this->database->insert($this->entityType->getBaseTable())->fields(array('menu_name' => $entity->menu_name))->execute(); $entity->enforceIsNew(); } // Unlike the save() method from EntityDatabaseStorage, we invoke the // 'presave' hook first because we want to allow modules to alter the // entity before all the logic from our preSave() method. $this->invokeHook('presave', $entity); $entity->preSave($this); // If every value in $entity->original is the same in the $entity, there // is no reason to run the update queries or clear the caches. We use // array_intersect_key() with the $entity as the first parameter because // $entity may have additional keys left over from building a router entry. // The intersect removes the extra keys, allowing a meaningful comparison. if ($entity->isNew() || array_intersect_key(get_object_vars($entity), get_object_vars($entity->original)) != get_object_vars($entity->original)) { $return = drupal_write_record($this->entityType->getBaseTable(), $entity, $this->idKey); if ($return) { if (!$entity->isNew()) { $this->resetCache(array($entity->{$this->idKey})); $entity->postSave($this, TRUE); $this->invokeHook('update', $entity); } else { $return = SAVED_NEW; $this->resetCache(); $entity->enforceIsNew(FALSE); $entity->postSave($this, FALSE); $this->invokeHook('insert', $entity); } } } // Ignore replica server temporarily. db_ignore_replica(); unset($entity->original); return $return; } catch (\Exception $e) { $transaction->rollback(); watchdog_exception($this->entityTypeId, $e); throw new EntityStorageException($e->getMessage(), $e->getCode(), $e); } }
/** * Form constructor. * * @param array $form * An associative array containing the structure of the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * @param \Drupal\Core\Entity\EntityInterface $node * The node being previews * * @return array * The form structure. */ public function buildForm(array $form, FormStateInterface $form_state, EntityInterface $node = NULL) { $view_mode = $node->preview_view_mode; $query_options = $node->isNew() ? array('query' => array('uuid' => $node->uuid())) : array(); $form['backlink'] = array('#type' => 'link', '#title' => $this->t('Back to content editing'), '#url' => $node->isNew() ? Url::fromRoute('node.add', ['node_type' => $node->bundle()]) : $node->urlInfo('edit-form'), '#options' => array('attributes' => array('class' => array('node-preview-backlink'))) + $query_options); $view_mode_options = $this->getViewModeOptions($node); $form['uuid'] = array('#type' => 'value', '#value' => $node->uuid()); $form['view_mode'] = array('#type' => 'select', '#title' => $this->t('View mode'), '#options' => $view_mode_options, '#default_value' => $view_mode, '#attributes' => array('data-drupal-autosubmit' => TRUE)); $form['submit'] = array('#type' => 'submit', '#value' => $this->t('Switch'), '#attributes' => array('class' => array('js-hide'))); return $form; }
/** * {@inheritdoc} */ public function save(array $form, FormStateInterface $form_state) { // Save as a new revision if requested to do so. if (!$form_state->isValueEmpty('revision')) { $this->entity->setNewRevision(); } $insert = $this->entity->isNew(); $this->entity->save(); $context = ['@type' => $this->entity->bundle(), '%info' => $this->entity->label()]; $logger = $this->logger($this->entity->id()); $bundle_entity = $this->getBundleEntity(); $t_args = ['@type' => $bundle_entity ? $bundle_entity->label() : 'None', '%info' => $this->entity->label()]; if ($insert) { $logger->notice('@type: added %info.', $context); drupal_set_message($this->t('@type %info has been created.', $t_args)); } else { $logger->notice('@type: updated %info.', $context); drupal_set_message($this->t('@type %info has been updated.', $t_args)); } if ($this->entity->id()) { $form_state->setValue('id', $this->entity->id()); $form_state->set('id', $this->entity->id()); if ($this->entity->getEntityType()->hasLinkTemplate('collection')) { $form_state->setRedirectUrl($this->entity->toUrl('collection')); } else { $form_state->setRedirectUrl($this->entity->toUrl('canonical')); } } else { // In the unlikely case something went wrong on save, the entity will be // rebuilt and entity form redisplayed. drupal_set_message($this->t('The entity could not be saved.'), 'error'); $form_state->setRebuild(); } }
/** * Creates entity path alias. * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity that should get an alias. * @param string $alias * The alias to be created. */ protected function doExecute(EntityInterface $entity, $alias) { // We need to save the entity before we can get its internal path. if ($entity->isNew()) { $entity->save(); } $path = $entity->urlInfo()->getInternalPath(); $langcode = $entity->language()->getId(); $this->aliasStorage->save($path, $alias, $langcode); }
/** * {@inheritdoc} */ protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) { switch ($operation) { case 'view': // There is no direct view. return FALSE; case 'update': // If there is a URL, this is an external link so always accessible. return $account->hasPermission('administer menu') && ($entity->getUrl() || $this->accessManager->checkNamedRoute($entity->getRouteName(), $entity->getRouteParameters(), $account)); case 'delete': return !$entity->isNew() && $account->hasPermission('administer menu'); } }
/** * Returns an array of supported actions for the current entity form. * * @todo Consider introducing a 'preview' action here, since it is used by * many entity types. */ protected function actions(array $form, FormStateInterface $form_state) { // @todo Rename the action key from submit to save. $actions['submit'] = array('#type' => 'submit', '#value' => $this->t('Save'), '#validate' => array(array($this, 'validate')), '#submit' => array(array($this, 'submit'), array($this, 'save'))); if (!$this->entity->isNew() && $this->entity->hasLinkTemplate('delete-form')) { $route_info = $this->entity->urlInfo('delete-form'); if ($this->getRequest()->query->has('destination')) { $query = $route_info->getOption('query'); $query['destination'] = $this->getRequest()->query->get('destination'); $route_info->setOption('query', $query); } $actions['delete'] = array('#type' => 'link', '#title' => $this->t('Delete'), '#access' => $this->entity->access('delete'), '#attributes' => array('class' => array('button', 'button--danger'))); $actions['delete'] += $route_info->toRenderArray(); } return $actions; }
/** * Returns an array of supported actions for the current entity form. * * @todo Consider introducing a 'preview' action here, since it is used by * many entity types. */ protected function actions(array $form, FormStateInterface $form_state) { // @todo Consider renaming the action key from submit to save. The impacts // are hard to predict. For example, see // \Drupal\language\Element\LanguageConfiguration::processLanguageConfiguration(). $actions['submit'] = array('#type' => 'submit', '#value' => $this->t('Save'), '#validate' => array('::validate'), '#submit' => array('::submitForm', '::save')); if (!$this->entity->isNew() && $this->entity->hasLinkTemplate('delete-form')) { $route_info = $this->entity->urlInfo('delete-form'); if ($this->getRequest()->query->has('destination')) { $query = $route_info->getOption('query'); $query['destination'] = $this->getRequest()->query->get('destination'); $route_info->setOption('query', $query); } $actions['delete'] = array('#type' => 'link', '#title' => $this->t('Delete'), '#access' => $this->entity->access('delete'), '#attributes' => array('class' => array('button', 'button--danger'))); $actions['delete']['#url'] = $route_info; } return $actions; }
/** * {@inheritdoc} */ protected function checkAccess(EntityInterface $feed, $operation, $langcode, AccountInterface $account) { $has_perm = $account->hasPermission('administer feeds') || $account->hasPermission("{$operation} {$feed->bundle()} feeds"); switch ($operation) { case 'view': case 'create': case 'update': return AccessResult::allowedIf($has_perm); case 'import': case 'clear': return AccessResult::allowedIf($has_perm && !$feed->isLocked()); case 'unlock': return AccessResult::allowedIf($has_perm && $feed->isLocked()); case 'delete': return AccessResult::allowedIf($has_perm && !$feed->isLocked() && !$feed->getItemCount() && !$feed->isNew()); default: return AccessResult::neutral(); } }
/** * Responds to entity POST requests and saves the new entity. * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity. * * @return \Drupal\rest\ResourceResponse * The HTTP response object. * * @throws \Symfony\Component\HttpKernel\Exception\HttpException */ public function post(EntityInterface $entity = NULL) { if ($entity == NULL) { throw new BadRequestHttpException('No entity content received.'); } if (!$entity->access('create')) { throw new AccessDeniedHttpException(); } $definition = $this->getPluginDefinition(); // Verify that the deserialized entity is of the type that we expect to // prevent security issues. if ($entity->getEntityTypeId() != $definition['entity_type']) { throw new BadRequestHttpException('Invalid entity type'); } // POSTed entities must not have an ID set, because we always want to create // new entities here. if (!$entity->isNew()) { throw new BadRequestHttpException('Only new entities can be created'); } // Only check 'edit' permissions for fields that were actually // submitted by the user. Field access makes no difference between 'create' // and 'update', so the 'edit' operation is used here. foreach ($entity->_restSubmittedFields as $key => $field_name) { if (!$entity->get($field_name)->access('edit')) { throw new AccessDeniedHttpException(String::format('Access denied on creating field @field', array('@field' => $field_name))); } } // Validate the received data before saving. $this->validate($entity); try { $entity->save(); $this->logger->notice('Created entity %type with ID %id.', array('%type' => $entity->getEntityTypeId(), '%id' => $entity->id())); // 201 Created responses have an empty body. return new ResourceResponse(NULL, 201, array('Location' => $entity->url('canonical', ['absolute' => TRUE]))); } catch (EntityStorageException $e) { throw new HttpException(500, 'Internal Server Error', $e); } }
/** * Responds to entity POST requests and saves the new entity. * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity. * * @return \Drupal\rest\ResourceResponse * The HTTP response object. * * @throws \Symfony\Component\HttpKernel\Exception\HttpException */ public function post(EntityInterface $entity = NULL) { if ($entity == NULL) { throw new BadRequestHttpException(t('No entity content received.')); } if (!$entity->access('create')) { throw new AccessDeniedHttpException(); } $definition = $this->getPluginDefinition(); // Verify that the deserialized entity is of the type that we expect to // prevent security issues. if ($entity->getEntityTypeId() != $definition['entity_type']) { throw new BadRequestHttpException(t('Invalid entity type')); } // POSTed entities must not have an ID set, because we always want to create // new entities here. if (!$entity->isNew()) { throw new BadRequestHttpException(t('Only new entities can be created')); } foreach ($entity as $field_name => $field) { if (!$field->access('create')) { throw new AccessDeniedHttpException(t('Access denied on creating field @field.', array('@field' => $field_name))); } } // Validate the received data before saving. $this->validate($entity); try { $entity->save(); $this->logger->notice('Created entity %type with ID %id.', array('%type' => $entity->getEntityTypeId(), '%id' => $entity->id())); $url = url(strtr($this->pluginId, ':', '/') . '/' . $entity->id(), array('absolute' => TRUE)); // 201 Created responses have an empty body. return new ResourceResponse(NULL, 201, array('Location' => $url)); } catch (EntityStorageException $e) { throw new HttpException(500, t('Internal Server Error'), $e); } }
/** * {@inheritdoc} */ protected function entitySaveAccess(EntityInterface $entity) { if ($this->configuration['authorize'] && !empty($entity->uid->value)) { // If the uid was mapped directly, rather than by email or username, it // could be invalid. if (!($account = $entity->uid->entity)) { $message = 'User %uid is not a valid user.'; throw new EntityAccessException(String::format($message, array('%uid' => $entity->uid->value))); } $op = $entity->isNew() ? 'create' : 'update'; if (!$entity->access($op, $account)) { $args = array('%name' => $account->getUsername(), '%op' => $op, '@bundle' => Unicode::strtolower($this->bundleLabel()), '%bundle' => $entity->bundle()); throw new EntityAccessException(String::format('User %name is not authorized to %op @bundle %bundle.', $args)); } } }
/** * {@inheritdoc} */ protected function getFormSubmitSuffix(EntityInterface $entity, $langcode) { if (!$entity->isNew() && $entity->isTranslatable()) { $translations = $entity->getTranslationLanguages(); if ((count($translations) > 1 || !isset($translations[$langcode])) && ($field = $entity->getFieldDefinition('status'))) { return ' ' . ($field->isTranslatable() ? t('(this translation)') : t('(all translations)')); } } return ''; }
/** * Constructs the entity URI. * * @param \Drupal\Core\Entity\EntityInterface * The entity. * @return string * The entity URI. */ protected function getEntityUri(EntityInterface $entity) { // Some entity types don't provide a canonical link template, at least call // out to ->url(). if ($entity->isNew() || !$entity->hasLinkTemplate('canonical')) { return $entity->url('canonical', []); } $url = $entity->urlInfo('canonical', ['absolute' => TRUE]); return $url->setRouteParameter('_format', 'hal_json')->toString(); }
/** * {@inheritdoc} */ protected function doSave($id, EntityInterface $entity) { $is_new = $entity->isNew(); $prefix = $this->getPrefix(); $config_name = $prefix . $entity->id(); if ($id !== $entity->id()) { // Renaming a config object needs to cater for: // - Storage needs to access the original object. // - The object needs to be renamed/copied in ConfigFactory and reloaded. // - All instances of the object need to be renamed. $this->configFactory->rename($prefix . $id, $config_name); } $config = $this->configFactory->getEditable($config_name); // Retrieve the desired properties and set them in config. $config->setData($this->mapToStorageRecord($entity)); $config->save($entity->hasTrustedData()); // Update the entity with the values stored in configuration. It is possible // that configuration schema has casted some of the values. if (!$entity->hasTrustedData()) { $data = $this->mapFromStorageRecords(array($config->get())); $updated_entity = current($data); foreach (array_keys($config->get()) as $property) { $value = $updated_entity->get($property); $entity->set($property, $value); } } return $is_new ? SAVED_NEW : SAVED_UPDATED; }
/** * {@inheritdoc} */ protected function doSave($id, EntityInterface $entity) { /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ if ($entity->isNew()) { // Ensure the entity is still seen as new after assigning it an id, while // storing its data. $entity->enforceIsNew(); if ($this->entityType->isRevisionable()) { $entity->setNewRevision(); } $return = SAVED_NEW; } else { // @todo Consider returning a different value when saving a non-default // entity revision. See https://www.drupal.org/node/2509360. $return = $entity->isDefaultRevision() ? SAVED_UPDATED : FALSE; } $this->populateAffectedRevisionTranslations($entity); $this->doSaveFieldItems($entity); return $return; }
/** * {@inheritdoc} * * @todo Revisit this logic with forward revisions in mind. */ protected function doSave($id, EntityInterface $entity) { if ($entity->_rev->is_stub) { $entity->isDefaultRevision(TRUE); } else { // Enforce new revision if any module messed with it in a hook. $entity->setNewRevision(); // Decide whether or not this is the default revision. if (!$entity->isNew()) { $default_rev = \Drupal::service('entity.index.rev.tree')->getDefaultRevision($entity->uuid()); if ($entity->_rev->value == $default_rev) { $entity->isDefaultRevision(TRUE); } else { $entity->isDefaultRevision(FALSE); } } } return parent::doSave($id, $entity); }
/** * Check if the provided entity is new. * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity to check. * * @return bool * TRUE if the provided entity is new. */ protected function doEvaluate(EntityInterface $entity) { return $entity->isNew(); }
/** * {@inheritdoc} */ protected function doSave($id, EntityInterface $entity) { $is_new = $entity->isNew(); $prefix = $this->getPrefix(); if ($id !== $entity->id()) { // Renaming a config object needs to cater for: // - Storage needs to access the original object. // - The object needs to be renamed/copied in ConfigFactory and reloaded. // - All instances of the object need to be renamed. $config = $this->configFactory->rename($prefix . $id, $prefix . $entity->id()); } else { $config = $this->configFactory->get($prefix . $id); } // Retrieve the desired properties and set them in config. $config->setData($this->mapToStorageRecord($entity)); $config->save(); return $is_new ? SAVED_NEW : SAVED_UPDATED; }
/** * {@inheritdoc} */ protected function buildValue(EntityInterface $entity) { !($is_new = $entity->isNew()); $revision_id = $is_new ? 0 : $entity->getRevisionId(); // We assign a temporary status to the revision since we are indexing it // pre save. It will be updated post save with the final status. This will // help identifying failures and exception scenarios during entity save. $status = 'indexed'; if (!$is_new && $revision_id) { $status = $entity->_deleted->value ? 'deleted' : 'available'; } return array('entity_type_id' => $entity->getEntityTypeId(), 'entity_id' => $is_new ? 0 : $entity->id(), 'revision_id' => $revision_id, 'uuid' => $entity->uuid(), 'rev' => $entity->_rev->value, 'is_stub' => $entity->_rev->is_stub, 'status' => $status); }
/** * {@inheritdoc} */ public function save(EntityInterface $entity) { // Track if this entity is new. $is_new = $entity->isNew(); // Execute presave logic and invoke the related hooks. $id = $this->doPreSave($entity); // Perform the save and reset the static cache for the changed entity. $return = $this->doSave($id, $entity); // Execute post save logic and invoke the related hooks. $this->doPostSave($entity, !$is_new); return $return; }
/** * {@inheritdoc} */ protected function entitySaveAccess(EntityInterface $entity) { // No need to authorize. if (!$this->configuration['authorize'] || !$entity instanceof EntityOwnerInterface) { return; } // If the uid was mapped directly, rather than by email or username, it // could be invalid. if (!($account = $entity->getOwner())) { throw new EntityAccessException($this->t('Invalid user mapped to %label.', ['%label' => $entity->label()])); } // We don't check access for anonymous users. if ($account->isAnonymous()) { return; } $op = $entity->isNew() ? 'create' : 'update'; // Access granted. if ($entity->access($op, $account)) { return; } $args = ['%name' => $account->getUsername(), '@op' => $op, '@bundle' => $this->getItemLabelPlural()]; throw new EntityAccessException($this->t('User %name is not authorized to @op @bundle.', $args)); }
/** * {@inheritdoc} */ protected function has($id, EntityInterface $entity) { return !$entity->isNew(); }
/** * Performs access checks. * * This method is supposed to be overwritten by extending classes that * do their own custom access checking. * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity for which to check access. * @param string $operation * The entity operation. Usually one of 'view', 'update' or 'delete'. * @param string $langcode * The language code for which to check access. * @param \Drupal\Core\Session\AccountInterface $account * The user for which to check access. * * @return \Drupal\Core\Access\AccessResultInterface * The access result. */ protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) { if ($operation == 'delete' && $entity->isNew()) { return AccessResult::forbidden()->cacheUntilEntityChanges($entity); } if ($admin_permission = $this->entityType->getAdminPermission()) { return AccessResult::allowedIfHasPermission($account, $this->entityType->getAdminPermission()); } else { // No opinion. return AccessResult::neutral(); } }
/** * Responds to entity POST requests and saves the new entity. * * @Thruway(name = "add", type="procedure") * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity. * * @throws \Symfony\Component\HttpKernel\Exception\HttpException * * @return array * */ public function post(EntityInterface $entity = NULL) { if ($entity == NULL) { throw new BadRequestHttpException(t('No entity content received.')); } if (!$entity->access('create')) { throw new AccessDeniedHttpException(); } $definition = $this->getPluginDefinition(); // Verify that the deserialized entity is of the type that we expect to // prevent security issues. if ($entity->getEntityTypeId() != $definition['entity_type']) { throw new BadRequestHttpException(t('Invalid entity type')); } // POSTed entities must not have an ID set, because we always want to create // new entities here. if (!$entity->isNew()) { throw new BadRequestHttpException(t('Only new entities can be created')); } foreach ($entity as $field_name => $field) { if (!$field->access('create')) { throw new AccessDeniedHttpException(t('Access denied on creating field @field.', array('@field' => $field_name))); } } // Validate the received data before saving. $this->validate($entity); try { $entity->save(); //@todo fix this hack. When you save, it doesn't load the field defaults, so we need to reload it return $entity::load($entity->id()); // $this->logger->notice( // 'Created entity %type with ID %id.', // array('%type' => $entity->getEntityTypeId(), '%id' => $entity->id()) // ); } catch (EntityStorageException $e) { throw new HttpException(500, t('Internal Server Error'), $e); } }
/** * {@inheritdoc} */ protected function doSave($id, EntityInterface $entity) { $is_new = $entity->isNew(); // Save the entity data in the key value store. $this->keyValueStore->set($entity->id(), $entity->toArray()); // If this is a rename, delete the original entity. if ($this->has($id, $entity) && $id !== $entity->id()) { $this->keyValueStore->delete($id); } return $is_new ? SAVED_NEW : SAVED_UPDATED; }
/** * {@inheritdoc} */ public function save(EntityInterface $entity) { $id = $entity->id(); // Track the original ID. if ($entity->getOriginalId() !== NULL) { $id = $entity->getOriginalId(); } // Track if this entity is new. $is_new = $entity->isNew(); // Track if this entity exists already. $id_exists = $this->has($id, $entity); // A new entity should not already exist. if ($id_exists && $is_new) { throw new EntityStorageException(SafeMarkup::format('@type entity with ID @id already exists.', array('@type' => $this->entityTypeId, '@id' => $id))); } // Load the original entity, if any. if ($id_exists && !isset($entity->original)) { $entity->original = $this->loadUnchanged($id); } // Allow code to run before saving. $entity->preSave($this); $this->invokeHook('presave', $entity); // Perform the save and reset the static cache for the changed entity. $return = $this->doSave($id, $entity); $this->resetCache(array($id)); // The entity is no longer new. $entity->enforceIsNew(FALSE); // Allow code to run after saving. $entity->postSave($this, !$is_new); $this->invokeHook($is_new ? 'insert' : 'update', $entity); // After saving, this is now the "original entity", and subsequent saves // will be updates instead of inserts, and updates must always be able to // correctly identify the original entity. $entity->setOriginalId($entity->id()); unset($entity->original); return $return; }
/** * Performs access checks. * * This method is supposed to be overwritten by extending classes that * do their own custom access checking. * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity for which to check 'create' access. * @param string $operation * The entity operation. Usually one of 'view', 'update', 'create' or * 'delete'. * @param string $langcode * The language code for which to check access. * @param \Drupal\Core\Session\AccountInterface $account * The user for which to check access. * * @return bool|null * TRUE if access was granted, FALSE if access was denied and NULL if access * could not be determined. */ protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) { if ($operation == 'delete' && $entity->isNew()) { return FALSE; } if ($admin_permission = $this->entityType->getAdminPermission()) { return $account->hasPermission($admin_permission); } else { return NULL; } }