diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index ec05c1a..97e3451 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -280,7 +280,7 @@ public function access($operation = 'view', AccountInterface $account = NULL) { } return \Drupal::entityManager() ->getAccessController($this->entityType) - ->access($this, $operation, Language::LANGCODE_DEFAULT, $account); + ->access($this, $operation, $this->language()->id, $account); } /** diff --git a/core/lib/Drupal/Core/Entity/EntityNG.php b/core/lib/Drupal/Core/Entity/EntityNG.php index 62b5bfb..bbf97ff 100644 --- a/core/lib/Drupal/Core/Entity/EntityNG.php +++ b/core/lib/Drupal/Core/Entity/EntityNG.php @@ -420,7 +420,7 @@ public function access($operation = 'view', AccountInterface $account = NULL) { } return \Drupal::entityManager() ->getAccessController($this->entityType) - ->access($this, $operation, $this->activeLangcode, $account); + ->access($this, $operation, $this->language()->id, $account); } /** diff --git a/core/modules/forum/lib/Drupal/forum/ForumBreadcrumbBuilder.php b/core/modules/forum/lib/Drupal/forum/ForumBreadcrumbBuilder.php index c8d523c..fcf2921 100644 --- a/core/modules/forum/lib/Drupal/forum/ForumBreadcrumbBuilder.php +++ b/core/modules/forum/lib/Drupal/forum/ForumBreadcrumbBuilder.php @@ -54,15 +54,6 @@ public function build(array $attributes) { $item = $attributes['_drupal_menu_item']; switch ($item['path']) { - case 'node/%': - $node = $item['map'][1]; - // Load the object in case of missing wildcard loaders. - $node = is_object($node) ? $node : node_load($node); - if (_forum_node_check_node_type($node)) { - $breadcrumb = $this->forumPostBreadcrumb($node); - } - break; - case 'forum/%': $term = $item['map'][1]; // Load the object in case of missing wildcard loaders. @@ -71,6 +62,12 @@ public function build(array $attributes) { break; } } + elseif (isset($attributes['node'])) { + $node = $attributes['node']; + if (_forum_node_check_node_type($node)) { + $breadcrumb = $this->forumPostBreadcrumb($node); + } + } if (!empty($breadcrumb)) { return $breadcrumb; diff --git a/core/modules/node/lib/Drupal/node/Access/NodeRevisionAccessCheck.php b/core/modules/node/lib/Drupal/node/Access/NodeRevisionAccessCheck.php index aca491d..6e31c31 100644 --- a/core/modules/node/lib/Drupal/node/Access/NodeRevisionAccessCheck.php +++ b/core/modules/node/lib/Drupal/node/Access/NodeRevisionAccessCheck.php @@ -66,7 +66,7 @@ public function __construct(EntityManager $entity_manager, Connection $connectio * {@inheritdoc} */ public function applies(Route $route) { - return array_key_exists('_access_node_revision', $route->getRequirements()); + return array_key_exists('_node_revision_access', $route->getRequirements()); } /** @@ -74,7 +74,7 @@ public function applies(Route $route) { */ public function access(Route $route, Request $request) { $revision = $this->nodeStorage->loadRevision($request->attributes->get('node_revision')); - return $this->checkAccess($revision, $route->getRequirement('_access_node_revision')) ? static::ALLOW : static::DENY; + return $this->checkAccess($revision, $route->getRequirement('_node_revision_access'), $revision->language()->id, $request->attributes->get('account')) ? static::ALLOW : static::DENY; } /** @@ -96,7 +96,7 @@ public function access(Route $route, Request $request) { * @return bool * TRUE if the operation may be performed, FALSE otherwise. */ - public function checkAccess(NodeInterface $node, $op = 'view', AccountInterface $account = NULL, $langcode = NULL) { + public function checkAccess(NodeInterface $node, $op = 'view', $langcode = NULL, AccountInterface $account = NULL) { $map = array( 'view' => 'view all revisions', 'update' => 'revert all revisions', @@ -115,6 +115,8 @@ public function checkAccess(NodeInterface $node, $op = 'view', AccountInterface return FALSE; } + // @todo, remove this check and the reference to $GLOBALS['user'] once + // https://drupal.org/node/2040065 is committed. if (!isset($account)) { $account = $GLOBALS['user']; } @@ -148,7 +150,10 @@ public function checkAccess(NodeInterface $node, $op = 'view', AccountInterface 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)); + $accessDefaultRevision = $this->nodeAccess->access($this->nodeStorage->load($node->id()), $op, $langcode, $account); + $isDefaultRevision = $node->isDefaultRevision(); + $accessSpecificRevision = $this->nodeAccess->access($node, $op, $langcode, $account); + $this->access[$cid] = $accessDefaultRevision && ($isDefaultRevision || $accessSpecificRevision); } } diff --git a/core/modules/node/lib/Drupal/node/Controller/NodeController.php b/core/modules/node/lib/Drupal/node/Controller/NodeController.php new file mode 100644 index 0000000..b2574cf --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Controller/NodeController.php @@ -0,0 +1,83 @@ + $node->label(), '%date' => format_date($node->revision_timestamp))), PASS_THROUGH); + + $node = $this->entityManager()->getStorageController('node')->loadRevision($node_revision); + + $render_array = $this->entityManager()->getRenderController('node')->view($node, 'full'); + + // Update the history table, stating that this user viewed this node. + if ($this->moduleHandler()->moduleExists('history')) { + history_write($node->id()); + } + + return $render_array; + } + + /** + * Displays a single node. + * + * @param \Drupal\Core\Entity\EntityInterface $node + * The node entity. + * + * @return + * A page array suitable for use by drupal_render(). + */ + public function nodeView(NodeInterface $node) { + // If there is a menu link to this node, the link becomes the last part + // of the active trail, and the link name becomes the page title. + // Thus, we must explicitly set the page title to be the node title. + // @todo Replace this once https://drupal.org/node/2032535 is in and we + // have a better way of dealing with titles. + drupal_set_title($node->label()); + + foreach ($node->uriRelationships() as $rel) { + $uri = $node->uri($rel); + // Set the node path as the canonical URL to prevent duplicate content. + drupal_add_html_head_link(array('rel' => $rel, 'href' => url($uri['path'], $uri['options'])), TRUE); + + if ($rel == 'canonical') { + // Set the non-aliased canonical path as a default shortlink. + drupal_add_html_head_link(array('rel' => 'shortlink', 'href' => url($uri['path'], array_merge($uri['options'], array('alias' => TRUE)))), TRUE); + } + } + + $render_array = $this->entityManager()->getRenderController('node')->view($node, 'full'); + + // Update the history table, stating that this user viewed this node. + if ($this->moduleHandler()->moduleExists('history')) { + history_write($node->id()); + } + + return $render_array; + } +} diff --git a/core/modules/node/lib/Drupal/node/NodeBreadcrumbBuilder.php b/core/modules/node/lib/Drupal/node/NodeBreadcrumbBuilder.php new file mode 100644 index 0000000..5e964b4 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/NodeBreadcrumbBuilder.php @@ -0,0 +1,55 @@ +translation = $translation; + } + + /** + * {@inheritdoc} + */ + public function build(array $attributes) { + $trail = array(); + $node = NULL; + + // @todo This only works for legacy routes. Once node/% has been converted + // to the new router this code will need to be updated. + if (isset($attributes['_drupal_menu_item']) && isset($attributes['node'])) { + $trail = explode('/', $attributes['_drupal_menu_item']['href']); + $node = $attributes['_drupal_menu_item']['map'][1]; + } + // This is for the new routing system. + elseif (isset($attributes['node'])) { + $trail = explode('/', $attributes['_system_path']); + $node = $attributes['node']; + } + + // Show breadcrumbs for all subpages like /edit, /delete or any other. + if (count($trail) > 2 && $trail[0] == 'node' && is_numeric($trail[1])) { + $breadcrumb = array(); + $breadcrumb[] = l($this->translation->translate('Home'), NULL); + $breadcrumb[] = l($node->label(), 'node/' . $node->id()); + return $breadcrumb; + } + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionPermissionsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionPermissionsTest.php index 3bcf362..368bdca 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionPermissionsTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionPermissionsTest.php @@ -7,6 +7,8 @@ namespace Drupal\node\Tests; +use Drupal\Core\Language\Language; + /** * Tests user permissions for node revisions. */ @@ -100,10 +102,10 @@ function testNodeRevisionAccessAnyType() { // Skip this test if there are no revisions for the node. if (!($revision->isDefaultRevision() && (db_query('SELECT COUNT(vid) FROM {node_field_revision} WHERE nid = :nid', array(':nid' => $revision->id()))->fetchField() == 1 || $case['op'] == 'update' || $case['op'] == 'delete'))) { if (!empty($case['account']->is_admin) || user_access($this->map[$case['op']], $case['account'])) { - $this->assertTrue(_node_revision_access($revision, $case['op'], $case['account']), "{$this->map[$case['op']]} granted."); + $this->assertTrue(_node_revision_access($revision, $case['op'], $revision->language()->id, $case['account']), "{$this->map[$case['op']]} granted."); } else { - $this->assertFalse(_node_revision_access($revision, $case['op'], $case['account']), "{$this->map[$case['op']]} not granted."); + $this->assertFalse(_node_revision_access($revision, $case['op'], $revision->language()->id, $case['account']), "{$this->map[$case['op']]} not granted."); } } } @@ -146,10 +148,10 @@ function testNodeRevisionAccessPerType() { // Skip this test if there are no revisions for the node. if (!($revision->isDefaultRevision() && (db_query('SELECT COUNT(vid) FROM {node_field_revision} WHERE nid = :nid', array(':nid' => $revision->id()))->fetchField() == 1 || $case['op'] == 'update' || $case['op'] == 'delete'))) { if (!empty($case['account']->is_admin) || user_access($this->type_map[$case['op']], $case['account'])) { - $this->assertTrue(_node_revision_access($revision, $case['op'], $case['account']), "{$this->type_map[$case['op']]} granted."); + $this->assertTrue(_node_revision_access($revision, $case['op'], $revision->language()->id, $case['account']), "{$this->type_map[$case['op']]} granted."); } else { - $this->assertFalse(_node_revision_access($revision, $case['op'], $case['account']), "{$this->type_map[$case['op']]} not granted."); + $this->assertFalse(_node_revision_access($revision, $case['op'], $revision->language()->id, $case['account']), "{$this->type_map[$case['op']]} not granted."); } } } @@ -158,7 +160,7 @@ function testNodeRevisionAccessPerType() { $revision = $this->node_revisions['article'][1]; foreach ($permutations as $case) { - $this->assertFalse(_node_revision_access($revision, $case['op'], $case['account']), "{$this->type_map[$case['op']]} did not grant revision permission for articles."); + $this->assertFalse(_node_revision_access($revision, $case['op'], $revision->language()->id, $case['account']), "{$this->type_map[$case['op']]} did not grant revision permission for articles."); } } } diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsAllTestCase.php b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsAllTestCase.php index 3906c94..9fb4157 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsAllTestCase.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsAllTestCase.php @@ -15,7 +15,6 @@ class NodeRevisionsAllTestCase extends NodeTestBase { protected $nodes; protected $logs; - protected $profile = "standard"; public static function getInfo() { return array( diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 26f11a2..c322f22 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -607,46 +607,18 @@ function node_revision_delete($revision_id) { } /** - * Page callback: Generates an array which displays a node detail page. - * - * @param \Drupal\Core\Entity\EntityInterface $node - * A node entity. - * @param $message - * (optional) A flag which sets a page title relevant to the revision being - * viewed. Default is FALSE. - * - * @return - * A $page element suitable for use by drupal_render(). - * - * @see node_menu() - */ -function node_show(EntityInterface $node, $message = FALSE) { - if ($message) { - drupal_set_title(t('Revision of %title from %date', array('%title' => $node->label(), '%date' => format_date($node->getRevisionCreationTime()))), PASS_THROUGH); - } - - // For markup consistency with other pages, use node_view_multiple() rather than node_view(). - $nodes = array('nodes' => node_view_multiple(array($node->id() => $node), 'full')); - - // Update the history table, stating that this user viewed this node. - if (module_exists('history')) { - history_write($node->id()); - } - - return $nodes; -} - -/** * Checks whether the current page is the full page view of the passed-in node. * * @param \Drupal\Core\Entity\EntityInterface $node * A node entity. * + * @deprecated This function relies on global state, which is unreliable. + * * @return * The ID of the node if this is a full page view, otherwise FALSE. */ -function node_is_page(EntityInterface $node) { - $page_node = menu_get_object(); +function node_is_page(NodeInterface $node) { + $page_node = Drupal::service('request')->attributes->get('node'); return (!empty($page_node) ? $page_node->id() == $node->id() : FALSE); } @@ -1208,12 +1180,7 @@ function node_menu() { $items['node/%node'] = array( 'title callback' => 'node_page_title', 'title arguments' => array(1), - // The page callback also invokes drupal_set_title() in case - // the menu router's title is overridden by a menu link. - 'page callback' => 'node_page_view', - 'page arguments' => array(1), - 'access callback' => 'node_access', - 'access arguments' => array('view', 1), + 'route_name' => 'node_view', ); $items['node/%node/view'] = array( 'title' => 'View', @@ -1244,10 +1211,7 @@ function node_menu() { ); $items['node/%node/revisions/%node_revision/view'] = array( 'title' => 'Revisions', - 'page callback' => 'node_show', - 'page arguments' => array(3, TRUE), - 'access callback' => '_node_revision_access', - 'access arguments' => array(3), + 'route_name' => 'node_revision_view', ); $items['node/%node/revisions/%node_revision/revert'] = array( 'title' => 'Revert to earlier revision', @@ -1677,37 +1641,6 @@ function node_view_multiple($nodes, $view_mode = 'teaser', $langcode = NULL) { } /** - * Page callback: Displays a single node. - * - * @param \Drupal\Core\Entity\EntityInterface $node - * The node entity. - * - * @return - * A page array suitable for use by drupal_render(). - * - * @see node_menu() - */ -function node_page_view(EntityInterface $node) { - // If there is a menu link to this node, the link becomes the last part - // of the active trail, and the link name becomes the page title. - // Thus, we must explicitly set the page title to be the node title. - drupal_set_title($node->label()); - - foreach ($node->uriRelationships() as $rel) { - $uri = $node->uri($rel); - // Set the node path as the canonical URL to prevent duplicate content. - drupal_add_html_head_link(array('rel' => $rel, 'href' => url($uri['path'], $uri['options'])), TRUE); - - if ($rel == 'canonical') { - // Set the non-aliased canonical path as a default shortlink. - drupal_add_html_head_link(array('rel' => 'shortlink', 'href' => url($uri['path'], array_merge($uri['options'], array('alias' => TRUE)))), TRUE); - } - } - - return node_show($node); -} - -/** * Implements hook_update_index(). */ function node_update_index() { diff --git a/core/modules/node/node.routing.yml b/core/modules/node/node.routing.yml index dfdc0a6..905b0dd 100644 --- a/core/modules/node/node.routing.yml +++ b/core/modules/node/node.routing.yml @@ -19,19 +19,40 @@ node_delete_confirm: requirements: _entity_access: 'node.delete' +node_revision_view: + pattern: /node/{node}/revisions/{node_revision}/view + defaults: + _content: '\Drupal\node\Controller\NodeController::nodeRevisionView' + requirements: + _node_revision_access: 'view' + options: + defaults: + node: \d+ + node_revision: \d+ + +node_view: + pattern: /node/{node} + defaults: + _content: '\Drupal\node\Controller\NodeController::nodeView' + requirements: + _permission: 'access content' + options: + defaults: + node: \d+ + node_revision_revert_confirm: pattern: '/node/{node}/revisions/{node_revision}/revert' defaults: _form: '\Drupal\node\Form\NodeRevisionRevertForm' requirements: - _access_node_revision: 'update' + _node_revision_access: 'update' node_revision_delete_confirm: pattern: '/node/{node}/revisions/{node_revision}/delete' defaults: _form: '\Drupal\node\Form\NodeRevisionDeleteForm' requirements: - _access_node_revision: 'delete' + _node_revision_access: 'delete' node_overview_types: pattern: '/admin/structure/types' diff --git a/core/modules/node/node.services.yml b/core/modules/node/node.services.yml index ab49617..0dac681 100644 --- a/core/modules/node/node.services.yml +++ b/core/modules/node/node.services.yml @@ -7,3 +7,8 @@ services: arguments: ['@plugin.manager.entity', '@database'] tags: - { name: access_check } + node.breadcrumb: + class: Drupal\node\NodeBreadcrumbBuilder + arguments: ['@string_translation'] + tags: + - { name: breadcrumb_builder, priority: 10 } diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php index dd4cee8..59813aa 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php @@ -175,9 +175,6 @@ function testBreadCrumbs() { $this->assertBreadcrumb("node/$nid1", $trail); // Also verify that the node does not appear elsewhere (e.g., menu trees). $this->assertNoLink($node1->title); - // The node itself should not be contained in the breadcrumb on the default - // local task, since there is no difference between both pages. - $this->assertBreadcrumb("node/$nid1/view", $trail); // Also verify that the node does not appear elsewhere (e.g., menu trees). $this->assertNoLink($node1->title); @@ -221,9 +218,6 @@ function testBreadCrumbs() { "node/$nid2" => $node2->menu['link_title'], ); $this->assertBreadcrumb("node/$nid2", $trail, $node2->title, $tree); - // The node itself should not be contained in the breadcrumb on the - // default local task, since there is no difference between both pages. - $this->assertBreadcrumb("node/$nid2/view", $trail, $node2->title, $tree); $trail += array( "node/$nid2" => $node2->menu['link_title'], ); @@ -245,9 +239,6 @@ function testBreadCrumbs() { $nid3 = $node3->id(); $this->assertBreadcrumb("node/$nid3", $trail, $node3->title, $tree, FALSE); - // The node itself should not be contained in the breadcrumb on the - // default local task, since there is no difference between both pages. - $this->assertBreadcrumb("node/$nid3/view", $trail, $node3->title, $tree, FALSE); $trail += array( "node/$nid3" => $node3->menu['link_title'], );