Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

View file

@ -0,0 +1,72 @@
<?php
/**
* @file
* Contains \Drupal\node\Access\NodeAddAccessCheck.
*/
namespace Drupal\node\Access;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\NodeTypeInterface;
/**
* Determines access to for node add pages.
*
* @ingroup node_access
*/
class NodeAddAccessCheck implements AccessInterface {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a EntityCreateAccessCheck object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* Checks access to the node add page for the node type.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
* @param \Drupal\node\NodeTypeInterface $node_type
* (optional) The node type. If not specified, access is allowed if there
* exists at least one node type for which the user may create a node.
*
* @return string
* A \Drupal\Core\Access\AccessInterface constant value.
*/
public function access(AccountInterface $account, NodeTypeInterface $node_type = NULL) {
$access_control_handler = $this->entityManager->getAccessControlHandler('node');
// If checking whether a node of a particular type may be created.
if ($account->hasPermission('administer content types')) {
return AccessResult::allowed()->cachePerPermissions();
}
if ($node_type) {
return $access_control_handler->createAccess($node_type->id(), $account, [], TRUE);
}
// If checking whether a node of any type may be created.
foreach ($this->entityManager->getStorage('node_type')->loadMultiple() as $node_type) {
if (($access = $access_control_handler->createAccess($node_type->id(), $account, [], TRUE)) && $access->isAllowed()) {
return $access;
}
}
// No opinion.
return AccessResult::neutral();
}
}

View file

@ -0,0 +1,60 @@
<?php
/**
* @file
* Contains \Drupal\node\Access\NodePreviewAccessCheck.
*/
namespace Drupal\node\Access;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\NodeInterface;
/**
* Determines access to node previews.
*
* @ingroup node_access
*/
class NodePreviewAccessCheck implements AccessInterface {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a EntityCreateAccessCheck object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* Checks access to the node preview page.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
* @param \Drupal\node\NodeInterface $node_preview
* The node that is being previewed.
*
* @return string
* A \Drupal\Core\Access\AccessInterface constant value.
*/
public function access(AccountInterface $account, NodeInterface $node_preview) {
if ($node_preview->isNew()) {
$access_controller = $this->entityManager->getAccessControlHandler('node');
return $access_controller->createAccess($node_preview->bundle(), $account, [], TRUE);
}
else {
return $node_preview->access('update', $account, TRUE);
}
}
}

View file

@ -0,0 +1,159 @@
<?php
/**
* @file
* Contains \Drupal\node\Access\NodeRevisionAccessCheck.
*/
namespace Drupal\node\Access;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\NodeInterface;
use Symfony\Component\Routing\Route;
/**
* Provides an access checker for node revisions.
*
* @ingroup node_access
*/
class NodeRevisionAccessCheck implements AccessInterface {
/**
* The node storage.
*
* @var \Drupal\node\NodeStorageInterface
*/
protected $nodeStorage;
/**
* The node access control handler.
*
* @var \Drupal\Core\Entity\EntityAccessControlHandlerInterface
*/
protected $nodeAccess;
/**
* A static cache of access checks.
*
* @var array
*/
protected $access = array();
/**
* Constructs a new NodeRevisionAccessCheck.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Database\Connection $connection
* The database connection.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->nodeStorage = $entity_manager->getStorage('node');
$this->nodeAccess = $entity_manager->getAccessControlHandler('node');
}
/**
* Checks routing access for the node revision.
*
* @param \Symfony\Component\Routing\Route $route
* The route to check against.
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
* @param int $node_revision
* (optional) The node revision ID. If not specified, but $node is, access
* is checked for that object's revision.
* @param \Drupal\node\NodeInterface $node
* (optional) A node object. Used for checking access to a node's default
* revision when $node_revision is unspecified. Ignored when $node_revision
* is specified. If neither $node_revision nor $node are specified, then
* access is denied.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(Route $route, AccountInterface $account, $node_revision = NULL, NodeInterface $node = NULL) {
if ($node_revision) {
$node = $this->nodeStorage->loadRevision($node_revision);
}
$operation = $route->getRequirement('_access_node_revision');
return AccessResult::allowedIf($node && $this->checkAccess($node, $account, $operation))->cachePerPermissions();
}
/**
* Checks node revision access.
*
* @param \Drupal\node\NodeInterface $node
* The node to check.
* @param \Drupal\Core\Session\AccountInterface $account
* A user object representing the user for whom the operation is to be
* performed.
* @param string $op
* (optional) The specific operation being checked. Defaults to 'view.'
* @param string|null $langcode
* (optional) Language code for the variant of the node. Different language
* variants might have different permissions associated. If NULL, the
* original langcode of the node is used. Defaults to NULL.
*
* @return bool
* TRUE if the operation may be performed, FALSE otherwise.
*/
public function checkAccess(NodeInterface $node, AccountInterface $account, $op = 'view', $langcode = NULL) {
$map = array(
'view' => 'view all revisions',
'update' => 'revert all revisions',
'delete' => 'delete all revisions',
);
$bundle = $node->bundle();
$type_map = array(
'view' => "view $bundle revisions",
'update' => "revert $bundle revisions",
'delete' => "delete $bundle revisions",
);
if (!$node || !isset($map[$op]) || !isset($type_map[$op])) {
// If there was no node to check against, or the $op was not one of the
// supported ones, we return access denied.
return FALSE;
}
// If no language code was provided, default to the node revision's langcode.
if (empty($langcode)) {
$langcode = $node->language()->getId();
}
// Statically cache access by revision ID, language code, user account ID,
// and operation.
$cid = $node->getRevisionId() . ':' . $langcode . ':' . $account->id() . ':' . $op;
if (!isset($this->access[$cid])) {
// Perform basic permission checks first.
if (!$account->hasPermission($map[$op]) && !$account->hasPermission($type_map[$op]) && !$account->hasPermission('administer nodes')) {
$this->access[$cid] = FALSE;
return FALSE;
}
// There should be at least two revisions. If the vid of the given node
// and the vid of the default revision differ, then we already have two
// different revisions so there is no need for a separate database check.
// Also, if you try to revert to or delete the default revision, that's
// not good.
if ($node->isDefaultRevision() && ($this->nodeStorage->countDefaultLanguageRevisions($node) == 1 || $op == 'update' || $op == 'delete')) {
$this->access[$cid] = FALSE;
}
elseif ($account->hasPermission('administer nodes')) {
$this->access[$cid] = TRUE;
}
else {
// First check the access to the default revision and finally, if the
// node passed in is not the default revision then access to that, too.
$this->access[$cid] = $this->nodeAccess->access($this->nodeStorage->load($node->id()), $op, $langcode, $account) && ($node->isDefaultRevision() || $this->nodeAccess->access($node, $op, $langcode, $account));
}
}
return $this->access[$cid];
}
}

View file

@ -0,0 +1,81 @@
<?php
/**
* @file
* Contains \Drupal\node\Cache\NodeAccessGrantsCacheContext.
*/
namespace Drupal\node\Cache;
use Drupal\Core\Cache\Context\CalculatedCacheContextInterface;
use Drupal\Core\Cache\Context\UserCacheContext;
/**
* Defines the node access view cache context service.
*
* This allows for node access grants-sensitive caching when listing nodes.
*
* @see node_query_node_access_alter()
* @ingroup node_access
*/
class NodeAccessGrantsCacheContext extends UserCacheContext implements CalculatedCacheContextInterface {
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t("Content access view grants");
}
/**
* {@inheritdoc}
*/
public function getContext($operation = NULL) {
// If the current user either can bypass node access then we don't need to
// determine the exact node grants for the current user.
if ($this->user->hasPermission('bypass node access')) {
return 'all';
}
// When no specific operation is specified, check the grants for all three
// possible operations.
if ($operation === NULL) {
$result = [];
foreach (['view', 'update', 'delete'] as $op) {
$result[] = $this->checkNodeGrants($op);
}
return implode('-', $result);
}
else {
return $this->checkNodeGrants($operation);
}
}
/**
* Checks the node grants for the given operation.
*
* @param string $operation
* The operation to check the node grants for.
*
* @return string
* The string representation of the cache context.
*/
protected function checkNodeGrants($operation) {
// When checking the grants for the 'view' operation and the current user
// has a global view grant (i.e. a view grant for node ID 0) — note that
// this is automatically the case if no node access modules exist (no
// hook_node_grants() implementations) then we don't need to determine the
// exact node view grants for the current user.
if ($operation === 'view' && node_access_view_all_nodes($this->user)) {
return 'view.all';
}
$grants = node_access_grants($operation, $this->user);
$grants_context_parts = [];
foreach ($grants as $realm => $gids) {
$grants_context_parts[] = $realm . ':' . implode(',', $gids);
}
return $operation . '.' . implode(';', $grants_context_parts);
}
}

View file

@ -0,0 +1,249 @@
<?php
/**
* @file
* Contains \Drupal\node\Controller\NodeController.
*/
namespace Drupal\node\Controller;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Datetime\DateFormatter;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Url;
use Drupal\node\NodeTypeInterface;
use Drupal\node\NodeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Returns responses for Node routes.
*/
class NodeController extends ControllerBase implements ContainerInjectionInterface {
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatter
*/
protected $dateFormatter;
/**
* The renderer service.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* Constructs a NodeController object.
*
* @param \Drupal\Core\Datetime\DateFormatter $date_formatter
* The date formatter service.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service.
*/
public function __construct(DateFormatter $date_formatter, RendererInterface $renderer) {
$this->dateFormatter = $date_formatter;
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('date.formatter'),
$container->get('renderer')
);
}
/**
* Displays add content links for available content types.
*
* Redirects to node/add/[type] if only one content type is available.
*
* @return array
* A render array for a list of the node types that can be added; however,
* if there is only one node type defined for the site, the function
* redirects to the node add page for that one node type and does not return
* at all.
*
* @see node_menu()
*/
public function addPage() {
$content = array();
// Only use node types the user has access to.
foreach ($this->entityManager()->getStorage('node_type')->loadMultiple() as $type) {
if ($this->entityManager()->getAccessControlHandler('node')->createAccess($type->id())) {
$content[$type->id()] = $type;
}
}
// Bypass the node/add listing if only one content type is available.
if (count($content) == 1) {
$type = array_shift($content);
return $this->redirect('node.add', array('node_type' => $type->id()));
}
return array(
'#theme' => 'node_add_list',
'#content' => $content,
);
}
/**
* Provides the node submission form.
*
* @param \Drupal\node\NodeTypeInterface $node_type
* The node type entity for the node.
*
* @return array
* A node submission form.
*/
public function add(NodeTypeInterface $node_type) {
$node = $this->entityManager()->getStorage('node')->create(array(
'type' => $node_type->id(),
));
$form = $this->entityFormBuilder()->getForm($node);
return $form;
}
/**
* Displays a node revision.
*
* @param int $node_revision
* The node revision ID.
*
* @return array
* An array suitable for drupal_render().
*/
public function revisionShow($node_revision) {
$node = $this->entityManager()->getStorage('node')->loadRevision($node_revision);
$node_view_controller = new NodeViewController($this->entityManager, $this->renderer);
$page = $node_view_controller->view($node);
unset($page['nodes'][$node->id()]['#cache']);
return $page;
}
/**
* Page title callback for a node revision.
*
* @param int $node_revision
* The node revision ID.
*
* @return string
* The page title.
*/
public function revisionPageTitle($node_revision) {
$node = $this->entityManager()->getStorage('node')->loadRevision($node_revision);
return $this->t('Revision of %title from %date', array('%title' => $node->label(), '%date' => format_date($node->getRevisionCreationTime())));
}
/**
* Generates an overview table of older revisions of a node.
*
* @param \Drupal\node\NodeInterface $node
* A node object.
*
* @return array
* An array as expected by drupal_render().
*/
public function revisionOverview(NodeInterface $node) {
$account = $this->currentUser();
$node_storage = $this->entityManager()->getStorage('node');
$type = $node->getType();
$build = array();
$build['#title'] = $this->t('Revisions for %title', array('%title' => $node->label()));
$header = array($this->t('Revision'), $this->t('Operations'));
$revert_permission = (($account->hasPermission("revert $type revisions") || $account->hasPermission('revert all revisions') || $account->hasPermission('administer nodes')) && $node->access('update'));
$delete_permission = (($account->hasPermission("delete $type revisions") || $account->hasPermission('delete all revisions') || $account->hasPermission('administer nodes')) && $node->access('delete'));
$rows = array();
$vids = $node_storage->revisionIds($node);
foreach (array_reverse($vids) as $vid) {
if ($revision = $node_storage->loadRevision($vid)) {
$row = array();
$revision_author = $revision->uid->entity;
if ($vid == $node->getRevisionId()) {
$username = array(
'#theme' => 'username',
'#account' => $revision_author,
);
$row[] = array('data' => $this->t('!date by !username', array('!date' => $node->link($this->dateFormatter->format($revision->revision_timestamp->value, 'short')), '!username' => drupal_render($username)))
. (($revision->revision_log->value != '') ? '<p class="revision-log">' . Xss::filter($revision->revision_log->value) . '</p>' : ''),
'class' => array('revision-current'));
$row[] = array('data' => SafeMarkup::placeholder($this->t('current revision')), 'class' => array('revision-current'));
}
else {
$username = array(
'#theme' => 'username',
'#account' => $revision_author,
);
$row[] = $this->t('!date by !username', array('!date' => $this->l($this->dateFormatter->format($revision->revision_timestamp->value, 'short'), new Url('entity.node.revision', array('node' => $node->id(), 'node_revision' => $vid))), '!username' => drupal_render($username)))
. (($revision->revision_log->value != '') ? '<p class="revision-log">' . Xss::filter($revision->revision_log->value) . '</p>' : '');
if ($revert_permission) {
$links['revert'] = array(
'title' => $this->t('Revert'),
'url' => Url::fromRoute('node.revision_revert_confirm', ['node' => $node->id(), 'node_revision' => $vid]),
);
}
if ($delete_permission) {
$links['delete'] = array(
'title' => $this->t('Delete'),
'url' => Url::fromRoute('node.revision_delete_confirm', ['node' => $node->id(), 'node_revision' => $vid]),
);
}
$row[] = array(
'data' => array(
'#type' => 'operations',
'#links' => $links,
),
);
}
$rows[] = $row;
}
}
$build['node_revisions_table'] = array(
'#theme' => 'table',
'#rows' => $rows,
'#header' => $header,
'#attached' => array(
'library' => array('node/drupal.node.admin'),
),
);
return $build;
}
/**
* The _title_callback for the node.add route.
*
* @param \Drupal\node\NodeTypeInterface $node_type
* The current node.
*
* @return string
* The page title.
*/
public function addPageTitle(NodeTypeInterface $node_type) {
return $this->t('Create @name', array('@name' => $node_type->label()));
}
}

View file

@ -0,0 +1,67 @@
<?php
/**
* @file
* Contains \Drupal\node\Controller\NodePreviewController.
*/
namespace Drupal\node\Controller;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Controller\EntityViewController;
/**
* Defines a controller to render a single node in preview.
*/
class NodePreviewController extends EntityViewController {
/**
* {@inheritdoc}
*/
public function view(EntityInterface $node_preview, $view_mode_id = 'full', $langcode = NULL) {
$node_preview->preview_view_mode = $view_mode_id;
$build = parent::view($node_preview, $view_mode_id);
$build['#attached']['library'][] = 'node/drupal.node.preview';
// Don't render cache previews.
unset($build['#cache']);
foreach ($node_preview->uriRelationships() as $rel) {
// Set the node path as the canonical URL to prevent duplicate content.
$build['#attached']['html_head_link'][] = array(
array(
'rel' => $rel,
'href' => $node_preview->url($rel),
)
, TRUE);
if ($rel == 'canonical') {
// Set the non-aliased canonical path as a default shortlink.
$build['#attached']['html_head_link'][] = array(
array(
'rel' => 'shortlink',
'href' => $node_preview->url($rel, array('alias' => TRUE)),
)
, TRUE);
}
}
return $build;
}
/**
* The _title_callback for the page that renders a single node in preview.
*
* @param \Drupal\Core\Entity\EntityInterface $node_preview
* The current node.
*
* @return string
* The page title.
*/
public function title(EntityInterface $node_preview) {
return SafeMarkup::checkPlain($this->entityManager->getTranslationFromContext($node_preview)->label());
}
}

View file

@ -0,0 +1,63 @@
<?php
/**
* @file
* Contains \Drupal\node\Controller\NodeViewController.
*/
namespace Drupal\node\Controller;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Controller\EntityViewController;
/**
* Defines a controller to render a single node.
*/
class NodeViewController extends EntityViewController {
/**
* {@inheritdoc}
*/
public function view(EntityInterface $node, $view_mode = 'full', $langcode = NULL) {
$build = parent::view($node);
foreach ($node->uriRelationships() as $rel) {
// Set the node path as the canonical URL to prevent duplicate content.
$build['#attached']['html_head_link'][] = array(
array(
'rel' => $rel,
'href' => $node->url($rel),
),
TRUE,
);
if ($rel == 'canonical') {
// Set the non-aliased canonical path as a default shortlink.
$build['#attached']['html_head_link'][] = array(
array(
'rel' => 'shortlink',
'href' => $node->url($rel, array('alias' => TRUE)),
),
TRUE,
);
}
}
return $build;
}
/**
* The _title_callback for the page that renders a single node.
*
* @param \Drupal\Core\Entity\EntityInterface $node
* The current node.
*
* @return string
* The page title.
*/
public function title(EntityInterface $node) {
return SafeMarkup::checkPlain($this->entityManager->getTranslationFromContext($node)->label());
}
}

View file

@ -0,0 +1,519 @@
<?php
/**
* @file
* Contains \Drupal\node\Entity\Node.
*/
namespace Drupal\node\Entity;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\NodeInterface;
use Drupal\user\UserInterface;
/**
* Defines the node entity class.
*
* @ContentEntityType(
* id = "node",
* label = @Translation("Content"),
* bundle_label = @Translation("Content type"),
* handlers = {
* "storage" = "Drupal\node\NodeStorage",
* "storage_schema" = "Drupal\node\NodeStorageSchema",
* "view_builder" = "Drupal\node\NodeViewBuilder",
* "access" = "Drupal\node\NodeAccessControlHandler",
* "views_data" = "Drupal\node\NodeViewsData",
* "form" = {
* "default" = "Drupal\node\NodeForm",
* "delete" = "Drupal\node\Form\NodeDeleteForm",
* "edit" = "Drupal\node\NodeForm"
* },
* "route_provider" = {
* "html" = "Drupal\node\Entity\NodeRouteProvider",
* },
* "list_builder" = "Drupal\node\NodeListBuilder",
* "translation" = "Drupal\node\NodeTranslationHandler"
* },
* base_table = "node",
* data_table = "node_field_data",
* revision_table = "node_revision",
* revision_data_table = "node_field_revision",
* translatable = TRUE,
* list_cache_contexts = { "user.node_grants:view" },
* entity_keys = {
* "id" = "nid",
* "revision" = "vid",
* "bundle" = "type",
* "label" = "title",
* "langcode" = "langcode",
* "uuid" = "uuid"
* },
* bundle_entity_type = "node_type",
* field_ui_base_route = "entity.node_type.edit_form",
* common_reference_target = TRUE,
* permission_granularity = "bundle",
* links = {
* "canonical" = "/node/{node}",
* "delete-form" = "/node/{node}/delete",
* "edit-form" = "/node/{node}/edit",
* "version-history" = "/node/{node}/revisions",
* "revision" = "/node/{node}/revisions/{node_revision}/view",
* }
* )
*/
class Node extends ContentEntityBase implements NodeInterface {
use EntityChangedTrait;
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
parent::preSave($storage);
// If no owner has been set explicitly, make the current user the owner.
if (!$this->getOwner()) {
$this->setOwnerId(\Drupal::currentUser()->id());
}
// If no revision author has been set explicitly, make the node owner the
// revision author.
if (!$this->getRevisionAuthor()) {
$this->setRevisionAuthorId($this->getOwnerId());
}
}
/**
* {@inheritdoc}
*/
public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) {
parent::preSaveRevision($storage, $record);
if (!$this->isNewRevision() && isset($this->original) && (!isset($record->revision_log) || $record->revision_log === '')) {
// If we are updating an existing node without adding a new revision, we
// need to make sure $entity->revision_log is reset whenever it is empty.
// Therefore, this code allows us to avoid clobbering an existing log
// entry with an empty one.
$record->revision_log = $this->original->revision_log->value;
}
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
parent::postSave($storage, $update);
// Update the node access table for this node, but only if it is the
// default revision. There's no need to delete existing records if the node
// is new.
if ($this->isDefaultRevision()) {
\Drupal::entityManager()->getAccessControlHandler('node')->writeGrants($this, $update);
}
// Reindex the node when it is updated. The node is automatically indexed
// when it is added, simply by being added to the node table.
if ($update) {
node_reindex_node_search($this->id());
}
}
/**
* {@inheritdoc}
*/
public static function preDelete(EntityStorageInterface $storage, array $entities) {
parent::preDelete($storage, $entities);
// Ensure that all nodes deleted are removed from the search index.
if (\Drupal::moduleHandler()->moduleExists('search')) {
foreach ($entities as $entity) {
search_index_clear('node_search', $entity->nid->value);
}
}
}
/**
* {@inheritdoc}
*/
public static function postDelete(EntityStorageInterface $storage, array $nodes) {
parent::postDelete($storage, $nodes);
\Drupal::service('node.grant_storage')->deleteNodeRecords(array_keys($nodes));
}
/**
* {@inheritdoc}
*/
public function getType() {
return $this->bundle();
}
/**
* {@inheritdoc}
*/
public function access($operation = 'view', AccountInterface $account = NULL, $return_as_object = FALSE) {
if ($operation == 'create') {
return parent::access($operation, $account, $return_as_object);
}
return \Drupal::entityManager()
->getAccessControlHandler($this->entityTypeId)
->access($this, $operation, $this->prepareLangcode(), $account, $return_as_object);
}
/**
* {@inheritdoc}
*/
public function prepareLangcode() {
$langcode = $this->language()->getId();
// If the Language module is enabled, try to use the language from content
// negotiation.
if (\Drupal::moduleHandler()->moduleExists('language')) {
// Load languages the node exists in.
$node_translations = $this->getTranslationLanguages();
// Load the language from content negotiation.
$content_negotiation_langcode = \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
// If there is a translation available, use it.
if (isset($node_translations[$content_negotiation_langcode])) {
$langcode = $content_negotiation_langcode;
}
}
return $langcode;
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->get('title')->value;
}
/**
* {@inheritdoc}
*/
public function setTitle($title) {
$this->set('title', $title);
return $this;
}
/**
* {@inheritdoc}
*/
public function getCreatedTime() {
return $this->get('created')->value;
}
/**
* {@inheritdoc}
*/
public function setCreatedTime($timestamp) {
$this->set('created', $timestamp);
return $this;
}
/**
* {@inheritdoc}
*/
public function getChangedTime() {
return $this->get('changed')->value;
}
/**
* {@inheritdoc}
*/
public function isPromoted() {
return (bool) $this->get('promote')->value;
}
/**
* {@inheritdoc}
*/
public function setPromoted($promoted) {
$this->set('promote', $promoted ? NODE_PROMOTED : NODE_NOT_PROMOTED);
return $this;
}
/**
* {@inheritdoc}
*/
public function isSticky() {
return (bool) $this->get('sticky')->value;
}
/**
* {@inheritdoc}
*/
public function setSticky($sticky) {
$this->set('sticky', $sticky ? NODE_STICKY : NODE_NOT_STICKY);
return $this;
}
/**
* {@inheritdoc}
*/
public function isPublished() {
return (bool) $this->get('status')->value;
}
/**
* {@inheritdoc}
*/
public function setPublished($published) {
$this->set('status', $published ? NODE_PUBLISHED : NODE_NOT_PUBLISHED);
return $this;
}
/**
* {@inheritdoc}
*/
public function getOwner() {
return $this->get('uid')->entity;
}
/**
* {@inheritdoc}
*/
public function getOwnerId() {
return $this->get('uid')->target_id;
}
/**
* {@inheritdoc}
*/
public function setOwnerId($uid) {
$this->set('uid', $uid);
return $this;
}
/**
* {@inheritdoc}
*/
public function setOwner(UserInterface $account) {
$this->set('uid', $account->id());
return $this;
}
/**
* {@inheritdoc}
*/
public function getRevisionCreationTime() {
return $this->get('revision_timestamp')->value;
}
/**
* {@inheritdoc}
*/
public function setRevisionCreationTime($timestamp) {
$this->set('revision_timestamp', $timestamp);
return $this;
}
/**
* {@inheritdoc}
*/
public function getRevisionAuthor() {
return $this->get('revision_uid')->entity;
}
/**
* {@inheritdoc}
*/
public function setRevisionAuthorId($uid) {
$this->set('revision_uid', $uid);
return $this;
}
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['nid'] = BaseFieldDefinition::create('integer')
->setLabel(t('Node ID'))
->setDescription(t('The node ID.'))
->setReadOnly(TRUE)
->setSetting('unsigned', TRUE);
$fields['uuid'] = BaseFieldDefinition::create('uuid')
->setLabel(t('UUID'))
->setDescription(t('The node UUID.'))
->setReadOnly(TRUE);
$fields['vid'] = BaseFieldDefinition::create('integer')
->setLabel(t('Revision ID'))
->setDescription(t('The node revision ID.'))
->setReadOnly(TRUE)
->setSetting('unsigned', TRUE);
$fields['type'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Type'))
->setDescription(t('The node type.'))
->setSetting('target_type', 'node_type')
->setReadOnly(TRUE);
$fields['langcode'] = BaseFieldDefinition::create('language')
->setLabel(t('Language'))
->setDescription(t('The node language code.'))
->setTranslatable(TRUE)
->setRevisionable(TRUE)
->setDisplayOptions('view', array(
'type' => 'hidden',
))
->setDisplayOptions('form', array(
'type' => 'language_select',
'weight' => 2,
));
$fields['title'] = BaseFieldDefinition::create('string')
->setLabel(t('Title'))
->setRequired(TRUE)
->setTranslatable(TRUE)
->setRevisionable(TRUE)
->setDefaultValue('')
->setSetting('max_length', 255)
->setDisplayOptions('view', array(
'label' => 'hidden',
'type' => 'string',
'weight' => -5,
))
->setDisplayOptions('form', array(
'type' => 'string_textfield',
'weight' => -5,
))
->setDisplayConfigurable('form', TRUE);
$fields['uid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Authored by'))
->setDescription(t('The username of the content author.'))
->setRevisionable(TRUE)
->setSetting('target_type', 'user')
->setSetting('handler', 'default')
->setDefaultValueCallback('Drupal\node\Entity\Node::getCurrentUserId')
->setTranslatable(TRUE)
->setDisplayOptions('view', array(
'label' => 'hidden',
'type' => 'author',
'weight' => 0,
))
->setDisplayOptions('form', array(
'type' => 'entity_reference_autocomplete',
'weight' => 5,
'settings' => array(
'match_operator' => 'CONTAINS',
'size' => '60',
'placeholder' => '',
),
))
->setDisplayConfigurable('form', TRUE);
$fields['status'] = BaseFieldDefinition::create('boolean')
->setLabel(t('Publishing status'))
->setDescription(t('A boolean indicating whether the node is published.'))
->setRevisionable(TRUE)
->setTranslatable(TRUE)
->setDefaultValue(TRUE);
$fields['created'] = BaseFieldDefinition::create('created')
->setLabel(t('Authored on'))
->setDescription(t('The time that the node was created.'))
->setRevisionable(TRUE)
->setTranslatable(TRUE)
->setDisplayOptions('view', array(
'label' => 'hidden',
'type' => 'timestamp',
'weight' => 0,
))
->setDisplayOptions('form', array(
'type' => 'datetime_timestamp',
'weight' => 10,
))
->setDisplayConfigurable('form', TRUE);
$fields['changed'] = BaseFieldDefinition::create('changed')
->setLabel(t('Changed'))
->setDescription(t('The time that the node was last edited.'))
->setRevisionable(TRUE)
->setTranslatable(TRUE);
$fields['promote'] = BaseFieldDefinition::create('boolean')
->setLabel(t('Promoted to front page'))
->setRevisionable(TRUE)
->setTranslatable(TRUE)
->setDefaultValue(TRUE)
->setDisplayOptions('form', array(
'type' => 'boolean_checkbox',
'settings' => array(
'display_label' => TRUE,
),
'weight' => 15,
))
->setDisplayConfigurable('form', TRUE);
$fields['sticky'] = BaseFieldDefinition::create('boolean')
->setLabel(t('Sticky at top of lists'))
->setRevisionable(TRUE)
->setTranslatable(TRUE)
->setDefaultValue(FALSE)
->setDisplayOptions('form', array(
'type' => 'boolean_checkbox',
'settings' => array(
'display_label' => TRUE,
),
'weight' => 16,
))
->setDisplayConfigurable('form', TRUE);
$fields['revision_timestamp'] = BaseFieldDefinition::create('created')
->setLabel(t('Revision timestamp'))
->setDescription(t('The time that the current revision was created.'))
->setQueryable(FALSE)
->setRevisionable(TRUE);
$fields['revision_uid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Revision user ID'))
->setDescription(t('The user ID of the author of the current revision.'))
->setSetting('target_type', 'user')
->setQueryable(FALSE)
->setRevisionable(TRUE);
$fields['revision_log'] = BaseFieldDefinition::create('string_long')
->setLabel(t('Revision log message'))
->setDescription(t('Briefly describe the changes you have made.'))
->setRevisionable(TRUE)
->setTranslatable(TRUE)
->setDisplayOptions('form', array(
'type' => 'string_textarea',
'weight' => 25,
'settings' => array(
'rows' => 4,
),
));
$fields['revision_translation_affected'] = BaseFieldDefinition::create('boolean')
->setLabel(t('Revision translation affected'))
->setDescription(t('Indicates if the last edit of a translation belongs to current revision.'))
->setReadOnly(TRUE)
->setRevisionable(TRUE)
->setTranslatable(TRUE);
return $fields;
}
/**
* Default value callback for 'uid' base field definition.
*
* @see ::baseFieldDefinitions()
*
* @return array
* An array of default values.
*/
public static function getCurrentUserId() {
return array(\Drupal::currentUser()->id());
}
}

View file

@ -0,0 +1,51 @@
<?php
/**
* @file
* Contains \Drupal\node\Entity\NodeRouteProvider.
*/
namespace Drupal\node\Entity;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Provides routes for nodes.
*/
class NodeRouteProvider implements EntityRouteProviderInterface {
/**
* {@inheritdoc}
*/
public function getRoutes( EntityTypeInterface $entity_type) {
$route_collection = new RouteCollection();
$route = (new Route('/node/{node}'))
->addDefaults([
'_controller' => '\Drupal\node\Controller\NodeViewController::view',
'_title_callback' => '\Drupal\node\Controller\NodeViewController::title',
])
->setRequirement('_entity_access', 'node.view');
$route_collection->add('entity.node.canonical', $route);
$route = (new Route('/node/{node}/delete'))
->addDefaults([
'_entity_form' => 'node.delete',
'_title' => 'Delete',
])
->setRequirement('_entity_access', 'node.delete')
->setOption('_node_operation_route', TRUE);
$route_collection->add('entity.node.delete_form', $route);
$route = (new Route('/node/{node}/edit'))
->setDefault('_entity_form', 'node.edit')
->setRequirement('_entity_access', 'node.update')
->setOption('_node_operation_route', TRUE);
$route_collection->add('entity.node.edit_form', $route);
return $route_collection;
}
}

View file

@ -0,0 +1,213 @@
<?php
/**
* @file
* Contains \Drupal\node\Entity\NodeType.
*/
namespace Drupal\node\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\node\NodeTypeInterface;
/**
* Defines the Node type configuration entity.
*
* @ConfigEntityType(
* id = "node_type",
* label = @Translation("Content type"),
* handlers = {
* "access" = "Drupal\node\NodeTypeAccessControlHandler",
* "form" = {
* "add" = "Drupal\node\NodeTypeForm",
* "edit" = "Drupal\node\NodeTypeForm",
* "delete" = "Drupal\node\Form\NodeTypeDeleteConfirm"
* },
* "list_builder" = "Drupal\node\NodeTypeListBuilder",
* },
* admin_permission = "administer content types",
* config_prefix = "type",
* bundle_of = "node",
* entity_keys = {
* "id" = "type",
* "label" = "name"
* },
* links = {
* "edit-form" = "/admin/structure/types/manage/{node_type}",
* "delete-form" = "/admin/structure/types/manage/{node_type}/delete",
* "collection" = "/admin/structure/types",
* },
* config_export = {
* "name",
* "type",
* "description",
* "help",
* "new_revision",
* "preview_mode",
* "display_submitted",
* }
* )
*/
class NodeType extends ConfigEntityBundleBase implements NodeTypeInterface {
/**
* The machine name of this node type.
*
* @var string
*
* @todo Rename to $id.
*/
protected $type;
/**
* The human-readable name of the node type.
*
* @var string
*
* @todo Rename to $label.
*/
protected $name;
/**
* A brief description of this node type.
*
* @var string
*/
protected $description;
/**
* Help information shown to the user when creating a Node of this type.
*
* @var string
*/
protected $help;
/**
* Default value of the 'Create new revision' checkbox of this node type.
*
* @var bool
*/
protected $new_revision = FALSE;
/**
* The preview mode.
*
* @var int
*/
protected $preview_mode = DRUPAL_OPTIONAL;
/**
* Display setting for author and date Submitted by post information.
*
* @var bool
*/
protected $display_submitted = TRUE;
/**
* {@inheritdoc}
*/
public function id() {
return $this->type;
}
/**
* {@inheritdoc}
*/
public function isLocked() {
$locked = \Drupal::state()->get('node.type.locked');
return isset($locked[$this->id()]) ? $locked[$this->id()] : FALSE;
}
/**
* {@inheritdoc}
*/
public function isNewRevision() {
return $this->new_revision;
}
/**
* {@inheritdoc}
*/
public function setNewRevision($new_revision) {
$this->new_revision = $new_revision;
}
/**
* {@inheritdoc}
*/
public function displaySubmitted() {
return $this->display_submitted;
}
/**
* {@inheritdoc}
*/
public function setDisplaySubmitted($display_submitted) {
$this->display_submitted = $display_submitted;
}
/**
* {@inheritdoc}
*/
public function getPreviewMode() {
return $this->preview_mode;
}
/**
* {@inheritdoc}
*/
public function setPreviewMode($preview_mode) {
$this->preview_mode = $preview_mode;
}
/**
* {@inheritdoc}
*/
public function getHelp() {
return $this->help;
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->description;
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
parent::postSave($storage, $update);
if ($update && $this->getOriginalId() != $this->id()) {
$update_count = node_type_update_nodes($this->getOriginalId(), $this->id());
if ($update_count) {
drupal_set_message(\Drupal::translation()->formatPlural($update_count,
'Changed the content type of 1 post from %old-type to %type.',
'Changed the content type of @count posts from %old-type to %type.',
array(
'%old-type' => $this->getOriginalId(),
'%type' => $this->id(),
)));
}
}
if ($update) {
// Clear the cached field definitions as some settings affect the field
// definitions.
$this->entityManager()->clearCachedFieldDefinitions();
}
}
/**
* {@inheritdoc}
*/
public static function postDelete(EntityStorageInterface $storage, array $entities) {
parent::postDelete($storage, $entities);
// Clear the node type cache to reflect the removal.
$storage->resetCache(array_keys($entities));
}
}

View file

@ -0,0 +1,49 @@
<?php
/**
* @file
* Contains \Drupal\node\EventSubscriber\NodeAdminRouteSubscriber.
*/
namespace Drupal\node\EventSubscriber;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;
/**
* Sets the _admin_route for specific node-related routes.
*/
class NodeAdminRouteSubscriber extends RouteSubscriberBase {
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* Constructs a new NodeAdminRouteSubscriber.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
*/
public function __construct(ConfigFactoryInterface $config_factory) {
$this->configFactory = $config_factory;
}
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
if ($this->configFactory->get('node.settings')->get('use_admin_theme')) {
foreach ($collection->all() as $route) {
if ($route->hasOption('_node_operation_route')) {
$route->setOption('_admin_route', TRUE);
}
}
}
}
}

View file

@ -0,0 +1,204 @@
<?php
/**
* @file
* Contains \Drupal\node\Form\DeleteMultiple.
*/
namespace Drupal\node\Form;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\user\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Provides a node deletion confirmation form.
*/
class DeleteMultiple extends ConfirmFormBase {
/**
* The array of nodes to delete.
*
* @var string[][]
*/
protected $nodeInfo = array();
/**
* The tempstore factory.
*
* @var \Drupal\user\PrivateTempStoreFactory
*/
protected $tempStoreFactory;
/**
* The node storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $manager;
/**
* Constructs a DeleteMultiple form object.
*
* @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
* @param \Drupal\Core\Entity\EntityManagerInterface $manager
* The entity manager.
*/
public function __construct(PrivateTempStoreFactory $temp_store_factory, EntityManagerInterface $manager) {
$this->tempStoreFactory = $temp_store_factory;
$this->storage = $manager->getStorage('node');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('user.private_tempstore'),
$container->get('entity.manager')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'node_multiple_delete_confirm';
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->formatPlural(count($this->nodeInfo), 'Are you sure you want to delete this item?', 'Are you sure you want to delete these items?');
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('system.admin_content');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return t('Delete');
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$this->nodeInfo = $this->tempStoreFactory->get('node_multiple_delete_confirm')->get(\Drupal::currentUser()->id());
if (empty($this->nodeInfo)) {
return new RedirectResponse($this->getCancelUrl()->setAbsolute()->toString());
}
/** @var \Drupal\node\NodeInterface[] $nodes */
$nodes = $this->storage->loadMultiple(array_keys($this->nodeInfo));
$items = [];
foreach ($this->nodeInfo as $id => $langcodes) {
foreach ($langcodes as $langcode) {
$node = $nodes[$id]->getTranslation($langcode);
$key = $id . ':' . $langcode;
$default_key = $id . ':' . $node->getUntranslated()->language()->getId();
// If we have a translated entity we build a nested list of translations
// that will be deleted.
$languages = $node->getTranslationLanguages();
if (count($languages) > 1 && $node->isDefaultTranslation()) {
$names = [];
foreach ($languages as $translation_langcode => $language) {
$names[] = $language->getName();
unset($items[$id . ':' . $translation_langcode]);
}
$items[$default_key] = [
'label' => [
'#markup' => $this->t('@label (Original translation) - <em>The following content translations will be deleted:</em>', ['@label' => $node->label()]),
],
'deleted_translations' => [
'#theme' => 'item_list',
'#items' => $names,
],
];
}
elseif (!isset($items[$default_key])) {
$items[$key] = $node->label();
}
}
}
$form['nodes'] = array(
'#theme' => 'item_list',
'#items' => $items,
);
$form = parent::buildForm($form, $form_state);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
if ($form_state->getValue('confirm') && !empty($this->nodeInfo)) {
$total_count = 0;
$delete_nodes = [];
/** @var \Drupal\Core\Entity\ContentEntityInterface[][] $delete_translations */
$delete_translations = [];
/** @var \Drupal\node\NodeInterface[] $nodes */
$nodes = $this->storage->loadMultiple(array_keys($this->nodeInfo));
foreach ($this->nodeInfo as $id => $langcodes) {
foreach ($langcodes as $langcode) {
$node = $nodes[$id]->getTranslation($langcode);
if ($node->isDefaultTranslation()) {
$delete_nodes[$id] = $node;
unset($delete_translations[$id]);
$total_count += count($node->getTranslationLanguages());
}
elseif (!isset($delete_nodes[$id])) {
$delete_translations[$id][] = $node;
}
}
}
if ($delete_nodes) {
$this->storage->delete($delete_nodes);
$this->logger('content')->notice('Deleted @count posts.', array('@count' => count($delete_nodes)));
}
if ($delete_translations) {
$count = 0;
foreach ($delete_translations as $id => $translations) {
$node = $nodes[$id]->getUntranslated();
foreach ($translations as $translation) {
$node->removeTranslation($translation->language()->getId());
}
$node->save();
$count += count($translations);
}
if ($count) {
$total_count += $count;
$this->logger('content')->notice('Deleted @count content translations.', array('@count' => $count));
}
}
if ($total_count) {
drupal_set_message($this->formatPlural($total_count, 'Deleted 1 post.', 'Deleted @count posts.'));
}
$this->tempStoreFactory->get('node_multiple_delete_confirm')->delete(\Drupal::currentUser()->id());
}
$form_state->setRedirect('system.admin_content');
}
}

View file

@ -0,0 +1,50 @@
<?php
/**
* @file
* Contains \Drupal\node\Form\NodeDeleteForm.
*/
namespace Drupal\node\Form;
use Drupal\Core\Entity\ContentEntityDeleteForm;
/**
* Provides a form for deleting a node.
*/
class NodeDeleteForm extends ContentEntityDeleteForm {
/**
* {@inheritdoc}
*/
protected function getDeletionMessage() {
/** @var \Drupal\node\NodeInterface $entity */
$entity = $this->getEntity();
$node_type_storage = $this->entityManager->getStorage('node_type');
$node_type = $node_type_storage->load($entity->bundle())->label();
if (!$entity->isDefaultTranslation()) {
return $this->t('@language translation of the @type %label has been deleted.', [
'@language' => $entity->language()->getName(),
'@type' => $node_type,
'%label' => $entity->label(),
]);
}
return $this->t('The @type %title has been deleted.', array(
'@type' => $node_type,
'%title' => $this->getEntity()->label(),
));
}
/**
* {@inheritdoc}
*/
protected function logDeletionMessage() {
/** @var \Drupal\node\NodeInterface $entity */
$entity = $this->getEntity();
$this->logger('content')->notice('@type: deleted %title.', ['@type' => $entity->getType(), '%title' => $entity->label()]);
}
}

View file

@ -0,0 +1,169 @@
<?php
/**
* @file
* Contains \Drupal\node\Form\NodePreviewForm.
*/
namespace Drupal\node\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Contains a form for switching the view mode of a node during preview.
*/
class NodePreviewForm extends FormBase implements ContainerInjectionInterface {
/**
* The entity manager service.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('entity.manager'), $container->get('config.factory'));
}
/**
* Constructs a new NodePreviewForm.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
*/
public function __construct(EntityManagerInterface $entity_manager, ConfigFactoryInterface $config_factory) {
$this->entityManager = $entity_manager;
$this->configFactory = $config_factory;
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'node_preview_form_select';
}
/**
* 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 submitForm(array &$form, FormStateInterface $form_state) {
$form_state->setRedirect('entity.node.preview', array(
'node_preview' => $form_state->getValue('uuid'),
'view_mode_id' => $form_state->getValue('view_mode'),
));
}
/**
* Gets the list of available view modes for the current node.
*
* @param EntityInterface $node
* The node being previewed.
*
* @return array
* List of available view modes for the current node.
*/
protected function getViewModeOptions(EntityInterface $node) {
$load_ids = array();
$view_mode_options = array();
// Load all the node's view modes.
$view_modes = $this->entityManager->getViewModes('node');
// Get the list of available view modes for the current node's bundle.
$ids = $this->configFactory->listAll('core.entity_view_display.node.' . $node->bundle());
foreach ($ids as $id) {
$config_id = str_replace('core.entity_view_display' . '.', '', $id);
$load_ids[] = $config_id;
}
$displays = entity_load_multiple('entity_view_display', $load_ids);
// Generate the display options array.
foreach ($displays as $display) {
$view_mode_name = $display->get('mode');
// Skip view modes that are not used in the front end.
if (in_array($view_mode_name, array('rss', 'search_index'))) {
continue;
}
if ($display->status()) {
$view_mode_options[$view_mode_name] = ($view_mode_name == 'default') ? t('Default') : $view_modes[$view_mode_name]['label'];
}
}
return $view_mode_options;
}
}

View file

@ -0,0 +1,138 @@
<?php
/**
* @file
* Contains \Drupal\node\Form\NodeRevisionDeleteForm.
*/
namespace Drupal\node\Form;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\node\NodeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a form for reverting a node revision.
*/
class NodeRevisionDeleteForm extends ConfirmFormBase {
/**
* The node revision.
*
* @var \Drupal\node\NodeInterface
*/
protected $revision;
/**
* The node storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $nodeStorage;
/**
* The node type storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $nodeTypeStorage;
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* Constructs a new NodeRevisionDeleteForm.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $node_storage
* The node storage.
* @param \Drupal\Core\Entity\EntityStorageInterface $node_type_storage
* The node type storage.
* @param \Drupal\Core\Database\Connection $connection
* The database connection.
*/
public function __construct(EntityStorageInterface $node_storage, EntityStorageInterface $node_type_storage, Connection $connection) {
$this->nodeStorage = $node_storage;
$this->nodeTypeStorage = $node_type_storage;
$this->connection = $connection;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$entity_manager = $container->get('entity.manager');
return new static(
$entity_manager->getStorage('node'),
$entity_manager->getStorage('node_type'),
$container->get('database')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'node_revision_delete_confirm';
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return t('Are you sure you want to delete the revision from %revision-date?', array('%revision-date' => format_date($this->revision->getRevisionCreationTime())));
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('entity.node.version_history', array('node' => $this->revision->id()));
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return t('Delete');
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $node_revision = NULL) {
$this->revision = $this->nodeStorage->loadRevision($node_revision);
$form = parent::buildForm($form, $form_state);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->nodeStorage->deleteRevision($this->revision->getRevisionId());
$this->logger('content')->notice('@type: deleted %title revision %revision.', array('@type' => $this->revision->bundle(), '%title' => $this->revision->label(), '%revision' => $this->revision->getRevisionId()));
$node_type = $this->nodeTypeStorage->load($this->revision->bundle())->label();
drupal_set_message(t('Revision from %revision-date of @type %title has been deleted.', array('%revision-date' => format_date($this->revision->getRevisionCreationTime()), '@type' => $node_type, '%title' => $this->revision->label())));
$form_state->setRedirect(
'entity.node.canonical',
array('node' => $this->revision->id())
);
if ($this->connection->query('SELECT COUNT(DISTINCT vid) FROM {node_field_revision} WHERE nid = :nid', array(':nid' => $this->revision->id()))->fetchField() > 1) {
$form_state->setRedirect(
'entity.node.version_history',
array('node' => $this->revision->id())
);
}
}
}

View file

@ -0,0 +1,160 @@
<?php
/**
* @file
* Contains \Drupal\node\Form\NodeRevisionRevertForm.
*/
namespace Drupal\node\Form;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\node\NodeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a form for reverting a node revision.
*/
class NodeRevisionRevertForm extends ConfirmFormBase {
/**
* The node revision.
*
* @var \Drupal\node\NodeInterface
*/
protected $revision;
/**
* The node storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $nodeStorage;
/**
* Constructs a new NodeRevisionRevertForm.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $node_storage
* The node storage.
*/
public function __construct(EntityStorageInterface $node_storage) {
$this->nodeStorage = $node_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager')->getStorage('node')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'node_revision_revert_confirm';
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return t('Are you sure you want to revert to the revision from %revision-date?', array('%revision-date' => format_date($this->revision->getRevisionCreationTime())));
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('entity.node.version_history', array('node' => $this->revision->id()));
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return t('Revert');
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return '';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $node_revision = NULL) {
$this->revision = $this->nodeStorage->loadRevision($node_revision);
$form = parent::buildForm($form, $form_state);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$revision = $this->prepareRevertedRevision($this->revision);
// The revision timestamp will be updated when the revision is saved. Keep the
// original one for the confirmation message.
$original_revision_timestamp = $revision->getRevisionCreationTime();
$revision->revision_log = t('Copy of the revision from %date.', array('%date' => format_date($original_revision_timestamp)));
$revision->save();
$this->logger('content')->notice('@type: reverted %title revision %revision.', array('@type' => $this->revision->bundle(), '%title' => $this->revision->label(), '%revision' => $this->revision->getRevisionId()));
drupal_set_message(t('@type %title has been reverted to the revision from %revision-date.', array('@type' => node_get_type_label($this->revision), '%title' => $this->revision->label(), '%revision-date' => format_date($original_revision_timestamp))));
$form_state->setRedirect(
'entity.node.version_history',
array('node' => $this->revision->id())
);
}
/**
* Prepares a revision to be reverted.
*
* @param \Drupal\node\NodeInterface $revision
* The revision to be reverted.
*
* @return \Drupal\node\NodeInterface
* The prepared revision ready to be stored.
*/
protected function prepareRevertedRevision(NodeInterface $revision) {
/** @var \Drupal\node\NodeInterface $default_revision */
$default_revision = $this->nodeStorage->load($revision->id());
// If the entity is translated, make sure only translations affected by the
// specified revision are reverted.
$languages = $default_revision->getTranslationLanguages();
if (count($languages) > 1) {
// @todo Instead of processing all the available translations, we should
// let the user decide which translations should be reverted. See
// https://www.drupal.org/node/2465907.
foreach ($languages as $langcode => $language) {
if ($revision->hasTranslation($langcode) && !$revision->getTranslation($langcode)->isRevisionTranslationAffected()) {
$revision_translation = $revision->getTranslation($langcode);
$default_translation = $default_revision->getTranslation($langcode);
foreach ($default_revision->getFieldDefinitions() as $field_name => $definition) {
if ($definition->isTranslatable()) {
$revision_translation->set($field_name, $default_translation->get($field_name)->getValue());
}
}
}
}
}
$revision->setNewRevision();
$revision->isDefaultRevision(TRUE);
return $revision;
}
}

View file

@ -0,0 +1,64 @@
<?php
/**
* @file
* Contains \Drupal\node\Form\NodeTypeDeleteConfirm.
*/
namespace Drupal\node\Form;
use Drupal\Core\Entity\Query\QueryFactory;
use Drupal\Core\Entity\EntityDeleteForm;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a form for content type deletion.
*/
class NodeTypeDeleteConfirm extends EntityDeleteForm {
/**
* The query factory to create entity queries.
*
* @var \Drupal\Core\Entity\Query\QueryFactory
*/
protected $queryFactory;
/**
* Constructs a new NodeTypeDeleteConfirm object.
*
* @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
* The entity query object.
*/
public function __construct(QueryFactory $query_factory) {
$this->queryFactory = $query_factory;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.query')
);
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$num_nodes = $this->queryFactory->get('node')
->condition('type', $this->entity->id())
->count()
->execute();
if ($num_nodes) {
$caption = '<p>' . $this->formatPlural($num_nodes, '%type is used by 1 piece of content on your site. You can not remove this content type until you have removed all of the %type content.', '%type is used by @count pieces of content on your site. You may not remove %type until you have removed all of the %type content.', array('%type' => $this->entity->label())) . '</p>';
$form['#title'] = $this->getQuestion();
$form['description'] = array('#markup' => $caption);
return $form;
}
return parent::buildForm($form, $form_state);
}
}

View file

@ -0,0 +1,59 @@
<?php
/**
* @file
* Contains \Drupal\node\Form\RebuildPermissionsForm.
*/
namespace Drupal\node\Form;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
class RebuildPermissionsForm extends ConfirmFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'node_configure_rebuild_confirm';
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return t('Are you sure you want to rebuild the permissions on site content?');
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('system.status');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return t('Rebuild permissions');
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return t('This action rebuilds all permissions on site content, and may be a lengthy process. This action cannot be undone.');
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
node_access_rebuild(TRUE);
$form_state->setRedirectUrl($this->getCancelUrl());
}
}

View file

@ -0,0 +1,202 @@
<?php
/**
* @file
* Contains \Drupal\node\NodeAccessControlHandler.
*/
namespace Drupal\node;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityHandlerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the access control handler for the node entity type.
*
* @see \Drupal\node\Entity\Node
* @ingroup node_access
*/
class NodeAccessControlHandler extends EntityAccessControlHandler implements NodeAccessControlHandlerInterface, EntityHandlerInterface {
/**
* The node grant storage.
*
* @var \Drupal\node\NodeGrantDatabaseStorageInterface
*/
protected $grantStorage;
/**
* Constructs a NodeAccessControlHandler object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\node\NodeGrantDatabaseStorageInterface $grant_storage
* The node grant storage.
*/
public function __construct(EntityTypeInterface $entity_type, NodeGrantDatabaseStorageInterface $grant_storage) {
parent::__construct($entity_type);
$this->grantStorage = $grant_storage;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('node.grant_storage')
);
}
/**
* {@inheritdoc}
*/
public function access(EntityInterface $entity, $operation, $langcode = LanguageInterface::LANGCODE_DEFAULT, AccountInterface $account = NULL, $return_as_object = FALSE) {
$account = $this->prepareUser($account);
if ($account->hasPermission('bypass node access')) {
$result = AccessResult::allowed()->cachePerPermissions();
return $return_as_object ? $result : $result->isAllowed();
}
if (!$account->hasPermission('access content')) {
$result = AccessResult::forbidden()->cachePerPermissions();
return $return_as_object ? $result : $result->isAllowed();
}
$result = parent::access($entity, $operation, $langcode, $account, TRUE)->cachePerPermissions();
return $return_as_object ? $result : $result->isAllowed();
}
/**
* {@inheritdoc}
*/
public function createAccess($entity_bundle = NULL, AccountInterface $account = NULL, array $context = array(), $return_as_object = FALSE) {
$account = $this->prepareUser($account);
if ($account->hasPermission('bypass node access')) {
$result = AccessResult::allowed()->cachePerPermissions();
return $return_as_object ? $result : $result->isAllowed();
}
if (!$account->hasPermission('access content')) {
$result = AccessResult::forbidden()->cachePerPermissions();
return $return_as_object ? $result : $result->isAllowed();
}
$result = parent::createAccess($entity_bundle, $account, $context, TRUE)->cachePerPermissions();
return $return_as_object ? $result : $result->isAllowed();
}
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $node, $operation, $langcode, AccountInterface $account) {
/** @var \Drupal\node\NodeInterface $node */
/** @var \Drupal\node\NodeInterface $translation */
$translation = $node->getTranslation($langcode);
// Fetch information from the node object if possible.
$status = $translation->isPublished();
$uid = $translation->getOwnerId();
// Check if authors can view their own unpublished nodes.
if ($operation === 'view' && !$status && $account->hasPermission('view own unpublished content') && $account->isAuthenticated() && $account->id() == $uid) {
return AccessResult::allowed()->cachePerPermissions()->cachePerUser()->cacheUntilEntityChanges($node);
}
// Evaluate node grants.
return $this->grantStorage->access($node, $operation, $langcode, $account);
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return AccessResult::allowedIf($account->hasPermission('create ' . $entity_bundle . ' content'))->cachePerPermissions();
}
/**
* {@inheritdoc}
*/
protected function checkFieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
// Only users with the administer nodes permission can edit administrative
// fields.
$administrative_fields = array('uid', 'status', 'created', 'promote', 'sticky');
if ($operation == 'edit' && in_array($field_definition->getName(), $administrative_fields, TRUE)) {
return AccessResult::allowedIfHasPermission($account, 'administer nodes');
}
// No user can change read only fields.
$read_only_fields = array('revision_timestamp', 'revision_uid');
if ($operation == 'edit' && in_array($field_definition->getName(), $read_only_fields, TRUE)) {
return AccessResult::forbidden();
}
// Users have access to the revision_log field either if they have
// administrative permissions or if the new revision option is enabled.
if ($operation == 'edit' && $field_definition->getName() == 'revision_log') {
if ($account->hasPermission('administer nodes')) {
return AccessResult::allowed()->cachePerPermissions();
}
return AccessResult::allowedIf($items->getEntity()->type->entity->isNewRevision())->cachePerPermissions();
}
return parent::checkFieldAccess($operation, $field_definition, $account, $items);
}
/**
* {@inheritdoc}
*/
public function acquireGrants(NodeInterface $node) {
$grants = $this->moduleHandler->invokeAll('node_access_records', array($node));
// Let modules alter the grants.
$this->moduleHandler->alter('node_access_records', $grants, $node);
// If no grants are set and the node is published, then use the default grant.
if (empty($grants) && $node->isPublished()) {
$grants[] = array('realm' => 'all', 'gid' => 0, 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0);
}
return $grants;
}
/**
* {@inheritdoc}
*/
public function writeGrants(NodeInterface $node, $delete = TRUE) {
$grants = $this->acquireGrants($node);
$this->grantStorage->write($node, $grants, NULL, $delete);
}
/**
* {@inheritdoc}
*/
public function writeDefaultGrant() {
$this->grantStorage->writeDefault();
}
/**
* {@inheritdoc}
*/
public function deleteGrants() {
$this->grantStorage->delete();
}
/**
* {@inheritdoc}
*/
public function countGrants() {
return $this->grantStorage->count();
}
/**
* {@inheritdoc}
*/
public function checkAllGrants(AccountInterface $account) {
return $this->grantStorage->checkAll($account);
}
}

View file

@ -0,0 +1,92 @@
<?php
/**
* @file
* Contains \Drupal\node\NodeAccessControlHandlerInterface.
*/
namespace Drupal\node;
use Drupal\Core\Session\AccountInterface;
/**
* Node specific entity access control methods.
*
* @ingroup node_access
*/
interface NodeAccessControlHandlerInterface {
/**
* Gets the list of node access grants.
*
* This function is called to check the access grants for a node. It collects
* all node access grants for the node from hook_node_access_records()
* implementations, allows these grants to be altered via
* hook_node_access_records_alter() implementations, and returns the grants to
* the caller.
*
* @param \Drupal\node\NodeInterface $node
* The $node to acquire grants for.
*
* @return array $grants
* The access rules for the node.
*/
public function acquireGrants(NodeInterface $node);
/**
* Writes a list of grants to the database, deleting any previously saved ones.
*
* If a realm is provided, it will only delete grants from that realm, but it
* will always delete a grant from the 'all' realm. Modules that use node
* access can use this function when doing mass updates due to widespread
* permission changes.
*
* Note: Don't call this function directly from a contributed module. Call
* node_access_acquire_grants() instead.
*
* @param \Drupal\node\NodeInterface $node
* The node whose grants are being written.
* @param $grants
* A list of grants to write. See hook_node_access_records() for the
* expected structure of the grants array.
* @param $realm
* (optional) If provided, read/write grants for that realm only. Defaults to
* NULL.
* @param $delete
* (optional) If false, does not delete records. This is only for optimization
* purposes, and assumes the caller has already performed a mass delete of
* some form. Defaults to TRUE.
*/
public function writeGrants(NodeInterface $node, $delete = TRUE);
/**
* Creates the default node access grant entry on the grant storage.
*/
public function writeDefaultGrant();
/**
* Deletes all node access entries.
*/
public function deleteGrants();
/**
* Counts available node grants.
*
* @return int
* Returns the amount of node grants.
*/
public function countGrants();
/**
* Checks all grants for a given account.
*
* @param \Drupal\Core\Session\AccountInterface $account
* A user object representing the user for whom the operation is to be
* performed.
*
* @return int.
* Status of the access check.
*/
public function checkAllGrants(AccountInterface $account);
}

View file

@ -0,0 +1,421 @@
<?php
/**
* @file
* Contains \Drupal\node\NodeForm.
*/
namespace Drupal\node;
use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\user\Entity\User;
use Drupal\user\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form controller for the node edit forms.
*/
class NodeForm extends ContentEntityForm {
/**
* The tempstore factory.
*
* @var \Drupal\user\PrivateTempStoreFactory
*/
protected $tempStoreFactory;
/**
* Whether this node has been previewed or not.
*/
protected $hasBeenPreviewed = FALSE;
/**
* Constructs a ContentEntityForm object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
* The factory for the temp store object.
*/
public function __construct(EntityManagerInterface $entity_manager, PrivateTempStoreFactory $temp_store_factory) {
parent::__construct($entity_manager);
$this->tempStoreFactory = $temp_store_factory;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager'),
$container->get('user.private_tempstore')
);
}
/**
* {@inheritdoc}
*/
protected function prepareEntity() {
/** @var \Drupal\node\NodeInterface $node */
$node = $this->entity;
if (!$node->isNew()) {
// Remove the revision log message from the original node entity.
$node->revision_log = NULL;
}
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
// Try to restore from temp store, this must be done before calling
// parent::form().
$uuid = $this->entity->uuid();
$store = $this->tempStoreFactory->get('node_preview');
// If the user is creating a new node, the UUID is passed in the request.
if ($request_uuid = \Drupal::request()->query->get('uuid')) {
$uuid = $request_uuid;
}
if ($preview = $store->get($uuid)) {
/** @var $preview \Drupal\Core\Form\FormStateInterface */
$form_state = $preview;
// Rebuild the form.
$form_state->setRebuild();
$this->entity = $preview->getFormObject()->getEntity();
unset($this->entity->in_preview);
// Remove the stale temp store entry for existing nodes.
if (!$this->entity->isNew()) {
$store->delete($uuid);
}
$this->hasBeenPreviewed = TRUE;
}
/** @var \Drupal\node\NodeInterface $node */
$node = $this->entity;
if ($this->operation == 'edit') {
$form['#title'] = $this->t('<em>Edit @type</em> @title', array('@type' => node_get_type_label($node), '@title' => $node->label()));
}
$current_user = $this->currentUser();
// Changed must be sent to the client, for later overwrite error checking.
$form['changed'] = array(
'#type' => 'hidden',
'#default_value' => $node->getChangedTime(),
);
$form['advanced'] = array(
'#type' => 'vertical_tabs',
'#attributes' => array('class' => array('entity-meta')),
'#weight' => 99,
);
$form = parent::form($form, $form_state);
// Add a revision_log field if the "Create new revision" option is checked,
// or if the current user has the ability to check that option.
$form['revision_information'] = array(
'#type' => 'details',
'#group' => 'advanced',
'#title' => t('Revision information'),
// Open by default when "Create new revision" is checked.
'#open' => $node->isNewRevision(),
'#attributes' => array(
'class' => array('node-form-revision-information'),
),
'#attached' => array(
'library' => array('node/drupal.node'),
),
'#weight' => 20,
'#optional' => TRUE,
);
$form['revision'] = array(
'#type' => 'checkbox',
'#title' => t('Create new revision'),
'#default_value' => $node->type->entity->isNewRevision(),
'#access' => $current_user->hasPermission('administer nodes'),
'#group' => 'revision_information',
);
$form['revision_log'] += array(
'#states' => array(
'visible' => array(
':input[name="revision"]' => array('checked' => TRUE),
),
),
'#group' => 'revision_information',
);
// Node author information for administrators.
$form['author'] = array(
'#type' => 'details',
'#title' => t('Authoring information'),
'#group' => 'advanced',
'#attributes' => array(
'class' => array('node-form-author'),
),
'#attached' => array(
'library' => array('node/drupal.node'),
),
'#weight' => 90,
'#optional' => TRUE,
);
if (isset($form['uid'])) {
$form['uid']['#group'] = 'author';
}
if (isset($form['created'])) {
$form['created']['#group'] = 'author';
}
// Node options for administrators.
$form['options'] = array(
'#type' => 'details',
'#title' => t('Promotion options'),
'#group' => 'advanced',
'#attributes' => array(
'class' => array('node-form-options'),
),
'#attached' => array(
'library' => array('node/drupal.node'),
),
'#weight' => 95,
'#optional' => TRUE,
);
if (isset($form['promote'])) {
$form['promote']['#group'] = 'options';
}
if (isset($form['sticky'])) {
$form['sticky']['#group'] = 'options';
}
$form['#attached']['library'][] = 'node/form';
return $form;
}
/**
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
$element = parent::actions($form, $form_state);
$node = $this->entity;
$preview_mode = $node->type->entity->getPreviewMode();
$element['submit']['#access'] = $preview_mode != DRUPAL_REQUIRED || $this->hasBeenPreviewed;
// If saving is an option, privileged users get dedicated form submit
// buttons to adjust the publishing status while saving in one go.
// @todo This adjustment makes it close to impossible for contributed
// modules to integrate with "the Save operation" of this form. Modules
// need a way to plug themselves into 1) the ::submit() step, and
// 2) the ::save() step, both decoupled from the pressed form button.
if ($element['submit']['#access'] && \Drupal::currentUser()->hasPermission('administer nodes')) {
// isNew | prev status » default & publish label & unpublish label
// 1 | 1 » publish & Save and publish & Save as unpublished
// 1 | 0 » unpublish & Save and publish & Save as unpublished
// 0 | 1 » publish & Save and keep published & Save and unpublish
// 0 | 0 » unpublish & Save and keep unpublished & Save and publish
// Add a "Publish" button.
$element['publish'] = $element['submit'];
$element['publish']['#dropbutton'] = 'save';
if ($node->isNew()) {
$element['publish']['#value'] = t('Save and publish');
}
else {
$element['publish']['#value'] = $node->isPublished() ? t('Save and keep published') : t('Save and publish');
}
$element['publish']['#weight'] = 0;
array_unshift($element['publish']['#submit'], '::publish');
// Add a "Unpublish" button.
$element['unpublish'] = $element['submit'];
$element['unpublish']['#dropbutton'] = 'save';
if ($node->isNew()) {
$element['unpublish']['#value'] = t('Save as unpublished');
}
else {
$element['unpublish']['#value'] = !$node->isPublished() ? t('Save and keep unpublished') : t('Save and unpublish');
}
$element['unpublish']['#weight'] = 10;
array_unshift($element['unpublish']['#submit'], '::unpublish');
// If already published, the 'publish' button is primary.
if ($node->isPublished()) {
unset($element['unpublish']['#button_type']);
}
// Otherwise, the 'unpublish' button is primary and should come first.
else {
unset($element['publish']['#button_type']);
$element['unpublish']['#weight'] = -10;
}
// Remove the "Save" button.
$element['submit']['#access'] = FALSE;
}
$element['preview'] = array(
'#type' => 'submit',
'#access' => $preview_mode != DRUPAL_DISABLED && ($node->access('create') || $node->access('update')),
'#value' => t('Preview'),
'#weight' => 20,
'#validate' => array('::validate'),
'#submit' => array('::submitForm', '::preview'),
);
$element['delete']['#access'] = $node->access('delete');
$element['delete']['#weight'] = 100;
return $element;
}
/**
* {@inheritdoc}
*
* Updates the node object by processing the submitted values.
*
* This function can be called by a "Next" button of a wizard to update the
* form state's entity with the current step's values before proceeding to the
* next step.
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Build the node object from the submitted values.
parent::submitForm($form, $form_state);
$node = $this->entity;
// Save as a new revision if requested to do so.
if (!$form_state->isValueEmpty('revision') && $form_state->getValue('revision') != FALSE) {
$node->setNewRevision();
// If a new revision is created, save the current user as revision author.
$node->setRevisionCreationTime(REQUEST_TIME);
$node->setRevisionAuthorId(\Drupal::currentUser()->id());
}
else {
$node->setNewRevision(FALSE);
}
}
/**
* Form submission handler for the 'preview' action.
*
* @param $form
* An associative array containing the structure of the form.
* @param $form_state
* The current state of the form.
*/
public function preview(array $form, FormStateInterface $form_state) {
$store = $this->tempStoreFactory->get('node_preview');
$this->entity->in_preview = TRUE;
$store->set($this->entity->uuid(), $form_state);
$form_state->setRedirect('entity.node.preview', array(
'node_preview' => $this->entity->uuid(),
'view_mode_id' => 'default',
));
}
/**
* Form submission handler for the 'publish' action.
*
* @param $form
* An associative array containing the structure of the form.
* @param $form_state
* The current state of the form.
*/
public function publish(array $form, FormStateInterface $form_state) {
$node = $this->entity;
$node->setPublished(TRUE);
return $node;
}
/**
* Form submission handler for the 'unpublish' action.
*
* @param $form
* An associative array containing the structure of the form.
* @param $form_state
* The current state of the form.
*/
public function unpublish(array $form, FormStateInterface $form_state) {
$node = $this->entity;
$node->setPublished(FALSE);
return $node;
}
/**
* {@inheritdoc}
*/
public function buildEntity(array $form, FormStateInterface $form_state) {
/** @var \Drupal\node\NodeInterface $entity */
$entity = parent::buildEntity($form, $form_state);
// A user might assign the node author by entering a user name in the node
// form, which we then need to translate to a user ID.
// @todo: Remove it when https://www.drupal.org/node/2322525 is pushed.
if (!empty($form_state->getValue('uid')[0]['target_id']) && $account = User::load($form_state->getValue('uid')[0]['target_id'])) {
$entity->setOwnerId($account->id());
}
else {
$entity->setOwnerId(0);
}
return $entity;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$node = $this->entity;
$insert = $node->isNew();
$node->save();
$node_link = $node->link($this->t('View'));
$context = array('@type' => $node->getType(), '%title' => $node->label(), 'link' => $node_link);
$t_args = array('@type' => node_get_type_label($node), '%title' => $node->label());
if ($insert) {
$this->logger('content')->notice('@type: added %title.', $context);
drupal_set_message(t('@type %title has been created.', $t_args));
}
else {
$this->logger('content')->notice('@type: updated %title.', $context);
drupal_set_message(t('@type %title has been updated.', $t_args));
}
if ($node->id()) {
$form_state->setValue('nid', $node->id());
$form_state->set('nid', $node->id());
if ($node->access('view')) {
$form_state->setRedirect(
'entity.node.canonical',
array('node' => $node->id())
);
}
else {
$form_state->setRedirect('<front>');
}
// Remove the preview entry from the temp store, if any.
$store = $this->tempStoreFactory->get('node_preview');
$store->delete($node->uuid());
}
else {
// In the unlikely case something went wrong on save, the node will be
// rebuilt and node form redisplayed the same way as in preview.
drupal_set_message(t('The post could not be saved.'), 'error');
$form_state->setRebuild();
}
}
}

View file

@ -0,0 +1,311 @@
<?php
/**
* @file
* Contains \Drupal\node\NodeGrantDatabaseStorage.
*/
namespace Drupal\node;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\Entity\User;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a controller class that handles the node grants system.
*
* This is used to build node query access.
*
* @ingroup node_access
*/
class NodeGrantDatabaseStorage implements NodeGrantDatabaseStorageInterface {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs a NodeGrantDatabaseStorage object.
*
* @param \Drupal\Core\Database\Connection $database
* The database connection.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(Connection $database, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager) {
$this->database = $database;
$this->moduleHandler = $module_handler;
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public function access(NodeInterface $node, $operation, $langcode, AccountInterface $account) {
// If no module implements the hook or the node does not have an id there is
// no point in querying the database for access grants.
if (!$this->moduleHandler->getImplementations('node_grants') || !$node->id()) {
// Return the equivalent of the default grant, defined by
// self::writeDefault().
if ($operation === 'view') {
return AccessResult::allowedIf($node->getTranslation($langcode)->isPublished())->cacheUntilEntityChanges($node);
}
else {
return AccessResult::neutral();
}
}
// Check the database for potential access grants.
$query = $this->database->select('node_access');
$query->addExpression('1');
// Only interested for granting in the current operation.
$query->condition('grant_' . $operation, 1, '>=');
// Check for grants for this node and the correct langcode.
$nids = $query->andConditionGroup()
->condition('nid', $node->id())
->condition('langcode', $langcode);
// If the node is published, also take the default grant into account. The
// default is saved with a node ID of 0.
$status = $node->isPublished();
if ($status) {
$nids = $query->orConditionGroup()
->condition($nids)
->condition('nid', 0);
}
$query->condition($nids);
$query->range(0, 1);
$grants = static::buildGrantsQueryCondition(node_access_grants($operation, $account));
if (count($grants) > 0) {
$query->condition($grants);
}
// Only the 'view' node grant can currently be cached; the others currently
// don't have any cacheability metadata. Hopefully, we can add that in the
// future, which would allow this access check result to be cacheable in all
// cases. For now, this must remain marked as uncacheable, even when it is
// theoretically cacheable, because we don't have the necessary metadata to
// know it for a fact.
$set_cacheability = function (AccessResult $access_result) use ($operation) {
$access_result->addCacheContexts(['user.node_grants:' . $operation]);
if ($operation !== 'view') {
$access_result->setCacheMaxAge(0);
}
return $access_result;
};
if ($query->execute()->fetchField()) {
return $set_cacheability(AccessResult::allowed());
}
else {
return $set_cacheability(AccessResult::neutral());
}
}
/**
* {@inheritdoc}
*/
public function checkAll(AccountInterface $account) {
$query = $this->database->select('node_access');
$query->addExpression('COUNT(*)');
$query
->condition('nid', 0)
->condition('grant_view', 1, '>=');
$grants = static::buildGrantsQueryCondition(node_access_grants('view', $account));
if (count($grants) > 0 ) {
$query->condition($grants);
}
return $query->execute()->fetchField();
}
/**
* {@inheritdoc}
*/
public function alterQuery($query, array $tables, $op, AccountInterface $account, $base_table) {
if (!$langcode = $query->getMetaData('langcode')) {
$langcode = FALSE;
}
// Find all instances of the base table being joined -- could appear
// more than once in the query, and could be aliased. Join each one to
// the node_access table.
$grants = node_access_grants($op, $account);
foreach ($tables as $nalias => $tableinfo) {
$table = $tableinfo['table'];
if (!($table instanceof SelectInterface) && $table == $base_table) {
// Set the subquery.
$subquery = $this->database->select('node_access', 'na')
->fields('na', array('nid'));
// If any grant exists for the specified user, then user has access to the
// node for the specified operation.
$grant_conditions = static::buildGrantsQueryCondition($grants);
// Attach conditions to the subquery for nodes.
if (count($grant_conditions->conditions())) {
$subquery->condition($grant_conditions);
}
$subquery->condition('na.grant_' . $op, 1, '>=');
// Add langcode-based filtering if this is a multilingual site.
if (\Drupal::languageManager()->isMultilingual()) {
// If no specific langcode to check for is given, use the grant entry
// which is set as a fallback.
// If a specific langcode is given, use the grant entry for it.
if ($langcode === FALSE) {
$subquery->condition('na.fallback', 1, '=');
}
else {
$subquery->condition('na.langcode', $langcode, '=');
}
}
$field = 'nid';
// Now handle entities.
$subquery->where("$nalias.$field = na.nid");
$query->exists($subquery);
}
}
}
/**
* {@inheritdoc}
*/
public function write(NodeInterface $node, array $grants, $realm = NULL, $delete = TRUE) {
if ($delete) {
$query = $this->database->delete('node_access')->condition('nid', $node->id());
if ($realm) {
$query->condition('realm', array($realm, 'all'), 'IN');
}
$query->execute();
}
// Only perform work when node_access modules are active.
if (!empty($grants) && count($this->moduleHandler->getImplementations('node_grants'))) {
$query = $this->database->insert('node_access')->fields(array('nid', 'langcode', 'fallback', 'realm', 'gid', 'grant_view', 'grant_update', 'grant_delete'));
// If we have defined a granted langcode, use it. But if not, add a grant
// for every language this node is translated to.
foreach ($grants as $grant) {
if ($realm && $realm != $grant['realm']) {
continue;
}
if (isset($grant['langcode'])) {
$grant_languages = array($grant['langcode'] => $this->languageManager->getLanguage($grant['langcode']));
}
else {
$grant_languages = $node->getTranslationLanguages(TRUE);
}
foreach ($grant_languages as $grant_langcode => $grant_language) {
// Only write grants; denies are implicit.
if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) {
$grant['nid'] = $node->id();
$grant['langcode'] = $grant_langcode;
// The record with the original langcode is used as the fallback.
if ($grant['langcode'] == $node->language()->getId()) {
$grant['fallback'] = 1;
}
else {
$grant['fallback'] = 0;
}
$query->values($grant);
}
}
}
$query->execute();
}
}
/**
* {@inheritdoc}
*/
public function delete() {
$this->database->truncate('node_access')->execute();
}
/**
* {@inheritdoc}
*/
public function writeDefault() {
$this->database->insert('node_access')
->fields(array(
'nid' => 0,
'realm' => 'all',
'gid' => 0,
'grant_view' => 1,
'grant_update' => 0,
'grant_delete' => 0,
))
->execute();
}
/**
* {@inheritdoc}
*/
public function count() {
return $this->database->query('SELECT COUNT(*) FROM {node_access}')->fetchField();
}
/**
* {@inheritdoc}
*/
public function deleteNodeRecords(array $nids) {
$this->database->delete('node_access')
->condition('nid', $nids, 'IN')
->execute();
}
/**
* Creates a query condition from an array of node access grants.
*
* @param array $node_access_grants
* An array of grants, as returned by node_access_grants().
* @return \Drupal\Core\Database\Query\Condition
* A condition object to be passed to $query->condition().
*
* @see node_access_grants()
*/
protected static function buildGrantsQueryCondition(array $node_access_grants) {
$grants = new Condition("OR");
foreach ($node_access_grants as $realm => $gids) {
if (!empty($gids)) {
$and = new Condition('AND');
$grants->condition($and
->condition('gid', $gids, 'IN')
->condition('realm', $realm)
);
}
}
return $grants;
}
}

View file

@ -0,0 +1,137 @@
<?php
/**
* @file
* Contains \Drupal\node\NodeGrantDatabaseStorageInterface.
*/
namespace Drupal\node;
use Drupal\Core\Session\AccountInterface;
/**
* Provides an interface for node access grant storage.
*
* @ingroup node_access
*/
interface NodeGrantDatabaseStorageInterface {
/**
* Checks all grants for a given account.
*
* @param \Drupal\Core\Session\AccountInterface $account
* A user object representing the user for whom the operation is to be
* performed.
*
* @return int.
* Status of the access check.
*/
public function checkAll(AccountInterface $account);
/**
* Alters a query when node access is required.
*
* @param mixed $query
* Query that is being altered.
* @param array $tables
* A list of tables that need to be part of the alter.
* @param string $op
* The operation to be performed on the node. Possible values are:
* - "view"
* - "update"
* - "delete"
* - "create"
* @param \Drupal\Core\Session\AccountInterface $account
* A user object representing the user for whom the operation is to be
* performed.
* @param string $base_table
* The base table of the query.
*
* @return int
* Status of the access check.
*/
public function alterQuery($query, array $tables, $op, AccountInterface $account, $base_table);
/**
* Writes a list of grants to the database, deleting previously saved ones.
*
* If a realm is provided, it will only delete grants from that realm, but
* it will always delete a grant from the 'all' realm. Modules that use
* node access can use this method when doing mass updates due to widespread
* permission changes.
*
* Note: Don't call this method directly from a contributed module. Call
* node_access_write_grants() instead.
*
* @param \Drupal\node\NodeInterface $node
* The node whose grants are being written.
* @param array $grants
* A list of grants to write. Each grant is an array that must contain the
* following keys: realm, gid, grant_view, grant_update, grant_delete.
* The realm is specified by a particular module; the gid is as well, and
* is a module-defined id to define grant privileges. each grant_* field
* is a boolean value.
* @param string $realm
* (optional) If provided, read/write grants for that realm only. Defaults to
* NULL.
* @param bool $delete
* (optional) If false, does not delete records. This is only for optimization
* purposes, and assumes the caller has already performed a mass delete of
* some form. Defaults to TRUE.
*
* @see node_access_write_grants()
* @see node_access_acquire_grants()
*/
public function write(NodeInterface $node, array $grants, $realm = NULL, $delete = TRUE);
/**
* Deletes all node access entries.
*/
public function delete();
/**
* Creates the default node access grant entry.
*/
public function writeDefault();
/**
* Determines access to nodes based on node grants.
*
* @param \Drupal\node\NodeInterface $node
* The entity for which to check 'create' access.
* @param string $operation
* The entity operation. Usually one of 'view', 'edit', '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 \Drupal\Core\Access\AccessResultInterface
* The access result, either allowed or neutral. If there are no node
* grants, the default grant defined by writeDefault() is applied.
*
* @see hook_node_grants()
* @see hook_node_access_records()
* @see \Drupal\node\NodeGrantDatabaseStorageInterface::writeDefault()
*/
public function access(NodeInterface $node, $operation, $langcode, AccountInterface $account);
/**
* Counts available node grants.
*
* @return int
* Returns the amount of node grants.
*/
public function count();
/**
* Remove the access records belonging to certain nodes.
*
* @param array $nids
* A list of node IDs. The grant records belonging to these nodes will be
* deleted.
*/
public function deleteNodeRecords(array $nids);
}

View file

@ -0,0 +1,171 @@
<?php
/**
* @file
* Contains \Drupal\node\NodeInterface.
*/
namespace Drupal\node;
use Drupal\user\EntityOwnerInterface;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\user\UserInterface;
/**
* Provides an interface defining a node entity.
*/
interface NodeInterface extends ContentEntityInterface, EntityChangedInterface, EntityOwnerInterface {
/**
* Gets the node type.
*
* @return string
* The node type.
*/
public function getType();
/**
* Gets the node title.
*
* @return string
* Title of the node.
*/
public function getTitle();
/**
* Sets the node title.
*
* @param string $title
* The node title.
*
* @return \Drupal\node\NodeInterface
* The called node entity.
*/
public function setTitle($title);
/**
* Gets the node creation timestamp.
*
* @return int
* Creation timestamp of the node.
*/
public function getCreatedTime();
/**
* Sets the node creation timestamp.
*
* @param int $timestamp
* The node creation timestamp.
*
* @return \Drupal\node\NodeInterface
* The called node entity.
*/
public function setCreatedTime($timestamp);
/**
* Returns the node promotion status.
*
* @return bool
* TRUE if the node is promoted.
*/
public function isPromoted();
/**
* Sets the node promoted status.
*
* @param bool $promoted
* TRUE to set this node to promoted, FALSE to set it to not promoted.
*
* @return \Drupal\node\NodeInterface
* The called node entity.
*/
public function setPromoted($promoted);
/**
* Returns the node sticky status.
*
* @return bool
* TRUE if the node is sticky.
*/
public function isSticky();
/**
* Sets the node sticky status.
*
* @param bool $sticky
* TRUE to set this node to sticky, FALSE to set it to not sticky.
*
* @return \Drupal\node\NodeInterface
* The called node entity.
*/
public function setSticky($sticky);
/**
* Returns the node published status indicator.
*
* Unpublished nodes are only visible to their authors and to administrators.
*
* @return bool
* TRUE if the node is published.
*/
public function isPublished();
/**
* Sets the published status of a node..
*
* @param bool $published
* TRUE to set this node to published, FALSE to set it to unpublished.
*
* @return \Drupal\node\NodeInterface
* The called node entity.
*/
public function setPublished($published);
/**
* Gets the node revision creation timestamp.
*
* @return int
* The UNIX timestamp of when this revision was created.
*/
public function getRevisionCreationTime();
/**
* Sets the node revision creation timestamp.
*
* @param int $timestamp
* The UNIX timestamp of when this revision was created.
*
* @return \Drupal\node\NodeInterface
* The called node entity.
*/
public function setRevisionCreationTime($timestamp);
/**
* Gets the node revision author.
*
* @return \Drupal\user\UserInterface
* The user entity for the revision author.
*/
public function getRevisionAuthor();
/**
* Sets the node revision author.
*
* @param int $uid
* The user ID of the revision author.
*
* @return \Drupal\node\NodeInterface
* The called node entity.
*/
public function setRevisionAuthorId($uid);
/**
* Prepares the langcode for a node.
*
* @return string
* The langcode for this node.
*/
public function prepareLangcode();
}

View file

@ -0,0 +1,150 @@
<?php
/**
* @file
* Contains \Drupal\node\NodeListBuilder.
*/
namespace Drupal\node;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Datetime\DateFormatter;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityListBuilder;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Routing\RedirectDestinationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a class to build a listing of node entities.
*
* @see \Drupal\node\Entity\Node
*/
class NodeListBuilder extends EntityListBuilder {
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatter
*/
protected $dateFormatter;
/**
* The redirect destination service.
*
* @var \Drupal\Core\Routing\RedirectDestinationInterface
*/
protected $redirectDestination;
/**
* Constructs a new NodeListBuilder object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage class.
* @param \Drupal\Core\Datetime\DateFormatter $date_formatter
* The date formatter service.
* @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination
* The redirect destination service.
*/
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, DateFormatter $date_formatter, RedirectDestinationInterface $redirect_destination) {
parent::__construct($entity_type, $storage);
$this->dateFormatter = $date_formatter;
$this->redirectDestination = $redirect_destination;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity.manager')->getStorage($entity_type->id()),
$container->get('date.formatter'),
$container->get('redirect.destination')
);
}
/**
* {@inheritdoc}
*/
public function buildHeader() {
// Enable language column and filter if multiple languages are added.
$header = array(
'title' => $this->t('Title'),
'type' => array(
'data' => $this->t('Content type'),
'class' => array(RESPONSIVE_PRIORITY_MEDIUM),
),
'author' => array(
'data' => $this->t('Author'),
'class' => array(RESPONSIVE_PRIORITY_LOW),
),
'status' => $this->t('Status'),
'changed' => array(
'data' => $this->t('Updated'),
'class' => array(RESPONSIVE_PRIORITY_LOW),
),
);
if (\Drupal::languageManager()->isMultilingual()) {
$header['language_name'] = array(
'data' => $this->t('Language'),
'class' => array(RESPONSIVE_PRIORITY_LOW),
);
}
return $header + parent::buildHeader();
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
/** @var \Drupal\node\NodeInterface $entity */
$mark = array(
'#theme' => 'mark',
'#mark_type' => node_mark($entity->id(), $entity->getChangedTime()),
);
$langcode = $entity->language()->getId();
$uri = $entity->urlInfo();
$options = $uri->getOptions();
$options += ($langcode != LanguageInterface::LANGCODE_NOT_SPECIFIED && isset($languages[$langcode]) ? array('language' => $languages[$langcode]) : array());
$uri->setOptions($options);
$row['title']['data'] = array(
'#type' => 'link',
'#title' => $entity->label(),
'#suffix' => ' ' . drupal_render($mark),
'#url' => $uri,
);
$row['type'] = SafeMarkup::checkPlain(node_get_type_label($entity));
$row['author']['data'] = array(
'#theme' => 'username',
'#account' => $entity->getOwner(),
);
$row['status'] = $entity->isPublished() ? $this->t('published') : $this->t('not published');
$row['changed'] = $this->dateFormatter->format($entity->getChangedTime(), 'short');
$language_manager = \Drupal::languageManager();
if ($language_manager->isMultilingual()) {
$row['language_name'] = $language_manager->getLanguageName($langcode);
}
$row['operations']['data'] = $this->buildOperations($entity);
return $row + parent::buildRow($entity);
}
/**
* {@inheritdoc}
*/
protected function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
$destination = $this->redirectDestination->getAsArray();
foreach ($operations as $key => $operation) {
$operations[$key]['query'] = $destination;
}
return $operations;
}
}

View file

@ -0,0 +1,82 @@
<?php
/**
* @file
* Contains \Drupal\node\NodePermissions.
*/
namespace Drupal\node;
use Drupal\Core\Routing\UrlGeneratorTrait;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\node\Entity\NodeType;
/**
* Defines a class containing permission callbacks.
*/
class NodePermissions {
use StringTranslationTrait;
use UrlGeneratorTrait;
/**
* Gets an array of node type permissions.
*
* @return array
* The node type permissions.
* @see \Drupal\user\PermissionHandlerInterface::getPermissions()
*/
public function nodeTypePermissions() {
$perms = array();
// Generate node permissions for all node types.
foreach (NodeType::loadMultiple() as $type) {
$perms += $this->buildPermissions($type);
}
return $perms;
}
/**
* Builds a standard list of node permissions for a given type.
*
* @param \Drupal\node\Entity\NodeType $type
* The machine name of the node type.
*
* @return array
* An array of permission names and descriptions.
*/
protected function buildPermissions(NodeType $type) {
$type_id = $type->id();
$type_params = array('%type_name' => $type->label());
return array(
"create $type_id content" => array(
'title' => $this->t('%type_name: Create new content', $type_params),
),
"edit own $type_id content" => array(
'title' => $this->t('%type_name: Edit own content', $type_params),
),
"edit any $type_id content" => array(
'title' => $this->t('%type_name: Edit any content', $type_params),
),
"delete own $type_id content" => array(
'title' => $this->t('%type_name: Delete own content', $type_params),
),
"delete any $type_id content" => array(
'title' => $this->t('%type_name: Delete any content', $type_params),
),
"view $type_id revisions" => array(
'title' => $this->t('%type_name: View revisions', $type_params),
),
"revert $type_id revisions" => array(
'title' => $this->t('%type_name: Revert revisions', $type_params),
'description' => t('Role requires permission <em>view revisions</em> and <em>edit rights</em> for nodes in question, or <em>administer nodes</em>.'),
),
"delete $type_id revisions" => array(
'title' => $this->t('%type_name: Delete revisions', $type_params),
'description' => $this->t('Role requires permission to <em>view revisions</em> and <em>delete rights</em> for nodes in question, or <em>administer nodes</em>.'),
),
);
}
}

View file

@ -0,0 +1,69 @@
<?php
/**
* @file
* Contains \Drupal\node\NodeStorage.
*/
namespace Drupal\node;
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Language\LanguageInterface;
/**
* Defines the controller class for nodes.
*
* This extends the base storage class, adding required special handling for
* node entities.
*/
class NodeStorage extends SqlContentEntityStorage implements NodeStorageInterface {
/**
* {@inheritdoc}
*/
public function revisionIds(NodeInterface $node) {
return $this->database->query(
'SELECT vid FROM {node_revision} WHERE nid=:nid ORDER BY vid',
array(':nid' => $node->id())
)->fetchCol();
}
/**
* {@inheritdoc}
*/
public function userRevisionIds(AccountInterface $account) {
return $this->database->query(
'SELECT vid FROM {node_field_revision} WHERE uid = :uid ORDER BY vid',
array(':uid' => $account->id())
)->fetchCol();
}
/**
* {@inheritdoc}
*/
public function countDefaultLanguageRevisions(NodeInterface $node) {
return $this->database->query('SELECT COUNT(*) FROM {node_field_revision} WHERE nid = :nid AND default_langcode = 1', array(':nid' => $node->id()))->fetchField();
}
/**
* {@inheritdoc}
*/
public function updateType($old_type, $new_type) {
return $this->database->update('node')
->fields(array('type' => $new_type))
->condition('type', $old_type)
->execute();
}
/**
* {@inheritdoc}
*/
public function clearRevisionsLanguage(LanguageInterface $language) {
return $this->database->update('node_revision')
->fields(array('langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED))
->condition('langcode', $language->getId())
->execute();
}
}

View file

@ -0,0 +1,72 @@
<?php
/**
* @file
* Contains \Drupal\node\NodeStorageInterface.
*/
namespace Drupal\node;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines an interface for node entity storage classes.
*/
interface NodeStorageInterface extends EntityStorageInterface {
/**
* Gets a list of node revision IDs for a specific node.
*
* @param \Drupal\node\NodeInterface
* The node entity.
*
* @return int[]
* Node revision IDs (in ascending order).
*/
public function revisionIds(NodeInterface $node);
/**
* Gets a list of revision IDs having a given user as node author.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The user entity.
*
* @return int[]
* Node revision IDs (in ascending order).
*/
public function userRevisionIds(AccountInterface $account);
/**
* Counts the number of revisions in the default language.
*
* @param \Drupal\node\NodeInterface
* The node entity.
*
* @return int
* The number of revisions in the default language.
*/
public function countDefaultLanguageRevisions(NodeInterface $node);
/**
* Updates all nodes of one type to be of another type.
*
* @param string $old_type
* The current node type of the nodes.
* @param string $new_type
* The new node type of the nodes.
*
* @return int
* The number of nodes whose node type field was modified.
*/
public function updateType($old_type, $new_type);
/**
* Unsets the language for all nodes with the given language.
*
* @param \Drupal\Core\Language\LanguageInterface $language
* The language object.
*/
public function clearRevisionsLanguage(LanguageInterface $language);
}

View file

@ -0,0 +1,95 @@
<?php
/**
* @file
* Contains \Drupal\node\NodeStorageSchema.
*/
namespace Drupal\node;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Defines the node schema handler.
*/
class NodeStorageSchema extends SqlContentEntityStorageSchema {
/**
* {@inheritdoc}
*/
protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
$schema = parent::getEntitySchema($entity_type, $reset);
// Marking the respective fields as NOT NULL makes the indexes more
// performant.
$schema['node_field_data']['fields']['default_langcode']['not null'] = TRUE;
$schema['node_field_revision']['fields']['default_langcode']['not null'] = TRUE;
$schema['node_field_data']['indexes'] += array(
'node__default_langcode' => array('default_langcode'),
'node__frontpage' => array('promote', 'status', 'sticky', 'created'),
'node__status_type' => array('status', 'type', 'nid'),
'node__title_type' => array('title', array('type', 4)),
);
$schema['node_field_revision']['indexes'] += array(
'node__default_langcode' => array('default_langcode'),
);
return $schema;
}
/**
* {@inheritdoc}
*/
protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
$schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
$field_name = $storage_definition->getName();
if ($table_name == 'node_revision') {
switch ($field_name) {
case 'langcode':
$this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
break;
case 'revision_uid':
$this->addSharedTableFieldForeignKey($storage_definition, $schema, 'users', 'uid');
break;
}
}
if ($table_name == 'node_field_data') {
switch ($field_name) {
case 'promote':
case 'status':
case 'sticky':
case 'title':
// Improves the performance of the indexes defined
// in getEntitySchema().
$schema['fields'][$field_name]['not null'] = TRUE;
break;
case 'changed':
case 'created':
case 'langcode':
// @todo Revisit index definitions:
// https://www.drupal.org/node/2015277.
$this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
break;
}
}
if ($table_name == 'node_field_revision') {
switch ($field_name) {
case 'langcode':
$this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
break;
}
}
return $schema;
}
}

View file

@ -0,0 +1,91 @@
<?php
/**
* @file
* Contains \Drupal\node\NodeTranslationHandler.
*/
namespace Drupal\node;
use Drupal\Core\Entity\EntityInterface;
use Drupal\content_translation\ContentTranslationHandler;
use Drupal\Core\Form\FormStateInterface;
/**
* Defines the translation handler for nodes.
*/
class NodeTranslationHandler extends ContentTranslationHandler {
/**
* {@inheritdoc}
*/
public function entityFormAlter(array &$form, FormStateInterface $form_state, EntityInterface $entity) {
parent::entityFormAlter($form, $form_state, $entity);
// Move the translation fieldset to a vertical tab.
if (isset($form['content_translation'])) {
$form['content_translation'] += array(
'#group' => 'advanced',
'#attributes' => array(
'class' => array('node-translation-options'),
),
);
$form['content_translation']['#weight'] = 100;
// We do not need to show these values on node forms: they inherit the
// basic node property values.
$form['content_translation']['status']['#access'] = FALSE;
$form['content_translation']['name']['#access'] = FALSE;
$form['content_translation']['created']['#access'] = FALSE;
}
$form_object = $form_state->getFormObject();
$form_langcode = $form_object->getFormLangcode($form_state);
$translations = $entity->getTranslationLanguages();
$status_translatable = NULL;
// Change the submit button labels if there was a status field they affect
// in which case their publishing / unpublishing may or may not apply
// to all translations.
if (!$entity->isNew() && (!isset($translations[$form_langcode]) || count($translations) > 1)) {
foreach ($entity->getFieldDefinitions() as $property_name => $definition) {
if ($property_name == 'status') {
$status_translatable = $definition->isTranslatable();
}
}
if (isset($status_translatable)) {
foreach (array('publish', 'unpublish', 'submit') as $button) {
if (isset($form['actions'][$button])) {
$form['actions'][$button]['#value'] .= ' ' . ($status_translatable ? t('(this translation)') : t('(all translations)'));
}
}
}
}
}
/**
* {@inheritdoc}
*/
protected function entityFormTitle(EntityInterface $entity) {
$type_name = node_get_type_label($entity);
return t('<em>Edit @type</em> @title', array('@type' => $type_name, '@title' => $entity->label()));
}
/**
* {@inheritdoc}
*/
public function entityFormEntityBuild($entity_type, EntityInterface $entity, array $form, FormStateInterface $form_state) {
if ($form_state->hasValue('content_translation')) {
$form_object = $form_state->getFormObject();
$translation = &$form_state->getValue('content_translation');
$translation['status'] = $form_object->getEntity()->isPublished();
// $form['content_translation']['name'] is the equivalent field
// for translation author uid.
$account = $entity->uid->entity;
$translation['name'] = $account ? $account->getUsername() : '';
$translation['created'] = format_date($entity->created->value, 'custom', 'Y-m-d H:i:s O');
}
parent::entityFormEntityBuild($entity_type, $entity, $form, $form_state);
}
}

View file

@ -0,0 +1,37 @@
<?php
/**
* @file
* Contains \Drupal\node\NodeTypeAccessControlHandler.
*/
namespace Drupal\node;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines the access control handler for the node type entity type.
*
* @see \Drupal\node\Entity\NodeType
*/
class NodeTypeAccessControlHandler extends EntityAccessControlHandler {
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
if ($operation == 'delete') {
if ($entity->isLocked()) {
return AccessResult::forbidden()->cacheUntilEntityChanges($entity);
}
else {
return parent::checkAccess($entity, $operation, $langcode, $account)->cacheUntilEntityChanges($entity);
}
}
return parent::checkAccess($entity, $operation, $langcode, $account);
}
}

View file

@ -0,0 +1,262 @@
<?php
/**
* @file
* Contains \Drupal\node\NodeTypeForm.
*/
namespace Drupal\node;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\language\Entity\ContentLanguageSettings;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form controller for node type forms.
*/
class NodeTypeForm extends EntityForm {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs the NodeTypeForm object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager')
);
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
$type = $this->entity;
if ($this->operation == 'add') {
$form['#title'] = SafeMarkup::checkPlain($this->t('Add content type'));
$fields = $this->entityManager->getBaseFieldDefinitions('node');
// Create a node with a fake bundle using the type's UUID so that we can
// get the default values for workflow settings.
// @todo Make it possible to get default values without an entity.
// https://www.drupal.org/node/2318187
$node = $this->entityManager->getStorage('node')->create(array('type' => $type->uuid()));
}
else {
$form['#title'] = $this->t('Edit %label content type', array('%label' => $type->label()));
$fields = $this->entityManager->getFieldDefinitions('node', $type->id());
// Create a node to get the current values for workflow settings fields.
$node = $this->entityManager->getStorage('node')->create(array('type' => $type->id()));
}
$form['name'] = array(
'#title' => t('Name'),
'#type' => 'textfield',
'#default_value' => $type->label(),
'#description' => t('The human-readable name of this content type. This text will be displayed as part of the list on the <em>Add content</em> page. This name must be unique.'),
'#required' => TRUE,
'#size' => 30,
);
$form['type'] = array(
'#type' => 'machine_name',
'#default_value' => $type->id(),
'#maxlength' => EntityTypeInterface::BUNDLE_MAX_LENGTH,
'#disabled' => $type->isLocked(),
'#machine_name' => array(
'exists' => ['Drupal\node\Entity\NodeType', 'load'],
'source' => array('name'),
),
'#description' => t('A unique machine-readable name for this content type. It must only contain lowercase letters, numbers, and underscores. This name will be used for constructing the URL of the %node-add page, in which underscores will be converted into hyphens.', array(
'%node-add' => t('Add content'),
)),
);
$form['description'] = array(
'#title' => t('Description'),
'#type' => 'textarea',
'#default_value' => $type->getDescription(),
'#description' => t('Describe this content type. The text will be displayed on the <em>Add content</em> page.'),
);
$form['additional_settings'] = array(
'#type' => 'vertical_tabs',
'#attached' => array(
'library' => array('node/drupal.content_types'),
),
);
$form['submission'] = array(
'#type' => 'details',
'#title' => t('Submission form settings'),
'#group' => 'additional_settings',
'#open' => TRUE,
);
$form['submission']['title_label'] = array(
'#title' => t('Title field label'),
'#type' => 'textfield',
'#default_value' => $fields['title']->getLabel(),
'#required' => TRUE,
);
$form['submission']['preview_mode'] = array(
'#type' => 'radios',
'#title' => t('Preview before submitting'),
'#default_value' => $type->getPreviewMode(),
'#options' => array(
DRUPAL_DISABLED => t('Disabled'),
DRUPAL_OPTIONAL => t('Optional'),
DRUPAL_REQUIRED => t('Required'),
),
);
$form['submission']['help'] = array(
'#type' => 'textarea',
'#title' => t('Explanation or submission guidelines'),
'#default_value' => $type->getHelp(),
'#description' => t('This text will be displayed at the top of the page when creating or editing content of this type.'),
);
$form['workflow'] = array(
'#type' => 'details',
'#title' => t('Publishing options'),
'#group' => 'additional_settings',
);
$workflow_options = array(
'status' => $node->status->value,
'promote' => $node->promote->value,
'sticky' => $node->sticky->value,
'revision' => $type->isNewRevision(),
);
// Prepare workflow options to be used for 'checkboxes' form element.
$keys = array_keys(array_filter($workflow_options));
$workflow_options = array_combine($keys, $keys);
$form['workflow']['options'] = array('#type' => 'checkboxes',
'#title' => t('Default options'),
'#default_value' => $workflow_options,
'#options' => array(
'status' => t('Published'),
'promote' => t('Promoted to front page'),
'sticky' => t('Sticky at top of lists'),
'revision' => t('Create new revision'),
),
'#description' => t('Users with the <em>Administer content</em> permission will be able to override these options.'),
);
if ($this->moduleHandler->moduleExists('language')) {
$form['language'] = array(
'#type' => 'details',
'#title' => t('Language settings'),
'#group' => 'additional_settings',
);
$language_configuration = ContentLanguageSettings::loadByEntityTypeBundle('node', $type->id());
$form['language']['language_configuration'] = array(
'#type' => 'language_configuration',
'#entity_information' => array(
'entity_type' => 'node',
'bundle' => $type->id(),
),
'#default_value' => $language_configuration,
);
}
$form['display'] = array(
'#type' => 'details',
'#title' => t('Display settings'),
'#group' => 'additional_settings',
);
$form['display']['display_submitted'] = array(
'#type' => 'checkbox',
'#title' => t('Display author and date information'),
'#default_value' => $type->displaySubmitted(),
'#description' => t('Author username and publish date will be displayed.'),
);
return $form;
}
/**
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
$actions = parent::actions($form, $form_state);
$actions['submit']['#value'] = t('Save content type');
$actions['delete']['#value'] = t('Delete content type');
return $actions;
}
/**
* {@inheritdoc}
*/
public function validate(array $form, FormStateInterface $form_state) {
parent::validate($form, $form_state);
$id = trim($form_state->getValue('type'));
// '0' is invalid, since elsewhere we check it using empty().
if ($id == '0') {
$form_state->setErrorByName('type', $this->t("Invalid machine-readable name. Enter a name other than %invalid.", array('%invalid' => $id)));
}
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$type = $this->entity;
$type->setNewRevision($form_state->getValue(array('options', 'revision')));
$type->set('type', trim($type->id()));
$type->set('name', trim($type->label()));
$status = $type->save();
$t_args = array('%name' => $type->label());
if ($status == SAVED_UPDATED) {
drupal_set_message(t('The content type %name has been updated.', $t_args));
}
elseif ($status == SAVED_NEW) {
node_add_body_field($type);
drupal_set_message(t('The content type %name has been added.', $t_args));
$context = array_merge($t_args, array('link' => $type->link($this->t('View'), 'collection')));
$this->logger('node')->notice('Added content type %name.', $context);
}
$fields = $this->entityManager->getFieldDefinitions('node', $type->id());
// Update title field definition.
$title_field = $fields['title'];
$title_label = $form_state->getValue('title_label');
if ($title_field->getLabel() != $title_label) {
$title_field->getConfig($type->id())->setLabel($title_label)->save();
}
// Update workflow options.
// @todo Make it possible to get default values without an entity.
// https://www.drupal.org/node/2318187
$node = $this->entityManager->getStorage('node')->create(array('type' => $type->id()));
foreach (array('status', 'promote', 'sticky') as $field_name) {
$value = (bool) $form_state->getValue(['options', $field_name]);
if ($node->$field_name->value != $value) {
$fields[$field_name]->getConfig($type->id())->setDefaultValue($value)->save();
}
}
$this->entityManager->clearCachedFieldDefinitions();
$form_state->setRedirectUrl($type->urlInfo('collection'));
}
}

View file

@ -0,0 +1,88 @@
<?php
/**
* @file
* Contains \Drupal\node\NodeTypeInterface.
*/
namespace Drupal\node;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
/**
* Provides an interface defining a node type entity.
*/
interface NodeTypeInterface extends ConfigEntityInterface {
/**
* Determines whether the node type is locked.
*
* @return string|false
* The module name that locks the type or FALSE.
*/
public function isLocked();
/**
* Gets whether a new revision should be created by default.
*
* @return bool
* TRUE if a new revision should be created by default.
*/
public function isNewRevision();
/**
* Sets whether a new revision should be created by default.
*
* @param bool $new_revision_
* TRUE if a new revision should be created by default.
*/
public function setNewRevision($new_revision);
/**
* Gets whether 'Submitted by' information should be shown.
*
* @return bool
* TRUE if the submitted by information should be shown.
*/
public function displaySubmitted();
/**
* Sets whether 'Submitted by' information should be shown.
*
* @param bool $display_submitted
* TRUE if the submitted by information should be shown.
*/
public function setDisplaySubmitted($display_submitted);
/**
* Gets the preview mode.
*
* @return int
* DRUPAL_DISABLED, DRUPAL_OPTIONAL or DRUPAL_REQUIRED.
*/
public function getPreviewMode();
/**
* Sets the preview mode.
*
* @param int $preview_mode
* DRUPAL_DISABLED, DRUPAL_OPTIONAL or DRUPAL_REQUIRED.
*/
public function setPreviewMode($preview_mode);
/**
* Gets the help information.
*
* @return string
* The help information of this node type.
*/
public function getHelp();
/**
* Gets the description.
*
* @return string
* The description of this node type.
*/
public function getDescription();
}

View file

@ -0,0 +1,70 @@
<?php
/**
* @file
* Contains \Drupal\node\NodeTypeListBuilder.
*/
namespace Drupal\node;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Url;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Component\Utility\Xss;
/**
* Defines a class to build a listing of node type entities.
*
* @see \Drupal\node\Entity\NodeType
*/
class NodeTypeListBuilder extends ConfigEntityListBuilder {
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header['title'] = t('Name');
$header['description'] = array(
'data' => t('Description'),
'class' => array(RESPONSIVE_PRIORITY_MEDIUM),
);
return $header + parent::buildHeader();
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
$row['title'] = array(
'data' => $this->getLabel($entity),
'class' => array('menu-label'),
);
$row['description'] = Xss::filterAdmin($entity->getDescription());
return $row + parent::buildRow($entity);
}
/**
* {@inheritdoc}
*/
public function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
// Place the edit operation after the operations added by field_ui.module
// which have the weights 15, 20, 25.
if (isset($operations['edit'])) {
$operations['edit']['weight'] = 30;
}
return $operations;
}
/**
* {@inheritdoc}
*/
public function render() {
$build = parent::render();
$build['table']['#empty'] = $this->t('No content types available. <a href="@link">Add content type</a>.', [
'@link' => Url::fromRoute('node.type_add')->toString()
]);
return $build;
}
}

View file

@ -0,0 +1,163 @@
<?php
/**
* @file
* Contains \Drupal\node\NodeViewBuilder.
*/
namespace Drupal\node;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityViewBuilder;
use Drupal\node\Entity\Node;
use Drupal\user\Entity\User;
/**
* Render controller for nodes.
*/
class NodeViewBuilder extends EntityViewBuilder {
/**
* {@inheritdoc}
*/
public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL) {
/** @var \Drupal\node\NodeInterface[] $entities */
if (empty($entities)) {
return;
}
parent::buildComponents($build, $entities, $displays, $view_mode, $langcode);
foreach ($entities as $id => $entity) {
$bundle = $entity->bundle();
$display = $displays[$bundle];
if ($display->getComponent('links')) {
$build[$id]['links'] = array(
'#lazy_builder' => [get_called_class() . '::renderLinks', [
$entity->id(),
$view_mode,
$langcode,
!empty($entity->in_preview),
]],
'#create_placeholder' => TRUE,
);
}
// Add Language field text element to node render array.
if ($display->getComponent('langcode')) {
$build[$id]['langcode'] = array(
'#type' => 'item',
'#title' => t('Language'),
'#markup' => $entity->language()->getName(),
'#prefix' => '<div id="field-language-display">',
'#suffix' => '</div>'
);
}
}
}
/**
* {@inheritdoc}
*/
protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langcode) {
$defaults = parent::getBuildDefaults($entity, $view_mode, $langcode);
// Don't cache nodes that are in 'preview' mode.
if (isset($defaults['#cache']) && isset($entity->in_preview)) {
unset($defaults['#cache']);
}
return $defaults;
}
/**
* #lazy_builder callback; builds a node's links.
*
* @param string $node_entity_id
* The node entity ID.
* @param string $view_mode
* The view mode in which the node entity is being viewed.
* @param string $langcode
* The language in which the node entity is being viewed.
* @param bool $is_in_preview
* Whether the node is currently being previewed.
*
* @return array
* A renderable array representing the node links.
*/
public static function renderLinks($node_entity_id, $view_mode, $langcode, $is_in_preview) {
$links = array(
'#theme' => 'links__node',
'#pre_render' => array('drupal_pre_render_links'),
'#attributes' => array('class' => array('links', 'inline')),
);
if (!$is_in_preview) {
$entity = Node::load($node_entity_id)->getTranslation($langcode);
$links['node'] = static::buildLinks($entity, $view_mode);
// Allow other modules to alter the node links.
$hook_context = array(
'view_mode' => $view_mode,
'langcode' => $langcode,
);
\Drupal::moduleHandler()->alter('node_links', $links, $entity, $hook_context);
}
return $links;
}
/**
* Build the default links (Read more) for a node.
*
* @param \Drupal\node\NodeInterface $entity
* The node object.
* @param string $view_mode
* A view mode identifier.
*
* @return array
* An array that can be processed by drupal_pre_render_links().
*/
protected static function buildLinks(NodeInterface $entity, $view_mode) {
$links = array();
// Always display a read more link on teasers because we have no way
// to know when a teaser view is different than a full view.
if ($view_mode == 'teaser') {
$node_title_stripped = strip_tags($entity->label());
$links['node-readmore'] = array(
'title' => t('Read more<span class="visually-hidden"> about @title</span>', array(
'@title' => $node_title_stripped,
)),
'url' => $entity->urlInfo(),
'language' => $entity->language(),
'attributes' => array(
'rel' => 'tag',
'title' => $node_title_stripped,
),
);
}
return array(
'#theme' => 'links__node__node',
'#links' => $links,
'#attributes' => array('class' => array('links', 'inline')),
);
}
/**
* {@inheritdoc}
*/
protected function alterBuild(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode, $langcode = NULL) {
/** @var \Drupal\node\NodeInterface $entity */
parent::alterBuild($build, $entity, $display, $view_mode, $langcode);
if ($entity->id()) {
$build['#contextual_links']['node'] = array(
'route_parameters' =>array('node' => $entity->id()),
'metadata' => array('changed' => $entity->getChangedTime()),
);
}
}
}

View file

@ -0,0 +1,388 @@
<?php
/**
* @file
* Contains \Drupal\node\NodeViewsData.
*/
namespace Drupal\node;
use Drupal\views\EntityViewsData;
/**
* Provides the views data for the node entity type.
*/
class NodeViewsData extends EntityViewsData {
/**
* {@inheritdoc}
*/
public function getViewsData() {
$data = parent::getViewsData();
$data['node_field_data']['table']['base']['weight'] = -10;
$data['node_field_data']['table']['base']['access query tag'] = 'node_access';
$data['node_field_data']['table']['wizard_id'] = 'node';
$data['node_field_data']['nid']['field']['argument'] = [
'id' => 'node_nid',
'name field' => 'title',
'numeric' => TRUE,
'validate type' => 'nid',
];
$data['node_field_data']['title']['field']['default_formatter_settings'] = ['link_to_entity' => TRUE];
$data['node_field_data']['title']['field']['link_to_node default'] = TRUE;
$data['node_field_data']['type']['argument']['id'] = 'node_type';
$data['node_field_data']['langcode']['help'] = t('The language of the content or translation.');
$data['node_field_data']['status']['filter']['label'] = t('Published status');
$data['node_field_data']['status']['filter']['type'] = 'yes-no';
// Use status = 1 instead of status <> 0 in WHERE statement.
$data['node_field_data']['status']['filter']['use_equal'] = TRUE;
$data['node_field_data']['status_extra'] = array(
'title' => t('Published status or admin user'),
'help' => t('Filters out unpublished content if the current user cannot view it.'),
'filter' => array(
'field' => 'status',
'id' => 'node_status',
'label' => t('Published status or admin user'),
),
);
$data['node_field_data']['promote']['filter']['label'] = t('Promoted to front page status');
$data['node_field_data']['promote']['filter']['type'] = 'yes-no';
$data['node_field_data']['sticky']['filter']['label'] = t('Sticky status');
$data['node_field_data']['sticky']['filter']['type'] = 'yes-no';
$data['node_field_data']['sticky']['sort']['help'] = t('Whether or not the content is sticky. To list sticky content first, set this to descending.');
$data['node']['path'] = array(
'field' => array(
'title' => t('Path'),
'help' => t('The aliased path to this content.'),
'id' => 'node_path',
),
);
$data['node']['node_bulk_form'] = array(
'title' => t('Node operations bulk form'),
'help' => t('Add a form element that lets you run operations on multiple nodes.'),
'field' => array(
'id' => 'node_bulk_form',
),
);
// Bogus fields for aliasing purposes.
// @todo Add similar support to any date field
// @see https://www.drupal.org/node/2337507
$data['node_field_data']['created_fulldate'] = array(
'title' => t('Created date'),
'help' => t('Date in the form of CCYYMMDD.'),
'argument' => array(
'field' => 'created',
'id' => 'date_fulldate',
),
);
$data['node_field_data']['created_year_month'] = array(
'title' => t('Created year + month'),
'help' => t('Date in the form of YYYYMM.'),
'argument' => array(
'field' => 'created',
'id' => 'date_year_month',
),
);
$data['node_field_data']['created_year'] = array(
'title' => t('Created year'),
'help' => t('Date in the form of YYYY.'),
'argument' => array(
'field' => 'created',
'id' => 'date_year',
),
);
$data['node_field_data']['created_month'] = array(
'title' => t('Created month'),
'help' => t('Date in the form of MM (01 - 12).'),
'argument' => array(
'field' => 'created',
'id' => 'date_month',
),
);
$data['node_field_data']['created_day'] = array(
'title' => t('Created day'),
'help' => t('Date in the form of DD (01 - 31).'),
'argument' => array(
'field' => 'created',
'id' => 'date_day',
),
);
$data['node_field_data']['created_week'] = array(
'title' => t('Created week'),
'help' => t('Date in the form of WW (01 - 53).'),
'argument' => array(
'field' => 'created',
'id' => 'date_week',
),
);
$data['node_field_data']['changed_fulldate'] = array(
'title' => t('Updated date'),
'help' => t('Date in the form of CCYYMMDD.'),
'argument' => array(
'field' => 'changed',
'id' => 'date_fulldate',
),
);
$data['node_field_data']['changed_year_month'] = array(
'title' => t('Updated year + month'),
'help' => t('Date in the form of YYYYMM.'),
'argument' => array(
'field' => 'changed',
'id' => 'date_year_month',
),
);
$data['node_field_data']['changed_year'] = array(
'title' => t('Updated year'),
'help' => t('Date in the form of YYYY.'),
'argument' => array(
'field' => 'changed',
'id' => 'date_year',
),
);
$data['node_field_data']['changed_month'] = array(
'title' => t('Updated month'),
'help' => t('Date in the form of MM (01 - 12).'),
'argument' => array(
'field' => 'changed',
'id' => 'date_month',
),
);
$data['node_field_data']['changed_day'] = array(
'title' => t('Updated day'),
'help' => t('Date in the form of DD (01 - 31).'),
'argument' => array(
'field' => 'changed',
'id' => 'date_day',
),
);
$data['node_field_data']['changed_week'] = array(
'title' => t('Updated week'),
'help' => t('Date in the form of WW (01 - 53).'),
'argument' => array(
'field' => 'changed',
'id' => 'date_week',
),
);
$data['node_field_data']['uid']['help'] = t('The user authoring the content. If you need more fields than the uid add the content: author relationship');
$data['node_field_data']['uid']['filter']['id'] = 'user_name';
$data['node_field_data']['uid']['relationship']['title'] = t('Content author');
$data['node_field_data']['uid']['relationship']['help'] = t('Relate content to the user who created it.');
$data['node_field_data']['uid']['relationship']['label'] = t('author');
$data['node']['node_listing_empty'] = array(
'title' => t('Empty Node Frontpage behavior'),
'help' => t('Provides a link to the node add overview page.'),
'area' => array(
'id' => 'node_listing_empty',
),
);
$data['node_field_data']['uid_revision']['title'] = t('User has a revision');
$data['node_field_data']['uid_revision']['help'] = t('All nodes where a certain user has a revision');
$data['node_field_data']['uid_revision']['real field'] = 'nid';
$data['node_field_data']['uid_revision']['filter']['id'] = 'node_uid_revision';
$data['node_field_data']['uid_revision']['argument']['id'] = 'node_uid_revision';
$data['node_field_revision']['table']['wizard_id'] = 'node_revision';
// Advertise this table as a possible base table.
$data['node_field_revision']['table']['base']['help'] = t('Content revision is a history of changes to content.');
$data['node_field_revision']['table']['base']['defaults']['title'] = 'title';
$data['node_field_revision']['nid']['argument'] = [
'id' => 'node_nid',
'numeric' => TRUE,
];
// @todo the NID field needs different behaviour on revision/non-revision
// tables. It would be neat if this could be encoded in the base field
// definition.
$data['node_field_revision']['nid']['relationship']['id'] = 'standard';
$data['node_field_revision']['nid']['relationship']['base'] = 'node_field_data';
$data['node_field_revision']['nid']['relationship']['base field'] = 'nid';
$data['node_field_revision']['nid']['relationship']['title'] = t('Content');
$data['node_field_revision']['nid']['relationship']['label'] = t('Get the actual content from a content revision.');
$data['node_field_revision']['vid'] = array(
'argument' => array(
'id' => 'node_vid',
'numeric' => TRUE,
),
'relationship' => array(
'id' => 'standard',
'base' => 'node_field_data',
'base field' => 'vid',
'title' => t('Content'),
'label' => t('Get the actual content from a content revision.'),
),
) + $data['node_revision']['vid'];
$data['node_field_revision']['langcode']['help'] = t('The language the original content is in.');
$data['node_revision']['revision_uid']['help'] = t('Relate a content revision to the user who created the revision.');
$data['node_revision']['revision_uid']['relationship']['label'] = t('revision user');
$data['node_field_revision']['table']['wizard_id'] = 'node_field_revision';
$data['node_field_revision']['table']['join']['node_field_data']['left_field'] = 'vid';
$data['node_field_revision']['table']['join']['node_field_data']['field'] = 'vid';
$data['node_field_revision']['status']['filter']['label'] = t('Published');
$data['node_field_revision']['status']['filter']['type'] = 'yes-no';
$data['node_field_revision']['status']['filter']['use_equal'] = TRUE;
$data['node_field_revision']['langcode']['help'] = t('The language of the content or translation.');
$data['node_field_revision']['link_to_revision'] = array(
'field' => array(
'title' => t('Link to revision'),
'help' => t('Provide a simple link to the revision.'),
'id' => 'node_revision_link',
'click sortable' => FALSE,
),
);
$data['node_field_revision']['revert_revision'] = array(
'field' => array(
'title' => t('Link to revert revision'),
'help' => t('Provide a simple link to revert to the revision.'),
'id' => 'node_revision_link_revert',
'click sortable' => FALSE,
),
);
$data['node_field_revision']['delete_revision'] = array(
'field' => array(
'title' => t('Link to delete revision'),
'help' => t('Provide a simple link to delete the content revision.'),
'id' => 'node_revision_link_delete',
'click sortable' => FALSE,
),
);
// Define the base group of this table. Fields that don't have a group defined
// will go into this field by default.
$data['node_access']['table']['group'] = t('Content access');
// For other base tables, explain how we join.
$data['node_access']['table']['join'] = array(
'node_field_data' => array(
'left_field' => 'nid',
'field' => 'nid',
),
);
$data['node_access']['nid'] = array(
'title' => t('Access'),
'help' => t('Filter by access.'),
'filter' => array(
'id' => 'node_access',
'help' => t('Filter for content by view access. <strong>Not necessary if you are using node as your base table.</strong>'),
),
);
// Add search table, fields, filters, etc., but only if a page using the
// node_search plugin is enabled.
if (\Drupal::moduleHandler()->moduleExists('search')) {
$enabled = FALSE;
$search_page_repository = \Drupal::service('search.search_page_repository');
foreach ($search_page_repository->getActiveSearchpages() as $page) {
if ($page->getPlugin()->getPluginId() == 'node_search') {
$enabled = TRUE;
break;
}
}
if ($enabled) {
$data['node_search_index']['table']['group'] = t('Search');
// Automatically join to the node table (or actually, node_field_data).
// Use a Views table alias to allow other modules to use this table too,
// if they use the search index.
$data['node_search_index']['table']['join'] = array(
'node_field_data' => array(
'left_field' => 'nid',
'field' => 'sid',
'table' => 'search_index',
'extra' => "node_search_index.type = 'node_search' AND node_search_index.langcode = node_field_data.langcode",
)
);
$data['node_search_total']['table']['join'] = array(
'node_search_index' => array(
'left_field' => 'word',
'field' => 'word',
),
);
$data['node_search_dataset']['table']['join'] = array(
'node_field_data' => array(
'left_field' => 'sid',
'left_table' => 'node_search_index',
'field' => 'sid',
'table' => 'search_dataset',
'extra' => 'node_search_index.type = node_search_dataset.type AND node_search_index.langcode = node_search_dataset.langcode',
'type' => 'INNER',
),
);
$data['node_search_index']['score'] = array(
'title' => t('Score'),
'help' => t('The score of the search item. This will not be used if the search filter is not also present.'),
'field' => array(
'id' => 'search_score',
'float' => TRUE,
'no group by' => TRUE,
),
'sort' => array(
'id' => 'search_score',
'no group by' => TRUE,
),
);
$data['node_search_index']['keys'] = array(
'title' => t('Search Keywords'),
'help' => t('The keywords to search for.'),
'filter' => array(
'id' => 'search_keywords',
'no group by' => TRUE,
'search_type' => 'node_search',
),
'argument' => array(
'id' => 'search',
'no group by' => TRUE,
'search_type' => 'node_search',
),
);
}
}
return $data;
}
}

View file

@ -0,0 +1,49 @@
<?php
/**
* @file
* Contains \Drupal\node\PageCache\DenyNodePreview.
*/
namespace Drupal\node\PageCache;
use Drupal\Core\PageCache\ResponsePolicyInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Cache policy for node preview page.
*
* This policy rule denies caching of responses generated by the
* entity.node.preview route.
*/
class DenyNodePreview implements ResponsePolicyInterface {
/**
* The current route match.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* Constructs a deny node preview page cache policy.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The current route match.
*/
public function __construct(RouteMatchInterface $route_match) {
$this->routeMatch = $route_match;
}
/**
* {@inheritdoc}
*/
public function check(Response $response, Request $request) {
if ($this->routeMatch->getRouteName() === 'entity.node.preview') {
return static::DENY;
}
}
}

View file

@ -0,0 +1,57 @@
<?php
/**
* @file
* Contains \Drupal\node\ParamConverter\NodePreviewConverter.
*/
namespace Drupal\node\ParamConverter;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\user\PrivateTempStoreFactory;
use Symfony\Component\Routing\Route;
use Drupal\Core\ParamConverter\ParamConverterInterface;
/**
* Provides upcasting for a node entity in preview.
*/
class NodePreviewConverter implements ParamConverterInterface {
/**
* Stores the tempstore factory.
*
* @var \Drupal\user\PrivateTempStoreFactory
*/
protected $tempStoreFactory;
/**
* Constructs a new NodePreviewConverter.
*
* @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
* The factory for the temp store object.
*/
public function __construct(PrivateTempStoreFactory $temp_store_factory) {
$this->tempStoreFactory = $temp_store_factory;
}
/**
* {@inheritdoc}
*/
public function convert($value, $definition, $name, array $defaults) {
$store = $this->tempStoreFactory->get('node_preview');
if ($form_state = $store->get($value)) {
return $form_state->getFormObject()->getEntity();
}
}
/**
* {@inheritdoc}
*/
public function applies($definition, $name, Route $route) {
if (!empty($definition['type']) && $definition['type'] == 'node_preview') {
return TRUE;
}
return FALSE;
}
}

View file

@ -0,0 +1,149 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\Action\AssignOwnerNode.
*/
namespace Drupal\node\Plugin\Action;
use Drupal\Core\Action\ConfigurableActionBase;
use Drupal\Core\Database\Connection;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\Entity\User;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Assigns ownership of a node to a user.
*
* @Action(
* id = "node_assign_owner_action",
* label = @Translation("Change the author of content"),
* type = "node"
* )
*/
class AssignOwnerNode extends ConfigurableActionBase implements ContainerFactoryPluginInterface {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* Constructs a new AssignOwnerNode action.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Database\Connection $connection
* The database connection.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $connection) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->connection = $connection;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition,
$container->get('database')
);
}
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
/** @var \Drupal\node\NodeInterface $entity */
$entity->setOwnerId($this->configuration['owner_uid'])->save();
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return array(
'owner_uid' => '',
);
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$description = t('The username of the user to which you would like to assign ownership.');
$count = $this->connection->query("SELECT COUNT(*) FROM {users}")->fetchField();
// Use dropdown for fewer than 200 users; textbox for more than that.
if (intval($count) < 200) {
$options = array();
$result = $this->connection->query("SELECT uid, name FROM {users_field_data} WHERE uid > 0 AND default_langcode = 1 ORDER BY name");
foreach ($result as $data) {
$options[$data->uid] = $data->name;
}
$form['owner_uid'] = array(
'#type' => 'select',
'#title' => t('Username'),
'#default_value' => $this->configuration['owner_uid'],
'#options' => $options,
'#description' => $description,
);
}
else {
$form['owner_uid'] = array(
'#type' => 'entity_autocomplete',
'#title' => t('Username'),
'#target_type' => 'user',
'#selection_setttings' => array(
'include_anonymous' => FALSE,
),
'#default_value' => User::load($this->configuration['owner_uid']),
// Validation is done in static::validateConfigurationForm().
'#validate_reference' => FALSE,
'#size' => '6',
'#maxlength' => '60',
'#description' => $description,
);
}
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
$exists = (bool) $this->connection->queryRange('SELECT 1 FROM {users_field_data} WHERE uid = :uid AND default_langcode = 1', 0, 1, array(':name' => $form_state->getValue('owner_uid')))->fetchField();
if (!$exists) {
$form_state->setErrorByName('owner_uid', t('Enter a valid username.'));
}
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['owner_uid'] = $form_state->getValue('owner_uid');
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\node\NodeInterface $object */
$result = $object->access('update', $account, TRUE)
->andIf($object->getOwner()->access('edit', $account, TRUE));
return $return_as_object ? $result : $result->isAllowed();
}
}

View file

@ -0,0 +1,104 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\Action\DeleteNode.
*/
namespace Drupal\node\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Redirects to a node deletion form.
*
* @Action(
* id = "node_delete_action",
* label = @Translation("Delete content"),
* type = "node",
* confirm_form_route_name = "node.multiple_delete_confirm"
* )
*/
class DeleteNode extends ActionBase implements ContainerFactoryPluginInterface {
/**
* The tempstore object.
*
* @var \Drupal\user\SharedTempStore
*/
protected $tempStore;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a new DeleteNode object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
* @param AccountInterface $current_user
* Current user.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) {
$this->currentUser = $current_user;
$this->tempStore = $temp_store_factory->get('node_multiple_delete_confirm');
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('user.private_tempstore'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
public function executeMultiple(array $entities) {
$info = [];
/** @var \Drupal\node\NodeInterface $node */
foreach ($entities as $node) {
$langcode = $node->language()->getId();
$info[$node->id()][$langcode] = $langcode;
}
$this->tempStore->set($this->currentUser->id(), $info);
}
/**
* {@inheritdoc}
*/
public function execute($object = NULL) {
$this->executeMultiple(array($object));
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\node\NodeInterface $object */
return $object->access('delete', $account, $return_as_object);
}
}

View file

@ -0,0 +1,43 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\Action\DemoteNode.
*/
namespace Drupal\node\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
/**
* Demotes a node.
*
* @Action(
* id = "node_unpromote_action",
* label = @Translation("Demote selected content from front page"),
* type = "node"
* )
*/
class DemoteNode extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
$entity->setPromoted(FALSE);
$entity->save();
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\node\NodeInterface $object */
$result = $object->access('update', $account, TRUE)
->andIf($object->promote->access('edit', $account, TRUE));
return $return_as_object ? $result : $result->isAllowed();
}
}

View file

@ -0,0 +1,42 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\Action\PromoteNode.
*/
namespace Drupal\node\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
/**
* Promotes a node.
*
* @Action(
* id = "node_promote_action",
* label = @Translation("Promote selected content to front page"),
* type = "node"
* )
*/
class PromoteNode extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
$entity->setPromoted(TRUE);
$entity->save();
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\node\NodeInterface $object */
$access = $object->access('update', $account, TRUE)
->andif($object->promote->access('edit', $account, TRUE));
return $return_as_object ? $access : $access->isAllowed();
}
}

View file

@ -0,0 +1,43 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\Action\PublishNode.
*/
namespace Drupal\node\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
/**
* Publishes a node.
*
* @Action(
* id = "node_publish_action",
* label = @Translation("Publish selected content"),
* type = "node"
* )
*/
class PublishNode extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
$entity->status = NODE_PUBLISHED;
$entity->save();
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\node\NodeInterface $object */
$result = $object->access('update', $account, TRUE)
->andIf($object->status->access('edit', $account, TRUE));
return $return_as_object ? $result : $result->isAllowed();
}
}

View file

@ -0,0 +1,42 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\Action\SaveNode.
*/
namespace Drupal\node\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
/**
* Provides an action that can save any entity.
*
* @Action(
* id = "node_save_action",
* label = @Translation("Save content"),
* type = "node"
* )
*/
class SaveNode extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
// We need to change at least one value, otherwise the changed timestamp
// will not be updated.
$entity->changed = 0;
$entity->save();
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\node\NodeInterface $object */
return $object->access('update', $account, $return_as_object);
}
}

View file

@ -0,0 +1,42 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\Action\StickyNode.
*/
namespace Drupal\node\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
/**
* Makes a node sticky.
*
* @Action(
* id = "node_make_sticky_action",
* label = @Translation("Make selected content sticky"),
* type = "node"
* )
*/
class StickyNode extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
$entity->sticky = NODE_STICKY;
$entity->save();
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\node\NodeInterface $object */
$access = $object->access('update', $account, TRUE)
->andif($object->sticky->access('edit', $account, TRUE));
return $return_as_object ? $access : $access->isAllowed();
}
}

View file

@ -0,0 +1,80 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\Action\UnpublishByKeywordNode.
*/
namespace Drupal\node\Plugin\Action;
use Drupal\Component\Utility\Tags;
use Drupal\Core\Action\ConfigurableActionBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Unpublishes a node containing certain keywords.
*
* @Action(
* id = "node_unpublish_by_keyword_action",
* label = @Translation("Unpublish content containing keyword(s)"),
* type = "node"
* )
*/
class UnpublishByKeywordNode extends ConfigurableActionBase {
/**
* {@inheritdoc}
*/
public function execute($node = NULL) {
foreach ($this->configuration['keywords'] as $keyword) {
$elements = node_view(clone $node);
if (strpos(drupal_render($elements), $keyword) !== FALSE || strpos($node->label(), $keyword) !== FALSE) {
$node->setPublished(FALSE);
$node->save();
break;
}
}
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return array(
'keywords' => array(),
);
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['keywords'] = array(
'#title' => t('Keywords'),
'#type' => 'textarea',
'#description' => t('The content will be unpublished if it contains any of the phrases above. Use a case-sensitive, comma-separated list of phrases. Example: funny, bungee jumping, "Company, Inc."'),
'#default_value' => Tags::implode($this->configuration['keywords']),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['keywords'] = Tags::explode($form_state->getValue('keywords'));
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\node\NodeInterface $object */
$access = $object->access('update', $account, TRUE)
->andIf($object->status->access('edit', $account, TRUE));
return $return_as_object ? $access : $access->isAllowed();
}
}

View file

@ -0,0 +1,43 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\Action\UnpublishNode.
*/
namespace Drupal\node\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
/**
* Unpublishes a node.
*
* @Action(
* id = "node_unpublish_action",
* label = @Translation("Unpublish selected content"),
* type = "node"
* )
*/
class UnpublishNode extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
$entity->status = NODE_NOT_PUBLISHED;
$entity->save();
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\node\NodeInterface $object */
$access = $object->access('update', $account, TRUE)
->andIf($object->status->access('edit', $account, TRUE));
return $return_as_object ? $access : $access->isAllowed();
}
}

View file

@ -0,0 +1,43 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\Action\UnstickyNode.
*/
namespace Drupal\node\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
/**
* Makes a node not sticky.
*
* @Action(
* id = "node_make_unsticky_action",
* label = @Translation("Make selected content not sticky"),
* type = "node"
* )
*/
class UnstickyNode extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
$entity->sticky = NODE_NOT_STICKY;
$entity->save();
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\node\NodeInterface $object */
$access = $object->access('update', $account, TRUE)
->andIf($object->sticky->access('edit', $account, TRUE));
return $return_as_object ? $access : $access->isAllowed();
}
}

View file

@ -0,0 +1,76 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\Block\SyndicateBlock.
*/
namespace Drupal\node\Plugin\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Provides a 'Syndicate' block that links to the site's RSS feed.
*
* @Block(
* id = "node_syndicate_block",
* admin_label = @Translation("Syndicate"),
* category = @Translation("System")
* )
*/
class SyndicateBlock extends BlockBase {
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return array(
'block_count' => 10,
);
}
/**
* {@inheritdoc}
*/
protected function blockAccess(AccountInterface $account) {
return AccessResult::allowedIfHasPermission($account, 'access content');
}
/**
* {@inheritdoc}
*/
public function build() {
return array(
'#theme' => 'feed_icon',
'#url' => 'rss.xml',
);
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
// @see ::getCacheMaxAge()
$form['cache']['#disabled'] = TRUE;
$form['cache']['max_age']['#value'] = Cache::PERMANENT;
$form['cache']['#description'] = $this->t('This block is always cached forever, it is not configurable.');
return $form;
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
// The 'Syndicate' block is permanently cacheable, because its
// contents can never change.
return Cache::PERMANENT;
}
}

View file

@ -0,0 +1,127 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\Condition\NodeType.
*/
namespace Drupal\node\Plugin\Condition;
use Drupal\Core\Condition\ConditionPluginBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a 'Node Type' condition.
*
* @Condition(
* id = "node_type",
* label = @Translation("Node Bundle"),
* context = {
* "node" = @ContextDefinition("entity:node", label = @Translation("Node"))
* }
* )
*
*/
class NodeType extends ConditionPluginBase implements ContainerFactoryPluginInterface {
/**
* The entity storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $entityStorage;
/**
* Creates a new NodeType instance.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage
* The entity storage.
* @param array $configuration
* The plugin configuration, i.e. an array with configuration values keyed
* by configuration option name. The special key 'context' may be used to
* initialize the defined contexts by setting it to an array of context
* values keyed by context names.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
*/
public function __construct(EntityStorageInterface $entity_storage, array $configuration, $plugin_id, $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityStorage = $entity_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$container->get('entity.manager')->getStorage('node_type'),
$configuration,
$plugin_id,
$plugin_definition
);
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$options = array();
$node_types = $this->entityStorage->loadMultiple();
foreach ($node_types as $type) {
$options[$type->id()] = $type->label();
}
$form['bundles'] = array(
'#title' => $this->t('Node types'),
'#type' => 'checkboxes',
'#options' => $options,
'#default_value' => $this->configuration['bundles'],
);
return parent::buildConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['bundles'] = array_filter($form_state->getValue('bundles'));
parent::submitConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function summary() {
if (count($this->configuration['bundles']) > 1) {
$bundles = $this->configuration['bundles'];
$last = array_pop($bundles);
$bundles = implode(', ', $bundles);
return $this->t('The node bundle is @bundles or @last', array('@bundles' => $bundles, '@last' => $last));
}
$bundle = reset($this->configuration['bundles']);
return $this->t('The node bundle is @bundle', array('@bundle' => $bundle));
}
/**
* {@inheritdoc}
*/
public function evaluate() {
if (empty($this->configuration['bundles']) && !$this->isNegated()) {
return TRUE;
}
$node = $this->getContextValue('node');
return !empty($this->configuration['bundles'][$node->getType()]);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return array('bundles' => array()) + parent::defaultConfiguration();
}
}

View file

@ -0,0 +1,51 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\EntityReferenceSelection\NodeSelection.
*/
namespace Drupal\node\Plugin\EntityReferenceSelection;
use Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides specific access control for the node entity type.
*
* @EntityReferenceSelection(
* id = "default:node",
* label = @Translation("Node selection"),
* entity_types = {"node"},
* group = "default",
* weight = 1
* )
*/
class NodeSelection extends SelectionBase {
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['target_bundles']['#title'] = $this->t('Content types');
return $form;
}
/**
* {@inheritdoc}
*/
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
$query = parent::buildEntityQuery($match, $match_operator);
// Adding the 'node_access' tag is sadly insufficient for nodes: core
// requires us to also know about the concept of 'published' and
// 'unpublished'. We need to do that as long as there are no access control
// modules in use on the site. As long as one access control module is there,
// it is supposed to handle this check.
if (!$this->currentUser->hasPermission('bypass node access') && !count($this->moduleHandler->getImplementations('node_grants'))) {
$query->condition('status', NODE_PUBLISHED);
}
return $query;
}
}

View file

@ -0,0 +1,702 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\Search\NodeSearch.
*/
namespace Drupal\node\Plugin\Search;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Config\Config;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\SelectExtender;
use Drupal\Core\Database\StatementInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessibleInterface;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Render\RendererInterface;
use Drupal\node\NodeInterface;
use Drupal\search\Plugin\ConfigurableSearchPluginBase;
use Drupal\search\Plugin\SearchIndexingInterface;
use Drupal\Search\SearchQuery;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Handles searching for node entities using the Search module index.
*
* @SearchPlugin(
* id = "node_search",
* title = @Translation("Content")
* )
*/
class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInterface, SearchIndexingInterface {
/**
* A database connection object.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* An entity manager object.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* A module manager object.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* A config object for 'search.settings'.
*
* @var \Drupal\Core\Config\Config
*/
protected $searchSettings;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The Drupal account to use for checking for access to advanced search.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* The Renderer service to format the username and node.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* An array of additional rankings from hook_ranking().
*
* @var array
*/
protected $rankings;
/**
* The list of options and info for advanced search filters.
*
* Each entry in the array has the option as the key and and for its value, an
* array that determines how the value is matched in the database query. The
* possible keys in that array are:
* - column: (required) Name of the database column to match against.
* - join: (optional) Information on a table to join. By default the data is
* matched against the {node_field_data} table.
* - operator: (optional) OR or AND, defaults to OR.
*
* @var array
*/
protected $advanced = array(
'type' => array('column' => 'n.type'),
'language' => array('column' => 'i.langcode'),
'author' => array('column' => 'n.uid'),
'term' => array('column' => 'ti.tid', 'join' => array('table' => 'taxonomy_index', 'alias' => 'ti', 'condition' => 'n.nid = ti.nid')),
);
/**
* {@inheritdoc}
*/
static public function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('database'),
$container->get('entity.manager'),
$container->get('module_handler'),
$container->get('config.factory')->get('search.settings'),
$container->get('language_manager'),
$container->get('renderer'),
$container->get('current_user')
);
}
/**
* Constructs a \Drupal\node\Plugin\Search\NodeSearch object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Database\Connection $database
* A database connection object.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* An entity manager object.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* A module manager object.
* @param \Drupal\Core\Config\Config $search_settings
* A config object for 'search.settings'.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Session\AccountInterface $account
* The $account object to use for checking for access to advanced search.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, Config $search_settings, LanguageManagerInterface $language_manager, RendererInterface $renderer, AccountInterface $account = NULL) {
$this->database = $database;
$this->entityManager = $entity_manager;
$this->moduleHandler = $module_handler;
$this->searchSettings = $search_settings;
$this->languageManager = $language_manager;
$this->renderer = $renderer;
$this->account = $account;
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
public function access($operation = 'view', AccountInterface $account = NULL, $return_as_object = FALSE) {
$result = AccessResult::allowedIfHasPermission($account, 'access content');
return $return_as_object ? $result : $result->isAllowed();
}
/**
* {@inheritdoc}
*/
public function isSearchExecutable() {
// Node search is executable if we have keywords or an advanced parameter.
// At least, we should parse out the parameters and see if there are any
// keyword matches in that case, rather than just printing out the
// "Please enter keywords" message.
return !empty($this->keywords) || (isset($this->searchParameters['f']) && count($this->searchParameters['f']));
}
/**
* {@inheritdoc}
*/
public function getType() {
return $this->getPluginId();
}
/**
* {@inheritdoc}
*/
public function execute() {
if ($this->isSearchExecutable()) {
$results = $this->findResults();
if ($results) {
return $this->prepareResults($results);
}
}
return array();
}
/**
* Queries to find search results, and sets status messages.
*
* This method can assume that $this->isSearchExecutable() has already been
* checked and returned TRUE.
*
* @return \Drupal\Core\Database\StatementInterface|null
* Results from search query execute() method, or NULL if the search
* failed.
*/
protected function findResults() {
$keys = $this->keywords;
// Build matching conditions.
$query = $this->database
->select('search_index', 'i', array('target' => 'replica'))
->extend('Drupal\search\SearchQuery')
->extend('Drupal\Core\Database\Query\PagerSelectExtender');
$query->join('node_field_data', 'n', 'n.nid = i.sid');
$query->condition('n.status', 1)
->addTag('node_access')
->searchExpression($keys, $this->getPluginId());
// Handle advanced search filters in the f query string.
// \Drupal::request()->query->get('f') is an array that looks like this in
// the URL: ?f[]=type:page&f[]=term:27&f[]=term:13&f[]=langcode:en
// So $parameters['f'] looks like:
// array('type:page', 'term:27', 'term:13', 'langcode:en');
// We need to parse this out into query conditions, some of which go into
// the keywords string, and some of which are separate conditions.
$parameters = $this->getParameters();
if (!empty($parameters['f']) && is_array($parameters['f'])) {
$filters = array();
// Match any query value that is an expected option and a value
// separated by ':' like 'term:27'.
$pattern = '/^(' . implode('|', array_keys($this->advanced)) . '):([^ ]*)/i';
foreach ($parameters['f'] as $item) {
if (preg_match($pattern, $item, $m)) {
// Use the matched value as the array key to eliminate duplicates.
$filters[$m[1]][$m[2]] = $m[2];
}
}
// Now turn these into query conditions. This assumes that everything in
// $filters is a known type of advanced search.
foreach ($filters as $option => $matched) {
$info = $this->advanced[$option];
// Insert additional conditions. By default, all use the OR operator.
$operator = empty($info['operator']) ? 'OR' : $info['operator'];
$where = new Condition($operator);
foreach ($matched as $value) {
$where->condition($info['column'], $value);
}
$query->condition($where);
if (!empty($info['join'])) {
$query->join($info['join']['table'], $info['join']['alias'], $info['join']['condition']);
}
}
}
// Add the ranking expressions.
$this->addNodeRankings($query);
// Run the query.
$find = $query
// Add the language code of the indexed item to the result of the query,
// since the node will be rendered using the respective language.
->fields('i', array('langcode'))
// And since SearchQuery makes these into GROUP BY queries, if we add
// a field, for PostgreSQL we also need to make it an aggregate or a
// GROUP BY. In this case, we want GROUP BY.
->groupBy('i.langcode')
->limit(10)
->execute();
// Check query status and set messages if needed.
$status = $query->getStatus();
if ($status & SearchQuery::EXPRESSIONS_IGNORED) {
drupal_set_message($this->t('Your search used too many AND/OR expressions. Only the first @count terms were included in this search.', array('@count' => $this->searchSettings->get('and_or_limit'))), 'warning');
}
if ($status & SearchQuery::LOWER_CASE_OR) {
drupal_set_message($this->t('Search for either of the two terms with uppercase <strong>OR</strong>. For example, <strong>cats OR dogs</strong>.'), 'warning');
}
if ($status & SearchQuery::NO_POSITIVE_KEYWORDS) {
drupal_set_message($this->formatPlural($this->searchSettings->get('index.minimum_word_size'), 'You must include at least one positive keyword with 1 character or more.', 'You must include at least one positive keyword with @count characters or more.'), 'warning');
}
return $find;
}
/**
* Prepares search results for rendering.
*
* @param \Drupal\Core\Database\StatementInterface $found
* Results found from a successful search query execute() method.
*
* @return array
* Array of search result item render arrays (empty array if no results).
*/
protected function prepareResults(StatementInterface $found) {
$results = array();
$node_storage = $this->entityManager->getStorage('node');
$node_render = $this->entityManager->getViewBuilder('node');
$keys = $this->keywords;
foreach ($found as $item) {
// Render the node.
/** @var \Drupal\node\NodeInterface $node */
$node = $node_storage->load($item->sid)->getTranslation($item->langcode);
$build = $node_render->view($node, 'search_result', $item->langcode);
/** @var \Drupal\node\NodeTypeInterface $type*/
$type = $this->entityManager->getStorage('node_type')->load($node->bundle());
unset($build['#theme']);
$build['#pre_render'][] = array($this, 'removeSubmittedInfo');
// Fetch comment count for snippet.
$rendered = SafeMarkup::set(
$this->renderer->renderPlain($build) . ' ' .
SafeMarkup::escape($this->moduleHandler->invoke('comment', 'node_update_index', array($node, $item->langcode)))
);
$extra = $this->moduleHandler->invokeAll('node_search_result', array($node, $item->langcode));
$language = $this->languageManager->getLanguage($item->langcode);
$username = array(
'#theme' => 'username',
'#account' => $node->getOwner(),
);
$result = array(
'link' => $node->url('canonical', array('absolute' => TRUE, 'language' => $language)),
'type' => SafeMarkup::checkPlain($type->label()),
'title' => $node->label(),
'node' => $node,
'extra' => $extra,
'score' => $item->calculated_score,
'snippet' => search_excerpt($keys, $rendered, $item->langcode),
'langcode' => $node->language()->getId(),
);
if ($type->displaySubmitted()) {
$result += array(
'user' => $this->renderer->renderPlain($username),
'date' => $node->getChangedTime(),
);
}
$results[] = $result;
}
return $results;
}
/**
* Removes the submitted by information from the build array.
*
* This information is being removed from the rendered node that is used to
* build the search result snippet. It just doesn't make sense to have it
* displayed in the snippet.
*
* @param array $build
* The build array.
*
* @return array
* The modified build array.
*/
public function removeSubmittedInfo(array $build) {
unset($build['created']);
unset($build['uid']);
return $build;
}
/**
* Adds the configured rankings to the search query.
*
* @param $query
* A query object that has been extended with the Search DB Extender.
*/
protected function addNodeRankings(SelectExtender $query) {
if ($ranking = $this->getRankings()) {
$tables = &$query->getTables();
foreach ($ranking as $rank => $values) {
if (isset($this->configuration['rankings'][$rank]) && !empty($this->configuration['rankings'][$rank])) {
$node_rank = $this->configuration['rankings'][$rank];
// If the table defined in the ranking isn't already joined, then add it.
if (isset($values['join']) && !isset($tables[$values['join']['alias']])) {
$query->addJoin($values['join']['type'], $values['join']['table'], $values['join']['alias'], $values['join']['on']);
}
$arguments = isset($values['arguments']) ? $values['arguments'] : array();
$query->addScore($values['score'], $arguments, $node_rank);
}
}
}
}
/**
* {@inheritdoc}
*/
public function updateIndex() {
// Interpret the cron limit setting as the maximum number of nodes to index
// per cron run.
$limit = (int) $this->searchSettings->get('index.cron_limit');
$result = $this->database->queryRange("SELECT n.nid, MAX(sd.reindex) FROM {node} n LEFT JOIN {search_dataset} sd ON sd.sid = n.nid AND sd.type = :type WHERE sd.sid IS NULL OR sd.reindex <> 0 GROUP BY n.nid ORDER BY MAX(sd.reindex) is null DESC, MAX(sd.reindex) ASC, n.nid ASC", 0, $limit, array(':type' => $this->getPluginId()), array('target' => 'replica'));
$nids = $result->fetchCol();
if (!$nids) {
return;
}
$node_storage = $this->entityManager->getStorage('node');
foreach ($node_storage->loadMultiple($nids) as $node) {
$this->indexNode($node);
}
}
/**
* Indexes a single node.
*
* @param \Drupal\node\NodeInterface $node
* The node to index.
*/
protected function indexNode(NodeInterface $node) {
$languages = $node->getTranslationLanguages();
$node_render = $this->entityManager->getViewBuilder('node');
foreach ($languages as $language) {
$node = $node->getTranslation($language->getId());
// Render the node.
$build = $node_render->view($node, 'search_index', $language->getId());
unset($build['#theme']);
$rendered = $this->renderer->renderPlain($build);
$text = '<h1>' . SafeMarkup::checkPlain($node->label($language->getId())) . '</h1>' . $rendered;
// Fetch extra data normally not visible.
$extra = $this->moduleHandler->invokeAll('node_update_index', array($node, $language->getId()));
foreach ($extra as $t) {
$text .= $t;
}
// Update index, using search index "type" equal to the plugin ID.
search_index($this->getPluginId(), $node->id(), $language->getId(), $text);
}
}
/**
* {@inheritdoc}
*/
public function indexClear() {
// All NodeSearch pages share a common search index "type" equal to
// the plugin ID.
search_index_clear($this->getPluginId());
}
/**
* {@inheritdoc}
*/
public function markForReindex() {
// All NodeSearch pages share a common search index "type" equal to
// the plugin ID.
search_mark_for_reindex($this->getPluginId());
}
/**
* {@inheritdoc}
*/
public function indexStatus() {
$total = $this->database->query('SELECT COUNT(*) FROM {node}')->fetchField();
$remaining = $this->database->query("SELECT COUNT(DISTINCT n.nid) FROM {node} n LEFT JOIN {search_dataset} sd ON sd.sid = n.nid AND sd.type = :type WHERE sd.sid IS NULL OR sd.reindex <> 0", array(':type' => $this->getPluginId()))->fetchField();
return array('remaining' => $remaining, 'total' => $total);
}
/**
* {@inheritdoc}
*/
public function searchFormAlter(array &$form, FormStateInterface $form_state) {
// Add advanced search keyword-related boxes.
$form['advanced'] = array(
'#type' => 'details',
'#title' => t('Advanced search'),
'#attributes' => array('class' => array('search-advanced')),
'#access' => $this->account && $this->account->hasPermission('use advanced search'),
);
$form['advanced']['keywords-fieldset'] = array(
'#type' => 'fieldset',
'#title' => t('Keywords'),
);
$form['advanced']['keywords'] = array(
'#prefix' => '<div class="criterion">',
'#suffix' => '</div>',
);
$form['advanced']['keywords-fieldset']['keywords']['or'] = array(
'#type' => 'textfield',
'#title' => t('Containing any of the words'),
'#size' => 30,
'#maxlength' => 255,
);
$form['advanced']['keywords-fieldset']['keywords']['phrase'] = array(
'#type' => 'textfield',
'#title' => t('Containing the phrase'),
'#size' => 30,
'#maxlength' => 255,
);
$form['advanced']['keywords-fieldset']['keywords']['negative'] = array(
'#type' => 'textfield',
'#title' => t('Containing none of the words'),
'#size' => 30,
'#maxlength' => 255,
);
// Add node types.
$types = array_map(array('\Drupal\Component\Utility\SafeMarkup', 'checkPlain'), node_type_get_names());
$form['advanced']['types-fieldset'] = array(
'#type' => 'fieldset',
'#title' => t('Types'),
);
$form['advanced']['types-fieldset']['type'] = array(
'#type' => 'checkboxes',
'#title' => t('Only of the type(s)'),
'#prefix' => '<div class="criterion">',
'#suffix' => '</div>',
'#options' => $types,
);
$form['advanced']['submit'] = array(
'#type' => 'submit',
'#value' => t('Advanced search'),
'#prefix' => '<div class="action">',
'#suffix' => '</div>',
'#weight' => 100,
);
// Add languages.
$language_options = array();
$language_list = $this->languageManager->getLanguages(LanguageInterface::STATE_ALL);
foreach ($language_list as $langcode => $language) {
// Make locked languages appear special in the list.
$language_options[$langcode] = $language->isLocked() ? t('- @name -', array('@name' => $language->getName())) : $language->getName();
}
if (count($language_options) > 1) {
$form['advanced']['lang-fieldset'] = array(
'#type' => 'fieldset',
'#title' => t('Languages'),
);
$form['advanced']['lang-fieldset']['language'] = array(
'#type' => 'checkboxes',
'#title' => t('Languages'),
'#prefix' => '<div class="criterion">',
'#suffix' => '</div>',
'#options' => $language_options,
);
}
}
/*
* {@inheritdoc}
*/
public function buildSearchUrlQuery(FormStateInterface $form_state) {
// Read keyword and advanced search information from the form values,
// and put these into the GET parameters.
$keys = trim($form_state->getValue('keys'));
// Collect extra filters.
$filters = array();
if ($form_state->hasValue('type') && is_array($form_state->getValue('type'))) {
// Retrieve selected types - Form API sets the value of unselected
// checkboxes to 0.
foreach ($form_state->getValue('type') as $type) {
if ($type) {
$filters[] = 'type:' . $type;
}
}
}
if ($form_state->hasValue('term') && is_array($form_state->getValue('term'))) {
foreach ($form_state->getValue('term') as $term) {
$filters[] = 'term:' . $term;
}
}
if ($form_state->hasValue('language') && is_array($form_state->getValue('language'))) {
foreach ($form_state->getValue('language') as $language) {
if ($language) {
$filters[] = 'language:' . $language;
}
}
}
if ($form_state->getValue('or') != '') {
if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state->getValue('or'), $matches)) {
$keys .= ' ' . implode(' OR ', $matches[1]);
}
}
if ($form_state->getValue('negative') != '') {
if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state->getValue('negative'), $matches)) {
$keys .= ' -' . implode(' -', $matches[1]);
}
}
if ($form_state->getValue('phrase') != '') {
$keys .= ' "' . str_replace('"', ' ', $form_state->getValue('phrase')) . '"';
}
$keys = trim($keys);
// Put the keywords and advanced parameters into GET parameters. Make sure
// to put keywords into the query even if it is empty, because the page
// controller uses that to decide it's time to check for search results.
$query = array('keys' => $keys);
if ($filters) {
$query['f'] = $filters;
}
return $query;
}
/**
* Gathers ranking definitions from hook_ranking().
*
* @return array
* An array of ranking definitions.
*/
protected function getRankings() {
if (!$this->rankings) {
$this->rankings = $this->moduleHandler->invokeAll('ranking');
}
return $this->rankings;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
$configuration = array(
'rankings' => array(),
);
return $configuration;
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
// Output form for defining rank factor weights.
$form['content_ranking'] = array(
'#type' => 'details',
'#title' => t('Content ranking'),
'#open' => TRUE,
);
$form['content_ranking']['info'] = array(
'#markup' => '<p><em>' . $this->t('Influence is a numeric multiplier used in ordering search results. A higher number means the corresponding factor has more influence on search results; zero means the factor is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '</em></p>'
);
// Prepare table.
$header = [$this->t('Factor'), $this->t('Influence')];
$form['content_ranking']['rankings'] = array(
'#type' => 'table',
'#header' => $header,
);
// Note: reversed to reflect that higher number = higher ranking.
$range = range(0, 10);
$options = array_combine($range, $range);
foreach ($this->getRankings() as $var => $values) {
$form['content_ranking']['rankings'][$var]['name'] = array(
'#markup' => $values['title'],
);
$form['content_ranking']['rankings'][$var]['value'] = array(
'#type' => 'select',
'#options' => $options,
'#attributes' => ['aria-label' => $this->t("Influence of '@title'", ['@title' => $values['title']])],
'#default_value' => isset($this->configuration['rankings'][$var]) ? $this->configuration['rankings'][$var] : 0,
);
}
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
foreach ($this->getRankings() as $var => $values) {
if (!$form_state->isValueEmpty(['rankings', $var, 'value'])) {
$this->configuration['rankings'][$var] = $form_state->getValue(['rankings', $var, 'value']);
}
else {
unset($this->configuration['rankings'][$var]);
}
}
}
}

View file

@ -0,0 +1,82 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\views\area\ListingEmpty.
*/
namespace Drupal\node\Plugin\views\area;
use Drupal\Core\Access\AccessManagerInterface;
use Drupal\Core\Url;
use Drupal\views\Plugin\views\area\AreaPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines an area plugin to display a node/add link.
*
* @ingroup views_area_handlers
*
* @ViewsArea("node_listing_empty")
*/
class ListingEmpty extends AreaPluginBase {
/**
* The access manager.
*
* @var \Drupal\Core\Access\AccessManagerInterface
*/
protected $accessManager;
/**
* Constructs a new ListingEmpty.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Access\AccessManagerInterface $access_manager
* The access manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, AccessManagerInterface $access_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->accessManager = $access_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('access_manager')
);
}
/**
* {@inheritdoc}
*/
public function render($empty = FALSE) {
$account = \Drupal::currentUser();
if (!$empty || !empty($this->options['empty'])) {
$element = array(
'#theme' => 'links',
'#links' => array(
array(
'url' => Url::fromRoute('node.add_page'),
'title' => $this->t('Add content'),
),
),
'#access' => $this->accessManager->checkNamedRoute('node.add_page', array(), $account),
);
return $element;
}
return array();
}
}

View file

@ -0,0 +1,70 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\views\argument\Nid.
*/
namespace Drupal\node\Plugin\views\argument;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\node\NodeStorageInterface;
use Drupal\views\Plugin\views\argument\NumericArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Argument handler to accept a node id.
*
* @ViewsArgument("node_nid")
*/
class Nid extends NumericArgument {
/**
* The node storage.
*
* @var \Drupal\node\NodeStorageInterface
*/
protected $nodeStorage;
/**
* Constructs the Nid object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param NodeStorageInterface $node_storage
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, NodeStorageInterface $node_storage) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->nodeStorage = $node_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager')->getStorage('node')
);
}
/**
* Override the behavior of title(). Get the title of the node.
*/
public function titleQuery() {
$titles = array();
$nodes = $this->nodeStorage->loadMultiple($this->value);
foreach ($nodes as $node) {
$titles[] = SafeMarkup::checkPlain($node->label());
}
return $titles;
}
}

View file

@ -0,0 +1,82 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\views\argument\Type.
*/
namespace Drupal\node\Plugin\views\argument;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\views\Plugin\views\argument\StringArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Argument handler to accept a node type.
*
* @ViewsArgument("node_type")
*/
class Type extends StringArgument {
/**
* NodeType storage controller.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $nodeTypeStorage;
/**
* Constructs a new Node Type object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage class.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityStorageInterface $node_type_storage) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->nodeTypeStorage = $node_type_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$entity_manager = $container->get('entity.manager');
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$entity_manager->getStorage('node_type')
);
}
/**
* Override the behavior of summaryName(). Get the user friendly version
* of the node type.
*/
public function summaryName($data) {
return $this->node_type($data->{$this->name_alias});
}
/**
* Override the behavior of title(). Get the user friendly version of the
* node type.
*/
function title() {
return $this->node_type($this->argument);
}
function node_type($type_name) {
$type = $this->nodeTypeStorage->load($type_name);
$output = $type ? $type->label() : $this->t('Unknown content type');
return SafeMarkup::checkPlain($output);
}
}

View file

@ -0,0 +1,26 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\views\argument\UidRevision.
*/
namespace Drupal\node\Plugin\views\argument;
use Drupal\user\Plugin\views\argument\Uid;
/**
* Filter handler to accept a user id to check for nodes that
* user posted or created a revision on.
*
* @ViewsArgument("node_uid_revision")
*/
class UidRevision extends Uid {
public function query($group_by = FALSE) {
$this->ensureMyTable();
$placeholder = $this->placeholder();
$this->query->addWhereExpression(0, "$this->tableAlias.revision_uid = $placeholder OR ((SELECT COUNT(DISTINCT vid) FROM {node_revision} nr WHERE nfr.revision_uid = $placeholder AND nr.nid = $this->tableAlias.nid) > 0)", array($placeholder => $this->argument));
}
}

View file

@ -0,0 +1,93 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\views\argument\Vid.
*/
namespace Drupal\node\Plugin\views\argument;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Database\Connection;
use Drupal\views\Plugin\views\argument\NumericArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\node\NodeStorageInterface;
/**
* Argument handler to accept a node revision id.
*
* @ViewsArgument("node_vid")
*/
class Vid extends NumericArgument {
/**
* Database Service Object.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* The node storage.
*
* @var \Drupal\node\NodeStorageInterface
*/
protected $nodeStorage;
/**
* Constructs a Drupal\Component\Plugin\PluginBase object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Database\Connection $database
* Database Service Object.
* @param \Drupal\node\NodeStorageInterface
* The node storage.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, NodeStorageInterface $node_storage) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->database = $database;
$this->nodeStorage = $node_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('database'),
$container->get('entity.manager')->getStorage('node')
);
}
/**
* Override the behavior of title(). Get the title of the revision.
*/
public function titleQuery() {
$titles = array();
$results = $this->database->query('SELECT nr.vid, nr.nid, npr.title FROM {node_revision} nr WHERE nr.vid IN ( :vids[] )', array(':vids[]' => $this->value))->fetchAllAssoc('vid', PDO::FETCH_ASSOC);
$nids = array();
foreach ($results as $result) {
$nids[] = $result['nid'];
}
$nodes = $this->nodeStorage->loadMultiple(array_unique($nids));
foreach ($results as $result) {
$nodes[$result['nid']]->set('title', $result['title']);
$titles[] = SafeMarkup::checkPlain($nodes[$result['nid']]->label());
}
return $titles;
}
}

View file

@ -0,0 +1,89 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\views\argument_default\Node.
*/
namespace Drupal\node\Plugin\views\argument_default;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\views\Plugin\CacheablePluginInterface;
use Drupal\views\Plugin\views\argument_default\ArgumentDefaultPluginBase;
use Drupal\node\NodeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Default argument plugin to extract a node.
*
* This plugin actually has no options so it odes not need to do a great deal.
*
* @ViewsArgumentDefault(
* id = "node",
* title = @Translation("Content ID from URL")
* )
*/
class Node extends ArgumentDefaultPluginBase implements CacheablePluginInterface {
/**
* The route match.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* Constructs a new Node instance.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteMatchInterface $route_match) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->routeMatch = $route_match;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('current_route_match')
);
}
/**
* {@inheritdoc}
*/
public function getArgument() {
if (($node = $this->routeMatch->getParameter('node')) && $node instanceof NodeInterface) {
return $node->id();
}
}
/**
* {@inheritdoc}
*/
public function isCacheable() {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return ['url'];
}
}

View file

@ -0,0 +1,105 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\views\field\Node.
*/
namespace Drupal\node\Plugin\views\field;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\field\FieldPluginBase;
/**
* Field handler to provide simple renderer that allows linking to a node.
* Definition terms:
* - link_to_node default: Should this field have the checkbox "link to node" enabled by default.
*
* @ingroup views_field_handlers
*
* @ViewsField("node")
*/
class Node extends FieldPluginBase {
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
// Don't add the additional fields to groupby
if (!empty($this->options['link_to_node'])) {
$this->additional_fields['nid'] = array('table' => 'node_field_data', 'field' => 'nid');
}
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['link_to_node'] = array('default' => isset($this->definition['link_to_node default']) ? $this->definition['link_to_node default'] : FALSE);
return $options;
}
/**
* Provide link to node option
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$form['link_to_node'] = array(
'#title' => $this->t('Link this field to the original piece of content'),
'#description' => $this->t("Enable to override this field's links."),
'#type' => 'checkbox',
'#default_value' => !empty($this->options['link_to_node']),
);
parent::buildOptionsForm($form, $form_state);
}
/**
* Prepares link to the node.
*
* @param string $data
* The XSS safe string for the link text.
* @param \Drupal\views\ResultRow $values
* The values retrieved from a single row of a view's query result.
*
* @return string
* Returns a string for the link text.
*/
protected function renderLink($data, ResultRow $values) {
if (!empty($this->options['link_to_node']) && !empty($this->additional_fields['nid'])) {
if ($data !== NULL && $data !== '') {
$this->options['alter']['make_link'] = TRUE;
$this->options['alter']['url'] = Url::fromRoute('entity.node.canonical', ['node' => $this->getValue($values, 'nid')]);
if (isset($this->aliases['langcode'])) {
$languages = \Drupal::languageManager()->getLanguages();
$langcode = $this->getValue($values, 'langcode');
if (isset($languages[$langcode])) {
$this->options['alter']['language'] = $languages[$langcode];
}
else {
unset($this->options['alter']['language']);
}
}
}
else {
$this->options['alter']['make_link'] = FALSE;
}
}
return $data;
}
/**
* {@inheritdoc}
*/
public function render(ResultRow $values) {
$value = $this->getValue($values);
return $this->renderLink($this->sanitizeValue($value), $values);
}
}

View file

@ -0,0 +1,27 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\views\field\NodeBulkForm.
*/
namespace Drupal\node\Plugin\views\field;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\system\Plugin\views\field\BulkForm;
/**
* Defines a node operations bulk form element.
*
* @ViewsField("node_bulk_form")
*/
class NodeBulkForm extends BulkForm {
/**
* {@inheritdoc}
*/
protected function emptySelectedMessage() {
return $this->t('No content selected.');
}
}

View file

@ -0,0 +1,74 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\views\field\Path.
*/
namespace Drupal\node\Plugin\views\field;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\field\FieldPluginBase;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
/**
* Field handler to present the path to the node.
*
* @ingroup views_field_handlers
*
* @ViewsField("node_path")
*/
class Path extends FieldPluginBase {
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
$this->additional_fields['nid'] = 'nid';
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['absolute'] = array('default' => FALSE);
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$form['absolute'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Use absolute link (begins with "http://")'),
'#default_value' => $this->options['absolute'],
'#description' => $this->t('Enable this option to output an absolute link. Required if you want to use the path as a link destination (as in "output this field as a link" above).'),
'#fieldset' => 'alter',
);
}
/**
* {@inheritdoc}
*/
public function query() {
$this->ensureMyTable();
$this->addAdditionalFields();
}
/**
* {@inheritdoc}
*/
public function render(ResultRow $values) {
$nid = $this->getValue($values, 'nid');
return \Drupal::url('entity.node.canonical', ['node' => $nid], ['absolute' => $this->options['absolute']]);
}
}

View file

@ -0,0 +1,56 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\views\field\RevisionLink.
*/
namespace Drupal\node\Plugin\views\field;
use Drupal\Core\Url;
use Drupal\views\Plugin\views\field\LinkBase;
use Drupal\views\ResultRow;
/**
* Field handler to present a link to a node revision.
*
* @ingroup views_field_handlers
*
* @ViewsField("node_revision_link")
*/
class RevisionLink extends LinkBase {
/**
* {@inheritdoc}
*/
protected function getUrlInfo(ResultRow $row) {
/** @var \Drupal\node\NodeInterface $node */
$node = $this->getEntity($row);
// Current revision uses the node view path.
return !$node->isDefaultRevision() ?
Url::fromRoute('entity.node.revision', ['node' => $node->id(), 'node_revision' => $node->getRevisionId()]) :
$node->urlInfo();
}
/**
* {@inheritdoc}
*/
protected function renderLink(ResultRow $row) {
/** @var \Drupal\node\NodeInterface $node */
$node = $this->getEntity($row);
if (!$node->getRevisionid()) {
return '';
}
$text = parent::renderLink($row);
$this->options['alter']['query'] = $this->getDestinationArray();
return $text;
}
/**
* {@inheritdoc}
*/
protected function getDefaultLabel() {
return $this->t('View');
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\views\field\RevisionLinkDelete.
*/
namespace Drupal\node\Plugin\views\field;
use Drupal\Core\Url;
use Drupal\views\ResultRow;
/**
* Field handler to present link to delete a node revision.
*
* @ingroup views_field_handlers
*
* @ViewsField("node_revision_link_delete")
*/
class RevisionLinkDelete extends RevisionLink {
/**
* {@inheritdoc}
*/
protected function getUrlInfo(ResultRow $row) {
/** @var \Drupal\node\NodeInterface $node */
$node = $this->getEntity($row);
return Url::fromRoute('node.revision_delete_confirm', ['node' => $node->id(), 'node_revision' => $node->getRevisionId()]);
}
/**
* {@inheritdoc}
*/
protected function getDefaultLabel() {
return $this->t('Delete');
}
}

View file

@ -0,0 +1,37 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\views\field\RevisionLinkRevert.
*/
namespace Drupal\node\Plugin\views\field;
use Drupal\Core\Url;
use Drupal\views\ResultRow;
/**
* Field handler to present a link to revert a node to a revision.
*
* @ingroup views_field_handlers
*
* @ViewsField("node_revision_link_revert")
*/
class RevisionLinkRevert extends RevisionLink {
/**
* {@inheritdoc}
*/
protected function getUrlInfo(ResultRow $row) {
/** @var \Drupal\node\NodeInterface $node */
$node = $this->getEntity($row);
return Url::fromRoute('node.revision_revert_confirm', ['node' => $node->id(), 'node_revision' => $node->getRevisionId()]);
}
/**
* {@inheritdoc}
*/
protected function getDefaultLabel() {
return $this->t('Revert');
}
}

View file

@ -0,0 +1,61 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\views\filter\Access.
*/
namespace Drupal\node\Plugin\views\filter;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\filter\FilterPluginBase;
/**
* Filter by node_access records.
*
* @ingroup views_filter_handlers
*
* @ViewsFilter("node_access")
*/
class Access extends FilterPluginBase {
public function adminSummary() { }
protected function operatorForm(&$form, FormStateInterface $form_state) { }
public function canExpose() {
return FALSE;
}
/**
* See _node_access_where_sql() for a non-views query based implementation.
*/
public function query() {
$account = $this->view->getUser();
if (!$account->hasPermission('administer nodes')) {
$table = $this->ensureMyTable();
$grants = db_or();
foreach (node_access_grants('view', $account) as $realm => $gids) {
foreach ($gids as $gid) {
$grants->condition(db_and()
->condition($table . '.gid', $gid)
->condition($table . '.realm', $realm)
);
}
}
$this->query->addWhere('AND', $grants);
$this->query->addWhere('AND', $table . '.grant_view', 1, '>=');
}
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
$contexts = parent::getCacheContexts();
$contexts[] = 'user.node_grants:view';
return $contexts;
}
}

View file

@ -0,0 +1,44 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\views\filter\Status.
*/
namespace Drupal\node\Plugin\views\filter;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\filter\FilterPluginBase;
/**
* Filter by published status.
*
* @ingroup views_filter_handlers
*
* @ViewsFilter("node_status")
*/
class Status extends FilterPluginBase {
public function adminSummary() { }
protected function operatorForm(&$form, FormStateInterface $form_state) { }
public function canExpose() { return FALSE; }
public function query() {
$table = $this->ensureMyTable();
$this->query->addWhereExpression($this->options['group'], "$table.status = 1 OR ($table.uid = ***CURRENT_USER*** AND ***CURRENT_USER*** <> 0 AND ***VIEW_OWN_UNPUBLISHED_NODES*** = 1) OR ***BYPASS_NODE_ACCESS*** = 1");
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
$contexts = parent::getCacheContexts();
$contexts[] = 'user';
return $contexts;
}
}

View file

@ -0,0 +1,33 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\views\filter\UidRevision.
*/
namespace Drupal\node\Plugin\views\filter;
use Drupal\user\Plugin\views\filter\Name;
/**
* Filter handler to check for revisions a certain user has created.
*
* @ingroup views_filter_handlers
*
* @ViewsFilter("node_uid_revision")
*/
class UidRevision extends Name {
public function query($group_by = FALSE) {
$this->ensureMyTable();
$placeholder = $this->placeholder() . '[]';
$args = array_values($this->value);
$this->query->addWhereExpression($this->options['group'], "$this->tableAlias.uid IN($placeholder) OR
((SELECT COUNT(DISTINCT vid) FROM {node_revision} nr WHERE nr.revision_uid IN ($placeholder) AND nr.nid = $this->tableAlias.nid) > 0)", array($placeholder => $args),
$args);
}
}

View file

@ -0,0 +1,36 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\views\row\NodeRow.
*/
namespace Drupal\node\Plugin\views\row;
use Drupal\views\Plugin\views\row\EntityRow;
/**
* Plugin which performs a node_view on the resulting object.
*
* Most of the code on this object is in the theme function.
*
* @ingroup views_row_plugins
*
* @ViewsRow(
* id = "entity:node",
* )
*/
class NodeRow extends EntityRow {
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['view_mode']['default'] = 'teaser';
return $options;
}
}

View file

@ -0,0 +1,178 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\views\row\Rss.
*/
namespace Drupal\node\Plugin\views\row;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\views\Plugin\views\row\RssPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\node\NodeStorageInterface;
/**
* Plugin which performs a node_view on the resulting object
* and formats it as an RSS item.
*
* @ViewsRow(
* id = "node_rss",
* title = @Translation("Content"),
* help = @Translation("Display the content with standard node view."),
* theme = "views_view_row_rss",
* register_theme = FALSE,
* base = {"node_field_data"},
* display_types = {"feed"}
* )
*/
class Rss extends RssPluginBase {
// Basic properties that let the row style follow relationships.
var $base_table = 'node_field_data';
var $base_field = 'nid';
// Stores the nodes loaded with preRender.
var $nodes = array();
/**
* {@inheritdoc}
*/
protected $entityTypeId = 'node';
/**
* The node storage
*
* @var \Drupal\node\NodeStorageInterface
*/
protected $nodeStorage;
/**
* Constructs the Rss object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_manager);
$this->nodeStorage = $entity_manager->getStorage('node');
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm_summary_options() {
$options = parent::buildOptionsForm_summary_options();
$options['title'] = $this->t('Title only');
$options['default'] = $this->t('Use site default RSS settings');
return $options;
}
public function summaryTitle() {
$options = $this->buildOptionsForm_summary_options();
return SafeMarkup::checkPlain($options[$this->options['view_mode']]);
}
public function preRender($values) {
$nids = array();
foreach ($values as $row) {
$nids[] = $row->{$this->field_alias};
}
if (!empty($nids)) {
$this->nodes = $this->nodeStorage->loadMultiple($nids);
}
}
public function render($row) {
global $base_url;
$nid = $row->{$this->field_alias};
if (!is_numeric($nid)) {
return;
}
$display_mode = $this->options['view_mode'];
if ($display_mode == 'default') {
$display_mode = \Drupal::config('system.rss')->get('items.view_mode');
}
// Load the specified node:
/** @var \Drupal\node\NodeInterface $node */
$node = $this->nodes[$nid];
if (empty($node)) {
return;
}
$description_build = [];
$node->link = $node->url('canonical', array('absolute' => TRUE));
$node->rss_namespaces = array();
$node->rss_elements = array(
array(
'key' => 'pubDate',
'value' => gmdate('r', $node->getCreatedTime()),
),
array(
'key' => 'dc:creator',
'value' => $node->getOwner()->getUsername(),
),
array(
'key' => 'guid',
'value' => $node->id() . ' at ' . $base_url,
'attributes' => array('isPermaLink' => 'false'),
),
);
// The node gets built and modules add to or modify $node->rss_elements
// and $node->rss_namespaces.
$build_mode = $display_mode;
$build = node_view($node, $build_mode);
unset($build['#theme']);
if (!empty($node->rss_namespaces)) {
$this->view->style_plugin->namespaces = array_merge($this->view->style_plugin->namespaces, $node->rss_namespaces);
}
elseif (function_exists('rdf_get_namespaces')) {
// Merge RDF namespaces in the XML namespaces in case they are used
// further in the RSS content.
$xml_rdf_namespaces = array();
foreach (rdf_get_namespaces() as $prefix => $uri) {
$xml_rdf_namespaces['xmlns:' . $prefix] = $uri;
}
$this->view->style_plugin->namespaces += $xml_rdf_namespaces;
}
if ($display_mode != 'title') {
// We render node contents.
$description_build = $build;
}
$item = new \stdClass();
$item->description = $description_build;
$item->title = $node->label();
$item->link = $node->link;
// Provide a reference so that the render call in
// template_preprocess_views_view_row_rss() can still access it.
$item->elements = &$node->rss_elements;
$item->nid = $node->id();
$build = array(
'#theme' => $this->themeFunctions(),
'#view' => $this->view,
'#options' => $this->options,
'#row' => $item,
);
return $build;
}
}

View file

@ -0,0 +1,271 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\views\wizard\Node.
*/
namespace Drupal\node\Plugin\views\wizard;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\wizard\WizardPluginBase;
/**
* @todo: replace numbers with constants.
*/
/**
* Tests creating node views with the wizard.
*
* @ViewsWizard(
* id = "node",
* base_table = "node_field_data",
* title = @Translation("Content")
* )
*/
class Node extends WizardPluginBase {
/**
* Set the created column.
*/
protected $createdColumn = 'node_field_data-created';
/**
* Set default values for the filters.
*/
protected $filters = array(
'status' => array(
'value' => TRUE,
'table' => 'node_field_data',
'field' => 'status',
'plugin_id' => 'boolean',
'entity_type' => 'node',
'entity_field' => 'status',
)
);
/**
* Overrides Drupal\views\Plugin\views\wizard\WizardPluginBase::getAvailableSorts().
*
* @return array
* An array whose keys are the available sort options and whose
* corresponding values are human readable labels.
*/
public function getAvailableSorts() {
// You can't execute functions in properties, so override the method
return array(
'node_field_data-title:DESC' => $this->t('Title')
);
}
/**
* Overrides Drupal\views\Plugin\views\wizard\WizardPluginBase::rowStyleOptions().
*/
protected function rowStyleOptions() {
$options = array();
$options['teasers'] = $this->t('teasers');
$options['full_posts'] = $this->t('full posts');
$options['titles'] = $this->t('titles');
$options['titles_linked'] = $this->t('titles (linked)');
$options['fields'] = $this->t('fields');
return $options;
}
/**
* Overrides Drupal\views\Plugin\views\wizard\WizardPluginBase::defaultDisplayOptions().
*/
protected function defaultDisplayOptions() {
$display_options = parent::defaultDisplayOptions();
// Add permission-based access control.
$display_options['access']['type'] = 'perm';
$display_options['access']['options']['perm'] = 'access content';
// Remove the default fields, since we are customizing them here.
unset($display_options['fields']);
// Add the title field, so that the display has content if the user switches
// to a row style that uses fields.
/* Field: Content: Title */
$display_options['fields']['title']['id'] = 'title';
$display_options['fields']['title']['table'] = 'node_field_data';
$display_options['fields']['title']['field'] = 'title';
$display_options['fields']['title']['entity_type'] = 'node';
$display_options['fields']['title']['entity_field'] = 'title';
$display_options['fields']['title']['label'] = '';
$display_options['fields']['title']['alter']['alter_text'] = 0;
$display_options['fields']['title']['alter']['make_link'] = 0;
$display_options['fields']['title']['alter']['absolute'] = 0;
$display_options['fields']['title']['alter']['trim'] = 0;
$display_options['fields']['title']['alter']['word_boundary'] = 0;
$display_options['fields']['title']['alter']['ellipsis'] = 0;
$display_options['fields']['title']['alter']['strip_tags'] = 0;
$display_options['fields']['title']['alter']['html'] = 0;
$display_options['fields']['title']['hide_empty'] = 0;
$display_options['fields']['title']['empty_zero'] = 0;
$display_options['fields']['title']['settings']['link_to_entity'] = 1;
$display_options['fields']['title']['plugin_id'] = 'field';
return $display_options;
}
/**
* Overrides Drupal\views\Plugin\views\wizard\WizardPluginBase::defaultDisplayFiltersUser().
*/
protected function defaultDisplayFiltersUser(array $form, FormStateInterface $form_state) {
$filters = parent::defaultDisplayFiltersUser($form, $form_state);
$tids = array();
if ($values = $form_state->getValue(array('show', 'tagged_with'))) {
foreach ($values as $value) {
$tids[] = $value['target_id'];
}
}
if (!empty($tids)) {
$vid = reset($form['displays']['show']['tagged_with']['#selection_settings']['target_bundles']);
$filters['tid'] = array(
'id' => 'tid',
'table' => 'taxonomy_index',
'field' => 'tid',
'value' => $tids,
'vid' => $vid,
'plugin_id' => 'taxonomy_index_tid',
);
// If the user entered more than one valid term in the autocomplete
// field, they probably intended both of them to be applied.
if (count($tids) > 1) {
$filters['tid']['operator'] = 'and';
// Sort the terms so the filter will be displayed as it normally would
// on the edit screen.
sort($filters['tid']['value']);
}
}
return $filters;
}
/**
* {@inheritdoc}
*/
protected function pageDisplayOptions(array $form, FormStateInterface $form_state) {
$display_options = parent::pageDisplayOptions($form, $form_state);
$row_plugin = $form_state->getValue(array('page', 'style', 'row_plugin'));
$row_options = $form_state->getValue(array('page', 'style', 'row_options'), array());
$this->display_options_row($display_options, $row_plugin, $row_options);
return $display_options;
}
/**
* {@inheritdoc}
*/
protected function blockDisplayOptions(array $form, FormStateInterface $form_state) {
$display_options = parent::blockDisplayOptions($form, $form_state);
$row_plugin = $form_state->getValue(array('block', 'style', 'row_plugin'));
$row_options = $form_state->getValue(array('block', 'style', 'row_options'), array());
$this->display_options_row($display_options, $row_plugin, $row_options);
return $display_options;
}
/**
* Set the row style and row style plugins to the display_options.
*/
protected function display_options_row(&$display_options, $row_plugin, $row_options) {
switch ($row_plugin) {
case 'full_posts':
$display_options['row']['type'] = 'entity:node';
$display_options['row']['options']['view_mode'] = 'full';
break;
case 'teasers':
$display_options['row']['type'] = 'entity:node';
$display_options['row']['options']['view_mode'] = 'teaser';
break;
case 'titles_linked':
case 'titles':
$display_options['row']['type'] = 'fields';
$display_options['fields']['title']['id'] = 'title';
$display_options['fields']['title']['table'] = 'node_field_data';
$display_options['fields']['title']['field'] = 'title';
$display_options['fields']['title']['settings']['link_to_entity'] = $row_plugin === 'titles_linked';
$display_options['fields']['title']['plugin_id'] = 'field';
break;
}
}
/**
* Overrides Drupal\views\Plugin\views\wizard\WizardPluginBase::buildFilters().
*
* Add some options for filter by taxonomy terms.
*/
protected function buildFilters(&$form, FormStateInterface $form_state) {
parent::buildFilters($form, $form_state);
$selected_bundle = static::getSelected($form_state, array('show', 'type'), 'all', $form['displays']['show']['type']);
// Add the "tagged with" filter to the view.
// We construct this filter using taxonomy_index.tid (which limits the
// filtering to a specific vocabulary) rather than
// taxonomy_term_field_data.name (which matches terms in any vocabulary).
// This is because it is a more commonly-used filter that works better with
// the autocomplete UI, and also to avoid confusion with other vocabularies
// on the site that may have terms with the same name but are not used for
// free tagging.
// The downside is that if there *is* more than one vocabulary on the site
// that is used for free tagging, the wizard will only be able to make the
// "tagged with" filter apply to one of them (see below for the method it
// uses to choose).
// Find all "tag-like" taxonomy fields associated with the view's
// entities. If a particular entity type (i.e., bundle) has been
// selected above, then we only search for taxonomy fields associated
// with that bundle. Otherwise, we use all bundles.
$bundles = array_keys(entity_get_bundles($this->entityTypeId));
// Double check that this is a real bundle before using it (since above
// we added a dummy option 'all' to the bundle list on the form).
if (isset($selected_bundle) && in_array($selected_bundle, $bundles)) {
$bundles = array($selected_bundle);
}
$tag_fields = array();
foreach ($bundles as $bundle) {
$display = entity_get_form_display($this->entityTypeId, $bundle, 'default');
$taxonomy_fields = array_filter(\Drupal::entityManager()->getFieldDefinitions($this->entityTypeId, $bundle), function ($field_definition) {
return $field_definition->getType() == 'entity_reference' && $field_definition->getSetting('target_type') == 'taxonomy_term';
});
foreach ($taxonomy_fields as $field_name => $field) {
$widget = $display->getComponent($field_name);
// We define "tag-like" taxonomy fields as ones that use the
// "Autocomplete (Tags style)" widget.
if ($widget['type'] == 'entity_reference_autocomplete_tags') {
$tag_fields[$field_name] = $field;
}
}
}
if (!empty($tag_fields)) {
// If there is more than one "tag-like" taxonomy field available to
// the view, we can only make our filter apply to one of them (as
// described above). We choose 'field_tags' if it is available, since
// that is created by the Standard install profile in core and also
// commonly used by contrib modules; thus, it is most likely to be
// associated with the "main" free-tagging vocabulary on the site.
if (array_key_exists('field_tags', $tag_fields)) {
$tag_field_name = 'field_tags';
}
else {
$tag_field_name = key($tag_fields);
}
// Add the autocomplete textfield to the wizard.
$target_bundles = $tag_fields[$tag_field_name]->getSetting('handler_settings')['target_bundles'];
$form['displays']['show']['tagged_with'] = array(
'#type' => 'entity_autocomplete',
'#title' => $this->t('tagged with'),
'#target_type' => 'taxonomy_term',
'#selection_settings' => ['target_bundles' => $target_bundles],
'#tags' => TRUE,
'#size' => 30,
'#maxlength' => 1024,
);
}
}
}

View file

@ -0,0 +1,111 @@
<?php
/**
* @file
* Contains \Drupal\node\Plugin\views\wizard\NodeRevision.
*/
namespace Drupal\node\Plugin\views\wizard;
use Drupal\views\Plugin\views\wizard\WizardPluginBase;
/**
* @todo: replace numbers with constants.
*/
/**
* Tests creating node revision views with the wizard.
*
* @ViewsWizard(
* id = "node_revision",
* base_table = "node_field_revision",
* title = @Translation("Content revisions")
* )
*/
class NodeRevision extends WizardPluginBase {
/**
* Set the created column.
*/
protected $createdColumn = 'changed';
/**
* Set default values for the filters.
*/
protected $filters = array(
'status' => array(
'value' => TRUE,
'table' => 'node_field_revision',
'field' => 'status',
'plugin_id' => 'boolean',
'entity_type' => 'node',
'entity_field' => 'status',
)
);
/**
* Overrides Drupal\views\Plugin\views\wizard\WizardPluginBase::rowStyleOptions().
*
* Node revisions do not support full posts or teasers, so remove them.
*/
protected function rowStyleOptions() {
$options = parent::rowStyleOptions();
unset($options['teasers']);
unset($options['full_posts']);
return $options;
}
/**
* Overrides Drupal\views\Plugin\views\wizard\WizardPluginBase::defaultDisplayOptions().
*/
protected function defaultDisplayOptions() {
$display_options = parent::defaultDisplayOptions();
// Add permission-based access control.
$display_options['access']['type'] = 'perm';
$display_options['access']['options']['perm'] = 'view all revisions';
// Remove the default fields, since we are customizing them here.
unset($display_options['fields']);
/* Field: Content revision: Created date */
$display_options['fields']['changed']['id'] = 'changed';
$display_options['fields']['changed']['table'] = 'node_field_revision';
$display_options['fields']['changed']['field'] = 'changed';
$display_options['fields']['changed']['entity_type'] = 'node';
$display_options['fields']['changed']['entity_field'] = 'changed';
$display_options['fields']['changed']['alter']['alter_text'] = FALSE;
$display_options['fields']['changed']['alter']['make_link'] = FALSE;
$display_options['fields']['changed']['alter']['absolute'] = FALSE;
$display_options['fields']['changed']['alter']['trim'] = FALSE;
$display_options['fields']['changed']['alter']['word_boundary'] = FALSE;
$display_options['fields']['changed']['alter']['ellipsis'] = FALSE;
$display_options['fields']['changed']['alter']['strip_tags'] = FALSE;
$display_options['fields']['changed']['alter']['html'] = FALSE;
$display_options['fields']['changed']['hide_empty'] = FALSE;
$display_options['fields']['changed']['empty_zero'] = FALSE;
$display_options['fields']['changed']['plugin_id'] = 'date';
/* Field: Content revision: Title */
$display_options['fields']['title']['id'] = 'title';
$display_options['fields']['title']['table'] = 'node_field_revision';
$display_options['fields']['title']['field'] = 'title';
$display_options['fields']['title']['entity_type'] = 'node';
$display_options['fields']['title']['entity_field'] = 'title';
$display_options['fields']['title']['label'] = '';
$display_options['fields']['title']['alter']['alter_text'] = 0;
$display_options['fields']['title']['alter']['make_link'] = 0;
$display_options['fields']['title']['alter']['absolute'] = 0;
$display_options['fields']['title']['alter']['trim'] = 0;
$display_options['fields']['title']['alter']['word_boundary'] = 0;
$display_options['fields']['title']['alter']['ellipsis'] = 0;
$display_options['fields']['title']['alter']['strip_tags'] = 0;
$display_options['fields']['title']['alter']['html'] = 0;
$display_options['fields']['title']['hide_empty'] = 0;
$display_options['fields']['title']['empty_zero'] = 0;
$display_options['fields']['title']['settings']['link_to_entity'] = 0;
$display_options['fields']['title']['plugin_id'] = 'field';
return $display_options;
}
}

View file

@ -0,0 +1,37 @@
<?php
/**
* @file
* Contains \Drupal\node\Routing\RouteSubscriber.
*/
namespace Drupal\node\Routing;
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;
/**
* Listens to the dynamic route events.
*/
class RouteSubscriber extends RouteSubscriberBase {
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
// As nodes are the primary type of content, the node listing should be
// easily available. In order to do that, override admin/content to show
// a node listing instead of the path's child links.
$route = $collection->get('system.admin_content');
if ($route) {
$route->setDefaults(array(
'_title' => 'Content',
'_entity_list' => 'node',
));
$route->setRequirements(array(
'_permission' => 'access content overview',
));
}
}
}

View file

@ -0,0 +1,85 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\Condition\NodeConditionTest.
*/
namespace Drupal\node\Tests\Condition;
use Drupal\system\Tests\Entity\EntityUnitTestBase;
/**
* Tests that conditions, provided by the node module, are working properly.
*
* @group node
*/
class NodeConditionTest extends EntityUnitTestBase {
public static $modules = array('node');
protected function setUp() {
parent::setUp();
// Create the node bundles required for testing.
$type = entity_create('node_type', array('type' => 'page', 'name' => 'page'));
$type->save();
$type = entity_create('node_type', array('type' => 'article', 'name' => 'article'));
$type->save();
$type = entity_create('node_type', array('type' => 'test', 'name' => 'test'));
$type->save();
}
/**
* Tests conditions.
*/
function testConditions() {
$manager = $this->container->get('plugin.manager.condition', $this->container->get('container.namespaces'));
$this->createUser();
// Get some nodes of various types to check against.
$page = entity_create('node', array('type' => 'page', 'title' => $this->randomMachineName(), 'uid' => 1));
$page->save();
$article = entity_create('node', array('type' => 'article', 'title' => $this->randomMachineName(), 'uid' => 1));
$article->save();
$test = entity_create('node', array('type' => 'test', 'title' => $this->randomMachineName(), 'uid' => 1));
$test->save();
// Grab the node type condition and configure it to check against node type
// of 'article' and set the context to the page type node.
$condition = $manager->createInstance('node_type')
->setConfig('bundles', array('article' => 'article'))
->setContextValue('node', $page);
$this->assertFalse($condition->execute(), 'Page type nodes fail node type checks for articles.');
// Check for the proper summary.
$this->assertEqual('The node bundle is article', $condition->summary());
// Set the node type check to page.
$condition->setConfig('bundles', array('page' => 'page'));
$this->assertTrue($condition->execute(), 'Page type nodes pass node type checks for pages');
// Check for the proper summary.
$this->assertEqual('The node bundle is page', $condition->summary());
// Set the node type check to page or article.
$condition->setConfig('bundles', array('page' => 'page', 'article' => 'article'));
$this->assertTrue($condition->execute(), 'Page type nodes pass node type checks for pages or articles');
// Check for the proper summary.
$this->assertEqual('The node bundle is page or article', $condition->summary());
// Set the context to the article node.
$condition->setContextValue('node', $article);
$this->assertTrue($condition->execute(), 'Article type nodes pass node type checks for pages or articles');
// Set the context to the test node.
$condition->setContextValue('node', $test);
$this->assertFalse($condition->execute(), 'Test type nodes pass node type checks for pages or articles');
// Check a greater than 2 bundles summary scenario.
$condition->setConfig('bundles', array('page' => 'page', 'article' => 'article', 'test' => 'test'));
$this->assertEqual('The node bundle is page, article or test', $condition->summary());
// Test Constructor injection.
$condition = $manager->createInstance('node_type', array('bundles' => array('article' => 'article'), 'context' => array('node' => $article)));
$this->assertTrue($condition->execute(), 'Constructor injection of context and configuration working as anticipated.');
}
}

View file

@ -0,0 +1,64 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\Config\NodeImportChangeTest.
*/
namespace Drupal\node\Tests\Config;
use Drupal\node\Entity\NodeType;
use Drupal\simpletest\KernelTestBase;
/**
* Change content types during config create method invocation.
*
* @group node
*/
class NodeImportChangeTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node', 'field', 'text', 'system', 'node_test_config', 'user', 'entity_reference');
/**
* Set the default field storage backend for fields created during tests.
*/
protected function setUp() {
parent::setUp();
// Set default storage backend.
$this->installConfig(array('field', 'node_test_config'));
}
/**
* Tests importing an updated content type.
*/
public function testImportChange() {
$node_type_id = 'default';
$node_type_config_name = "node.type.$node_type_id";
// Simulate config data to import:
// - a modified version (modified label) of the node type config.
$active = $this->container->get('config.storage');
$staging = $this->container->get('config.storage.staging');
$this->copyConfig($active, $staging);
$node_type = $active->read($node_type_config_name);
$new_label = 'Test update import field';
$node_type['name'] = $new_label;
// Save as files in the staging directory.
$staging->write($node_type_config_name, $node_type);
// Import the content of the staging directory.
$this->configImporter()->import();
// Check that the updated config was correctly imported.
$node_type = NodeType::load($node_type_id);
$this->assertEqual($node_type->label(), $new_label, 'Node type name has been updated.');
}
}

View file

@ -0,0 +1,80 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\Config\NodeImportCreateTest.
*/
namespace Drupal\node\Tests\Config;
use Drupal\field\Entity\FieldConfig;
use Drupal\node\Entity\NodeType;
use Drupal\simpletest\KernelTestBase;
/**
* Create content types during config create method invocation.
*
* @group node
*/
class NodeImportCreateTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node', 'field', 'text', 'system', 'user', 'entity_reference');
/**
* Set the default field storage backend for fields created during tests.
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('user');
// Set default storage backend.
$this->installConfig(array('field'));
}
/**
* Tests creating a content type during default config import.
*/
public function testImportCreateDefault() {
$node_type_id = 'default';
// Check that the content type does not exist yet.
$this->assertFalse(NodeType::load($node_type_id));
// Enable node_test_config module and check that the content type
// shipped in the module's default config is created.
$this->container->get('module_installer')->install(array('node_test_config'));
$node_type = NodeType::load($node_type_id);
$this->assertTrue($node_type, 'The default content type was created.');
}
/**
* Tests creating a content type during config import.
*/
public function testImportCreate() {
$node_type_id = 'import';
$node_type_config_name = "node.type.$node_type_id";
// Simulate config data to import.
$active = $this->container->get('config.storage');
$staging = $this->container->get('config.storage.staging');
$this->copyConfig($active, $staging);
// Manually add new node type.
$src_dir = drupal_get_path('module', 'node_test_config') . '/staging';
$target_dir = $this->configDirectories[CONFIG_STAGING_DIRECTORY];
$this->assertTrue(file_unmanaged_copy("$src_dir/$node_type_config_name.yml", "$target_dir/$node_type_config_name.yml"));
// Import the content of the staging directory.
$this->configImporter()->import();
// Check that the content type was created.
$node_type = NodeType::load($node_type_id);
$this->assertTrue($node_type, 'Import node type from staging was created.');
$this->assertFalse(FieldConfig::loadByName('node', $node_type_id, 'body'));
}
}

View file

@ -0,0 +1,66 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\MultiStepNodeFormBasicOptionsTest.
*/
namespace Drupal\node\Tests;
use Drupal\Component\Utility\Unicode;
/**
* Tests the persistence of basic options through multiple steps.
*
* @group node
*/
class MultiStepNodeFormBasicOptionsTest extends NodeTestBase {
/**
* The field name to create.
*
* @var string
*/
protected $fieldName;
/**
* Tests changing the default values of basic options to ensure they persist.
*/
function testMultiStepNodeFormBasicOptions() {
// Prepare a user to create the node.
$web_user = $this->drupalCreateUser(array('administer nodes', 'create page content'));
$this->drupalLogin($web_user);
// Create an unlimited cardinality field.
$this->fieldName = Unicode::strtolower($this->randomMachineName());
entity_create('field_storage_config', array(
'field_name' => $this->fieldName,
'entity_type' => 'node',
'type' => 'text',
'cardinality' => -1,
))->save();
// Attach an instance of the field to the page content type.
entity_create('field_config', array(
'field_name' => $this->fieldName,
'entity_type' => 'node',
'bundle' => 'page',
'label' => $this->randomMachineName() . '_label',
))->save();
entity_get_form_display('node', 'page', 'default')
->setComponent($this->fieldName, array(
'type' => 'text_textfield',
))
->save();
$edit = array(
'title[0][value]' => 'a',
'promote[value]' => FALSE,
'sticky[value]' => 1,
"{$this->fieldName}[0][value]" => $this->randomString(32),
);
$this->drupalPostForm('node/add/page', $edit, t('Add another item'));
$this->assertNoFieldChecked('edit-promote-value', 'Promote stayed unchecked');
$this->assertFieldChecked('edit-sticky-value', 'Sticky stayed checked');
}
}

View file

@ -0,0 +1,214 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeAccessBaseTableTest.
*/
namespace Drupal\node\Tests;
use Drupal\node\Entity\NodeType;
/**
* Tests behavior of the node access subsystem if the base table is not node.
*
* @group node
*/
class NodeAccessBaseTableTest extends NodeTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node_access_test', 'views');
/**
* The installation profile to use with this test.
*
* This test class requires the "tags" taxonomy field.
*
* @var string
*/
protected $profile = 'standard';
/**
* Nodes by user.
*
* @var array
*/
protected $nodesByUser;
/**
* A public tid.
*
* @var \Drupal\Core\Database\StatementInterface
*/
protected $publicTid;
/**
* A private tid.
*
* @var \Drupal\Core\Database\StatementInterface
*/
protected $privateTid;
/**
* A web user.
*/
protected $webUser;
/**
* The nids visible.
*
* @var array
*/
protected $nidsVisible;
protected function setUp() {
parent::setUp();
node_access_test_add_field(NodeType::load('article'));
node_access_rebuild();
\Drupal::state()->set('node_access_test.private', TRUE);
}
/**
* Tests the "private" node access functionality.
*
* - Create 2 users with "access content" and "create article" permissions.
* - Each user creates one private and one not private article.
*
* - Test that each user can view the other user's non-private article.
* - Test that each user cannot view the other user's private article.
* - Test that each user finds only appropriate (non-private + own private)
* in taxonomy listing.
* - Create another user with 'view any private content'.
* - Test that user 4 can view all content created above.
* - Test that user 4 can view all content on taxonomy listing.
*/
function testNodeAccessBasic() {
$num_simple_users = 2;
$simple_users = array();
// Nodes keyed by uid and nid: $nodes[$uid][$nid] = $is_private;
$this->nodesByUser = array();
// Titles keyed by nid.
$titles = [];
// Array of nids marked private.
$private_nodes = [];
for ($i = 0; $i < $num_simple_users; $i++) {
$simple_users[$i] = $this->drupalCreateUser(array('access content', 'create article content'));
}
foreach ($simple_users as $this->webUser) {
$this->drupalLogin($this->webUser);
foreach (array(0 => 'Public', 1 => 'Private') as $is_private => $type) {
$edit = array(
'title[0][value]' => t('@private_public Article created by @user', array('@private_public' => $type, '@user' => $this->webUser->getUsername())),
);
if ($is_private) {
$edit['private[0][value]'] = TRUE;
$edit['body[0][value]'] = 'private node';
$edit['field_tags[target_id]'] = 'private';
}
else {
$edit['body[0][value]'] = 'public node';
$edit['field_tags[target_id]'] = 'public';
}
$this->drupalPostForm('node/add/article', $edit, t('Save'));
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$this->assertEqual($is_private, (int)$node->private->value, 'The private status of the node was properly set in the node_access_test table.');
if ($is_private) {
$private_nodes[] = $node->id();
}
$titles[$node->id()] = $edit['title[0][value]'];
$this->nodesByUser[$this->webUser->id()][$node->id()] = $is_private;
}
}
$this->publicTid = db_query('SELECT tid FROM {taxonomy_term_field_data} WHERE name = :name AND default_langcode = 1', array(':name' => 'public'))->fetchField();
$this->privateTid = db_query('SELECT tid FROM {taxonomy_term_field_data} WHERE name = :name AND default_langcode = 1', array(':name' => 'private'))->fetchField();
$this->assertTrue($this->publicTid, 'Public tid was found');
$this->assertTrue($this->privateTid, 'Private tid was found');
foreach ($simple_users as $this->webUser) {
$this->drupalLogin($this->webUser);
// Check own nodes to see that all are readable.
foreach ($this->nodesByUser as $uid => $data) {
foreach ($data as $nid => $is_private) {
$this->drupalGet('node/' . $nid);
if ($is_private) {
$should_be_visible = $uid == $this->webUser->id();
}
else {
$should_be_visible = TRUE;
}
$this->assertResponse($should_be_visible ? 200 : 403, strtr('A %private node by user %uid is %visible for user %current_uid.', array(
'%private' => $is_private ? 'private' : 'public',
'%uid' => $uid,
'%visible' => $should_be_visible ? 'visible' : 'not visible',
'%current_uid' => $this->webUser->id(),
)));
}
}
// Check to see that the correct nodes are shown on taxonomy/private
// and taxonomy/public.
$this->assertTaxonomyPage(FALSE);
}
// Now test that a user with 'node test view' permissions can view content.
$access_user = $this->drupalCreateUser(array('access content', 'create article content', 'node test view', 'search content'));
$this->drupalLogin($access_user);
foreach ($this->nodesByUser as $private_status) {
foreach ($private_status as $nid => $is_private) {
$this->drupalGet('node/' . $nid);
$this->assertResponse(200);
}
}
// This user should be able to see all of the nodes on the relevant
// taxonomy pages.
$this->assertTaxonomyPage(TRUE);
}
/**
* Checks taxonomy/term listings to ensure only accessible nodes are listed.
*
* @param $is_admin
* A boolean indicating whether the current user is an administrator. If
* TRUE, all nodes should be listed. If FALSE, only public nodes and the
* user's own private nodes should be listed.
*/
protected function assertTaxonomyPage($is_admin) {
foreach (array($this->publicTid, $this->privateTid) as $tid_is_private => $tid) {
$this->drupalGet("taxonomy/term/$tid");
$this->nidsVisible = [];
foreach ($this->xpath("//a[text()='Read more']") as $link) {
// See also testTranslationRendering() in NodeTranslationUITest.
$this->assertTrue(preg_match('|node/(\d+)$|', (string) $link['href'], $matches), 'Read more points to a node');
$this->nidsVisible[$matches[1]] = TRUE;
}
foreach ($this->nodesByUser as $uid => $data) {
foreach ($data as $nid => $is_private) {
// Private nodes should be visible on the private term page,
// public nodes should be visible on the public term page.
$should_be_visible = $tid_is_private == $is_private;
// Non-administrators can only see their own nodes on the private
// term page.
if (!$is_admin && $tid_is_private) {
$should_be_visible = $should_be_visible && $uid == $this->webUser->id();
}
$this->assertIdentical(isset($this->nidsVisible[$nid]), $should_be_visible, strtr('A %private node by user %uid is %visible for user %current_uid on the %tid_is_private page.', array(
'%private' => $is_private ? 'private' : 'public',
'%uid' => $uid,
'%visible' => isset($this->nidsVisible[$nid]) ? 'visible' : 'not visible',
'%current_uid' => $this->webUser->id(),
'%tid_is_private' => $tid_is_private ? 'private' : 'public',
)));
}
}
}
}
}

View file

@ -0,0 +1,115 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeAccessFieldTest.
*/
namespace Drupal\node\Tests;
use Drupal\Component\Utility\Unicode;
/**
* Tests the interaction of the node access system with fields.
*
* @group node
*/
class NodeAccessFieldTest extends NodeTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node_access_test', 'field_ui');
/**
* A user with permission to bypass access content.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* A user with permission to manage content types and fields.
*
* @var \Drupal\user\UserInterface
*/
protected $contentAdminUser;
/**
* The name of the created field.
*
* @var string
*/
protected $fieldName;
protected function setUp() {
parent::setUp();
node_access_rebuild();
// Create some users.
$this->adminUser = $this->drupalCreateUser(array('access content', 'bypass node access'));
$this->contentAdminUser = $this->drupalCreateUser(array('access content', 'administer content types', 'administer node fields'));
// Add a custom field to the page content type.
$this->fieldName = Unicode::strtolower($this->randomMachineName() . '_field_name');
entity_create('field_storage_config', array(
'field_name' => $this->fieldName,
'entity_type' => 'node',
'type' => 'text'
))->save();
entity_create('field_config', array(
'field_name' => $this->fieldName,
'entity_type' => 'node',
'bundle' => 'page',
))->save();
entity_get_display('node', 'page', 'default')
->setComponent($this->fieldName)
->save();
entity_get_form_display('node', 'page', 'default')
->setComponent($this->fieldName)
->save();
}
/**
* Tests administering fields when node access is restricted.
*/
function testNodeAccessAdministerField() {
// Create a page node.
$fieldData = array();
$value = $fieldData[0]['value'] = $this->randomMachineName();
$node = $this->drupalCreateNode(array($this->fieldName => $fieldData));
// Log in as the administrator and confirm that the field value is present.
$this->drupalLogin($this->adminUser);
$this->drupalGet('node/' . $node->id());
$this->assertText($value, 'The saved field value is visible to an administrator.');
// Log in as the content admin and try to view the node.
$this->drupalLogin($this->contentAdminUser);
$this->drupalGet('node/' . $node->id());
$this->assertText('Access denied', 'Access is denied for the content admin.');
// Modify the field default as the content admin.
$edit = array();
$default = 'Sometimes words have two meanings';
$edit["default_value_input[{$this->fieldName}][0][value]"] = $default;
$this->drupalPostForm(
"admin/structure/types/manage/page/fields/node.page.{$this->fieldName}",
$edit,
t('Save settings')
);
// Log in as the administrator.
$this->drupalLogin($this->adminUser);
// Confirm that the existing node still has the correct field value.
$this->drupalGet('node/' . $node->id());
$this->assertText($value, 'The original field value is visible to an administrator.');
// Confirm that the new default value appears when creating a new node.
$this->drupalGet('node/add/page');
$this->assertRaw($default, 'The updated default value is displayed when creating a new node.');
}
}

View file

@ -0,0 +1,143 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeAccessGrantsCacheContextTest.
*/
namespace Drupal\node\Tests;
/**
* Tests the node access grants cache context service.
*
* @group node
* @group Cache
*/
class NodeAccessGrantsCacheContextTest extends NodeTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node_access_test');
/**
* User with permission to view content.
*/
protected $accessUser;
/**
* User without permission to view content.
*/
protected $noAccessUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
node_access_rebuild();
// Create some content.
$this->drupalCreateNode();
$this->drupalCreateNode();
$this->drupalCreateNode();
$this->drupalCreateNode();
// Create user with simple node access permission. The 'node test view'
// permission is implemented and granted by the node_access_test module.
$this->accessUser = $this->drupalCreateUser(array('access content overview', 'access content', 'node test view'));
$this->noAccessUser = $this->drupalCreateUser(array('access content overview', 'access content'));
$this->noAccessUser2 = $this->drupalCreateUser(array('access content overview', 'access content'));
$this->userMapping = [
1 => $this->rootUser,
2 => $this->accessUser,
3 => $this->noAccessUser,
];
}
/**
* Asserts that for each given user, the expected cache context is returned.
*
* @param array $expected
* Expected values, keyed by user ID, expected cache contexts as values.
*/
protected function assertUserCacheContext(array $expected) {
foreach ($expected as $uid => $context) {
if ($uid > 0) {
$this->drupalLogin($this->userMapping[$uid]);
}
$this->pass('Asserting cache context for user ' . $uid . '.');
$this->assertIdentical($context, $this->container->get('cache_context.user.node_grants')->getContext('view'));
}
$this->drupalLogout();
}
/**
* Tests NodeAccessGrantsCacheContext::getContext().
*/
public function testCacheContext() {
$this->assertUserCacheContext([
0 => 'view.all:0;node_access_test_author:0;node_access_all:0',
1 => 'all',
2 => 'view.all:0;node_access_test_author:2;node_access_test:8888,8889',
3 => 'view.all:0;node_access_test_author:3',
]);
// Grant view to all nodes (because nid = 0) for users in the
// 'node_access_all' realm.
$record = array(
'nid' => 0,
'gid' => 0,
'realm' => 'node_access_all',
'grant_view' => 1,
'grant_update' => 0,
'grant_delete' => 0,
);
db_insert('node_access')->fields($record)->execute();
// Put user accessUser (uid 0) in the realm.
\Drupal::state()->set('node_access_test.no_access_uid', 0);
drupal_static_reset('node_access_view_all_nodes');
$this->assertUserCacheContext([
0 => 'view.all',
1 => 'all',
2 => 'view.all:0;node_access_test_author:2;node_access_test:8888,8889',
3 => 'view.all:0;node_access_test_author:3',
]);
// Put user accessUser (uid 2) in the realm.
\Drupal::state()->set('node_access_test.no_access_uid', $this->accessUser->id());
drupal_static_reset('node_access_view_all_nodes');
$this->assertUserCacheContext([
0 => 'view.all:0;node_access_test_author:0',
1 => 'all',
2 => 'view.all',
3 => 'view.all:0;node_access_test_author:3',
]);
// Put user noAccessUser (uid 3) in the realm.
\Drupal::state()->set('node_access_test.no_access_uid', $this->noAccessUser->id());
drupal_static_reset('node_access_view_all_nodes');
$this->assertUserCacheContext([
0 => 'view.all:0;node_access_test_author:0',
1 => 'all',
2 => 'view.all:0;node_access_test_author:2;node_access_test:8888,8889',
3 => 'view.all',
]);
// Uninstall the node_access_test module
$this->container->get('module_installer')->uninstall(['node_access_test']);
drupal_static_reset('node_access_view_all_nodes');
$this->assertUserCacheContext([
0 => 'view.all',
1 => 'all',
2 => 'view.all',
3 => 'view.all',
]);
}
}

View file

@ -0,0 +1,29 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeAccessGrantsTest.
*/
namespace Drupal\node\Tests;
/**
* Tests basic node_access functionality with hook_node_grants().
*
* This test just wraps the existing default permissions test while a module
* that implements hook_node_grants() is enabled.
*
* @see \Drupal\node\NodeGrantDatabaseStorage
*
* @group node
*/
class NodeAccessGrantsTest extends NodeAccessTest {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node_access_test_empty');
}

View file

@ -0,0 +1,351 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeAccessLanguageAwareCombinationTest.
*/
namespace Drupal\node\Tests;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\NodeType;
use Drupal\user\Entity\User;
/**
* Tests node access functionality with multiple languages and two node access
* modules.
*
* @group node
*/
class NodeAccessLanguageAwareCombinationTest extends NodeTestBase {
/**
* Enable language and two node access modules.
*
* @var array
*/
public static $modules = array('language', 'node_access_test_language', 'node_access_test');
/**
* A set of nodes to use in testing.
*
* @var array
*/
protected $nodes = array();
/**
* A normal authenticated user.
*
* @var \Drupal\user\Entity\UserInterface.
*/
protected $webUser;
/**
* User 1.
*
* @var \Drupal\user\Entity\UserInterface.
*/
protected $adminUser;
protected function setUp() {
parent::setUp();
node_access_test_add_field(NodeType::load('page'));
// Create the 'private' field, which allows the node to be marked as private
// (restricted access) in a given translation.
$field_storage = entity_create('field_storage_config', array(
'field_name' => 'field_private',
'entity_type' => 'node',
'type' => 'boolean',
'cardinality' => 1,
));
$field_storage->save();
entity_create('field_config', array(
'field_storage' => $field_storage,
'bundle' => 'page',
'widget' => array(
'type' => 'options_buttons',
),
'settings' => array(
'on_label' => 'Private',
'off_label' => 'Not private',
),
))->save();
// After enabling a node access module, the access table has to be rebuild.
node_access_rebuild();
// Add Hungarian and Catalan.
ConfigurableLanguage::createFromLangcode('hu')->save();
ConfigurableLanguage::createFromLangcode('ca')->save();
// Create a normal authenticated user.
$this->webUser = $this->drupalCreateUser(array('access content'));
// Load the user 1 user for later use as an admin user with permission to
// see everything.
$this->adminUser = User::load(1);
// The node_access_test_language module allows individual translations of a
// node to be marked private (not viewable by normal users), and the
// node_access_test module allows whole nodes to be marked private. (In a
// real-world implementation, hook_node_access_records_alter() might be
// implemented by one or both modules to enforce that private nodes or
// translations are always private, but we want to test the default,
// additive behavior of node access).
// Create six Hungarian nodes with Catalan translations:
// 1. One public with neither language marked as private.
// 2. One private with neither language marked as private.
// 3. One public with only the Hungarian translation private.
// 4. One public with only the Catalan translation private.
// 5. One public with both the Hungarian and Catalan translations private.
// 6. One private with both the Hungarian and Catalan translations private.
$this->nodes['public_both_public'] = $node = $this->drupalCreateNode(array(
'body' => array(array()),
'langcode' => 'hu',
'field_private' => array(array('value' => 0)),
'private' => FALSE,
));
$translation = $node->getTranslation('ca');
$translation->field_private->value = 0;
$node->save();
$this->nodes['private_both_public'] = $node = $this->drupalCreateNode(array(
'body' => array(array()),
'langcode' => 'hu',
'field_private' => array(array('value' => 0)),
'private' => TRUE,
));
$translation = $node->getTranslation('ca');
$translation->field_private->value = 0;
$node->save();
$this->nodes['public_hu_private'] = $node = $this->drupalCreateNode(array(
'body' => array(array()),
'langcode' => 'hu',
'field_private' => array(array('value' => 1)),
'private' => FALSE,
));
$translation = $node->getTranslation('ca');
$translation->field_private->value = 0;
$node->save();
$this->nodes['public_ca_private'] = $node = $this->drupalCreateNode(array(
'body' => array(array()),
'langcode' => 'hu',
'field_private' => array(array('value' => 0)),
'private' => FALSE,
));
$translation = $node->getTranslation('ca');
$translation->field_private->value = 1;
$node->save();
$this->nodes['public_both_private'] = $node = $this->drupalCreateNode(array(
'body' => array(array()),
'langcode' => 'hu',
'field_private' => array(array('value' => 1)),
'private' => FALSE,
));
$translation = $node->getTranslation('ca');
$translation->field_private->value = 1;
$node->save();
$this->nodes['private_both_private'] = $node = $this->drupalCreateNode(array(
'body' => array(array()),
'langcode' => 'hu',
'field_private' => array(array('value' => 1)),
'private' => TRUE,
));
$translation = $node->getTranslation('ca');
$translation->field_private->value = 1;
$node->save();
$this->nodes['public_no_language_private'] = $this->drupalCreateNode(array(
'field_private' => array(array('value' => 1)),
'private' => FALSE,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
));
$this->nodes['public_no_language_public'] = $this->drupalCreateNode(array(
'field_private' => array(array('value' => 0)),
'private' => FALSE,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
));
$this->nodes['private_no_language_private'] = $this->drupalCreateNode(array(
'field_private' => array(array('value' => 1)),
'private' => TRUE,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
));
$this->nodes['private_no_language_public'] = $this->drupalCreateNode(array(
'field_private' => array(array('value' => 1)),
'private' => TRUE,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
));
}
/**
* Tests node access and node access queries with multiple node languages.
*/
function testNodeAccessLanguageAwareCombination() {
$expected_node_access = array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE);
$expected_node_access_no_access = array('view' => FALSE, 'update' => FALSE, 'delete' => FALSE);
// When the node and both translations are public, access should only be
// denied when a translation that does not exist is requested.
$this->assertNodeAccess($expected_node_access, $this->nodes['public_both_public'], $this->webUser);
$this->assertNodeAccess($expected_node_access, $this->nodes['public_both_public'], $this->webUser, 'hu');
$this->assertNodeAccess($expected_node_access, $this->nodes['public_both_public'], $this->webUser, 'ca');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_both_public'], $this->webUser, 'en');
// If the node is marked private but both existing translations are not,
// access should still be granted, because the grants are additive.
$this->assertNodeAccess($expected_node_access, $this->nodes['private_both_public'], $this->webUser);
$this->assertNodeAccess($expected_node_access, $this->nodes['private_both_public'], $this->webUser, 'hu');
$this->assertNodeAccess($expected_node_access, $this->nodes['private_both_public'], $this->webUser, 'ca');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_both_public'], $this->webUser, 'en');
// If the node is marked private, but a existing translation is public,
// access should only be granted for the public translation. For a
// translation that does not exist yet (English translation), the access is
// denied. With the Hungarian translation marked as private, but the Catalan
// translation public, the access is granted.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_hu_private'], $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_hu_private'], $this->webUser, 'hu');
$this->assertNodeAccess($expected_node_access, $this->nodes['public_hu_private'], $this->webUser, 'ca');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_hu_private'], $this->webUser, 'en');
// With the Catalan translation marked as private, but the node public,
// access is granted for the existing Hungarian translation, but not for the
// Catalan nor the English ones.
$this->assertNodeAccess($expected_node_access, $this->nodes['public_ca_private'], $this->webUser);
$this->assertNodeAccess($expected_node_access, $this->nodes['public_ca_private'], $this->webUser, 'hu');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_ca_private'], $this->webUser, 'ca');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_ca_private'], $this->webUser, 'en');
// With both translations marked as private, but the node public, access
// should be denied in all cases.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_both_private'], $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_both_private'], $this->webUser, 'hu');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_both_private'], $this->webUser, 'ca');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_both_private'], $this->webUser, 'en');
// If the node and both its existing translations are private, access should
// be denied in all cases.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_both_private'], $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_both_private'], $this->webUser, 'hu');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_both_private'], $this->webUser, 'ca');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_both_private'], $this->webUser, 'en');
// No access for all languages as the language aware node access module
// denies access.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_no_language_private'], $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_no_language_private'], $this->webUser, 'hu');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_no_language_private'], $this->webUser, 'ca');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_no_language_private'], $this->webUser, 'en');
// Access only for request with no language defined.
$this->assertNodeAccess($expected_node_access, $this->nodes['public_no_language_public'], $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_no_language_public'], $this->webUser, 'hu');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_no_language_public'], $this->webUser, 'ca');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_no_language_public'], $this->webUser, 'en');
// No access for all languages as both node access modules deny access.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_no_language_private'], $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_no_language_private'], $this->webUser, 'hu');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_no_language_private'], $this->webUser, 'ca');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_no_language_private'], $this->webUser, 'en');
// No access for all languages as the non language aware node access module
// denies access.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_no_language_public'], $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_no_language_public'], $this->webUser, 'hu');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_no_language_public'], $this->webUser, 'ca');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_no_language_public'], $this->webUser, 'en');
// Query the node table with the node access tag in several languages.
// Query with no language specified. The fallback (hu or und) will be used.
$select = db_select('node', 'n')
->fields('n', array('nid'))
->addMetaData('account', $this->webUser)
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// Four nodes should be returned with public Hungarian translations or the
// no language public node.
$this->assertEqual(count($nids), 4, 'db_select() returns 4 nodes when no langcode is specified.');
$this->assertTrue(array_key_exists($this->nodes['public_both_public']->id(), $nids), 'Returned node ID is full public node.');
$this->assertTrue(array_key_exists($this->nodes['public_ca_private']->id(), $nids), 'Returned node ID is Hungarian public only node.');
$this->assertTrue(array_key_exists($this->nodes['private_both_public']->id(), $nids), 'Returned node ID is both public non-language-aware private only node.');
$this->assertTrue(array_key_exists($this->nodes['public_no_language_public']->id(), $nids), 'Returned node ID is no language public node.');
// Query with Hungarian (hu) specified.
$select = db_select('node', 'n')
->fields('n', array('nid'))
->addMetaData('account', $this->webUser)
->addMetaData('langcode', 'hu')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// Three nodes should be returned (with public Hungarian translations).
$this->assertEqual(count($nids), 3, 'db_select() returns 3 nodes.');
$this->assertTrue(array_key_exists($this->nodes['public_both_public']->id(), $nids), 'Returned node ID is both public node.');
$this->assertTrue(array_key_exists($this->nodes['public_ca_private']->id(), $nids), 'Returned node ID is Hungarian public only node.');
$this->assertTrue(array_key_exists($this->nodes['private_both_public']->id(), $nids), 'Returned node ID is both public non-language-aware private only node.');
// Query with Catalan (ca) specified.
$select = db_select('node', 'n')
->fields('n', array('nid'))
->addMetaData('account', $this->webUser)
->addMetaData('langcode', 'ca')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// Three nodes should be returned (with public Catalan translations).
$this->assertEqual(count($nids), 3, 'db_select() returns 3 nodes.');
$this->assertTrue(array_key_exists($this->nodes['public_both_public']->id(), $nids), 'Returned node ID is both public node.');
$this->assertTrue(array_key_exists($this->nodes['public_hu_private']->id(), $nids), 'Returned node ID is Catalan public only node.');
$this->assertTrue(array_key_exists($this->nodes['private_both_public']->id(), $nids), 'Returned node ID is both public non-language-aware private only node.');
// Query with German (de) specified.
$select = db_select('node', 'n')
->fields('n', array('nid'))
->addMetaData('account', $this->webUser)
->addMetaData('langcode', 'de')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// There are no nodes with German translations, so no results are returned.
$this->assertTrue(empty($nids), 'db_select() returns an empty result.');
// Query the nodes table as admin user (full access) with the node access
// tag and no specific langcode.
$select = db_select('node', 'n')
->fields('n', array('nid'))
->addMetaData('account', $this->adminUser)
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// All nodes are returned.
$this->assertEqual(count($nids), 10, 'db_select() returns all nodes.');
// Query the nodes table as admin user (full access) with the node access
// tag and langcode de.
$select = db_select('node', 'n')
->fields('n', array('nid'))
->addMetaData('account', $this->adminUser)
->addMetaData('langcode', 'de')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// Even though there is no German translation, all nodes are returned
// because node access filtering does not occur when the user is user 1.
$this->assertEqual(count($nids), 10, 'db_select() returns all nodes.');
}
}

View file

@ -0,0 +1,293 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeAccessLanguageAwareTest.
*/
namespace Drupal\node\Tests;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\user\Entity\User;
/**
* Tests node_access and db_select() with node_access tag functionality with
* multiple languages with node_access_test_language which is language-aware.
*
* @group node
*/
class NodeAccessLanguageAwareTest extends NodeTestBase {
/**
* Enable language and a language-aware node access module.
*
* @var array
*/
public static $modules = array('language', 'node_access_test_language');
/**
* A set of nodes to use in testing.
*
* @var array
*/
protected $nodes = array();
/**
* A user with permission to bypass access content.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* A normal authenticated user.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
protected function setUp() {
parent::setUp();
// Create the 'private' field, which allows the node to be marked as private
// (restricted access) in a given translation.
$field_storage = entity_create('field_storage_config', array(
'field_name' => 'field_private',
'entity_type' => 'node',
'type' => 'boolean',
'cardinality' => 1,
));
$field_storage->save();
entity_create('field_config', array(
'field_storage' => $field_storage,
'bundle' => 'page',
'widget' => array(
'type' => 'options_buttons',
),
'settings' => array(
'on_label' => 'Private',
'off_label' => 'Not private',
),
))->save();
// After enabling a node access module, the access table has to be rebuild.
node_access_rebuild();
// Create a normal authenticated user.
$this->webUser = $this->drupalCreateUser(array('access content'));
// Load the user 1 user for later use as an admin user with permission to
// see everything.
$this->adminUser = User::load(1);
// Add Hungarian and Catalan.
ConfigurableLanguage::createFromLangcode('hu')->save();
ConfigurableLanguage::createFromLangcode('ca')->save();
// The node_access_test_language module allows individual translations of a
// node to be marked private (not viewable by normal users).
// Create six nodes:
// 1. Four Hungarian nodes with Catalan translations
// - One with neither language marked as private.
// - One with only the Hungarian translation private.
// - One with only the Catalan translation private.
// - One with both the Hungarian and Catalan translations private.
// 2. Two nodes with no language specified.
// - One public.
// - One private.
$this->nodes['both_public'] = $node = $this->drupalCreateNode(array(
'body' => array(array()),
'langcode' => 'hu',
'field_private' => array(array('value' => 0)),
));
$translation = $node->getTranslation('ca');
$translation->field_private->value = 0;
$node->save();
$this->nodes['ca_private'] = $node = $this->drupalCreateNode(array(
'body' => array(array()),
'langcode' => 'hu',
'field_private' => array(array('value' => 0)),
));
$translation = $node->getTranslation('ca');
$translation->field_private->value = 1;
$node->save();
$this->nodes['hu_private'] = $node = $this->drupalCreateNode(array(
'body' => array(array()),
'langcode' => 'hu',
'field_private' => array(array('value' => 1)),
));
$translation = $node->getTranslation('ca');
$translation->field_private->value = 0;
$node->save();
$this->nodes['both_private'] = $node = $this->drupalCreateNode(array(
'body' => array(array()),
'langcode' => 'hu',
'field_private' => array(array('value' => 1)),
));
$translation = $node->getTranslation('ca');
$translation->field_private->value = 1;
$node->save();
$this->nodes['no_language_public'] = $this->drupalCreateNode(array(
'field_private' => array(array('value' => 0)),
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
));
$this->nodes['no_language_private'] = $this->drupalCreateNode(array(
'field_private' => array(array('value' => 1)),
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
));
}
/**
* Tests node access and node access queries with multiple node languages.
*/
function testNodeAccessLanguageAware() {
// The node_access_test_language module only grants view access.
$expected_node_access = array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE);
$expected_node_access_no_access = array('view' => FALSE, 'update' => FALSE, 'delete' => FALSE);
// When both Hungarian and Catalan are marked as public, access to the
// Hungarian translation should be granted when no language is specified or
// when the Hungarian translation is specified explicitly.
$this->assertNodeAccess($expected_node_access, $this->nodes['both_public'], $this->webUser);
$this->assertNodeAccess($expected_node_access, $this->nodes['both_public'], $this->webUser, 'hu');
// Access to the Catalan translation should also be granted.
$this->assertNodeAccess($expected_node_access, $this->nodes['both_public'], $this->webUser, 'ca');
// There is no English translation, so a request to access the English
// translation is denied.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['both_public'], $this->webUser, 'en');
// When Hungarian is marked as private, access to the Hungarian translation
// should be denied when no language is specified or when the Hungarian
// translation is specified explicitly.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['hu_private'], $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['hu_private'], $this->webUser, 'hu');
// Access to the Catalan translation should be granted.
$this->assertNodeAccess($expected_node_access, $this->nodes['hu_private'], $this->webUser, 'ca');
// There is no English translation, so a request to access the English
// translation is denied.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['hu_private'], $this->webUser, 'en');
// When Catalan is marked as private, access to the Hungarian translation
// should be granted when no language is specified or when the Hungarian
// translation is specified explicitly.
$this->assertNodeAccess($expected_node_access, $this->nodes['ca_private'], $this->webUser);
$this->assertNodeAccess($expected_node_access, $this->nodes['ca_private'], $this->webUser, 'hu');
// Access to the Catalan translation should be granted.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['ca_private'], $this->webUser, 'ca');
// There is no English translation, so a request to access the English
// translation is denied.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['ca_private'], $this->webUser, 'en');
// When both translations are marked as private, access should be denied
// regardless of the language specified.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['both_private'], $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['both_private'], $this->webUser, 'hu');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['both_private'], $this->webUser, 'ca');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['both_private'], $this->webUser, 'en');
// When no language is specified for a private node, access to every
// language is denied.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['no_language_private'], $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['no_language_private'], $this->webUser, 'hu');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['no_language_private'], $this->webUser, 'ca');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['no_language_private'], $this->webUser, 'en');
// When no language is specified for a public node, access should be granted
// only for the existing language (not specified), so only the request with
// no language will give access, as this request will be made with the
// langcode of the node, which is "not specified".
$this->assertNodeAccess($expected_node_access, $this->nodes['no_language_public'], $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['no_language_public'], $this->webUser, 'hu');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['no_language_public'], $this->webUser, 'ca');
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['no_language_public'], $this->webUser, 'en');
// Query the node table with the node access tag in several languages.
// Query with no language specified. The fallback (hu) will be used.
$select = db_select('node', 'n')
->fields('n', array('nid'))
->addMetaData('account', $this->webUser)
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// Three nodes should be returned:
// - Node with both translations public.
// - Node with only the Catalan translation marked as private.
// - No language node marked as public.
$this->assertEqual(count($nids), 3, 'db_select() returns 3 nodes when no langcode is specified.');
$this->assertTrue(array_key_exists($this->nodes['both_public']->id(), $nids), 'The node with both translations public is returned.');
$this->assertTrue(array_key_exists($this->nodes['ca_private']->id(), $nids), 'The node with only the Catalan translation private is returned.');
$this->assertTrue(array_key_exists($this->nodes['no_language_public']->id(), $nids), 'The node with no language is returned.');
// Query with Hungarian (hu) specified.
$select = db_select('node', 'n')
->fields('n', array('nid'))
->addMetaData('account', $this->webUser)
->addMetaData('langcode', 'hu')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// Two nodes should be returned: the node with both translations public, and
// the node with only the Catalan translation marked as private.
$this->assertEqual(count($nids), 2, 'db_select() returns 2 nodes when the hu langcode is specified.');
$this->assertTrue(array_key_exists($this->nodes['both_public']->id(), $nids), 'The node with both translations public is returned.');
$this->assertTrue(array_key_exists($this->nodes['ca_private']->id(), $nids), 'The node with only the Catalan translation private is returned.');
// Query with Catalan (ca) specified.
$select = db_select('node', 'n')
->fields('n', array('nid'))
->addMetaData('account', $this->webUser)
->addMetaData('langcode', 'ca')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// Two nodes should be returned: the node with both translations public, and
// the node with only the Hungarian translation marked as private.
$this->assertEqual(count($nids), 2, 'db_select() returns 2 nodes when the hu langcode is specified.');
$this->assertTrue(array_key_exists($this->nodes['both_public']->id(), $nids), 'The node with both translations public is returned.');
$this->assertTrue(array_key_exists($this->nodes['hu_private']->id(), $nids), 'The node with only the Hungarian translation private is returned.');
// Query with German (de) specified.
$select = db_select('node', 'n')
->fields('n', array('nid'))
->addMetaData('account', $this->webUser)
->addMetaData('langcode', 'de')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// There are no nodes with German translations, so no results are returned.
$this->assertTrue(empty($nids), 'db_select() returns an empty result when the de langcode is specified.');
// Query the nodes table as admin user (full access) with the node access
// tag and no specific langcode.
$select = db_select('node', 'n')
->fields('n', array('nid'))
->addMetaData('account', $this->adminUser)
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// All nodes are returned.
$this->assertEqual(count($nids), 6, 'db_select() returns all nodes.');
// Query the nodes table as admin user (full access) with the node access
// tag and langcode de.
$select = db_select('node', 'n')
->fields('n', array('nid'))
->addMetaData('account', $this->adminUser)
->addMetaData('langcode', 'de')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// Even though there is no German translation, all nodes are returned
// because node access filtering does not occur when the user is user 1.
$this->assertEqual(count($nids), 6, 'db_select() returns all nodes.');
}
}

View file

@ -0,0 +1,301 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeAccessLanguageTest.
*/
namespace Drupal\node\Tests;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\NodeType;
use Drupal\user\Entity\User;
/**
* Tests node_access and db_select() with node_access tag functionality with
* multiple languages with a test node access module that is not language-aware.
*
* @group node
*/
class NodeAccessLanguageTest extends NodeTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language', 'node_access_test');
protected function setUp() {
parent::setUp();
node_access_test_add_field(NodeType::load('page'));
// After enabling a node access module, the access table has to be rebuild.
node_access_rebuild();
// Enable the private node feature of the node_access_test module.
\Drupal::state()->set('node_access_test.private', TRUE);
// Add Hungarian, Catalan and Croatian.
ConfigurableLanguage::createFromLangcode('hu')->save();
ConfigurableLanguage::createFromLangcode('ca')->save();
ConfigurableLanguage::createFromLangcode('hr')->save();
}
/**
* Tests node access with multiple node languages and no private nodes.
*/
function testNodeAccess() {
$web_user = $this->drupalCreateUser(array('access content'));
$expected_node_access = array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE);
$expected_node_access_no_access = array('view' => FALSE, 'update' => FALSE, 'delete' => FALSE);
// Creating a public node with langcode Hungarian, will be saved as the
// fallback in node access table.
$node_public_hu = $this->drupalCreateNode(array('body' => array(array()), 'langcode' => 'hu', 'private' => FALSE));
$this->assertTrue($node_public_hu->language()->getId() == 'hu', 'Node created as Hungarian.');
// Tests the default access is provided for the public Hungarian node.
$this->assertNodeAccess($expected_node_access, $node_public_hu, $web_user);
// Tests that Hungarian provided specifically results in the same.
$this->assertNodeAccess($expected_node_access, $node_public_hu, $web_user, 'hu');
// There is no specific Catalan version of this node and Croatian is not
// even set up on the system in this scenario, so the user will not get
// access to these nodes.
$this->assertNodeAccess($expected_node_access_no_access, $node_public_hu, $web_user, 'ca');
$this->assertNodeAccess($expected_node_access_no_access, $node_public_hu, $web_user, 'hr');
// Creating a public node with no special langcode, like when no language
// module enabled.
$node_public_no_language = $this->drupalCreateNode(array(
'private' => FALSE,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
));
$this->assertTrue($node_public_no_language->language()->getId() == LanguageInterface::LANGCODE_NOT_SPECIFIED, 'Node created with not specified language.');
// Tests that access is granted if requested with no language.
$this->assertNodeAccess($expected_node_access, $node_public_no_language, $web_user);
// Tests that access is not granted if requested with Hungarian language.
$this->assertNodeAccess($expected_node_access_no_access, $node_public_no_language, $web_user, 'hu');
// There is no specific Catalan version of this node and Croatian is not
// even set up on the system in this scenario, so the user will not get
// access to these nodes.
$this->assertNodeAccess($expected_node_access_no_access, $node_public_no_language, $web_user, 'ca');
$this->assertNodeAccess($expected_node_access_no_access, $node_public_no_language, $web_user, 'hr');
// Reset the node access cache and turn on our test node access code.
\Drupal::entityManager()->getAccessControlHandler('node')->resetCache();
\Drupal::state()->set('node_access_test_secret_catalan', 1);
$node_public_ca = $this->drupalCreateNode(array('body' => array(array()), 'langcode' => 'ca', 'private' => FALSE));
$this->assertTrue($node_public_ca->language()->getId() == 'ca', 'Node created as Catalan.');
// Tests that access is granted if requested with no language.
$this->assertNodeAccess($expected_node_access, $node_public_no_language, $web_user);
$this->assertNodeAccess($expected_node_access_no_access, $node_public_ca, $web_user);
// Tests that Hungarian is still not accessible.
$this->assertNodeAccess($expected_node_access_no_access, $node_public_no_language, $web_user, 'hu');
$this->assertNodeAccess($expected_node_access_no_access, $node_public_ca, $web_user, 'hu');
// Tests that Hungarian node is still accessible.
$this->assertNodeAccess($expected_node_access, $node_public_hu, $web_user, 'hu');
// Tests that Catalan is still not accessible.
$this->assertNodeAccess($expected_node_access_no_access, $node_public_no_language, $web_user, 'ca');
$this->assertNodeAccess($expected_node_access_no_access, $node_public_ca, $web_user, 'ca');
// Make Catalan accessible.
\Drupal::state()->set('node_access_test_secret_catalan', 0);
// Tests that Catalan is accessible on a node with a Catalan version as the
// static cache has not been reset.
$this->assertNodeAccess($expected_node_access_no_access, $node_public_ca, $web_user, 'ca');
\Drupal::entityManager()->getAccessControlHandler('node')->resetCache();
// Tests that access is granted if requested with no language.
$this->assertNodeAccess($expected_node_access, $node_public_no_language, $web_user);
$this->assertNodeAccess($expected_node_access, $node_public_ca, $web_user);
// Tests that Hungarian is still not accessible.
$this->assertNodeAccess($expected_node_access_no_access, $node_public_no_language, $web_user, 'hu');
$this->assertNodeAccess($expected_node_access_no_access, $node_public_ca, $web_user, 'hu');
// Tests that Hungarian node is still accessible.
$this->assertNodeAccess($expected_node_access, $node_public_hu, $web_user, 'hu');
// Tests that Catalan is still not accessible on a node without a language.
$this->assertNodeAccess($expected_node_access_no_access, $node_public_no_language, $web_user, 'ca');
// Tests that Catalan is accessible on a node with a Catalan version.
$this->assertNodeAccess($expected_node_access, $node_public_ca, $web_user, 'ca');
}
/**
* Tests node access with multiple node languages and private nodes.
*/
function testNodeAccessPrivate() {
$web_user = $this->drupalCreateUser(array('access content'));
$expected_node_access = array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE);
$expected_node_access_no_access = array('view' => FALSE, 'update' => FALSE, 'delete' => FALSE);
// Creating a private node with langcode Hungarian, will be saved as the
// fallback in node access table.
$node_private_hu = $this->drupalCreateNode(array('body' => array(array()), 'langcode' => 'hu', 'private' => TRUE));
$this->assertTrue($node_private_hu->language()->getId() == 'hu', 'Node created as Hungarian.');
// Tests the default access is not provided for the private Hungarian node.
$this->assertNodeAccess($expected_node_access_no_access, $node_private_hu, $web_user);
// Tests that Hungarian provided specifically results in the same.
$this->assertNodeAccess($expected_node_access_no_access, $node_private_hu, $web_user, 'hu');
// There is no specific Catalan version of this node and Croatian is not
// even set up on the system in this scenario, so the user will not get
// access to these nodes.
$this->assertNodeAccess($expected_node_access_no_access, $node_private_hu, $web_user, 'ca');
$this->assertNodeAccess($expected_node_access_no_access, $node_private_hu, $web_user, 'hr');
// Creating a private node with no special langcode, like when no language
// module enabled.
$node_private_no_language = $this->drupalCreateNode(array(
'private' => TRUE,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
));
$this->assertTrue($node_private_no_language->language()->getId() == LanguageInterface::LANGCODE_NOT_SPECIFIED, 'Node created with not specified language.');
// Tests that access is not granted if requested with no language.
$this->assertNodeAccess($expected_node_access_no_access, $node_private_no_language, $web_user);
// Tests that access is not granted if requested with Hungarian language.
$this->assertNodeAccess($expected_node_access_no_access, $node_private_no_language, $web_user, 'hu');
// There is no specific Catalan version of this node and Croatian is not
// even set up on the system in this scenario, so the user will not get
// access to these nodes.
$this->assertNodeAccess($expected_node_access_no_access, $node_private_no_language, $web_user, 'ca');
$this->assertNodeAccess($expected_node_access_no_access, $node_private_no_language, $web_user, 'hr');
// Reset the node access cache and turn on our test node access code.
\Drupal::entityManager()->getAccessControlHandler('node')->resetCache();
\Drupal::state()->set('node_access_test_secret_catalan', 1);
// Tests that access is not granted if requested with no language.
$this->assertNodeAccess($expected_node_access_no_access, $node_private_no_language, $web_user);
// Tests that Hungarian is still not accessible.
$this->assertNodeAccess($expected_node_access_no_access, $node_private_no_language, $web_user, 'hu');
// Tests that Catalan is still not accessible.
$this->assertNodeAccess($expected_node_access_no_access, $node_private_no_language, $web_user, 'ca');
// Creating a private node with langcode Catalan to test that the
// node_access_test_secret_catalan flag works.
$private_ca_user = $this->drupalCreateUser(array('access content', 'node test view'));
$node_private_ca = $this->drupalCreateNode(array('body' => array(array()), 'langcode' => 'ca', 'private' => TRUE));
$this->assertTrue($node_private_ca->language()->getId() == 'ca', 'Node created as Catalan.');
// Tests that Catalan is still not accessible to either user.
$this->assertNodeAccess($expected_node_access_no_access, $node_private_ca, $web_user, 'ca');
$this->assertNodeAccess($expected_node_access_no_access, $node_private_ca, $private_ca_user, 'ca');
\Drupal::entityManager()->getAccessControlHandler('node')->resetCache();
\Drupal::state()->set('node_access_test_secret_catalan', 0);
// Tests that Catalan is still not accessible for a user with no access to
// private nodes.
$this->assertNodeAccess($expected_node_access_no_access, $node_private_ca, $web_user, 'ca');
// Tests that Catalan is accessible by a user with the permission to see
// private nodes.
$this->assertNodeAccess($expected_node_access, $node_private_ca, $private_ca_user, 'ca');
}
/**
* Tests db_select() with a 'node_access' tag and langcode metadata.
*/
function testNodeAccessQueryTag() {
// Create a normal authenticated user.
$web_user = $this->drupalCreateUser(array('access content'));
// Load the user 1 user for later use as an admin user with permission to
// see everything.
$admin_user = User::load(1);
// Creating a private node with langcode Hungarian, will be saved as
// the fallback in node access table.
$node_private = $this->drupalCreateNode(array('body' => array(array()), 'langcode' => 'hu', 'private' => TRUE));
$this->assertTrue($node_private->language()->getId() == 'hu', 'Node created as Hungarian.');
// Creating a public node with langcode Hungarian, will be saved as
// the fallback in node access table.
$node_public = $this->drupalCreateNode(array('body' => array(array()), 'langcode' => 'hu', 'private' => FALSE));
$this->assertTrue($node_public->language()->getId() == 'hu', 'Node created as Hungarian.');
// Creating a public node with no special langcode, like when no language
// module enabled.
$node_no_language = $this->drupalCreateNode(array(
'private' => FALSE,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
));
$this->assertTrue($node_no_language->language()->getId() == LanguageInterface::LANGCODE_NOT_SPECIFIED, 'Node created with not specified language.');
// Query the nodes table as the web user with the node access tag and no
// specific langcode.
$select = db_select('node', 'n')
->fields('n', array('nid'))
->addMetaData('account', $web_user)
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// The public node and no language node should be returned. Because no
// langcode is given it will use the fallback node.
$this->assertEqual(count($nids), 2, 'db_select() returns 2 node');
$this->assertTrue(array_key_exists($node_public->id(), $nids), 'Returned node ID is public node.');
$this->assertTrue(array_key_exists($node_no_language->id(), $nids), 'Returned node ID is no language node.');
// Query the nodes table as the web user with the node access tag and
// langcode de.
$select = db_select('node', 'n')
->fields('n', array('nid'))
->addMetaData('account', $web_user)
->addMetaData('langcode', 'de')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// Because no nodes are created in German, no nodes are returned.
$this->assertTrue(empty($nids), 'db_select() returns an empty result.');
// Query the nodes table as admin user (full access) with the node access
// tag and no specific langcode.
$select = db_select('node', 'n')
->fields('n', array('nid'))
->addMetaData('account', $admin_user)
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// All nodes are returned.
$this->assertEqual(count($nids), 3, 'db_select() returns all three nodes.');
// Query the nodes table as admin user (full access) with the node access
// tag and langcode de.
$select = db_select('node', 'n')
->fields('n', array('nid'))
->addMetaData('account', $admin_user)
->addMetaData('langcode', 'de')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// All nodes are returned because node access tag is not invoked when the
// user is user 1.
$this->assertEqual(count($nids), 3, 'db_select() returns all three nodes.');
}
}

View file

@ -0,0 +1,103 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeAccessPagerTest.
*/
namespace Drupal\node\Tests;
use Drupal\comment\CommentInterface;
use Drupal\comment\Tests\CommentTestTrait;
use Drupal\simpletest\WebTestBase;
/**
* Tests access controlled node views have the right amount of comment pages.
*
* @group node
*/
class NodeAccessPagerTest extends WebTestBase {
use CommentTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node_access_test', 'comment', 'forum');
protected function setUp() {
parent::setUp();
node_access_rebuild();
$this->drupalCreateContentType(array('type' => 'page', 'name' => t('Basic page')));
$this->addDefaultCommentField('node', 'page');
$this->webUser = $this->drupalCreateUser(array('access content', 'access comments', 'node test view'));
}
/**
* Tests the comment pager for nodes with multiple grants per realm.
*/
public function testCommentPager() {
// Create a node.
$node = $this->drupalCreateNode();
// Create 60 comments.
for ($i = 0; $i < 60; $i++) {
$comment = entity_create('comment', array(
'entity_id' => $node->id(),
'entity_type' => 'node',
'field_name' => 'comment',
'subject' => $this->randomMachineName(),
'comment_body' => array(
array('value' => $this->randomMachineName()),
),
'status' => CommentInterface::PUBLISHED,
));
$comment->save();
}
$this->drupalLogin($this->webUser);
// View the node page. With the default 50 comments per page there should
// be two pages (0, 1) but no third (2) page.
$this->drupalGet('node/' . $node->id());
$this->assertText($node->label());
$this->assertText(t('Comments'));
$this->assertRaw('page=1');
$this->assertNoRaw('page=2');
}
/**
* Tests the forum node pager for nodes with multiple grants per realm.
*/
public function testForumPager() {
// Look up the forums vocabulary ID.
$vid = $this->config('forum.settings')->get('vocabulary');
$this->assertTrue($vid, 'Forum navigation vocabulary ID is set.');
// Look up the general discussion term.
$tree = \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree($vid, 0, 1);
$tid = reset($tree)->tid;
$this->assertTrue($tid, 'General discussion term is found in the forum vocabulary.');
// Create 30 nodes.
for ($i = 0; $i < 30; $i++) {
$this->drupalCreateNode(array(
'nid' => NULL,
'type' => 'forum',
'taxonomy_forums' => array(
array('target_id' => $tid),
),
));
}
// View the general discussion forum page. With the default 25 nodes per
// page there should be two pages for 30 nodes, no more.
$this->drupalLogin($this->webUser);
$this->drupalGet('forum/' . $tid);
$this->assertRaw('page=1');
$this->assertNoRaw('page=2');
}
}

View file

@ -0,0 +1,85 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeAccessRebuildNodeGrantsTest.
*/
namespace Drupal\node\Tests;
/**
* Ensures that node access rebuild functions work correctly even
* when other modules implements hook_node_grants().
*
* @group node
*/
class NodeAccessRebuildNodeGrantsTest extends NodeTestBase {
/**
* A user to test the rebuild nodes feature.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$admin_user = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports', 'bypass node access'));
$this->drupalLogin($admin_user);
$this->webUser = $this->drupalCreateUser();
}
/**
* Tests rebuilding the node access permissions table with content.
*/
public function testNodeAccessRebuildNodeGrants() {
\Drupal::service('module_installer')->install(['node_access_test']);
$this->resetAll();
$node = $this->drupalCreateNode(array(
'uid' => $this->webUser->id(),
));
// Default realm access and node records are present.
$this->assertTrue(\Drupal::service('node.grant_storage')->access($node, 'view', 'en', $this->webUser), 'The expected node access records are present');
$this->assertEqual(1, \Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is an all realm access record');
$this->assertTrue(\Drupal::state()->get('node.node_access_needs_rebuild'), 'Node access permissions need to be rebuilt');
// Rebuild permissions.
$this->drupalGet('admin/reports/status/rebuild');
$this->drupalPostForm(NULL, array(), t('Rebuild permissions'));
$this->assertText(t('The content access permissions have been rebuilt.'));
// Test if the rebuild has been successful.
$this->assertNull(\Drupal::state()->get('node.node_access_needs_rebuild'), 'Node access permissions have been rebuilt');
$this->assertTrue(\Drupal::service('node.grant_storage')->access($node, 'view', 'en', $this->webUser), 'The expected node access records are present');
$this->assertFalse(\Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is no all realm access record');
}
/**
* Tests rebuilding the node access permissions table with no content.
*/
public function testNodeAccessRebuildNoAccessModules() {
// Default realm access is present.
$this->assertEqual(1, \Drupal::service('node.grant_storage')->count(), 'There is an all realm access record');
// No need to rebuild permissions.
$this->assertFalse(\Drupal::state()->get('node.node_access_needs_rebuild'), 'Node access permissions need to be rebuilt');
// Rebuild permissions.
$this->drupalGet('admin/reports/status');
$this->clickLink(t('Rebuild permissions'));
$this->drupalPostForm(NULL, array(), t('Rebuild permissions'));
$this->assertText(t('Content permissions have been rebuilt.'));
$this->assertNull(\Drupal::state()->get('node.node_access_needs_rebuild'), 'Node access permissions have been rebuilt');
// Default realm access is still present.
$this->assertEqual(1, \Drupal::service('node.grant_storage')->count(), 'There is an all realm access record');
}
}

View file

@ -0,0 +1,40 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeAccessRebuildTest.
*/
namespace Drupal\node\Tests;
/**
* Ensures that node access rebuild functions work correctly.
*
* @group node
*/
class NodeAccessRebuildTest extends NodeTestBase {
/**
* A normal authenticated user.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
protected function setUp() {
parent::setUp();
$web_user = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports'));
$this->drupalLogin($web_user);
$this->webUser = $web_user;
}
/**
* Tests rebuilding the node access permissions table.
*/
function testNodeAccessRebuild() {
$this->drupalGet('admin/reports/status');
$this->clickLink(t('Rebuild permissions'));
$this->drupalPostForm(NULL, array(), t('Rebuild permissions'));
$this->assertText(t('Content permissions have been rebuilt.'));
}
}

View file

@ -0,0 +1,88 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeAccessRecordsTest.
*/
namespace Drupal\node\Tests;
use Drupal\node\Entity\Node;
/**
* Tests hook_node_access_records when acquiring grants.
*
* @group node
*/
class NodeAccessRecordsTest extends NodeTestBase {
/**
* Enable a module that implements node access API hooks and alter hook.
*
* @var array
*/
public static $modules = array('node_test');
/**
* Creates a node and tests the creation of node access rules.
*/
function testNodeAccessRecords() {
// Create an article node.
$node1 = $this->drupalCreateNode(array('type' => 'article'));
$this->assertTrue(Node::load($node1->id()), 'Article node created.');
// Check to see if grants added by node_test_node_access_records made it in.
$records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node1->id()))->fetchAll();
$this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
$this->assertEqual($records[0]->realm, 'test_article_realm', 'Grant with article_realm acquired for node without alteration.');
$this->assertEqual($records[0]->gid, 1, 'Grant with gid = 1 acquired for node without alteration.');
// Create an unpromoted "Basic page" node.
$node2 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 0));
$this->assertTrue(Node::load($node2->id()), 'Unpromoted basic page node created.');
// Check to see if grants added by node_test_node_access_records made it in.
$records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node2->id()))->fetchAll();
$this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
$this->assertEqual($records[0]->realm, 'test_page_realm', 'Grant with page_realm acquired for node without alteration.');
$this->assertEqual($records[0]->gid, 1, 'Grant with gid = 1 acquired for node without alteration.');
// Create an unpromoted, unpublished "Basic page" node.
$node3 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 0, 'status' => 0));
$this->assertTrue(Node::load($node3->id()), 'Unpromoted, unpublished basic page node created.');
// Check to see if grants added by node_test_node_access_records made it in.
$records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node3->id()))->fetchAll();
$this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
$this->assertEqual($records[0]->realm, 'test_page_realm', 'Grant with page_realm acquired for node without alteration.');
$this->assertEqual($records[0]->gid, 1, 'Grant with gid = 1 acquired for node without alteration.');
// Create a promoted "Basic page" node.
$node4 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 1));
$this->assertTrue(Node::load($node4->id()), 'Promoted basic page node created.');
// Check to see if grant added by node_test_node_access_records was altered
// by node_test_node_access_records_alter.
$records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node4->id()))->fetchAll();
$this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
$this->assertEqual($records[0]->realm, 'test_alter_realm', 'Altered grant with alter_realm acquired for node.');
$this->assertEqual($records[0]->gid, 2, 'Altered grant with gid = 2 acquired for node.');
// Check to see if we can alter grants with hook_node_grants_alter().
$operations = array('view', 'update', 'delete');
// Create a user that is allowed to access content.
$web_user = $this->drupalCreateUser(array('access content'));
foreach ($operations as $op) {
$grants = node_test_node_grants($web_user, $op);
$altered_grants = $grants;
\Drupal::moduleHandler()->alter('node_grants', $altered_grants, $web_user, $op);
$this->assertNotEqual($grants, $altered_grants, format_string('Altered the %op grant for a user.', array('%op' => $op)));
}
// Check that core does not grant access to an unpublished node when an
// empty $grants array is returned.
$node6 = $this->drupalCreateNode(array('status' => 0, 'disable_node_access' => TRUE));
$records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node6->id()))->fetchAll();
$this->assertEqual(count($records), 0, 'Returned no records for unpublished node.');
}
}

View file

@ -0,0 +1,77 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeAccessTest.
*/
namespace Drupal\node\Tests;
use Drupal\user\RoleInterface;
/**
* Tests basic node_access functionality.
*
* Note that hook_node_access_records() is covered in another test class.
*
* @group node
* @todo Cover hook_node_access in a separate test class.
*/
class NodeAccessTest extends NodeTestBase {
protected function setUp() {
parent::setUp();
// Clear permissions for authenticated users.
$this->config('user.role.' . RoleInterface::AUTHENTICATED_ID)->set('permissions', array())->save();
}
/**
* Runs basic tests for node_access function.
*/
function testNodeAccess() {
// Ensures user without 'access content' permission can do nothing.
$web_user1 = $this->drupalCreateUser(array('create page content', 'edit any page content', 'delete any page content'));
$node1 = $this->drupalCreateNode(array('type' => 'page'));
$this->assertNodeCreateAccess($node1->bundle(), FALSE, $web_user1);
$this->assertNodeAccess(array('view' => FALSE, 'update' => FALSE, 'delete' => FALSE), $node1, $web_user1);
// Ensures user with 'bypass node access' permission can do everything.
$web_user2 = $this->drupalCreateUser(array('bypass node access'));
$node2 = $this->drupalCreateNode(array('type' => 'page'));
$this->assertNodeCreateAccess($node2->bundle(), TRUE, $web_user2);
$this->assertNodeAccess(array('view' => TRUE, 'update' => TRUE, 'delete' => TRUE), $node2, $web_user2);
// User cannot 'view own unpublished content'.
$web_user3 = $this->drupalCreateUser(array('access content'));
$node3 = $this->drupalCreateNode(array('status' => 0, 'uid' => $web_user3->id()));
$this->assertNodeAccess(array('view' => FALSE), $node3, $web_user3);
// User cannot create content without permission.
$this->assertNodeCreateAccess($node3->bundle(), FALSE, $web_user3);
// User can 'view own unpublished content', but another user cannot.
$web_user4 = $this->drupalCreateUser(array('access content', 'view own unpublished content'));
$web_user5 = $this->drupalCreateUser(array('access content', 'view own unpublished content'));
$node4 = $this->drupalCreateNode(array('status' => 0, 'uid' => $web_user4->id()));
$this->assertNodeAccess(array('view' => TRUE, 'update' => FALSE), $node4, $web_user4);
$this->assertNodeAccess(array('view' => FALSE), $node4, $web_user5);
// Tests the default access provided for a published node.
$node5 = $this->drupalCreateNode();
$this->assertNodeAccess(array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE), $node5, $web_user3);
// Tests the "edit any BUNDLE" and "delete any BUNDLE" permissions.
$web_user6 = $this->drupalCreateUser(array('access content', 'edit any page content', 'delete any page content'));
$node6 = $this->drupalCreateNode(array('type' => 'page'));
$this->assertNodeAccess(array('view' => TRUE, 'update' => TRUE, 'delete' => TRUE), $node6, $web_user6);
// Tests the "edit own BUNDLE" and "delete own BUNDLE" permission.
$web_user7 = $this->drupalCreateUser(array('access content', 'edit own page content', 'delete own page content'));
// User should not be able to edit or delete nodes they do not own.
$this->assertNodeAccess(array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE), $node6, $web_user7);
// User should be able to edit or delete nodes they own.
$node7 = $this->drupalCreateNode(array('type' => 'page', 'uid' => $web_user7->id()));
$this->assertNodeAccess(array('view' => TRUE, 'update' => TRUE, 'delete' => TRUE), $node7, $web_user7);
}
}

View file

@ -0,0 +1,198 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeAdminTest.
*/
namespace Drupal\node\Tests;
use Drupal\user\RoleInterface;
/**
* Tests node administration page functionality.
*
* @group node
*/
class NodeAdminTest extends NodeTestBase {
/**
* A user with permission to bypass access content.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* A user with the 'access content overview' permission.
*
* @var \Drupal\user\UserInterface
*/
protected $baseUser1;
/**
* A normal user with permission to view own unpublished content.
*
* @var \Drupal\user\UserInterface
*/
protected $baseUser2;
/**
* A normal user with permission to bypass node access content.
*
* @var \Drupal\user\UserInterface
*/
protected $baseUser3;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('views');
protected function setUp() {
parent::setUp();
// Remove the "view own unpublished content" permission which is set
// by default for authenticated users so we can test this permission
// correctly.
user_role_revoke_permissions(RoleInterface::AUTHENTICATED_ID, array('view own unpublished content'));
$this->adminUser = $this->drupalCreateUser(array('access administration pages', 'access content overview', 'administer nodes', 'bypass node access'));
$this->baseUser1 = $this->drupalCreateUser(['access content overview']);
$this->baseUser2 = $this->drupalCreateUser(['access content overview', 'view own unpublished content']);
$this->baseUser3 = $this->drupalCreateUser(['access content overview', 'bypass node access']);
}
/**
* Tests that the table sorting works on the content admin pages.
*/
function testContentAdminSort() {
$this->drupalLogin($this->adminUser);
$changed = REQUEST_TIME;
foreach (array('dd', 'aa', 'DD', 'bb', 'cc', 'CC', 'AA', 'BB') as $prefix) {
$changed += 1000;
$node = $this->drupalCreateNode(array('title' => $prefix . $this->randomMachineName(6)));
db_update('node_field_data')
->fields(array('changed' => $changed))
->condition('nid', $node->id())
->execute();
}
// Test that the default sort by node.changed DESC actually fires properly.
$nodes_query = db_select('node_field_data', 'n')
->fields('n', array('title'))
->orderBy('changed', 'DESC')
->execute()
->fetchCol();
$this->drupalGet('admin/content');
foreach ($nodes_query as $delta => $string) {
$elements = $this->xpath('//table[contains(@class, :class)]/tbody/tr[' . ($delta + 1) . ']/td[2]/a[normalize-space(text())=:label]', array(':class' => 'views-table', ':label' => $string));
$this->assertTrue(!empty($elements), 'The node was found in the correct order.');
}
// Compare the rendered HTML node list to a query for the nodes ordered by
// title to account for possible database-dependent sort order.
$nodes_query = db_select('node_field_data', 'n')
->fields('n', array('title'))
->orderBy('title')
->execute()
->fetchCol();
$this->drupalGet('admin/content', array('query' => array('sort' => 'asc', 'order' => 'title')));
foreach ($nodes_query as $delta => $string) {
$elements = $this->xpath('//table[contains(@class, :class)]/tbody/tr[' . ($delta + 1) . ']/td[2]/a[normalize-space(text())=:label]', array(':class' => 'views-table', ':label' => $string));
$this->assertTrue(!empty($elements), 'The node was found in the correct order.');
}
}
/**
* Tests content overview with different user permissions.
*
* Taxonomy filters are tested separately.
*
* @see TaxonomyNodeFilterTestCase
*/
function testContentAdminPages() {
$this->drupalLogin($this->adminUser);
$nodes['published_page'] = $this->drupalCreateNode(array('type' => 'page'));
$nodes['published_article'] = $this->drupalCreateNode(array('type' => 'article'));
$nodes['unpublished_page_1'] = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->baseUser1->id(), 'status' => 0));
$nodes['unpublished_page_2'] = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->baseUser2->id(), 'status' => 0));
// Verify view, edit, and delete links for any content.
$this->drupalGet('admin/content');
$this->assertResponse(200);
foreach ($nodes as $node) {
$this->assertLinkByHref('node/' . $node->id());
$this->assertLinkByHref('node/' . $node->id() . '/edit');
$this->assertLinkByHref('node/' . $node->id() . '/delete');
}
// Verify filtering by publishing status.
$this->drupalGet('admin/content', array('query' => array('status' => TRUE)));
$this->assertLinkByHref('node/' . $nodes['published_page']->id() . '/edit');
$this->assertLinkByHref('node/' . $nodes['published_article']->id() . '/edit');
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->id() . '/edit');
// Verify filtering by status and content type.
$this->drupalGet('admin/content', array('query' => array('status' => TRUE, 'type' => 'page')));
$this->assertLinkByHref('node/' . $nodes['published_page']->id() . '/edit');
$this->assertNoLinkByHref('node/' . $nodes['published_article']->id() . '/edit');
// Verify no operation links are displayed for regular users.
$this->drupalLogout();
$this->drupalLogin($this->baseUser1);
$this->drupalGet('admin/content');
$this->assertResponse(200);
$this->assertLinkByHref('node/' . $nodes['published_page']->id());
$this->assertLinkByHref('node/' . $nodes['published_article']->id());
$this->assertNoLinkByHref('node/' . $nodes['published_page']->id() . '/edit');
$this->assertNoLinkByHref('node/' . $nodes['published_page']->id() . '/delete');
$this->assertNoLinkByHref('node/' . $nodes['published_article']->id() . '/edit');
$this->assertNoLinkByHref('node/' . $nodes['published_article']->id() . '/delete');
// Verify no unpublished content is displayed without permission.
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->id());
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->id() . '/edit');
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->id() . '/delete');
// Verify no tableselect.
$this->assertNoFieldByName('nodes[' . $nodes['published_page']->id() . ']', '', 'No tableselect found.');
// Verify unpublished content is displayed with permission.
$this->drupalLogout();
$this->drupalLogin($this->baseUser2);
$this->drupalGet('admin/content');
$this->assertResponse(200);
$this->assertLinkByHref('node/' . $nodes['unpublished_page_2']->id());
// Verify no operation links are displayed.
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_2']->id() . '/edit');
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_2']->id() . '/delete');
// Verify user cannot see unpublished content of other users.
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->id());
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->id() . '/edit');
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->id() . '/delete');
// Verify no tableselect.
$this->assertNoFieldByName('nodes[' . $nodes['unpublished_page_2']->id() . ']', '', 'No tableselect found.');
// Verify node access can be bypassed.
$this->drupalLogout();
$this->drupalLogin($this->baseUser3);
$this->drupalGet('admin/content');
$this->assertResponse(200);
foreach ($nodes as $node) {
$this->assertLinkByHref('node/' . $node->id());
$this->assertLinkByHref('node/' . $node->id() . '/edit');
$this->assertLinkByHref('node/' . $node->id() . '/delete');
}
}
}

View file

@ -0,0 +1,161 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeBlockFunctionalTest.
*/
namespace Drupal\node\Tests;
use Drupal\block\Entity\Block;
use Drupal\user\RoleInterface;
/**
* Tests node block functionality.
*
* @group node
*/
class NodeBlockFunctionalTest extends NodeTestBase {
/**
* An administrative user for testing.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* An unprivileged user for testing.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('block', 'views');
protected function setUp() {
parent::setUp();
// Create users and test node.
$this->adminUser = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'administer blocks', 'access content overview'));
$this->webUser = $this->drupalCreateUser(array('access content', 'create article content'));
}
/**
* Tests the recent comments block.
*/
public function testRecentNodeBlock() {
$this->drupalLogin($this->adminUser);
// Disallow anonymous users to view content.
user_role_change_permissions(RoleInterface::ANONYMOUS_ID, array(
'access content' => FALSE,
));
// Enable the recent content block with two items.
$block = $this->drupalPlaceBlock('views_block:content_recent-block_1', array('id' => 'test_block', 'items_per_page' => 2));
// Test that block is not visible without nodes.
$this->drupalGet('');
$this->assertText(t('No content available.'), 'Block with "No content available." found.');
// Add some test nodes.
$default_settings = array('uid' => $this->webUser->id(), 'type' => 'article');
$node1 = $this->drupalCreateNode($default_settings);
$node2 = $this->drupalCreateNode($default_settings);
$node3 = $this->drupalCreateNode($default_settings);
// Change the changed time for node so that we can test ordering.
db_update('node_field_data')
->fields(array(
'changed' => $node1->getChangedTime() + 100,
))
->condition('nid', $node2->id())
->execute();
db_update('node_field_data')
->fields(array(
'changed' => $node1->getChangedTime() + 200,
))
->condition('nid', $node3->id())
->execute();
// Test that a user without the 'access content' permission cannot
// see the block.
$this->drupalLogout();
$this->drupalGet('');
$this->assertNoText($block->label(), 'Block was not found.');
// Test that only the 2 latest nodes are shown.
$this->drupalLogin($this->webUser);
$this->assertNoText($node1->label(), 'Node not found in block.');
$this->assertText($node2->label(), 'Node found in block.');
$this->assertText($node3->label(), 'Node found in block.');
// Check to make sure nodes are in the right order.
$this->assertTrue($this->xpath('//div[@id="block-test-block"]//table/tbody/tr[position() = 1]/td/a[text() = "' . $node3->label() . '"]'), 'Nodes were ordered correctly in block.');
$this->drupalLogout();
$this->drupalLogin($this->adminUser);
// Verify that the More link is shown and leads to the admin content page.
$this->drupalGet('');
$this->clickLink('More');
$this->assertResponse('200');
$this->assertUrl('admin/content');
// Set the number of recent nodes to show to 10.
$block->getPlugin()->setConfigurationValue('items_per_page', 10);
$block->save();
// Post an additional node.
$node4 = $this->drupalCreateNode($default_settings);
// Test that all four nodes are shown.
$this->drupalGet('');
$this->assertText($node1->label(), 'Node found in block.');
$this->assertText($node2->label(), 'Node found in block.');
$this->assertText($node3->label(), 'Node found in block.');
$this->assertText($node4->label(), 'Node found in block.');
// Enable the "Powered by Drupal" block only on article nodes.
$edit = [
'id' => strtolower($this->randomMachineName()),
'region' => 'sidebar_first',
'visibility[node_type][bundles][article]' => 'article',
];
$theme = \Drupal::service('theme_handler')->getDefault();
$this->drupalPostForm("admin/structure/block/add/system_powered_by_block/$theme", $edit, t('Save block'));
$block = Block::load($edit['id']);
$visibility = $block->getVisibility();
$this->assertTrue(isset($visibility['node_type']['bundles']['article']), 'Visibility settings were saved to configuration');
// Create a page node.
$node5 = $this->drupalCreateNode(array('uid' => $this->adminUser->id(), 'type' => 'page'));
$this->drupalLogout();
$this->drupalLogin($this->webUser);
// Verify visibility rules.
$this->drupalGet('');
$label = $block->label();
$this->assertNoText($label, 'Block was not displayed on the front page.');
$this->drupalGet('node/add/article');
$this->assertText($label, 'Block was displayed on the node/add/article page.');
$this->drupalGet('node/' . $node1->id());
$this->assertText($label, 'Block was displayed on the node/N when node is of type article.');
$this->drupalGet('node/' . $node5->id());
$this->assertNoText($label, 'Block was not displayed on nodes of type page.');
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/structure/block');
$this->assertText($label, 'Block was displayed on the admin/structure/block page.');
$this->assertLinkByHref($block->url());
}
}

View file

@ -0,0 +1,63 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeBodyFieldStorageTest.
*/
namespace Drupal\node\Tests;
use Drupal\Core\Field\Entity\BaseFieldOverride;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\simpletest\KernelTestBase;
use Drupal\system\Tests\Entity\EntityUnitTestBase;
/**
* Tests node body field storage.
*
* @group node
*/
class NodeBodyFieldStorageTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('user', 'system', 'field', 'node', 'text', 'filter', 'entity_reference');
protected function setUp() {
parent::setUp();
$this->installSchema('system', 'sequences');
$this->installSchema('system', array('router'));
// Necessary for module uninstall.
$this->installSchema('user', 'users_data');
$this->installEntitySchema('user');
$this->installEntitySchema('node');
$this->installConfig(array('field', 'node'));
}
/**
* Tests node body field storage persistence even if there are no instances.
*/
public function testFieldOverrides() {
$field_storage = FieldStorageConfig::loadByName('node', 'body');
$this->assertTrue($field_storage, 'Node body field storage exists.');
$type = NodeType::create(['name' => 'Ponies', 'type' => 'ponies']);
$type->save();
node_add_body_field($type);
$field_storage = FieldStorageConfig::loadByName('node', 'body');
$this->assertTrue(count($field_storage->getBundles()) == 1, 'Node body field storage is being used on the new node type.');
$field = FieldConfig::loadByName('node', 'ponies', 'body');
$field->delete();
$field_storage = FieldStorageConfig::loadByName('node', 'body');
$this->assertTrue(count($field_storage->getBundles()) == 0, 'Node body field storage exists after deleting the only instance of a field.');
\Drupal::service('module_installer')->uninstall(array('node'));
$field_storage = FieldStorageConfig::loadByName('node', 'body');
$this->assertFalse($field_storage, 'Node body field storage does not exist after uninstalling the Node module.');
}
}

View file

@ -0,0 +1,68 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeCacheTagsTest.
*/
namespace Drupal\node\Tests;
use Drupal\Core\Entity\EntityInterface;
use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase;
use Drupal\user\Entity\Role;
/**
* Tests the Node entity's cache tags.
*
* @group node
*/
class NodeCacheTagsTest extends EntityWithUriCacheTagsTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('node');
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Camelids" node type.
entity_create('node_type', array(
'name' => 'Camelids',
'type' => 'camelids',
))->save();
// Create a "Llama" node.
$node = entity_create('node', array('type' => 'camelids'));
$node->setTitle('Llama')
->setPublished(TRUE)
->save();
return $node;
}
/**
* {@inheritdoc}
*/
protected function getAdditionalCacheContextsForEntity(EntityInterface $entity) {
return ['timezone'];
}
/**
* {@inheritdoc}
*
* Each node must have an author.
*/
protected function getAdditionalCacheTagsForEntity(EntityInterface $node) {
return array('user:' . $node->getOwnerId(), 'user_view');
}
/**
* {@inheritdoc}
*/
protected function getAdditionalCacheContextsForEntityListing() {
return ['user.node_grants:view'];
}
}

View file

@ -0,0 +1,224 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeCreationTest.
*/
namespace Drupal\node\Tests;
use Drupal\Core\Database\Database;
use Drupal\Core\Language\LanguageInterface;
/**
* Create a node and test saving it.
*
* @group node
*/
class NodeCreationTest extends NodeTestBase {
/**
* Modules to enable.
*
* Enable dummy module that implements hook_ENTITY_TYPE_insert() for
* exceptions (function node_test_exception_node_insert() ).
*
* @var array
*/
public static $modules = array('node_test_exception', 'dblog', 'test_page_test');
protected function setUp() {
parent::setUp();
$web_user = $this->drupalCreateUser(array('create page content', 'edit own page content'));
$this->drupalLogin($web_user);
}
/**
* Creates a "Basic page" node and verifies its consistency in the database.
*/
function testNodeCreation() {
$node_type_storage = \Drupal::entityManager()->getStorage('node_type');
// Test /node/add page with only one content type.
$node_type_storage->load('article')->delete();
$this->drupalGet('node/add');
$this->assertResponse(200);
$this->assertUrl('node/add/page');
// Create a node.
$edit = array();
$edit['title[0][value]'] = $this->randomMachineName(8);
$edit['body[0][value]'] = $this->randomMachineName(16);
$this->drupalPostForm('node/add/page', $edit, t('Save'));
// Check that the Basic page has been created.
$this->assertRaw(t('!post %title has been created.', array('!post' => 'Basic page', '%title' => $edit['title[0][value]'])), 'Basic page created.');
// Check that the node exists in the database.
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$this->assertTrue($node, 'Node found in database.');
// Verify that pages do not show submitted information by default.
$this->drupalGet('node/' . $node->id());
$this->assertNoText($node->getOwner()->getUsername());
$this->assertNoText(format_date($node->getCreatedTime()));
// Change the node type setting to show submitted by information.
/** @var \Drupal\node\NodeTypeInterface $node_type */
$node_type = $node_type_storage->load('page');
$node_type->setDisplaySubmitted(TRUE);
$node_type->save();
$this->drupalGet('node/' . $node->id());
$this->assertText($node->getOwner()->getUsername());
$this->assertText(format_date($node->getCreatedTime()));
}
/**
* Verifies that a transaction rolls back the failed creation.
*/
function testFailedPageCreation() {
// Create a node.
$edit = array(
'uid' => $this->loggedInUser->id(),
'name' => $this->loggedInUser->name,
'type' => 'page',
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
'title' => 'testing_transaction_exception',
);
try {
// An exception is generated by node_test_exception_node_insert() if the
// title is 'testing_transaction_exception'.
entity_create('node', $edit)->save();
$this->fail(t('Expected exception has not been thrown.'));
}
catch (\Exception $e) {
$this->pass(t('Expected exception has been thrown.'));
}
if (Database::getConnection()->supportsTransactions()) {
// Check that the node does not exist in the database.
$node = $this->drupalGetNodeByTitle($edit['title']);
$this->assertFalse($node, 'Transactions supported, and node not found in database.');
}
else {
// Check that the node exists in the database.
$node = $this->drupalGetNodeByTitle($edit['title']);
$this->assertTrue($node, 'Transactions not supported, and node found in database.');
// Check that the failed rollback was logged.
$records = static::getWatchdogIdsForFailedExplicitRollback();
$this->assertTrue(count($records) > 0, 'Transactions not supported, and rollback error logged to watchdog.');
}
// Check that the rollback error was logged.
$records = static::getWatchdogIdsForTestExceptionRollback();
$this->assertTrue(count($records) > 0, 'Rollback explanatory error logged to watchdog.');
}
/**
* Creates an unpublished node and confirms correct redirect behavior.
*/
function testUnpublishedNodeCreation() {
// Set the front page to the test page.
$this->config('system.site')->set('page.front', '/test-page')->save();
// Set "Basic page" content type to be unpublished by default.
$fields = \Drupal::entityManager()->getFieldDefinitions('node', 'page');
$fields['status']->getConfig('page')
->setDefaultValue(FALSE)
->save();
// Create a node.
$edit = array();
$edit['title[0][value]'] = $this->randomMachineName(8);
$edit['body[0][value]'] = $this->randomMachineName(16);
$this->drupalPostForm('node/add/page', $edit, t('Save'));
// Check that the user was redirected to the home page.
$this->assertUrl('');
$this->assertText(t('Test page text'));
// Confirm that the node was created.
$this->assertRaw(t('!post %title has been created.', array('!post' => 'Basic page', '%title' => $edit['title[0][value]'])));
}
/**
* Tests the author autocompletion textfield.
*/
public function testAuthorAutocomplete() {
$admin_user = $this->drupalCreateUser(array('administer nodes', 'create page content'));
$this->drupalLogin($admin_user);
$this->drupalGet('node/add/page');
$result = $this->xpath('//input[@id="edit-uid-0-value" and contains(@data-autocomplete-path, "user/autocomplete")]');
$this->assertEqual(count($result), 0, 'No autocompletion without access user profiles.');
$admin_user = $this->drupalCreateUser(array('administer nodes', 'create page content', 'access user profiles'));
$this->drupalLogin($admin_user);
$this->drupalGet('node/add/page');
$result = $this->xpath('//input[@id="edit-uid-0-target-id" and contains(@data-autocomplete-path, "/entity_reference_autocomplete/user/default")]');
$this->assertEqual(count($result), 1, 'Ensure that the user does have access to the autocompletion');
}
/**
* Check node/add when no node types exist.
*/
function testNodeAddWithoutContentTypes () {
$this->drupalGet('node/add');
$this->assertResponse(200);
$this->assertNoLinkByHref('/admin/structure/types/add');
// Test /node/add page without content types.
foreach (\Drupal::entityManager()->getStorage('node_type')->loadMultiple() as $entity ) {
$entity->delete();
}
$this->drupalGet('node/add');
$this->assertResponse(403);
$admin_content_types = $this->drupalCreateUser(array('administer content types'));
$this->drupalLogin($admin_content_types);
$this->drupalGet('node/add');
$this->assertLinkByHref('/admin/structure/types/add');
}
/**
* Gets the watchdog IDs of the records with the rollback exception message.
*
* @return int[]
* Array containing the IDs of the log records with the rollback exception
* message.
*/
protected static function getWatchdogIdsForTestExceptionRollback() {
// PostgreSQL doesn't support bytea LIKE queries, so we need to unserialize
// first to check for the rollback exception message.
$matches = array();
$query = db_query("SELECT wid, variables FROM {watchdog}");
foreach ($query as $row) {
$variables = (array) unserialize($row->variables);
if (isset($variables['!message']) && $variables['!message'] === 'Test exception for rollback.') {
$matches[] = $row->wid;
}
}
return $matches;
}
/**
* Gets the log records with the explicit rollback failed exception message.
*
* @return \Drupal\Core\Database\StatementInterface
* A prepared statement object (already executed), which contains the log
* records with the explicit rollback failed exception message.
*/
protected static function getWatchdogIdsForFailedExplicitRollback() {
return db_query("SELECT wid FROM {watchdog} WHERE message LIKE 'Explicit rollback failed%'")->fetchAll();
}
}

View file

@ -0,0 +1,51 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeEntityViewModeAlterTest.
*/
namespace Drupal\node\Tests;
/**
* Tests changing view modes for nodes.
*
* @group node
*/
class NodeEntityViewModeAlterTest extends NodeTestBase {
/**
* Enable dummy module that implements hook_ENTITY_TYPE_view() for nodes.
*/
public static $modules = array('node_test');
/**
* Create a "Basic page" node and verify its consistency in the database.
*/
function testNodeViewModeChange() {
$web_user = $this->drupalCreateUser(array('create page content', 'edit own page content'));
$this->drupalLogin($web_user);
// Create a node.
$edit = array();
$edit['title[0][value]'] = $this->randomMachineName(8);
$edit['body[0][value]'] = t('Data that should appear only in the body for the node.');
$edit['body[0][summary]'] = t('Extra data that should appear only in the teaser for the node.');
$this->drupalPostForm('node/add/page', $edit, t('Save'));
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
// Set the flag to alter the view mode and view the node.
\Drupal::state()->set('node_test_change_view_mode', 'teaser');
$this->drupalGet('node/' . $node->id());
// Check that teaser mode is viewed.
$this->assertText('Extra data that should appear only in the teaser for the node.', 'Teaser text present');
// Make sure body text is not present.
$this->assertNoText('Data that should appear only in the body for the node.', 'Body text not present');
// Test that the correct build mode has been set.
$build = $this->drupalBuildEntityView($node);
$this->assertEqual($build['#view_mode'], 'teaser', 'The view mode has correctly been set to teaser.');
}
}

View file

@ -0,0 +1,153 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeFieldAccessTest.
*/
namespace Drupal\node\Tests;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\system\Tests\Entity\EntityUnitTestBase;
/**
* Tests node field level access.
*
* @group node
*/
class NodeFieldAccessTest extends EntityUnitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node');
/**
* Fields that only users with administer nodes permissions can change.
*
* @var array
*/
protected $administrativeFields = array(
'status',
'promote',
'sticky',
'created',
'uid',
);
/**
* These fields are automatically managed and can not be changed by any user.
*
* @var array
*/
protected $readOnlyFields = array('changed', 'revision_uid', 'revision_timestamp');
/**
* Test permissions on nodes status field.
*/
function testAccessToAdministrativeFields() {
// Create the page node type with revisions disabled.
$page = NodeType::create([
'type' => 'page',
'new_revision' => FALSE,
]);
$page->save();
// Create the article node type with revisions disabled.
$article = NodeType::create([
'type' => 'article',
'new_revision' => TRUE,
]);
$article->save();
// An administrator user. No user exists yet, ensure that the first user
// does not have UID 1.
$content_admin_user = $this->createUser(array('uid' => 2), array('administer nodes'));
// Two different editor users.
$page_creator_user = $this->createUser(array(), array('create page content', 'edit own page content', 'delete own page content'));
$page_manager_user = $this->createUser(array(), array('create page content', 'edit any page content', 'delete any page content'));
// An unprivileged user.
$page_unrelated_user = $this->createUser(array(), array('access content'));
// List of all users
$test_users = array(
$content_admin_user,
$page_creator_user,
$page_manager_user,
$page_unrelated_user,
);
// Create three "Basic pages". One is owned by our test-user
// "page_creator", one by "page_manager", and one by someone else.
$node1 = Node::create(array(
'title' => $this->randomMachineName(8),
'uid' => $page_creator_user->id(),
'type' => 'page',
));
$node2 = Node::create(array(
'title' => $this->randomMachineName(8),
'uid' => $page_manager_user->id(),
'type' => 'article',
));
$node3 = Node::create(array(
'title' => $this->randomMachineName(8),
'type' => 'page',
));
foreach ($this->administrativeFields as $field) {
// Checks on view operations.
foreach ($test_users as $account) {
$may_view = $node1->{$field}->access('view', $account);
$this->assertTrue($may_view, SafeMarkup::format('Any user may view the field @name.', array('@name' => $field)));
}
// Checks on edit operations.
$may_update = $node1->{$field}->access('edit', $page_creator_user);
$this->assertFalse($may_update, SafeMarkup::format('Users with permission "edit own page content" is not allowed to the field @name.', array('@name' => $field)));
$may_update = $node2->{$field}->access('edit', $page_creator_user);
$this->assertFalse($may_update, SafeMarkup::format('Users with permission "edit own page content" is not allowed to the field @name.', array('@name' => $field)));
$may_update = $node2->{$field}->access('edit', $page_manager_user);
$this->assertFalse($may_update, SafeMarkup::format('Users with permission "edit any page content" is not allowed to the field @name.', array('@name' => $field)));
$may_update = $node1->{$field}->access('edit', $page_manager_user);
$this->assertFalse($may_update, SafeMarkup::format('Users with permission "edit any page content" is not allowed to the field @name.', array('@name' => $field)));
$may_update = $node2->{$field}->access('edit', $page_unrelated_user);
$this->assertFalse($may_update, SafeMarkup::format('Users not having permission "edit any page content" is not allowed to the field @name.', array('@name' => $field)));
$may_update = $node1->{$field}->access('edit', $content_admin_user) && $node3->status->access('edit', $content_admin_user);
$this->assertTrue($may_update, SafeMarkup::format('Users with permission "administer nodes" may edit @name fields on all nodes.', array('@name' => $field)));
}
foreach ($this->readOnlyFields as $field) {
// Check view operation.
foreach ($test_users as $account) {
$may_view = $node1->{$field}->access('view', $account);
$this->assertTrue($may_view, SafeMarkup::format('Any user may view the field @name.', array('@name' => $field)));
}
// Check edit operation.
foreach ($test_users as $account) {
$may_view = $node1->{$field}->access('edit', $account);
$this->assertFalse($may_view, SafeMarkup::format('No user is not allowed to edit the field @name.', array('@name' => $field)));
}
}
// Check the revision_log field on node 1 which has revisions disabled.
$may_update = $node1->revision_log->access('edit', $content_admin_user);
$this->assertTrue($may_update, 'A user with permission "administer nodes" can edit the revision_log field when revisions are disabled.');
$may_update = $node1->revision_log->access('edit', $page_creator_user);
$this->assertFalse($may_update, 'A user without permission "administer nodes" can not edit the revision_log field when revisions are disabled.');
// Check the revision_log field on node 2 which has revisions enabled.
$may_update = $node2->revision_log->access('edit', $content_admin_user);
$this->assertTrue($may_update, 'A user with permission "administer nodes" can edit the revision_log field when revisions are enabled.');
$may_update = $node2->revision_log->access('edit', $page_creator_user);
$this->assertTrue($may_update, 'A user without permission "administer nodes" can edit the revision_log field when revisions are enabled.');
}
}

View file

@ -0,0 +1,134 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeFieldMultilingualTest.
*/
namespace Drupal\node\Tests;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
use Drupal\simpletest\WebTestBase;
use Drupal\Core\Language\LanguageInterface;
/**
* Tests multilingual support for fields.
*
* @group node
*/
class NodeFieldMultilingualTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node', 'language');
protected function setUp() {
parent::setUp();
// Create Basic page node type.
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
// Setup users.
$admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages', 'create page content', 'edit own page content'));
$this->drupalLogin($admin_user);
// Add a new language.
ConfigurableLanguage::createFromLangcode('it')->save();
// Enable URL language detection and selection.
$edit = array('language_interface[enabled][language-url]' => '1');
$this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
// Set "Basic page" content type to use multilingual support.
$edit = array(
'language_configuration[language_alterable]' => TRUE,
);
$this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type'));
$this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), 'Basic page content type has been updated.');
// Make node body translatable.
$field_storage = FieldStorageConfig::loadByName('node', 'body');
$field_storage->setTranslatable(TRUE);
$field_storage->save();
}
/**
* Tests whether field languages are correctly set through the node form.
*/
function testMultilingualNodeForm() {
// Create "Basic page" content.
$langcode = language_get_default_langcode('node', 'page');
$title_key = 'title[0][value]';
$title_value = $this->randomMachineName(8);
$body_key = 'body[0][value]';
$body_value = $this->randomMachineName(16);
// Create node to edit.
$edit = array();
$edit[$title_key] = $title_value;
$edit[$body_key] = $body_value;
$this->drupalPostForm('node/add/page', $edit, t('Save'));
// Check that the node exists in the database.
$node = $this->drupalGetNodeByTitle($edit[$title_key]);
$this->assertTrue($node, 'Node found in database.');
$this->assertTrue($node->language()->getId() == $langcode && $node->body->value == $body_value, 'Field language correctly set.');
// Change node language.
$langcode = 'it';
$this->drupalGet("node/{$node->id()}/edit");
$edit = array(
$title_key => $this->randomMachineName(8),
'langcode[0][value]' => $langcode,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$node = $this->drupalGetNodeByTitle($edit[$title_key], TRUE);
$this->assertTrue($node, 'Node found in database.');
$this->assertTrue($node->language()->getId() == $langcode && $node->body->value == $body_value, 'Field language correctly changed.');
// Enable content language URL detection.
$this->container->get('language_negotiator')->saveConfiguration(LanguageInterface::TYPE_CONTENT, array(LanguageNegotiationUrl::METHOD_ID => 0));
// Test multilingual field language fallback logic.
$this->drupalGet("it/node/{$node->id()}");
$this->assertRaw($body_value, 'Body correctly displayed using Italian as requested language');
$this->drupalGet("node/{$node->id()}");
$this->assertRaw($body_value, 'Body correctly displayed using English as requested language');
}
/*
* Tests multilingual field display settings.
*/
function testMultilingualDisplaySettings() {
// Create "Basic page" content.
$title_key = 'title[0][value]';
$title_value = $this->randomMachineName(8);
$body_key = 'body[0][value]';
$body_value = $this->randomMachineName(16);
// Create node to edit.
$edit = array();
$edit[$title_key] = $title_value;
$edit[$body_key] = $body_value;
$this->drupalPostForm('node/add/page', $edit, t('Save'));
// Check that the node exists in the database.
$node = $this->drupalGetNodeByTitle($edit[$title_key]);
$this->assertTrue($node, 'Node found in database.');
// Check if node body is showed.
$this->drupalGet('node/' . $node->id());
$body = $this->xpath('//article[contains(concat(" ", normalize-space(@class), " "), :node-class)]//div[contains(concat(" ", normalize-space(@class), " "), :content-class)]/descendant::p', array(
':node-class' => ' node ',
':content-class' => 'node__content',
));
$this->assertEqual(current($body), $node->body->value, 'Node body found.');
}
}

View file

@ -0,0 +1,68 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeFieldOverridesTest.
*/
namespace Drupal\node\Tests;
use Drupal\Core\Field\Entity\BaseFieldOverride;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\system\Tests\Entity\EntityUnitTestBase;
/**
* Tests node field overrides.
*
* @group node
*/
class NodeFieldOverridesTest extends EntityUnitTestBase {
/**
* Current logged in user.
*
* @var \Drupal\user\UserInterface
*/
protected $user;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('user', 'system', 'field', 'node');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(array('user'));
$this->user = $this->createUser();
\Drupal::service('current_user')->setAccount($this->user);
}
/**
* Tests that field overrides work as expected.
*/
public function testFieldOverrides() {
if (!NodeType::load('ponies')) {
NodeType::create(['name' => 'Ponies', 'type' => 'ponies'])->save();
}
$override = BaseFieldOverride::loadByName('node', 'ponies', 'uid');
if ($override) {
$override->delete();
}
$uid_field = \Drupal::entityManager()->getBaseFieldDefinitions('node')['uid'];
$config = $uid_field->getConfig('ponies');
$config->save();
$this->assertEqual($config->get('default_value_callback'), 'Drupal\node\Entity\Node::getCurrentUserId');
/** @var \Drupal\node\NodeInterface $node */
$node = Node::create(['type' => 'ponies']);
$owner = $node->getOwner();
$this->assertTrue($owner instanceof \Drupal\user\UserInterface);
$this->assertEqual($owner->id(), $this->user->id());
}
}

View file

@ -0,0 +1,175 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeFormButtonsTest.
*/
namespace Drupal\node\Tests;
/**
* Tests all the different buttons on the node form.
*
* @group node
*/
class NodeFormButtonsTest extends NodeTestBase {
/**
* A normal logged in user.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
/**
* A user with permission to bypass access content.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
protected function setUp() {
parent::setUp();
// Create a user that has no access to change the state of the node.
$this->webUser = $this->drupalCreateUser(array('create article content', 'edit own article content'));
// Create a user that has access to change the state of the node.
$this->adminUser = $this->drupalCreateUser(array('administer nodes', 'bypass node access'));
}
/**
* Tests that the right buttons are displayed for saving nodes.
*/
function testNodeFormButtons() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
// Login as administrative user.
$this->drupalLogin($this->adminUser);
// Verify the buttons on a node add form.
$this->drupalGet('node/add/article');
$this->assertButtons(array(t('Save and publish'), t('Save as unpublished')));
// Save the node and assert it's published after clicking
// 'Save and publish'.
$edit = array('title[0][value]' => $this->randomString());
$this->drupalPostForm('node/add/article', $edit, t('Save and publish'));
// Get the node.
$node_1 = $node_storage->load(1);
$this->assertTrue($node_1->isPublished(), 'Node is published');
// Verify the buttons on a node edit form.
$this->drupalGet('node/' . $node_1->id() . '/edit');
$this->assertButtons(array(t('Save and keep published'), t('Save and unpublish')));
// Save the node and verify it's still published after clicking
// 'Save and keep published'.
$this->drupalPostForm(NULL, $edit, t('Save and keep published'));
$node_storage->resetCache(array(1));
$node_1 = $node_storage->load(1);
$this->assertTrue($node_1->isPublished(), 'Node is published');
// Save the node and verify it's unpublished after clicking
// 'Save and unpublish'.
$this->drupalPostForm('node/' . $node_1->id() . '/edit', $edit, t('Save and unpublish'));
$node_storage->resetCache(array(1));
$node_1 = $node_storage->load(1);
$this->assertFalse($node_1->isPublished(), 'Node is unpublished');
// Verify the buttons on an unpublished node edit screen.
$this->drupalGet('node/' . $node_1->id() . '/edit');
$this->assertButtons(array(t('Save and keep unpublished'), t('Save and publish')));
// Create a node as a normal user.
$this->drupalLogout();
$this->drupalLogin($this->webUser);
// Verify the buttons for a normal user.
$this->drupalGet('node/add/article');
$this->assertButtons(array(t('Save')), FALSE);
// Create the node.
$edit = array('title[0][value]' => $this->randomString());
$this->drupalPostForm('node/add/article', $edit, t('Save'));
$node_2 = $node_storage->load(2);
$this->assertTrue($node_2->isPublished(), 'Node is published');
// Login as an administrator and unpublish the node that just
// was created by the normal user.
$this->drupalLogout();
$this->drupalLogin($this->adminUser);
$this->drupalPostForm('node/' . $node_2->id() . '/edit', array(), t('Save and unpublish'));
$node_storage->resetCache(array(2));
$node_2 = $node_storage->load(2);
$this->assertFalse($node_2->isPublished(), 'Node is unpublished');
// Login again as the normal user, save the node and verify
// it's still unpublished.
$this->drupalLogout();
$this->drupalLogin($this->webUser);
$this->drupalPostForm('node/' . $node_2->id() . '/edit', array(), t('Save'));
$node_storage->resetCache(array(2));
$node_2 = $node_storage->load(2);
$this->assertFalse($node_2->isPublished(), 'Node is still unpublished');
$this->drupalLogout();
// Set article content type default to unpublished. This will change the
// the initial order of buttons and/or status of the node when creating
// a node.
$fields = \Drupal::entityManager()->getFieldDefinitions('node', 'article');
$fields['status']->getConfig('article')
->setDefaultValue(FALSE)
->save();
// Verify the buttons on a node add form for an administrator.
$this->drupalLogin($this->adminUser);
$this->drupalGet('node/add/article');
$this->assertButtons(array(t('Save as unpublished'), t('Save and publish')));
// Verify the node is unpublished by default for a normal user.
$this->drupalLogout();
$this->drupalLogin($this->webUser);
$edit = array('title[0][value]' => $this->randomString());
$this->drupalPostForm('node/add/article', $edit, t('Save'));
$node_3 = $node_storage->load(3);
$this->assertFalse($node_3->isPublished(), 'Node is unpublished');
}
/**
* Assert method to verify the buttons in the dropdown element.
*
* @param array $buttons
* A collection of buttons to assert for on the page.
* @param bool $dropbutton
* Whether to check if the buttons are in a dropbutton widget or not.
*/
public function assertButtons($buttons, $dropbutton = TRUE) {
// Try to find a Save button.
$save_button = $this->xpath('//input[@type="submit"][@value="Save"]');
// Verify that the number of buttons passed as parameters is
// available in the dropbutton widget.
if ($dropbutton) {
$i = 0;
$count = count($buttons);
// Assert there is no save button.
$this->assertTrue(empty($save_button));
// Dropbutton elements.
$elements = $this->xpath('//div[@class="dropbutton-wrapper"]//input[@type="submit"]');
$this->assertEqual($count, count($elements));
foreach ($elements as $element) {
$value = isset($element['value']) ? (string) $element['value'] : '';
$this->assertEqual($buttons[$i], $value);
$i++;
}
}
else {
// Assert there is a save button.
$this->assertTrue(!empty($save_button));
$this->assertNoRaw('dropbutton-wrapper');
}
}
}

View file

@ -0,0 +1,81 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeHelpTest.
*/
namespace Drupal\node\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests help functionality for nodes.
*
* @group node
*/
class NodeHelpTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array.
*/
public static $modules = array('block', 'node', 'help');
/**
* The name of the test node type to create.
*
* @var string
*/
protected $testType;
/**
* The test 'node help' text to be checked.
*
* @var string
*/
protected $testText;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create user.
$admin_user = $this->drupalCreateUser(array(
'administer content types',
'administer nodes',
'bypass node access',
));
$this->drupalLogin($admin_user);
$this->drupalPlaceBlock('help_block');
$this->testType = 'type';
$this->testText = t('Help text to find on node forms.');
// Create content type.
$this->drupalCreateContentType(array(
'type' => $this->testType,
'help' => $this->testText,
));
}
/**
* Verifies that help text appears on node add/edit forms.
*/
public function testNodeShowHelpText() {
// Check the node add form.
$this->drupalGet('node/add/' . $this->testType);
$this->assertResponse(200);
$this->assertText($this->testText);
// Create node and check the node edit form.
$node = $this->drupalCreateNode(array('type' => $this->testType));
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertResponse(200);
$this->assertText($this->testText);
}
}

View file

@ -0,0 +1,47 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeLinksTest.
*/
namespace Drupal\node\Tests;
/**
* Tests the output of node links (read more, add new comment, etc).
*
* @group node
*/
class NodeLinksTest extends NodeTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('views');
/**
* Tests that the links can be hidden in the view display settings.
*/
public function testHideLinks() {
$node = $this->drupalCreateNode(array(
'type' => 'article',
'promote' => NODE_PROMOTED,
));
// Links are displayed by default.
$this->drupalGet('node');
$this->assertText($node->getTitle());
$this->assertLink('Read more');
// Hide links.
entity_get_display('node', 'article', 'teaser')
->removeComponent('links')
->save();
$this->drupalGet('node');
$this->assertText($node->getTitle());
$this->assertNoLink('Read more');
}
}

Some files were not shown because too many files have changed in this diff Show more