Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
72
core/modules/node/src/Access/NodeAddAccessCheck.php
Normal file
72
core/modules/node/src/Access/NodeAddAccessCheck.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
60
core/modules/node/src/Access/NodePreviewAccessCheck.php
Normal file
60
core/modules/node/src/Access/NodePreviewAccessCheck.php
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
159
core/modules/node/src/Access/NodeRevisionAccessCheck.php
Normal file
159
core/modules/node/src/Access/NodeRevisionAccessCheck.php
Normal 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];
|
||||
}
|
||||
|
||||
}
|
81
core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php
Normal file
81
core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
249
core/modules/node/src/Controller/NodeController.php
Normal file
249
core/modules/node/src/Controller/NodeController.php
Normal 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()));
|
||||
}
|
||||
|
||||
}
|
67
core/modules/node/src/Controller/NodePreviewController.php
Normal file
67
core/modules/node/src/Controller/NodePreviewController.php
Normal 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());
|
||||
}
|
||||
|
||||
}
|
63
core/modules/node/src/Controller/NodeViewController.php
Normal file
63
core/modules/node/src/Controller/NodeViewController.php
Normal 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());
|
||||
}
|
||||
|
||||
}
|
519
core/modules/node/src/Entity/Node.php
Normal file
519
core/modules/node/src/Entity/Node.php
Normal 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());
|
||||
}
|
||||
|
||||
}
|
51
core/modules/node/src/Entity/NodeRouteProvider.php
Normal file
51
core/modules/node/src/Entity/NodeRouteProvider.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
213
core/modules/node/src/Entity/NodeType.php
Normal file
213
core/modules/node/src/Entity/NodeType.php
Normal 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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
204
core/modules/node/src/Form/DeleteMultiple.php
Normal file
204
core/modules/node/src/Form/DeleteMultiple.php
Normal 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');
|
||||
}
|
||||
|
||||
}
|
50
core/modules/node/src/Form/NodeDeleteForm.php
Normal file
50
core/modules/node/src/Form/NodeDeleteForm.php
Normal 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()]);
|
||||
}
|
||||
|
||||
}
|
169
core/modules/node/src/Form/NodePreviewForm.php
Normal file
169
core/modules/node/src/Form/NodePreviewForm.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
138
core/modules/node/src/Form/NodeRevisionDeleteForm.php
Normal file
138
core/modules/node/src/Form/NodeRevisionDeleteForm.php
Normal 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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
160
core/modules/node/src/Form/NodeRevisionRevertForm.php
Normal file
160
core/modules/node/src/Form/NodeRevisionRevertForm.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
64
core/modules/node/src/Form/NodeTypeDeleteConfirm.php
Normal file
64
core/modules/node/src/Form/NodeTypeDeleteConfirm.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
59
core/modules/node/src/Form/RebuildPermissionsForm.php
Normal file
59
core/modules/node/src/Form/RebuildPermissionsForm.php
Normal 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());
|
||||
}
|
||||
|
||||
}
|
202
core/modules/node/src/NodeAccessControlHandler.php
Normal file
202
core/modules/node/src/NodeAccessControlHandler.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
92
core/modules/node/src/NodeAccessControlHandlerInterface.php
Normal file
92
core/modules/node/src/NodeAccessControlHandlerInterface.php
Normal 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);
|
||||
|
||||
}
|
421
core/modules/node/src/NodeForm.php
Normal file
421
core/modules/node/src/NodeForm.php
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
311
core/modules/node/src/NodeGrantDatabaseStorage.php
Normal file
311
core/modules/node/src/NodeGrantDatabaseStorage.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
137
core/modules/node/src/NodeGrantDatabaseStorageInterface.php
Normal file
137
core/modules/node/src/NodeGrantDatabaseStorageInterface.php
Normal 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);
|
||||
|
||||
}
|
171
core/modules/node/src/NodeInterface.php
Normal file
171
core/modules/node/src/NodeInterface.php
Normal 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();
|
||||
|
||||
}
|
150
core/modules/node/src/NodeListBuilder.php
Normal file
150
core/modules/node/src/NodeListBuilder.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
82
core/modules/node/src/NodePermissions.php
Normal file
82
core/modules/node/src/NodePermissions.php
Normal 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>.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
69
core/modules/node/src/NodeStorage.php
Normal file
69
core/modules/node/src/NodeStorage.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
72
core/modules/node/src/NodeStorageInterface.php
Normal file
72
core/modules/node/src/NodeStorageInterface.php
Normal 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);
|
||||
}
|
95
core/modules/node/src/NodeStorageSchema.php
Normal file
95
core/modules/node/src/NodeStorageSchema.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
91
core/modules/node/src/NodeTranslationHandler.php
Normal file
91
core/modules/node/src/NodeTranslationHandler.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
37
core/modules/node/src/NodeTypeAccessControlHandler.php
Normal file
37
core/modules/node/src/NodeTypeAccessControlHandler.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
262
core/modules/node/src/NodeTypeForm.php
Normal file
262
core/modules/node/src/NodeTypeForm.php
Normal 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'));
|
||||
}
|
||||
|
||||
}
|
88
core/modules/node/src/NodeTypeInterface.php
Normal file
88
core/modules/node/src/NodeTypeInterface.php
Normal 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();
|
||||
}
|
70
core/modules/node/src/NodeTypeListBuilder.php
Normal file
70
core/modules/node/src/NodeTypeListBuilder.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
163
core/modules/node/src/NodeViewBuilder.php
Normal file
163
core/modules/node/src/NodeViewBuilder.php
Normal 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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
388
core/modules/node/src/NodeViewsData.php
Normal file
388
core/modules/node/src/NodeViewsData.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
49
core/modules/node/src/PageCache/DenyNodePreview.php
Normal file
49
core/modules/node/src/PageCache/DenyNodePreview.php
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
149
core/modules/node/src/Plugin/Action/AssignOwnerNode.php
Normal file
149
core/modules/node/src/Plugin/Action/AssignOwnerNode.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
104
core/modules/node/src/Plugin/Action/DeleteNode.php
Normal file
104
core/modules/node/src/Plugin/Action/DeleteNode.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
43
core/modules/node/src/Plugin/Action/DemoteNode.php
Normal file
43
core/modules/node/src/Plugin/Action/DemoteNode.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
42
core/modules/node/src/Plugin/Action/PromoteNode.php
Normal file
42
core/modules/node/src/Plugin/Action/PromoteNode.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
43
core/modules/node/src/Plugin/Action/PublishNode.php
Normal file
43
core/modules/node/src/Plugin/Action/PublishNode.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
42
core/modules/node/src/Plugin/Action/SaveNode.php
Normal file
42
core/modules/node/src/Plugin/Action/SaveNode.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
42
core/modules/node/src/Plugin/Action/StickyNode.php
Normal file
42
core/modules/node/src/Plugin/Action/StickyNode.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
43
core/modules/node/src/Plugin/Action/UnpublishNode.php
Normal file
43
core/modules/node/src/Plugin/Action/UnpublishNode.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
43
core/modules/node/src/Plugin/Action/UnstickyNode.php
Normal file
43
core/modules/node/src/Plugin/Action/UnstickyNode.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
76
core/modules/node/src/Plugin/Block/SyndicateBlock.php
Normal file
76
core/modules/node/src/Plugin/Block/SyndicateBlock.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
127
core/modules/node/src/Plugin/Condition/NodeType.php
Normal file
127
core/modules/node/src/Plugin/Condition/NodeType.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
702
core/modules/node/src/Plugin/Search/NodeSearch.php
Normal file
702
core/modules/node/src/Plugin/Search/NodeSearch.php
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
82
core/modules/node/src/Plugin/views/area/ListingEmpty.php
Normal file
82
core/modules/node/src/Plugin/views/area/ListingEmpty.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
70
core/modules/node/src/Plugin/views/argument/Nid.php
Normal file
70
core/modules/node/src/Plugin/views/argument/Nid.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
82
core/modules/node/src/Plugin/views/argument/Type.php
Normal file
82
core/modules/node/src/Plugin/views/argument/Type.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
26
core/modules/node/src/Plugin/views/argument/UidRevision.php
Normal file
26
core/modules/node/src/Plugin/views/argument/UidRevision.php
Normal 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));
|
||||
}
|
||||
|
||||
}
|
93
core/modules/node/src/Plugin/views/argument/Vid.php
Normal file
93
core/modules/node/src/Plugin/views/argument/Vid.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
89
core/modules/node/src/Plugin/views/argument_default/Node.php
Normal file
89
core/modules/node/src/Plugin/views/argument_default/Node.php
Normal 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'];
|
||||
}
|
||||
|
||||
}
|
105
core/modules/node/src/Plugin/views/field/Node.php
Normal file
105
core/modules/node/src/Plugin/views/field/Node.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
27
core/modules/node/src/Plugin/views/field/NodeBulkForm.php
Normal file
27
core/modules/node/src/Plugin/views/field/NodeBulkForm.php
Normal 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.');
|
||||
}
|
||||
|
||||
}
|
74
core/modules/node/src/Plugin/views/field/Path.php
Normal file
74
core/modules/node/src/Plugin/views/field/Path.php
Normal 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']]);
|
||||
}
|
||||
|
||||
}
|
56
core/modules/node/src/Plugin/views/field/RevisionLink.php
Normal file
56
core/modules/node/src/Plugin/views/field/RevisionLink.php
Normal 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');
|
||||
}
|
||||
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
61
core/modules/node/src/Plugin/views/filter/Access.php
Normal file
61
core/modules/node/src/Plugin/views/filter/Access.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
44
core/modules/node/src/Plugin/views/filter/Status.php
Normal file
44
core/modules/node/src/Plugin/views/filter/Status.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
33
core/modules/node/src/Plugin/views/filter/UidRevision.php
Normal file
33
core/modules/node/src/Plugin/views/filter/UidRevision.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
36
core/modules/node/src/Plugin/views/row/NodeRow.php
Normal file
36
core/modules/node/src/Plugin/views/row/NodeRow.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
178
core/modules/node/src/Plugin/views/row/Rss.php
Normal file
178
core/modules/node/src/Plugin/views/row/Rss.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
271
core/modules/node/src/Plugin/views/wizard/Node.php
Normal file
271
core/modules/node/src/Plugin/views/wizard/Node.php
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
111
core/modules/node/src/Plugin/views/wizard/NodeRevision.php
Normal file
111
core/modules/node/src/Plugin/views/wizard/NodeRevision.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
37
core/modules/node/src/Routing/RouteSubscriber.php
Normal file
37
core/modules/node/src/Routing/RouteSubscriber.php
Normal 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',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
85
core/modules/node/src/Tests/Condition/NodeConditionTest.php
Normal file
85
core/modules/node/src/Tests/Condition/NodeConditionTest.php
Normal 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.');
|
||||
}
|
||||
}
|
64
core/modules/node/src/Tests/Config/NodeImportChangeTest.php
Normal file
64
core/modules/node/src/Tests/Config/NodeImportChangeTest.php
Normal 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.');
|
||||
}
|
||||
|
||||
}
|
80
core/modules/node/src/Tests/Config/NodeImportCreateTest.php
Normal file
80
core/modules/node/src/Tests/Config/NodeImportCreateTest.php
Normal 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'));
|
||||
}
|
||||
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
214
core/modules/node/src/Tests/NodeAccessBaseTableTest.php
Normal file
214
core/modules/node/src/Tests/NodeAccessBaseTableTest.php
Normal 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',
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
115
core/modules/node/src/Tests/NodeAccessFieldTest.php
Normal file
115
core/modules/node/src/Tests/NodeAccessFieldTest.php
Normal 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.');
|
||||
}
|
||||
}
|
143
core/modules/node/src/Tests/NodeAccessGrantsCacheContextTest.php
Normal file
143
core/modules/node/src/Tests/NodeAccessGrantsCacheContextTest.php
Normal 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',
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
29
core/modules/node/src/Tests/NodeAccessGrantsTest.php
Normal file
29
core/modules/node/src/Tests/NodeAccessGrantsTest.php
Normal 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');
|
||||
|
||||
}
|
|
@ -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.');
|
||||
}
|
||||
|
||||
}
|
293
core/modules/node/src/Tests/NodeAccessLanguageAwareTest.php
Normal file
293
core/modules/node/src/Tests/NodeAccessLanguageAwareTest.php
Normal 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.');
|
||||
}
|
||||
|
||||
}
|
301
core/modules/node/src/Tests/NodeAccessLanguageTest.php
Normal file
301
core/modules/node/src/Tests/NodeAccessLanguageTest.php
Normal 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.');
|
||||
}
|
||||
|
||||
}
|
103
core/modules/node/src/Tests/NodeAccessPagerTest.php
Normal file
103
core/modules/node/src/Tests/NodeAccessPagerTest.php
Normal 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');
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
40
core/modules/node/src/Tests/NodeAccessRebuildTest.php
Normal file
40
core/modules/node/src/Tests/NodeAccessRebuildTest.php
Normal 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.'));
|
||||
}
|
||||
}
|
88
core/modules/node/src/Tests/NodeAccessRecordsTest.php
Normal file
88
core/modules/node/src/Tests/NodeAccessRecordsTest.php
Normal 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.');
|
||||
}
|
||||
}
|
77
core/modules/node/src/Tests/NodeAccessTest.php
Normal file
77
core/modules/node/src/Tests/NodeAccessTest.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
198
core/modules/node/src/Tests/NodeAdminTest.php
Normal file
198
core/modules/node/src/Tests/NodeAdminTest.php
Normal 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');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
161
core/modules/node/src/Tests/NodeBlockFunctionalTest.php
Normal file
161
core/modules/node/src/Tests/NodeBlockFunctionalTest.php
Normal 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());
|
||||
}
|
||||
|
||||
}
|
63
core/modules/node/src/Tests/NodeBodyFieldStorageTest.php
Normal file
63
core/modules/node/src/Tests/NodeBodyFieldStorageTest.php
Normal 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.');
|
||||
}
|
||||
|
||||
}
|
68
core/modules/node/src/Tests/NodeCacheTagsTest.php
Normal file
68
core/modules/node/src/Tests/NodeCacheTagsTest.php
Normal 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'];
|
||||
}
|
||||
|
||||
}
|
224
core/modules/node/src/Tests/NodeCreationTest.php
Normal file
224
core/modules/node/src/Tests/NodeCreationTest.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
51
core/modules/node/src/Tests/NodeEntityViewModeAlterTest.php
Normal file
51
core/modules/node/src/Tests/NodeEntityViewModeAlterTest.php
Normal 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.');
|
||||
}
|
||||
}
|
153
core/modules/node/src/Tests/NodeFieldAccessTest.php
Normal file
153
core/modules/node/src/Tests/NodeFieldAccessTest.php
Normal 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.');
|
||||
}
|
||||
|
||||
}
|
134
core/modules/node/src/Tests/NodeFieldMultilingualTest.php
Normal file
134
core/modules/node/src/Tests/NodeFieldMultilingualTest.php
Normal 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.');
|
||||
}
|
||||
|
||||
}
|
68
core/modules/node/src/Tests/NodeFieldOverridesTest.php
Normal file
68
core/modules/node/src/Tests/NodeFieldOverridesTest.php
Normal 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());
|
||||
}
|
||||
|
||||
}
|
175
core/modules/node/src/Tests/NodeFormButtonsTest.php
Normal file
175
core/modules/node/src/Tests/NodeFormButtonsTest.php
Normal 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');
|
||||
}
|
||||
}
|
||||
}
|
81
core/modules/node/src/Tests/NodeHelpTest.php
Normal file
81
core/modules/node/src/Tests/NodeHelpTest.php
Normal 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);
|
||||
}
|
||||
}
|
47
core/modules/node/src/Tests/NodeLinksTest.php
Normal file
47
core/modules/node/src/Tests/NodeLinksTest.php
Normal 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
Reference in a new issue