From ef2e45b0e86fc6863b8ee6d36413c66f7058aafe Mon Sep 17 00:00:00 2001
From: webchick <webchick@24967.no-reply.drupal.org>
Date: Wed, 18 Sep 2013 11:30:30 -0700
Subject: [PATCH] Issue #2089635 by tim.plunkett, disasm, larowlan: Convert
 non-test non-form page callbacks to routes and controllers.

---
 core/core.services.yml                        |   4 +
 core/includes/menu.inc                        |  23 +++-
 core/includes/theme.inc                       |   9 +-
 .../Core/Entity/EntityConfirmFormBase.php     |  11 +-
 .../Core/Entity/EntityNGConfirmFormBase.php   |  11 +-
 core/lib/Drupal/Core/Form/ConfirmFormBase.php |   2 +-
 .../Drupal/Core/Theme/ThemeAccessCheck.php    |  47 +++++++
 core/modules/aggregator/aggregator.module     |   7 -
 core/modules/aggregator/aggregator.pages.inc  |   2 +
 .../modules/aggregator/aggregator.routing.yml |   9 ++
 .../Controller/AggregatorController.php       |   8 ++
 core/modules/block/block.admin.inc            |   2 +
 core/modules/block/block.module               |   6 +-
 core/modules/block/block.routing.yml          |  10 ++
 core/modules/block/block.services.yml         |   4 -
 .../block/Access/BlockThemeAccessCheck.php    |  34 -----
 .../block/Controller/BlockController.php      |  23 ++++
 .../Drupal/block/Routing/RouteSubscriber.php  |   6 +-
 core/modules/comment/comment.admin.inc        |   2 +
 core/modules/comment/comment.module           |   7 +-
 core/modules/comment/comment.routing.yml      |  18 +++
 .../comment/Controller/CommentController.php  |   8 ++
 .../comment/Tests/CommentInterfaceTest.php    |   2 +-
 .../comment/Tests/CommentNodeAccessTest.php   |   2 +-
 core/modules/contact/contact.module           |  51 +-------
 core/modules/contact/contact.pages.inc        |   4 +
 core/modules/contact/contact.routing.yml      |  25 ++++
 core/modules/contact/contact.services.yml     |   6 +
 .../contact/Access/ContactPageAccess.php      |  98 ++++++++++++++
 .../contact/Controller/ContactController.php  |  34 +++++
 .../contact/Tests/MessageEntityTest.php       |   2 +-
 .../content_translation.module                |  19 +--
 .../content_translation.pages.inc             |   6 +
 .../content_translation.services.yml          |  18 +++
 .../ContentTranslationManageAccessCheck.php   |  85 ++++++++++++
 .../ContentTranslationOverviewAccess.php      |  75 +++++++++++
 .../ContentTranslationController.php          |  44 +++++++
 .../ContentTranslationRouteSubscriber.php     | 121 ++++++++++++++++++
 core/modules/dblog/dblog.admin.inc            |   4 +
 core/modules/dblog/dblog.module               |  15 +--
 core/modules/dblog/dblog.routing.yml          |  16 +++
 core/modules/dblog/dblog.services.yml         |   6 +
 .../dblog/Controller/DbLogController.php      |  24 ++++
 .../Drupal/dblog/Routing/RouteSubscriber.php  |  73 +++++++++++
 .../lib/Drupal/dblog/Tests/DbLogTest.php      |   6 +-
 core/modules/forum/forum.module               |   2 +-
 .../Drupal/forum/ForumBreadcrumbBuilder.php   |  25 ++--
 .../lib/Drupal/forum/Tests/ForumBlockTest.php |   2 +-
 .../lib/Drupal/forum/Tests/ForumIndexTest.php |   2 +-
 .../forum/Tests/ForumNodeAccessTest.php       |   4 +-
 .../lib/Drupal/forum/Tests/ForumTest.php      |   2 +-
 core/modules/language/language.admin.inc      |   2 +
 core/modules/language/language.module         |   4 +-
 core/modules/language/language.routing.yml    |   8 ++
 .../Controller/LanguageController.php         |  23 ++++
 .../Drupal/node/Access/NodeAddAccessCheck.php |  42 ++++++
 .../node/Access/NodeRevisionAccessCheck.php   |  14 +-
 .../Drupal/node/Controller/NodeController.php |  57 +++++++++
 core/modules/node/node.module                 |  73 ++++-------
 core/modules/node/node.pages.inc              |   6 +
 core/modules/node/node.routing.yml            |  39 ++++++
 core/modules/node/node.services.yml           |   5 +
 .../Tests/OptionsFieldUnitTestBase.php        |   2 +-
 .../Drupal/path/Controller/PathController.php |  40 ++++++
 core/modules/path/path.admin.inc              |   5 +
 core/modules/path/path.module                 |  13 +-
 core/modules/path/path.routing.yml            |  25 ++++
 .../search/Access/SearchAccessCheck.php       |  51 ++++++++
 .../search/Access/SearchPluginAccessCheck.php |  34 +++++
 .../search/Controller/SearchController.php    |  31 +++++
 .../search/Routing/SearchRouteSubscriber.php  |  75 +++++++++++
 .../Tests/SearchKeywordsConditionsTest.php    |   2 +-
 .../search/Tests/SearchPageOverrideTest.php   |   2 +-
 core/modules/search/search.module             |  44 +------
 core/modules/search/search.pages.inc          |   2 +
 core/modules/search/search.routing.yml        |  14 ++
 core/modules/search/search.services.yml       |  18 +++
 .../system/Controller/SystemController.php    |  16 +++
 .../system/Tests/Menu/BreadcrumbTest.php      |   9 --
 .../system/Tests/Menu/TreeOutputTest.php      |   2 +-
 core/modules/system/system.admin.inc          |   2 +
 core/modules/system/system.module             |  18 +--
 core/modules/system/system.routing.yml        |  24 ++++
 .../Controller/TaxonomyController.php         |  17 +++
 core/modules/taxonomy/taxonomy.module         |  12 +-
 core/modules/taxonomy/taxonomy.pages.inc      |   4 +
 core/modules/taxonomy/taxonomy.routing.yml    |  13 ++
 .../TranslationNodeOverviewAccessCheck.php    |  38 ++++++
 .../Controller/TranslationController.php      |  25 ++++
 core/modules/translation/translation.module   |   6 +-
 .../modules/translation/translation.pages.inc |   4 +-
 .../translation/translation.routing.yml       |   7 +
 .../translation/translation.services.yml      |   5 +
 .../update/Controller/UpdateController.php    |   8 ++
 core/modules/update/update.fetch.inc          |   2 +
 core/modules/update/update.module             |   7 -
 core/modules/update/update.routing.yml        |   8 ++
 .../Drupal/user/Controller/UserController.php |   9 ++
 core/modules/user/user.module                 |  25 +---
 core/modules/user/user.pages.inc              |   2 +
 core/modules/user/user.routing.yml            |  20 ++-
 .../FilterBooleanOperatorStringTest.php       |   2 +-
 .../Handler/FilterBooleanOperatorTest.php     |   2 +-
 .../Tests/Handler/FilterEqualityTest.php      |   2 +-
 .../Tests/Handler/FilterInOperatorTest.php    |   2 +-
 .../views/Tests/Handler/FilterNumericTest.php |   2 +-
 .../views/Tests/Handler/FilterStringTest.php  |   2 +-
 .../views/Tests/Plugin/DisplayPageTest.php    |   4 +-
 .../views/Tests/Plugin/RowEntityTest.php      |   2 +-
 .../views/Tests/ViewPageControllerTest.php    |   2 +-
 .../xmlrpc/Controller/XmlrpcController.php    |  23 ++++
 core/modules/xmlrpc/xmlrpc.module             |  14 --
 core/modules/xmlrpc/xmlrpc.routing.yml        |   7 +
 core/modules/xmlrpc/xmlrpc.server.inc         |   2 +
 114 files changed, 1620 insertions(+), 386 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Theme/ThemeAccessCheck.php
 delete mode 100644 core/modules/block/lib/Drupal/block/Access/BlockThemeAccessCheck.php
 create mode 100644 core/modules/block/lib/Drupal/block/Controller/BlockController.php
 create mode 100644 core/modules/contact/contact.services.yml
 create mode 100644 core/modules/contact/lib/Drupal/contact/Access/ContactPageAccess.php
 create mode 100644 core/modules/contact/lib/Drupal/contact/Controller/ContactController.php
 create mode 100644 core/modules/content_translation/lib/Drupal/content_translation/Access/ContentTranslationManageAccessCheck.php
 create mode 100644 core/modules/content_translation/lib/Drupal/content_translation/Access/ContentTranslationOverviewAccess.php
 create mode 100644 core/modules/content_translation/lib/Drupal/content_translation/Controller/ContentTranslationController.php
 create mode 100644 core/modules/content_translation/lib/Drupal/content_translation/Routing/ContentTranslationRouteSubscriber.php
 create mode 100644 core/modules/dblog/dblog.services.yml
 create mode 100644 core/modules/dblog/lib/Drupal/dblog/Routing/RouteSubscriber.php
 create mode 100644 core/modules/language/lib/Drupal/language/Controller/LanguageController.php
 create mode 100644 core/modules/node/lib/Drupal/node/Access/NodeAddAccessCheck.php
 create mode 100644 core/modules/node/lib/Drupal/node/Controller/NodeController.php
 create mode 100644 core/modules/path/lib/Drupal/path/Controller/PathController.php
 create mode 100644 core/modules/search/lib/Drupal/search/Access/SearchAccessCheck.php
 create mode 100644 core/modules/search/lib/Drupal/search/Access/SearchPluginAccessCheck.php
 create mode 100644 core/modules/search/lib/Drupal/search/Controller/SearchController.php
 create mode 100644 core/modules/search/lib/Drupal/search/Routing/SearchRouteSubscriber.php
 create mode 100644 core/modules/translation/lib/Drupal/translation/Access/TranslationNodeOverviewAccessCheck.php
 create mode 100644 core/modules/translation/lib/Drupal/translation/Controller/TranslationController.php
 create mode 100644 core/modules/translation/translation.routing.yml
 create mode 100644 core/modules/translation/translation.services.yml
 create mode 100644 core/modules/xmlrpc/lib/Drupal/xmlrpc/Controller/XmlrpcController.php
 create mode 100644 core/modules/xmlrpc/xmlrpc.routing.yml

diff --git a/core/core.services.yml b/core/core.services.yml
index 4fab42207720..7d56112c5803 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -410,6 +410,10 @@ services:
     arguments: ['@entity.manager']
     tags:
       - { name: access_check }
+  access_check.theme:
+    class: Drupal\Core\Theme\ThemeAccessCheck
+    tags:
+      - { name: access_check }
   maintenance_mode_subscriber:
     class: Drupal\Core\EventSubscriber\MaintenanceModeSubscriber
     tags:
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index f289cbbe1bd9..e38afad6f562 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -475,6 +475,7 @@ function menu_get_item($path = NULL, $router_item = NULL) {
     // occurs rarely, likely due to a race condition of multiple rebuilds.
     if (\Drupal::state()->get('menu_rebuild_needed') || !\Drupal::state()->get('menu.masks')) {
       menu_router_rebuild();
+      \Drupal::service('router.builder')->rebuild();
     }
     $original_map = arg(NULL, $path);
 
@@ -1024,12 +1025,32 @@ function menu_item_route_access(Route $route, $href, &$map) {
  *   it's 2. Defaults to 1.
  * @param $path
  *   See menu_get_item() for more on this. Defaults to the current path.
+ *
+ * @todo Remove this function in https://drupal.org/node/2091399.
  */
 function menu_get_object($type = 'node', $position = 1, $path = NULL) {
   $router_item = menu_get_item($path);
-  if (isset($router_item['load_functions'][$position]) && !empty($router_item['map'][$position]) && $router_item['load_functions'][$position] == $type . '_load') {
+  if (empty($router_item['map'][$position])) {
+    return;
+  }
+
+  if (isset($router_item['load_functions'][$position]) && $router_item['load_functions'][$position] == $type . '_load') {
     return $router_item['map'][$position];
   }
+
+  // If the path is route-based, use the route path instead of the menu item.
+  // The most common use of this function is for the node page, which has a
+  // route path of '/node/{node}'. By splitting that path into parts, we check
+  // for the $type wrapped in curly braces at the correct $position, returning
+  // the value found there.
+  $request = \Drupal::request();
+  if ($request->attributes->has(RouteObjectInterface::ROUTE_OBJECT)) {
+    $path = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)->getPath();
+    $parts = explode('/', ltrim($path, '/'));
+    if (isset($parts[$position]) && $parts[$position] == '{' . $type . '}') {
+      return $router_item['map'][$position];
+    }
+  }
 }
 
 /**
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index d583eb7f5181..d382b3978a0d 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -70,15 +70,14 @@
  * @return
  *   Boolean TRUE if the theme is enabled or is the site administration theme;
  *   FALSE otherwise.
+ *
+ * @deprecated Use \Drupal::service('access_check.theme')->checkAccess().
  */
 function drupal_theme_access($theme) {
   if (is_object($theme)) {
-    return !empty($theme->status);
-  }
-  else {
-    $themes = list_themes();
-    return !empty($themes[$theme]->status);
+    $theme = $theme->name;
   }
+  return Drupal::service('access_check.theme')->checkAccess($theme);
 }
 
 /**
diff --git a/core/lib/Drupal/Core/Entity/EntityConfirmFormBase.php b/core/lib/Drupal/Core/Entity/EntityConfirmFormBase.php
index 28b80d41e6cd..f8a7e2b82519 100644
--- a/core/lib/Drupal/Core/Entity/EntityConfirmFormBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityConfirmFormBase.php
@@ -57,6 +57,8 @@ public function getFormName() {
   public function buildForm(array $form, array &$form_state) {
     $form = parent::buildForm($form, $form_state);
 
+    $form['#title'] = $this->getQuestion();
+
     $form['#attributes']['class'][] = 'confirmation';
     $form['description'] = array('#markup' => $this->getDescription());
     $form[$this->getFormName()] = array('#type' => 'hidden', '#value' => 1);
@@ -68,15 +70,6 @@ public function buildForm(array $form, array &$form_state) {
     return $form;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function init(array &$form_state) {
-    parent::init($form_state);
-
-    drupal_set_title($this->getQuestion(), PASS_THROUGH);
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/core/lib/Drupal/Core/Entity/EntityNGConfirmFormBase.php b/core/lib/Drupal/Core/Entity/EntityNGConfirmFormBase.php
index 0d60b7241786..d0bf4efd0c82 100644
--- a/core/lib/Drupal/Core/Entity/EntityNGConfirmFormBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityNGConfirmFormBase.php
@@ -56,6 +56,8 @@ public function getFormName() {
   public function buildForm(array $form, array &$form_state) {
     $form = parent::buildForm($form, $form_state);
 
+    $form['#title'] = $this->getQuestion();
+
     $form['#attributes']['class'][] = 'confirmation';
     $form['description'] = array('#markup' => $this->getDescription());
     $form[$this->getFormName()] = array('#type' => 'hidden', '#value' => 1);
@@ -75,15 +77,6 @@ public function form(array $form, array &$form_state) {
     return $form;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function init(array &$form_state) {
-    parent::init($form_state);
-
-    drupal_set_title($this->getQuestion(), PASS_THROUGH);
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/core/lib/Drupal/Core/Form/ConfirmFormBase.php b/core/lib/Drupal/Core/Form/ConfirmFormBase.php
index ec0e5ceb2531..a6930f4003ef 100644
--- a/core/lib/Drupal/Core/Form/ConfirmFormBase.php
+++ b/core/lib/Drupal/Core/Form/ConfirmFormBase.php
@@ -44,7 +44,7 @@ public function getFormName() {
    * {@inheritdoc}
    */
   public function buildForm(array $form, array &$form_state) {
-    drupal_set_title($this->getQuestion(), PASS_THROUGH);
+    $form['#title'] = $this->getQuestion();
 
     $form['#attributes']['class'][] = 'confirmation';
     $form['description'] = array('#markup' => $this->getDescription());
diff --git a/core/lib/Drupal/Core/Theme/ThemeAccessCheck.php b/core/lib/Drupal/Core/Theme/ThemeAccessCheck.php
new file mode 100644
index 000000000000..90e4a79634d9
--- /dev/null
+++ b/core/lib/Drupal/Core/Theme/ThemeAccessCheck.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Theme\ThemeAccessCheck.
+ */
+
+namespace Drupal\Core\Theme;
+
+use Drupal\Core\Access\StaticAccessCheckInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Access check for a theme.
+ */
+class ThemeAccessCheck implements StaticAccessCheckInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function appliesTo() {
+    return array('_access_theme');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, Request $request) {
+    return $this->checkAccess($request->attributes->get('theme')) ? static::ALLOW : static::DENY;
+  }
+
+  /**
+   * Checks access to a theme.
+   *
+   * @param string $theme
+   *   The name of a theme.
+   *
+   * @return bool
+   *   TRUE if the theme is enabled, FALSE otherwise.
+   */
+  public function checkAccess($theme) {
+    $themes = list_themes();
+    return !empty($themes[$theme]->status);
+  }
+
+}
diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module
index 1534cb3004c1..56f926fcd722 100644
--- a/core/modules/aggregator/aggregator.module
+++ b/core/modules/aggregator/aggregator.module
@@ -142,13 +142,6 @@ function aggregator_menu() {
     'title' => 'Categories',
     'route_name' => 'aggregator.categories',
   );
-  $items['aggregator/opml'] = array(
-    'title' => 'OPML feed',
-    'page callback' => 'aggregator_page_opml',
-    'access arguments' => array('access news feeds'),
-    'type' => MENU_CALLBACK,
-    'file' => 'aggregator.pages.inc',
-  );
   $items['aggregator/categories/%aggregator_category'] = array(
     'title callback' => '_aggregator_category_title',
     'title arguments' => array(2),
diff --git a/core/modules/aggregator/aggregator.pages.inc b/core/modules/aggregator/aggregator.pages.inc
index 57e2b5ca322a..cd938e7a816b 100644
--- a/core/modules/aggregator/aggregator.pages.inc
+++ b/core/modules/aggregator/aggregator.pages.inc
@@ -114,6 +114,8 @@ function template_preprocess_aggregator_item(&$variables) {
  *   An OPML formatted string.
  *
  * @see aggregator_menu()
+ *
+ * @deprecated Use \Drupal\aggregator\Controller\AggregatorController::opmlPage()
  */
 function aggregator_page_opml($cid = NULL) {
   if ($cid) {
diff --git a/core/modules/aggregator/aggregator.routing.yml b/core/modules/aggregator/aggregator.routing.yml
index fef52714e83c..5c04ba62a06a 100644
--- a/core/modules/aggregator/aggregator.routing.yml
+++ b/core/modules/aggregator/aggregator.routing.yml
@@ -137,3 +137,12 @@ aggregator.categorize_feed_form:
     _form: '\Drupal\aggregator\Form\CategorizeFeedForm'
   requirements:
     _permission: 'administer news feeds'
+
+aggregator.opml_page:
+  path: '/aggregator/opml/{cid}'
+  defaults:
+    _title: 'OPML feed'
+    _controller: '\Drupal\aggregator\Controller\AggregatorController::opmlPage'
+    cid: null
+  requirements:
+    _permission: 'access news feeds'
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Controller/AggregatorController.php b/core/modules/aggregator/lib/Drupal/aggregator/Controller/AggregatorController.php
index 238693a543dd..1e6826bab63f 100644
--- a/core/modules/aggregator/lib/Drupal/aggregator/Controller/AggregatorController.php
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Controller/AggregatorController.php
@@ -346,4 +346,12 @@ public function sources() {
     return $build;
   }
 
+  /**
+   * @todo Remove aggregator_opml().
+   */
+  public function opmlPage($cid = NULL) {
+    module_load_include('pages.inc', 'aggregator');
+    return aggregator_page_opml($cid);
+  }
+
 }
diff --git a/core/modules/block/block.admin.inc b/core/modules/block/block.admin.inc
index 0f6ea1e3d360..86c59a8f40af 100644
--- a/core/modules/block/block.admin.inc
+++ b/core/modules/block/block.admin.inc
@@ -12,6 +12,8 @@
  * Page callback: Attaches CSS for the block region demo.
  *
  * @see block_menu()
+ *
+ * @deprecated Use \Drupal\block\Controller\BlockController::demo()
  */
 function block_admin_demo($theme = NULL) {
   return array(
diff --git a/core/modules/block/block.module b/core/modules/block/block.module
index a12dcb68c9ae..f2e1a73d0b54 100644
--- a/core/modules/block/block.module
+++ b/core/modules/block/block.module
@@ -136,14 +136,10 @@ function block_menu() {
     );
     $items["admin/structure/block/demo/$key"] = array(
       'title' => check_plain($theme->info['name']),
-      'page callback' => 'block_admin_demo',
-      'page arguments' => array($key),
+      'route_name' => 'block.admin_demo',
       'type' => MENU_CALLBACK,
-      'access callback' => '_block_themes_access',
-      'access arguments' => array($key),
       'theme callback' => '_block_custom_theme',
       'theme arguments' => array($key),
-      'file' => 'block.admin.inc',
     );
   }
   return $items;
diff --git a/core/modules/block/block.routing.yml b/core/modules/block/block.routing.yml
index bde1e0e12fd1..5b87e2f99938 100644
--- a/core/modules/block/block.routing.yml
+++ b/core/modules/block/block.routing.yml
@@ -1,3 +1,13 @@
+block.admin_demo:
+  path: '/admin/structure/block/demo/{theme}'
+  defaults:
+    _content: '\Drupal\block\Controller\BlockController::demo'
+  options:
+    _access_mode: 'ALL'
+  requirements:
+    _access_theme: 'TRUE'
+    _permission: 'administer blocks'
+
 block.admin_block_delete:
   path: '/admin/structure/block/manage/{block}/delete'
   defaults:
diff --git a/core/modules/block/block.services.yml b/core/modules/block/block.services.yml
index 5f3dab9f8681..c6704bc7af7b 100644
--- a/core/modules/block/block.services.yml
+++ b/core/modules/block/block.services.yml
@@ -13,7 +13,3 @@ services:
     class: Drupal\block\Routing\RouteSubscriber
     tags:
       - { name: event_subscriber}
-  block.theme_access_check:
-    class: Drupal\block\Access\BlockThemeAccessCheck
-    tags:
-      - { name: access_check}
diff --git a/core/modules/block/lib/Drupal/block/Access/BlockThemeAccessCheck.php b/core/modules/block/lib/Drupal/block/Access/BlockThemeAccessCheck.php
deleted file mode 100644
index 97b53d46d380..000000000000
--- a/core/modules/block/lib/Drupal/block/Access/BlockThemeAccessCheck.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\block\Access\BlockThemeAccessCheck.
- */
-
-namespace Drupal\block\Access;
-
-use Drupal\Core\Access\StaticAccessCheckInterface;
-use Symfony\Component\Routing\Route;
-use Symfony\Component\HttpFoundation\Request;
-
-/**
- * Checks access for displaying block page.
- */
-class BlockThemeAccessCheck implements StaticAccessCheckInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function appliesTo() {
-    return array('_block_themes_access');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function access(Route $route, Request $request) {
-    $theme = $request->attributes->get('theme');
-    return (user_access('administer blocks') && drupal_theme_access($theme)) ? static::ALLOW : static::DENY;
-  }
-
-}
diff --git a/core/modules/block/lib/Drupal/block/Controller/BlockController.php b/core/modules/block/lib/Drupal/block/Controller/BlockController.php
new file mode 100644
index 000000000000..c5c3fc36b2d5
--- /dev/null
+++ b/core/modules/block/lib/Drupal/block/Controller/BlockController.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\block\Controller\BlockController.
+ */
+
+namespace Drupal\block\Controller;
+
+/**
+ * Controller routines for block routes.
+ */
+class BlockController {
+
+  /**
+   * @todo Remove block_admin_demo().
+   */
+  public function demo($theme) {
+    module_load_include('admin.inc', 'block');
+    return block_admin_demo($theme);
+  }
+
+}
diff --git a/core/modules/block/lib/Drupal/block/Routing/RouteSubscriber.php b/core/modules/block/lib/Drupal/block/Routing/RouteSubscriber.php
index 27113660695f..f60e51ac044c 100644
--- a/core/modules/block/lib/Drupal/block/Routing/RouteSubscriber.php
+++ b/core/modules/block/lib/Drupal/block/Routing/RouteSubscriber.php
@@ -46,7 +46,11 @@ public function routes(RouteBuildEvent $event) {
           'theme' => $key,
         ),
         array(
-          '_block_themes_access' => 'TRUE',
+          '_access_theme' => 'TRUE',
+          '_permission' => 'administer blocks',
+        ),
+        array(
+          '_access_mode' => 'ALL',
         )
       );
       $collection->add("block.admin_display_$key", $route);
diff --git a/core/modules/comment/comment.admin.inc b/core/modules/comment/comment.admin.inc
index f732a57969c5..6502873e7988 100644
--- a/core/modules/comment/comment.admin.inc
+++ b/core/modules/comment/comment.admin.inc
@@ -16,6 +16,8 @@
  *
  * @see comment_menu()
  * @see comment_multiple_delete_confirm()
+ *
+ * @deprecated Use \Drupal\comment\Controller\CommentController::adminPage()
  */
 function comment_admin($type = 'new') {
   $request = \Drupal::request();
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 64fe92b18145..cb62bea8c70a 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -185,10 +185,8 @@ function comment_menu() {
   $items['admin/content/comment'] = array(
     'title' => 'Comments',
     'description' => 'List and edit site comments and the comment approval queue.',
-    'page callback' => 'comment_admin',
-    'access arguments' => array('administer comments'),
+    'route_name' => 'comment.admin',
     'type' => MENU_LOCAL_TASK | MENU_NORMAL_ITEM,
-    'file' => 'comment.admin.inc',
   );
   // Tabs begin here.
   $items['admin/content/comment/new'] = array(
@@ -198,8 +196,7 @@ function comment_menu() {
   $items['admin/content/comment/approval'] = array(
     'title' => 'Unapproved comments',
     'title callback' => 'comment_count_unpublished',
-    'page arguments' => array('approval'),
-    'access arguments' => array('administer comments'),
+    'route_name' => 'comment.admin_approval',
     'type' => MENU_LOCAL_TASK,
   );
   $items['comment/%comment'] = array(
diff --git a/core/modules/comment/comment.routing.yml b/core/modules/comment/comment.routing.yml
index fb0a1c420055..305d7b2991b8 100644
--- a/core/modules/comment/comment.routing.yml
+++ b/core/modules/comment/comment.routing.yml
@@ -1,3 +1,21 @@
+comment.admin:
+  path: '/admin/content/comment'
+  defaults:
+    _title: 'Comments'
+    _content: '\Drupal\comment\Controller\CommentController::adminPage'
+    type: 'new'
+  requirements:
+    _permission: 'administer comments'
+
+comment.admin_approval:
+  path: '/admin/content/comment/approval'
+  defaults:
+    _title: 'Unapproved comments'
+    _content: '\Drupal\comment\Controller\CommentController::adminPage'
+    type: 'approval'
+  requirements:
+    _permission: 'administer comments'
+
 comment.edit_page:
   path: '/comment/{comment}/edit'
   defaults:
diff --git a/core/modules/comment/lib/Drupal/comment/Controller/CommentController.php b/core/modules/comment/lib/Drupal/comment/Controller/CommentController.php
index 48a23cddfeb2..137def5179fd 100644
--- a/core/modules/comment/lib/Drupal/comment/Controller/CommentController.php
+++ b/core/modules/comment/lib/Drupal/comment/Controller/CommentController.php
@@ -271,4 +271,12 @@ public function renderNewCommentsNodeLinks(Request $request) {
     return new JsonResponse($links);
   }
 
+  /**
+   * @todo Remove comment_admin().
+   */
+  public function adminPage($type) {
+    module_load_include('admin.inc', 'comment');
+    return comment_admin($type);
+  }
+
 }
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentInterfaceTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentInterfaceTest.php
index bec3930584fb..c19ee808931b 100644
--- a/core/modules/comment/lib/Drupal/comment/Tests/CommentInterfaceTest.php
+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentInterfaceTest.php
@@ -53,7 +53,7 @@ function testCommentInterface() {
     $this->assertTrue($this->commentExists($comment), 'Comment found.');
 
     // Check comment display.
-    $this->drupalGet('node/' . $this->node->id() . '/' . $comment->id());
+    $this->drupalGet('node/' . $this->node->id());
     $this->assertText($subject_text, 'Individual comment subject found.');
     $this->assertText($comment_text, 'Individual comment body found.');
 
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentNodeAccessTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentNodeAccessTest.php
index 9a4dc174e45b..ec26245c85b0 100644
--- a/core/modules/comment/lib/Drupal/comment/Tests/CommentNodeAccessTest.php
+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentNodeAccessTest.php
@@ -68,7 +68,7 @@ function testThreadedCommentView() {
     $this->assertTrue($this->commentExists($comment), 'Comment found.');
 
     // Check comment display.
-    $this->drupalGet('node/' . $this->node->id() . '/' . $comment->id());
+    $this->drupalGet('node/' . $this->node->id());
     $this->assertText($comment_subject, 'Individual comment subject found.');
     $this->assertText($comment_text, 'Individual comment body found.');
 
diff --git a/core/modules/contact/contact.module b/core/modules/contact/contact.module
index 020fd17fd394..4cdea7583a37 100644
--- a/core/modules/contact/contact.module
+++ b/core/modules/contact/contact.module
@@ -80,31 +80,22 @@ function contact_menu() {
 
   $items['contact'] = array(
     'title' => 'Contact',
-    'page callback' => 'contact_site_page',
-    'access arguments' => array('access site-wide contact form'),
+    'route_name' => 'contact.site_page',
     'menu_name' => 'footer',
     'type' => MENU_SUGGESTED_ITEM,
-    'file' => 'contact.pages.inc',
   );
   $items['contact/%contact_category'] = array(
     'title' => 'Contact category form',
     'title callback' => 'entity_page_label',
     'title arguments' => array(1),
-    'page callback' => 'contact_site_page',
-    'page arguments' => array(1),
-    'access arguments' => array('access site-wide contact form'),
+    'route_name' => 'contact.site_page_category',
     'type' => MENU_VISIBLE_IN_BREADCRUMB,
-    'file' => 'contact.pages.inc',
   );
   $items['user/%user/contact'] = array(
     'title' => 'Contact',
-    'page callback' => 'contact_personal_page',
-    'page arguments' => array(1),
+    'route_name' => 'contact.personal_page',
     'type' => MENU_LOCAL_TASK,
-    'access callback' => '_contact_personal_tab_access',
-    'access arguments' => array(1),
     'weight' => 2,
-    'file' => 'contact.pages.inc',
   );
   return $items;
 }
@@ -118,41 +109,7 @@ function contact_menu() {
  * @see contact_menu()
  */
 function _contact_personal_tab_access(UserInterface $account) {
-  global $user;
-
-  // Anonymous users cannot have contact forms.
-  if ($account->isAnonymous()) {
-    return FALSE;
-  }
-
-  // Users may not contact themselves.
-  if ($user->id() == $account->id()) {
-    return FALSE;
-  }
-
-  // User administrators should always have access to personal contact forms.
-  if (user_access('administer users')) {
-    return TRUE;
-  }
-
-  // If requested user has been blocked, do not allow users to contact them.
-  if ($account->isBlocked()) {
-    return FALSE;
-  }
-
-  // If the requested user has disabled their contact form, do not allow users
-  // to contact them.
-  $account_data = \Drupal::service('user.data')->get('contact', $account->id(), 'enabled');
-  if (isset($account_data) && empty($account_data)) {
-    return FALSE;
-  }
-  // If the requested user did not save a preference yet, deny access if the
-  // configured default is disabled.
-  elseif (!\Drupal::config('contact.settings')->get('user_default_enabled')) {
-    return FALSE;
-  }
-
-  return user_access('access user contact forms');
+  return \Drupal::service('access_manager')->checkNamedRoute('contact.personal_page', array('user' => $account->id()));
 }
 
 /**
diff --git a/core/modules/contact/contact.pages.inc b/core/modules/contact/contact.pages.inc
index 5a8cea387474..491bb30ab5fa 100644
--- a/core/modules/contact/contact.pages.inc
+++ b/core/modules/contact/contact.pages.inc
@@ -21,6 +21,8 @@
  * @see contact_menu()
  * @see contact_site_form_submit()
  * @ingroup forms
+ *
+ * @deprecated Use \Drupal\contact\Controller\ContactController::contactSitePage()
  */
 function contact_site_page(Category $category = NULL) {
   // Check if flood control has been activated for sending e-mails.
@@ -66,6 +68,8 @@ function contact_site_page(Category $category = NULL) {
  * @see contact_personal_form_submit()
  *
  * @ingroup forms
+ *
+ * @deprecated Use \Drupal\contact\Controller\ContactController::contactPersonalPage()
  */
 function contact_personal_page($recipient) {
   global $user;
diff --git a/core/modules/contact/contact.routing.yml b/core/modules/contact/contact.routing.yml
index 1dc977342c68..c616c25d62fc 100644
--- a/core/modules/contact/contact.routing.yml
+++ b/core/modules/contact/contact.routing.yml
@@ -26,3 +26,28 @@ contact.category_edit:
     _entity_form: contact_category.edit
   requirements:
     _entity_access: contact_category.update
+
+contact.site_page:
+  path: '/contact'
+  defaults:
+    _title: 'Contact'
+    _content: '\Drupal\contact\Controller\ContactController::contactSitePage'
+    contact_category: NULL
+  requirements:
+    _permission: 'access site-wide contact form'
+
+contact.site_page_category:
+  path: '/contact/{contact_category}'
+  defaults:
+    _title: 'Contact category form'
+    _content: '\Drupal\contact\Controller\ContactController::contactSitePage'
+  requirements:
+    _permission: 'access site-wide contact form'
+
+contact.personal_page:
+  path: '/user/{user}/contact'
+  defaults:
+    _title: 'Contact'
+    _content: '\Drupal\contact\Controller\ContactController::contactPersonalPage'
+  requirements:
+    _access_contact_personal_tab: 'TRUE'
diff --git a/core/modules/contact/contact.services.yml b/core/modules/contact/contact.services.yml
new file mode 100644
index 000000000000..cccd1fd542ba
--- /dev/null
+++ b/core/modules/contact/contact.services.yml
@@ -0,0 +1,6 @@
+services:
+  access_check.contact_personal:
+    class: Drupal\contact\Access\ContactPageAccess
+    tags:
+      - { name: access_check }
+    arguments: ['@config.factory', '@user.data']
diff --git a/core/modules/contact/lib/Drupal/contact/Access/ContactPageAccess.php b/core/modules/contact/lib/Drupal/contact/Access/ContactPageAccess.php
new file mode 100644
index 000000000000..0bfee5c33a10
--- /dev/null
+++ b/core/modules/contact/lib/Drupal/contact/Access/ContactPageAccess.php
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\contact\Access\ContactPageAccess.
+ */
+
+namespace Drupal\contact\Access;
+
+use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\user\UserDataInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Access check for contact_personal_page route.
+ */
+class ContactPageAccess implements StaticAccessCheckInterface {
+
+  /**
+   * The contact settings config object.
+   *
+   * @var \Drupal\Core\Config\ConfigFactory
+   */
+  protected $configFactory;
+
+  /**
+   * The user data service.
+   *
+   * @var \Drupal\user\UserDataInterface;
+   */
+  protected $userData;
+
+  /**
+   * Constructs a ContactPageAccess instance.
+   *
+   * @param \Drupal\Core\Config\ConfigFactory $config_factory
+   *   The config factory.
+   * @param \Drupal\user\UserDataInterface $user_data
+   *   The user data service.
+   */
+  public function __construct(ConfigFactory $config_factory, UserDataInterface $user_data) {
+    $this->configFactory = $config_factory;
+    $this->userData = $user_data;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function appliesTo() {
+    return array('_access_contact_personal_tab');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, Request $request) {
+    $contact_account = $request->attributes->get('user');
+    // @todo revisit after https://drupal.org/node/2048223
+    $user = \Drupal::currentUser();
+
+    // Anonymous users cannot have contact forms.
+    if ($contact_account->isAnonymous()) {
+      return static::DENY;
+    }
+
+    // Users may not contact themselves.
+    if ($user->id() == $contact_account->id()) {
+      return static::DENY;
+    }
+
+    // User administrators should always have access to personal contact forms.
+    if ($user->hasPermission('administer users')) {
+      return static::ALLOW;
+    }
+
+    // If requested user has been blocked, do not allow users to contact them.
+    if ($contact_account->isBlocked()) {
+      return static::DENY;
+    }
+
+    // If the requested user has disabled their contact form, do not allow users
+    // to contact them.
+    $account_data = $this->userData->get('contact', $contact_account->id(), 'enabled');
+    if (isset($account_data) && empty($account_data)) {
+      return static::DENY;
+    }
+    // If the requested user did not save a preference yet, deny access if the
+    // configured default is disabled.
+    else if (!$this->configFactory->get('contact.settings')->get('user_default_enabled')) {
+      return static::DENY;
+    }
+
+    return $user->hasPermission('access user contact forms') ? static::ALLOW : static::DENY;
+  }
+
+}
diff --git a/core/modules/contact/lib/Drupal/contact/Controller/ContactController.php b/core/modules/contact/lib/Drupal/contact/Controller/ContactController.php
new file mode 100644
index 000000000000..cb6c62d6aba4
--- /dev/null
+++ b/core/modules/contact/lib/Drupal/contact/Controller/ContactController.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\contact\Controller\ContactController.
+ */
+
+namespace Drupal\contact\Controller;
+
+use Drupal\contact\CategoryInterface;
+use Drupal\user\UserInterface;
+
+/**
+ * Controller routines for contact routes.
+ */
+class ContactController {
+
+  /**
+   * @todo Remove contact_site_page().
+   */
+  public function contactSitePage(CategoryInterface $contact_category = NULL) {
+    module_load_include('pages.inc', 'contact');
+    return contact_site_page($contact_category);
+  }
+
+  /**
+   * @todo Remove contact_personal_page().
+   */
+  public function contactPersonalPage(UserInterface $user) {
+    module_load_include('pages.inc', 'contact');
+    return contact_personal_page($user);
+  }
+
+}
diff --git a/core/modules/contact/lib/Drupal/contact/Tests/MessageEntityTest.php b/core/modules/contact/lib/Drupal/contact/Tests/MessageEntityTest.php
index baa6f3f53b56..fd5300bf50a7 100644
--- a/core/modules/contact/lib/Drupal/contact/Tests/MessageEntityTest.php
+++ b/core/modules/contact/lib/Drupal/contact/Tests/MessageEntityTest.php
@@ -21,7 +21,7 @@ class MessageEntityTest extends DrupalUnitTestBase {
    *
    * @var array
    */
-  public static $modules = array('system', 'contact', 'field');
+  public static $modules = array('system', 'contact', 'field', 'user');
 
   public static function getInfo() {
     return array(
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index f26ff200996b..7b5c3599de1d 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -149,8 +149,7 @@ function content_translation_menu() {
 
       $items["$path/translations"] = array(
         'title' => 'Translate',
-        'page callback' => 'content_translation_overview',
-        'page arguments' => array($entity_position),
+        'route_name' => "content_translation.translation_overview_$entity_type",
         'type' => MENU_LOCAL_TASK,
         'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
         'weight' => 2,
@@ -169,25 +168,17 @@ function content_translation_menu() {
       $args = array($entity_position, $language_position, $language_position + 1);
       $items["$path/translations/add/%language/%language"] = array(
         'title' => 'Add',
-        'page callback' => 'content_translation_add_page',
-        'page arguments' => $args,
-        'access callback' => 'content_translation_add_access',
-        'access arguments' => $args,
-        'type' => MENU_LOCAL_TASK,
+        'route_name' => "content_translation.translation_add_$entity_type",
         'weight' => 1,
-      ) + $item;
+      );
 
       // Edit translation callback.
       $args = array($entity_position, $language_position);
       $items["$path/translations/edit/%language"] = array(
         'title' => 'Edit',
-        'page callback' => 'content_translation_edit_page',
-        'page arguments' => $args,
-        'access callback' => 'content_translation_edit_access',
-        'access arguments' => $args,
-        'type' => MENU_LOCAL_TASK,
+        'route_name' => "content_translation.translation_edit_$entity_type",
         'weight' => 1,
-      ) + $item;
+      );
 
       // Delete translation callback.
       $items["$path/translations/delete/%language"] = array(
diff --git a/core/modules/content_translation/content_translation.pages.inc b/core/modules/content_translation/content_translation.pages.inc
index a7ce4af2bd80..eb80f327f8fb 100644
--- a/core/modules/content_translation/content_translation.pages.inc
+++ b/core/modules/content_translation/content_translation.pages.inc
@@ -14,6 +14,8 @@
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity whose translation overview should be displayed.
+ *
+ * @deprecated Use \Drupal\content_translation\Controller\ContentTranslationController::overview()
  */
 function content_translation_overview(EntityInterface $entity) {
   $controller = content_translation_controller($entity->entityType());
@@ -188,6 +190,8 @@ function _content_translation_get_switch_links($path) {
  *
  * @return array
  *   A processed form array ready to be rendered.
+ *
+ * @deprecated Use \Drupal\content_translation\Controller\ContentTranslationController::add()
  */
 function content_translation_add_page(EntityInterface $entity, Language $source = NULL, Language $target = NULL) {
   $source = !empty($source) ? $source : $entity->language();
@@ -215,6 +219,8 @@ function content_translation_add_page(EntityInterface $entity, Language $source
  *
  * @return array
  *   A processed form array ready to be rendered.
+ *
+ * @deprecated Use \Drupal\content_translation\Controller\ContentTranslationController::edit()
  */
 function content_translation_edit_page(EntityInterface $entity, Language $language = NULL) {
   $language = !empty($language) ? $language : language(Language::TYPE_CONTENT);
diff --git a/core/modules/content_translation/content_translation.services.yml b/core/modules/content_translation/content_translation.services.yml
index 678724dc0e52..e81c13ddf59b 100644
--- a/core/modules/content_translation/content_translation.services.yml
+++ b/core/modules/content_translation/content_translation.services.yml
@@ -2,3 +2,21 @@ services:
   content_translation.synchronizer:
     class: Drupal\content_translation\FieldTranslationSynchronizer
     arguments: ['@entity.manager']
+
+  content_translation.subscriber:
+    class: Drupal\content_translation\Routing\ContentTranslationRouteSubscriber
+    arguments: ['@plugin.manager.entity']
+    tags:
+      - { name: event_subscriber }
+
+  content_translation.overview_access:
+    class: Drupal\content_translation\Access\ContentTranslationOverviewAccess
+    arguments: ['@plugin.manager.entity']
+    tags:
+      - { name: access_check }
+
+  content_translation.manage_access:
+    class: Drupal\content_translation\Access\ContentTranslationManageAccessCheck
+    arguments: ['@plugin.manager.entity']
+    tags:
+      - { name: access_check }
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Access/ContentTranslationManageAccessCheck.php b/core/modules/content_translation/lib/Drupal/content_translation/Access/ContentTranslationManageAccessCheck.php
new file mode 100644
index 000000000000..21eeea81f974
--- /dev/null
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Access/ContentTranslationManageAccessCheck.php
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\content_translation\Access\ContentTranslationManageAccessCheck.
+ */
+
+namespace Drupal\content_translation\Access;
+
+use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\Core\Language\Language;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Access check for entity translation CRUD operation.
+ */
+class ContentTranslationManageAccessCheck implements StaticAccessCheckInterface {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManager
+   */
+  protected $entityManager;
+
+  /**
+   * Constructs a ContentTranslationManageAccessCheck object.
+   *
+   * @param \Drupal\Core\Entity\EntityManager $manager
+   *   The entity type manager.
+   */
+  public function __construct(EntityManager $manager) {
+    $this->entityManager = $manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function appliesTo() {
+    return array('_access_content_translation_manage');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, Request $request) {
+    if ($entity = $request->attributes->get('entity')) {
+      $route_requirements = $route->getRequirements();
+      $operation = $route_requirements['_access_content_translation_manage'];
+      $entity_type = $entity->entityType();
+      $controller_class = $this->entityManager->getControllerClass($entity_type, 'translation');
+      $controller = new $controller_class($entity_type, $entity->entityInfo());
+
+      // Load translation.
+      $translations = $entity->getTranslationLanguages();
+      $languages = language_list();
+
+      if ($operation == 'create') {
+        $source = language_load($request->attributes->get('source'));
+        $target = language_load($request->attributes->get('target'));
+        $source = !empty($source) ? $source : $entity->language();
+        $target = !empty($target) ? $target : language(Language::TYPE_CONTENT);
+        return ($source->id != $target->id
+          && isset($languages[$source->id])
+          && isset($languages[$target->id])
+          && !isset($translations[$target->id])
+          && $controller->getTranslationAccess($entity, $operation))
+          ? static::ALLOW : static::DENY;
+      }
+      elseif ($operation == 'update') {
+        $language = language_load($request->attributes->get('language'));
+        $language = !empty($language) ? $language : language(Language::TYPE_CONTENT);
+        return isset($languages[$language->id])
+          && $language->id != $entity->getUntranslated()->language()->id
+          && isset($translations[$language->id])
+          && $controller->getTranslationAccess($entity, $operation)
+          ? static::ALLOW : static::DENY;
+      }
+    }
+    return static::DENY;
+  }
+
+}
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Access/ContentTranslationOverviewAccess.php b/core/modules/content_translation/lib/Drupal/content_translation/Access/ContentTranslationOverviewAccess.php
new file mode 100644
index 000000000000..116e9c51e799
--- /dev/null
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Access/ContentTranslationOverviewAccess.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\content_translation\Access\ContentTranslationOverviewAccess.
+ */
+
+namespace Drupal\content_translation\Access;
+
+use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Access\StaticAccessCheckInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Access check for entity translation overview.
+ */
+class ContentTranslationOverviewAccess implements StaticAccessCheckInterface {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManager
+   */
+  protected $entityManager;
+
+  /**
+   * Constructs a ContentTranslationOverviewAccess object.
+   *
+   * @param \Drupal\Core\Entity\EntityManager $manager
+   *   The entity type manager.
+   */
+  public function __construct(EntityManager $manager) {
+    $this->entityManager = $manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function appliesTo() {
+    return array('_access_content_translation_overview');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, Request $request) {
+    if ($entity = $request->attributes->get('entity')) {
+      // Get entity base info.
+      $entity_type = $entity->entityType();
+      $bundle = $entity->bundle();
+
+      // Get account details from request.
+      $account = \Drupal::currentUser();
+
+      // Get entity access callback.
+      $definitions = $this->entityManager->getDefinitions();
+      $access_callback = $definitions[$entity_type]['translation']['content_translation']['access_callback'];
+      if (call_user_func($access_callback, $entity)) {
+        return static::ALLOW;
+      }
+
+      // Check per entity permission.
+      $permission = "translate {$entity_type}";
+      if ($definitions[$entity_type]['permission_granularity'] == 'bundle') {
+        $permission = "translate {$bundle} {$entity_type}";
+      }
+      if ($account->hasPermission($permission)) {
+        return static::ALLOW;
+      }
+    }
+
+    return static::DENY;
+  }
+}
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Controller/ContentTranslationController.php b/core/modules/content_translation/lib/Drupal/content_translation/Controller/ContentTranslationController.php
new file mode 100644
index 000000000000..ec229e1c5666
--- /dev/null
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Controller/ContentTranslationController.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\content_translation\Controller\ContentTranslationController.
+ */
+
+namespace Drupal\content_translation\Controller;
+
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Base class for entity translation controllers.
+ */
+class ContentTranslationController {
+
+  /**
+   * @todo Remove content_translation_overview().
+   */
+  public function overview(EntityInterface $entity) {
+    module_load_include('pages.inc', 'content_translation');
+    return content_translation_overview($entity);
+  }
+
+  /**
+   * @todo Remove content_translation_add_page().
+   */
+  public function add(EntityInterface $entity, $source, $target) {
+    module_load_include('pages.inc', 'content_translation');
+    $source = language_load($source);
+    $target = language_load($target);
+    return content_translation_add_page($entity, $source, $target);
+  }
+
+  /**
+   * @todo Remove content_translation_edit_page().
+   */
+  public function edit(EntityInterface $entity, $language) {
+    module_load_include('pages.inc', 'content_translation');
+    $language = language_load($language);
+    return content_translation_edit_page($entity, $language);
+  }
+
+}
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Routing/ContentTranslationRouteSubscriber.php b/core/modules/content_translation/lib/Drupal/content_translation/Routing/ContentTranslationRouteSubscriber.php
new file mode 100644
index 000000000000..03e5d7500e74
--- /dev/null
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Routing/ContentTranslationRouteSubscriber.php
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\content_translation\Routing\ContentTranslationRouteSubscriber.
+ */
+
+namespace Drupal\content_translation\Routing;
+
+use Drupal\Core\Routing\RouteBuildEvent;
+use Drupal\Core\Routing\RoutingEvents;
+use Drupal\Core\Entity\EntityManager;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Subscriber for entity translation routes.
+ */
+class ContentTranslationRouteSubscriber implements EventSubscriberInterface {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManager
+   */
+  protected $entityManager;
+
+  /**
+   * Constructs a ContentTranslationRouteSubscriber object.
+   *
+   * @param \Drupal\Core\Entity\EntityManager $entityManager
+   *   The entity type manager.
+   */
+  public function __construct(EntityManager $entityManager) {
+    $this->entityManager = $entityManager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[RoutingEvents::DYNAMIC] = 'routes';
+    return $events;
+  }
+
+  /**
+   * Adds routes for entity translations.
+   */
+  public function routes(RouteBuildEvent $event) {
+    $collection = $event->getRouteCollection();
+    foreach ($this->entityManager->getDefinitions() as $entity_type => $entity_info) {
+      if ($entity_info['translatable'] && isset($entity_info['translation'])) {
+        $route = new Route(
+         '/' . str_replace($entity_info['menu_path_wildcard'], '{entity}', $entity_info['menu_base_path']) . "/translations",
+          array(
+            '_content' => '\Drupal\content_translation\Controller\ContentTranslationController::overview',
+            '_title' => 'Translate',
+            'account' => 'NULL',
+          ),
+          array(
+            '_access_content_translation_overview' => $entity_type,
+            '_permission' => 'translate any entity',
+          ),
+          array(
+            'parameters' => array(
+              'entity' => array(
+                'type' => 'entity:' . $entity_type,
+              ),
+            ),
+          )
+        );
+        $collection->add("content_translation.translation_overview_$entity_type", $route);
+
+        $route = new Route(
+          '/' . str_replace($entity_info['menu_path_wildcard'], '{entity}', $entity_info['menu_base_path']) . "/translations/add/{source}/{target}",
+          array(
+            '_content' => '\Drupal\content_translation\Controller\ContentTranslationController::add',
+            'source' => NULL,
+            'target' => NULL,
+            '_title' => 'Add',
+
+          ),
+          array(
+            '_permission' => 'translate any entity',
+            '_access_content_translation_manage' => 'create',
+          ),
+          array(
+            'parameters' => array(
+              'entity' => array(
+                'type' => 'entity:' . $entity_type,
+              ),
+            ),
+          )
+        );
+        $collection->add("content_translation.translation_add_$entity_type", $route);
+
+        $route = new Route(
+          '/' . str_replace($entity_info['menu_path_wildcard'], '{entity}', $entity_info['menu_base_path']) . "/translations/edit/{language}",
+          array(
+            '_content' => '\Drupal\content_translation\Controller\ContentTranslationController::edit',
+            'language' => NULL,
+            '_title' => 'Edit',
+          ),
+          array(
+            '_permission' => 'translate any entity',
+            '_access_content_translation_manage' => 'update',
+          ),
+          array(
+            'parameters' => array(
+              'entity' => array(
+                'type' => 'entity:' . $entity_type,
+              ),
+            ),
+          )
+        );
+        $collection->add("content_translation.translation_edit_$entity_type", $route);
+      }
+    }
+  }
+
+}
diff --git a/core/modules/dblog/dblog.admin.inc b/core/modules/dblog/dblog.admin.inc
index ec0e5e99dc1b..9a5ef3b12689 100644
--- a/core/modules/dblog/dblog.admin.inc
+++ b/core/modules/dblog/dblog.admin.inc
@@ -18,6 +18,10 @@
  *   A build array in the format expected by drupal_render().
  *
  * @see dblog_menu()
+ *
+ * @deprecated Use \Drupal\dblog\Controller\DblogController::pageNotFound(),
+ *   \Drupal\dblog\Controller\DblogController::accessDenied(), or
+ *   \Drupal\dblog\Controller\DblogController::search()
  */
 function dblog_top($type) {
 
diff --git a/core/modules/dblog/dblog.module b/core/modules/dblog/dblog.module
index d14b6e2b39cf..3e6bf9d3e2bc 100644
--- a/core/modules/dblog/dblog.module
+++ b/core/modules/dblog/dblog.module
@@ -49,18 +49,12 @@ function dblog_menu() {
   $items['admin/reports/page-not-found'] = array(
     'title' => "Top 'page not found' errors",
     'description' => "View 'page not found' errors (404s).",
-    'page callback' => 'dblog_top',
-    'page arguments' => array('page not found'),
-    'access arguments' => array('access site reports'),
-    'file' => 'dblog.admin.inc',
+    'route_name' => 'dblog.page_not_found',
   );
   $items['admin/reports/access-denied'] = array(
     'title' => "Top 'access denied' errors",
     'description' => "View 'access denied' errors (403s).",
-    'page callback' => 'dblog_top',
-    'page arguments' => array('access denied'),
-    'access arguments' => array('access site reports'),
-    'file' => 'dblog.admin.inc',
+    'route_name' => 'dblog.access_denied',
   );
   $items['admin/reports/event/%'] = array(
     'title' => 'Details',
@@ -71,10 +65,7 @@ function dblog_menu() {
     $items['admin/reports/search'] = array(
       'title' => 'Top search phrases',
       'description' => 'View most popular search phrases.',
-      'page callback' => 'dblog_top',
-      'page arguments' => array('search'),
-      'access arguments' => array('access site reports'),
-      'file' => 'dblog.admin.inc',
+      'route_name' => 'dblog.search',
     );
   }
 
diff --git a/core/modules/dblog/dblog.routing.yml b/core/modules/dblog/dblog.routing.yml
index d0de09b58c28..31f1b1c781d5 100644
--- a/core/modules/dblog/dblog.routing.yml
+++ b/core/modules/dblog/dblog.routing.yml
@@ -11,3 +11,19 @@ dblog.event:
     _content: '\Drupal\dblog\Controller\DbLogController::eventDetails'
   requirements:
     _permission: 'access site reports'
+
+dblog.page_not_found:
+  path: '/admin/reports/page-not-found'
+  defaults:
+    _title: "Top 'page not found' errors"
+    _content: '\Drupal\dblog\Controller\DbLogController::pageNotFound'
+  requirements:
+    _permission: 'access site reports'
+
+dblog.access_denied:
+  path: '/admin/reports/access-denied'
+  defaults:
+    _title: "Top 'access denied' errors"
+    _content: '\Drupal\dblog\Controller\DbLogController::accessDenied'
+  requirements:
+    _permission: 'access site reports'
diff --git a/core/modules/dblog/dblog.services.yml b/core/modules/dblog/dblog.services.yml
new file mode 100644
index 000000000000..815734ce2869
--- /dev/null
+++ b/core/modules/dblog/dblog.services.yml
@@ -0,0 +1,6 @@
+services:
+  dblog.route_subscriber:
+    class: Drupal\dblog\Routing\RouteSubscriber
+    arguments: ['@module_handler']
+    tags:
+      - { name: event_subscriber}
diff --git a/core/modules/dblog/lib/Drupal/dblog/Controller/DbLogController.php b/core/modules/dblog/lib/Drupal/dblog/Controller/DbLogController.php
index 9dc63f0493d0..795f6890f495 100644
--- a/core/modules/dblog/lib/Drupal/dblog/Controller/DbLogController.php
+++ b/core/modules/dblog/lib/Drupal/dblog/Controller/DbLogController.php
@@ -319,4 +319,28 @@ protected function buildFilterQuery() {
     );
   }
 
+  /**
+   * @todo Remove dblog_top().
+   */
+  public function pageNotFound() {
+    module_load_include('admin.inc', 'dblog');
+    return dblog_top('page not found');
+  }
+
+  /**
+   * @todo Remove dblog_top().
+   */
+  public function accessDenied() {
+    module_load_include('admin.inc', 'dblog');
+    return dblog_top('access denied');
+  }
+
+  /**
+   * @todo Remove dblog_top().
+   */
+  public function search() {
+    module_load_include('admin.inc', 'dblog');
+    return dblog_top('search');
+  }
+
 }
diff --git a/core/modules/dblog/lib/Drupal/dblog/Routing/RouteSubscriber.php b/core/modules/dblog/lib/Drupal/dblog/Routing/RouteSubscriber.php
new file mode 100644
index 000000000000..f04492e9599c
--- /dev/null
+++ b/core/modules/dblog/lib/Drupal/dblog/Routing/RouteSubscriber.php
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\dblog\Routing\RouteSubscriber.
+ */
+
+namespace Drupal\dblog\Routing;
+
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Routing\RouteBuildEvent;
+use Drupal\Core\Routing\RoutingEvents;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Provides dynamic routes for dblog.
+ */
+class RouteSubscriber implements EventSubscriberInterface {
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * Creates a new RouteSubscriber.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   */
+  public function __construct(ModuleHandlerInterface $module_handler) {
+    $this->moduleHandler = $module_handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[RoutingEvents::DYNAMIC] = 'routes';
+    return $events;
+  }
+
+  /**
+   * Generate dynamic routes for various dblog pages.
+   *
+   * @param \Drupal\Core\Routing\RouteBuildEvent $event
+   *   The route building event.
+   *
+   * @return \Symfony\Component\Routing\RouteCollection
+   *   The route collection that contains the new dynamic route.
+   */
+  public function routes(RouteBuildEvent $event) {
+    $collection = $event->getRouteCollection();
+    if ($this->moduleHandler->moduleExists('search')) {
+      // The block entity listing page.
+      $route = new Route(
+        'admin/reports/search',
+        array(
+          '_content' => '\Drupal\dblog\Controller\DbLogController::search',
+          '_title' => 'Top search phrases',
+        ),
+        array(
+          '_permission' => 'access site reports',
+        )
+      );
+      $collection->add('dblog.search', $route);
+    }
+  }
+
+}
diff --git a/core/modules/dblog/lib/Drupal/dblog/Tests/DbLogTest.php b/core/modules/dblog/lib/Drupal/dblog/Tests/DbLogTest.php
index 2ccef59a726d..735cd953c694 100644
--- a/core/modules/dblog/lib/Drupal/dblog/Tests/DbLogTest.php
+++ b/core/modules/dblog/lib/Drupal/dblog/Tests/DbLogTest.php
@@ -155,8 +155,6 @@ private function generateLogEntries($count, $type = 'custom', $severity = WATCHD
    *   (optional) HTTP response code. Defaults to 200.
    */
   private function verifyReports($response = 200) {
-    $quote = '&#039;';
-
     // View the database log help page.
     $this->drupalGet('admin/help/dblog');
     $this->assertResponse($response);
@@ -175,14 +173,14 @@ private function verifyReports($response = 200) {
     $this->drupalGet('admin/reports/page-not-found');
     $this->assertResponse($response);
     if ($response == 200) {
-      $this->assertText(t('Top ' . $quote . 'page not found' . $quote . ' errors'), 'DBLog page-not-found report was displayed');
+      $this->assertText("Top 'page not found' errors", 'DBLog page-not-found report was displayed');
     }
 
     // View the database log access-denied report page.
     $this->drupalGet('admin/reports/access-denied');
     $this->assertResponse($response);
     if ($response == 200) {
-      $this->assertText(t('Top ' . $quote . 'access denied' . $quote . ' errors'), 'DBLog access-denied report was displayed');
+      $this->assertText("Top 'access denied' errors", 'DBLog access-denied report was displayed');
     }
 
     // View the database log event page.
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index 0fe30f683cf4..f7bf99ab2db9 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -548,7 +548,7 @@ function forum_form_node_form_alter(&$form, &$form_state, $form_id) {
       // If there is no default forum already selected, try to get the forum
       // ID from the URL (e.g., if we are on a page like node/add/forum/2, we
       // expect "2" to be the ID of the forum that was requested).
-      $requested_forum_id = arg(3);
+      $requested_forum_id = Drupal::request()->query->get('forum_id');
       $widget['#default_value'] = is_numeric($requested_forum_id) ? $requested_forum_id : '';
     }
   }
diff --git a/core/modules/forum/lib/Drupal/forum/ForumBreadcrumbBuilder.php b/core/modules/forum/lib/Drupal/forum/ForumBreadcrumbBuilder.php
index d28dc3381125..22230413144f 100644
--- a/core/modules/forum/lib/Drupal/forum/ForumBreadcrumbBuilder.php
+++ b/core/modules/forum/lib/Drupal/forum/ForumBreadcrumbBuilder.php
@@ -59,23 +59,16 @@ public function __construct(EntityManager $entity_manager, ConfigFactory $config
    * {@inheritdoc}
    */
   public function build(array $attributes) {
-    // @todo This only works for legacy routes. Once node/% and forum/% are
-    //   converted to the new router this code will need to be updated.
-    if (isset($attributes['_drupal_menu_item']) && ($item = $attributes['_drupal_menu_item']) && $item['path'] == 'node/%') {
-      $node = $item['map'][1];
-      // Load the object in case of missing wildcard loaders.
-      $node = is_object($node) ? $node : node_load($node);
-      if ($this->forumManager->checkNodeType($node)) {
-        $breadcrumb = $this->forumPostBreadcrumb($node);
+    if (!empty($attributes[RouteObjectInterface::ROUTE_NAME])) {
+      $route_name = $attributes[RouteObjectInterface::ROUTE_NAME];
+      if ($route_name == 'node.view' && isset($attributes['node'])) {
+        if ($this->forumManager->checkNodeType($attributes['node'])) {
+          return $this->forumPostBreadcrumb($attributes['node']);
+        }
+      }
+      if ($route_name == 'forum.page' && isset($attributes['taxonomy_term'])) {
+        return $this->forumTermBreadcrumb($attributes['taxonomy_term']);
       }
-    }
-
-    if (!empty($attributes[RouteObjectInterface::ROUTE_NAME]) && $attributes[RouteObjectInterface::ROUTE_NAME] == 'forum.page' && isset($attributes['taxonomy_term'])) {
-      $breadcrumb = $this->forumTermBreadcrumb($attributes['taxonomy_term']);
-    }
-
-    if (!empty($breadcrumb)) {
-      return $breadcrumb;
     }
   }
 
diff --git a/core/modules/forum/lib/Drupal/forum/Tests/ForumBlockTest.php b/core/modules/forum/lib/Drupal/forum/Tests/ForumBlockTest.php
index cc9ff6a31a61..47495e471eae 100644
--- a/core/modules/forum/lib/Drupal/forum/Tests/ForumBlockTest.php
+++ b/core/modules/forum/lib/Drupal/forum/Tests/ForumBlockTest.php
@@ -177,7 +177,7 @@ protected function createForumTopics($count = 5) {
       );
 
       // Create the forum topic, preselecting the forum ID via a URL parameter.
-      $this->drupalPostForm('node/add/forum/1', $edit, t('Save and publish'));
+      $this->drupalPostForm('node/add/forum', $edit, t('Save and publish'), array('query' => array('forum_id' => 1)));
       $topics[] = $title;
     }
 
diff --git a/core/modules/forum/lib/Drupal/forum/Tests/ForumIndexTest.php b/core/modules/forum/lib/Drupal/forum/Tests/ForumIndexTest.php
index 6003194b620b..c50fe4384f62 100644
--- a/core/modules/forum/lib/Drupal/forum/Tests/ForumIndexTest.php
+++ b/core/modules/forum/lib/Drupal/forum/Tests/ForumIndexTest.php
@@ -52,7 +52,7 @@ function testForumIndexStatus() {
     );
 
     // Create the forum topic, preselecting the forum ID via a URL parameter.
-    $this->drupalPostForm('node/add/forum/' . $tid, $edit, t('Save and publish'));
+    $this->drupalPostForm('node/add/forum', $edit, t('Save and publish'), array('query' => array('forum_id' => $tid)));
 
     // Check that the node exists in the database.
     $node = $this->drupalGetNodeByTitle($title);
diff --git a/core/modules/forum/lib/Drupal/forum/Tests/ForumNodeAccessTest.php b/core/modules/forum/lib/Drupal/forum/Tests/ForumNodeAccessTest.php
index 5101ddc98aab..489cc1b03a21 100644
--- a/core/modules/forum/lib/Drupal/forum/Tests/ForumNodeAccessTest.php
+++ b/core/modules/forum/lib/Drupal/forum/Tests/ForumNodeAccessTest.php
@@ -56,7 +56,7 @@ function testForumNodeAccess() {
       'body[0][value]' => $this->randomName(200),
       'private' => TRUE,
     );
-    $this->drupalPostForm('node/add/forum/1', $edit, t('Save'));
+    $this->drupalPostForm('node/add/forum', $edit, t('Save'), array('query' => array('forum_id' => 1)));
     $private_node = $this->drupalGetNodeByTitle($private_node_title);
     $this->assertTrue(!empty($private_node), 'New private forum node found in database.');
 
@@ -66,7 +66,7 @@ function testForumNodeAccess() {
       'title' => $public_node_title,
       'body[0][value]' => $this->randomName(200),
     );
-    $this->drupalPostForm('node/add/forum/1', $edit, t('Save'));
+    $this->drupalPostForm('node/add/forum', $edit, t('Save'), array('query' => array('forum_id' => 1)));
     $public_node = $this->drupalGetNodeByTitle($public_node_title);
     $this->assertTrue(!empty($public_node), 'New public forum node found in database.');
 
diff --git a/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php b/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php
index 26b4dc6ab18e..97c975299dfd 100644
--- a/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php
+++ b/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php
@@ -511,7 +511,7 @@ function createForumTopic($forum, $container = FALSE) {
     $tid = $forum['tid'];
 
     // Create the forum topic, preselecting the forum ID via a URL parameter.
-    $this->drupalPostForm('node/add/forum/' . $tid, $edit, t('Save'));
+    $this->drupalPostForm('node/add/forum', $edit, t('Save'), array('query' => array('forum_id' => $tid)));
 
     $type = t('Forum topic');
     if ($container) {
diff --git a/core/modules/language/language.admin.inc b/core/modules/language/language.admin.inc
index 562580b3b297..c3f669c8f8dd 100644
--- a/core/modules/language/language.admin.inc
+++ b/core/modules/language/language.admin.inc
@@ -336,6 +336,8 @@ function theme_language_negotiation_configure_browser_form_table($variables) {
 
 /**
  * Returns the content language settings form.
+ *
+ * @deprecated Use \Drupal\language\Controller\LanguageController::contentSettings()
  */
 function language_content_settings_page() {
   return drupal_get_form('language_content_settings_form', language_entity_supported());
diff --git a/core/modules/language/language.module b/core/modules/language/language.module
index 69a9db4f4bb0..54ef2f1a1d82 100644
--- a/core/modules/language/language.module
+++ b/core/modules/language/language.module
@@ -126,9 +126,7 @@ function language_menu() {
   $items['admin/config/regional/content-language'] = array(
     'title' => 'Content language settings',
     'description' => 'Configure content language support for any multilingual element.',
-    'page callback' => 'language_content_settings_page',
-    'access arguments' => array('administer languages'),
-    'file' => 'language.admin.inc',
+    'route_name' => 'language.content_settings_page',
   );
 
   return $items;
diff --git a/core/modules/language/language.routing.yml b/core/modules/language/language.routing.yml
index ae980ec93f56..7494f1e57402 100644
--- a/core/modules/language/language.routing.yml
+++ b/core/modules/language/language.routing.yml
@@ -61,3 +61,11 @@ language.negotiation_browser_delete:
     _form: '\Drupal\language\Form\NegotiationBrowserDeleteForm'
   requirements:
     _permission: 'administer languages'
+
+language.content_settings_page:
+  path: '/admin/config/regional/content-language'
+  defaults:
+    _title: 'Content language settings'
+    _content: '\Drupal\language\Controller\LanguageController::contentSettings'
+  requirements:
+    _permission: 'administer languages'
diff --git a/core/modules/language/lib/Drupal/language/Controller/LanguageController.php b/core/modules/language/lib/Drupal/language/Controller/LanguageController.php
new file mode 100644
index 000000000000..07244710b949
--- /dev/null
+++ b/core/modules/language/lib/Drupal/language/Controller/LanguageController.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\language\Controller\LanguageController.
+ */
+
+namespace Drupal\language\Controller;
+
+/**
+ * Returns responses for language routes.
+ */
+class LanguageController {
+
+  /**
+   * @todo Remove language_content_settings_page().
+   */
+  public function contentSettings() {
+    module_load_include('admin.inc', 'language');
+    return language_content_settings_page();
+  }
+
+}
diff --git a/core/modules/node/lib/Drupal/node/Access/NodeAddAccessCheck.php b/core/modules/node/lib/Drupal/node/Access/NodeAddAccessCheck.php
new file mode 100644
index 000000000000..6608ff84dfdf
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Access/NodeAddAccessCheck.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Access\NodeAddAccessCheck.
+ */
+
+namespace Drupal\node\Access;
+
+use Drupal\Core\Entity\EntityCreateAccessCheck;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Determines access to for node add pages.
+ */
+class NodeAddAccessCheck extends EntityCreateAccessCheck {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $requirementsKey = '_node_add_access';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, Request $request) {
+    $access_controller = $this->entityManager->getAccessController('node');
+    // If a node type is set on the request, just check that.
+    if ($request->attributes->has('node_type')) {
+      return $access_controller->createAccess($request->attributes->get('node_type')->type) ? static::ALLOW : static::DENY;
+    }
+    foreach (node_permissions_get_configured_types() as $type) {
+      if ($access_controller->createAccess($type->type)) {
+        // Allow access if at least one type is permitted.
+        return static::ALLOW;
+      }
+    }
+    return static::DENY;
+  }
+
+}
diff --git a/core/modules/node/lib/Drupal/node/Access/NodeRevisionAccessCheck.php b/core/modules/node/lib/Drupal/node/Access/NodeRevisionAccessCheck.php
index aca491d7bdbe..c44e1f650f12 100644
--- a/core/modules/node/lib/Drupal/node/Access/NodeRevisionAccessCheck.php
+++ b/core/modules/node/lib/Drupal/node/Access/NodeRevisionAccessCheck.php
@@ -73,8 +73,18 @@ public function applies(Route $route) {
    * {@inheritdoc}
    */
   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;
+    // If the route has a {node_revision} placeholder, load the node for that
+    // revision. Otherwise, try to use a {node} placeholder.
+    if ($request->attributes->has('node_revision')) {
+      $node = $this->nodeStorage->loadRevision($request->attributes->get('node_revision'));
+    }
+    elseif ($request->attributes->has('node')) {
+      $node = $request->attributes->get('node');
+    }
+    else {
+      return static::DENY;
+    }
+    return $this->checkAccess($node, $route->getRequirement('_access_node_revision')) ? static::ALLOW : static::DENY;
   }
 
   /**
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 000000000000..fdc41e627cb1
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Controller/NodeController.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Controller\NodeController.
+ */
+
+namespace Drupal\node\Controller;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\node\NodeInterface;
+
+/**
+ * Returns responses for Node routes.
+ */
+class NodeController {
+
+  /**
+   * @todo Remove node_add_page().
+   */
+  public function addPage() {
+    module_load_include('pages.inc', 'node');
+    return node_add_page();
+  }
+
+  /**
+   * @todo Remove node_add().
+   */
+  public function add(EntityInterface $node_type) {
+    module_load_include('pages.inc', 'node');
+    return node_add($node_type);
+  }
+
+  /**
+   * @todo Remove node_page_view().
+   */
+  public function viewPage(NodeInterface $node) {
+    return node_page_view($node);
+  }
+
+  /**
+   * @todo Remove node_show().
+   */
+  public function revisionShow($node_revision) {
+    $node_revision = entity_revision_load('node', $node_revision);
+    return node_show($node_revision, TRUE);
+  }
+
+  /**
+   * @todo Remove node_revision_overview().
+   */
+  public function revisionOverview(NodeInterface $node) {
+    module_load_include('pages.inc', 'node');
+    return node_revision_overview($node);
+  }
+
+}
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 67bfceced945..e1bb1e496993 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -8,6 +8,7 @@
  * API pattern.
  */
 
+use Drupal\Component\Utility\String;
 use Drupal\Core\Language\Language;
 use Symfony\Component\HttpFoundation\Response;
 use Drupal\Core\Cache\CacheBackendInterface;
@@ -575,15 +576,17 @@ function node_revision_delete($revision_id) {
  *   A $page element suitable for use by drupal_render().
  *
  * @see node_menu()
+ *
+ * @deprecated Use \Drupal\node\Controller\NodeController::revisionShow()
  */
 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().
   $page = array('nodes' => node_view_multiple(array($node->id() => $node), 'full'));
 
+  if ($message) {
+    $page['#title'] = t('Revision of %title from %date', array('%title' => $node->label(), '%date' => format_date($node->getRevisionCreationTime())));
+  }
+
   // Update the history table, stating that this user viewed this node.
   global $user;
   if (\Drupal::moduleHandler()->moduleExists('history') && $user->isAuthenticated()) {
@@ -941,21 +944,12 @@ function _node_revision_access(EntityInterface $node, $op = 'view', $account = N
  *   TRUE if the user has add permission, otherwise FALSE.
  *
  * @see node_menu()
+ *
+ * @deprecated
+ *   Use \Drupal::service('access_manager')->checkNamedRoute('node.add_page');
  */
 function _node_add_access() {
-  $types = node_type_get_types();
-  foreach ($types as $type) {
-    if (node_access('create', $type->type)) {
-      return TRUE;
-    }
-  }
-  if (user_access('administer content types')) {
-    // There are no content types defined that the user has permission to create,
-    // but the user does have the permission to administer the content types, so
-    // grant them access to the page anyway.
-    return TRUE;
-  }
-  return FALSE;
+  return \Drupal::service('access_manager')->checkNamedRoute('node.add_page');
 }
 
 /**
@@ -1001,30 +995,21 @@ function node_menu() {
   );
   $items['node/add'] = array(
     'title' => 'Add content',
-    'page callback' => 'node_add_page',
-    'access callback' => '_node_add_access',
-    'file' => 'node.pages.inc',
+    'route_name' => 'node.add_page',
   );
   $items['node/add/%node_type'] = array(
     'title callback' => 'entity_page_label',
     'title arguments' => array(2),
-    'page callback' => 'node_add',
-    'page arguments' => array(2),
-    'access callback' => 'node_access',
-    'access arguments' => array('create', 2),
     'description callback' => 'node_type_get_description',
     'description arguments' => array(2),
-    'file' => 'node.pages.inc',
+    'route_name' => 'node.add',
   );
   $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),
+    // The controller also sets the #title in case the routes' title is
+    // overridden by a menu link.
+    'route_name' => 'node.view',
   );
   $items['node/%node/view'] = array(
     'title' => 'View',
@@ -1045,20 +1030,13 @@ function node_menu() {
   );
   $items['node/%node/revisions'] = array(
     'title' => 'Revisions',
-    'page callback' => 'node_revision_overview',
-    'page arguments' => array(1),
-    'access callback' => '_node_revision_access',
-    'access arguments' => array(1),
+    'route_name' => 'node.revision_overview',
     'weight' => 20,
     'type' => MENU_LOCAL_TASK,
-    'file' => 'node.pages.inc',
   );
   $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_show',
   );
   $items['node/%node/revisions/%node_revision/revert'] = array(
     'title' => 'Revert to earlier revision',
@@ -1501,13 +1479,10 @@ function node_view_multiple($nodes, $view_mode = 'teaser', $langcode = NULL) {
  *   A page array suitable for use by drupal_render().
  *
  * @see node_menu()
+ *
+ * @deprecated Use \Drupal\node\Controller\NodeController::viewPage()
  */
 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.
@@ -1519,7 +1494,13 @@ function node_page_view(EntityInterface $node) {
     }
   }
 
-  return node_show($node);
+  $build = node_show($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.
+  $build['#title'] = String::checkPlain($node->label());
+  return $build;
 }
 
 /**
diff --git a/core/modules/node/node.pages.inc b/core/modules/node/node.pages.inc
index 1b6a1e575815..8518383467a5 100644
--- a/core/modules/node/node.pages.inc
+++ b/core/modules/node/node.pages.inc
@@ -24,6 +24,8 @@
  *   to the node add page for that one node type and does not return at all.
  *
  * @see node_menu()
+ *
+ * @deprecated Use \Drupal\node\Controller\NodeController::addPage()
  */
 function node_add_page() {
   $content = array();
@@ -80,6 +82,8 @@ function theme_node_add_list($variables) {
  *   A node submission form.
  *
  * @see node_menu()
+ *
+ * @deprecated Use \Drupal\node\Controller\NodeController::add()
  */
 function node_add($node_type) {
   global $user;
@@ -175,6 +179,8 @@ function theme_node_preview($variables) {
  *   An array as expected by drupal_render().
  *
  * @see node_menu()
+ *
+ * @deprecated Use \Drupal\node\Controller\NodeController::revisionOverview()
  */
 function node_revision_overview($node) {
   drupal_set_title(t('Revisions for %title', array('%title' => $node->label())), PASS_THROUGH);
diff --git a/core/modules/node/node.routing.yml b/core/modules/node/node.routing.yml
index afa2c1cea05e..c2a4ff15323a 100644
--- a/core/modules/node/node.routing.yml
+++ b/core/modules/node/node.routing.yml
@@ -12,6 +12,29 @@ node.page_edit:
   requirements:
     _entity_access: 'node.update'
 
+node.add_page:
+  path: '/node/add'
+  defaults:
+    _title: 'Add page'
+    _content: '\Drupal\node\Controller\NodeController::addPage'
+  requirements:
+    _permission: 'administer content types'
+    _node_add_access: 'node'
+
+node.add:
+  path: '/node/add/{node_type}'
+  defaults:
+    _content: '\Drupal\node\Controller\NodeController::add'
+  requirements:
+    _node_add_access: 'node:{node_type}'
+
+node.view:
+  path: '/node/{node}'
+  defaults:
+    _content: '\Drupal\node\Controller\NodeController::viewPage'
+  requirements:
+    _entity_access: 'node.view'
+
 node.delete_confirm:
   path: '/node/{node}/delete'
   defaults:
@@ -19,6 +42,22 @@ node.delete_confirm:
   requirements:
     _entity_access: 'node.delete'
 
+node.revision_overview:
+  path: '/node/{node}/revisions'
+  defaults:
+    _title: 'Revisions'
+    _content: '\Drupal\node\Controller\NodeController::revisionOverview'
+  requirements:
+    _access_node_revision: 'view'
+
+node.revision_show:
+  path: '/node/{node}/revisions/{node_revision}/view'
+  defaults:
+    _title: 'Revisions'
+    _content: '\Drupal\node\Controller\NodeController::revisionShow'
+  requirements:
+    _access_node_revision: 'view'
+
 node.revision_revert_confirm:
   path: '/node/{node}/revisions/{node_revision}/revert'
   defaults:
diff --git a/core/modules/node/node.services.yml b/core/modules/node/node.services.yml
index bf4c4eee8139..0b839a5b2c1e 100644
--- a/core/modules/node/node.services.yml
+++ b/core/modules/node/node.services.yml
@@ -7,3 +7,8 @@ services:
     arguments: ['@entity.manager', '@database']
     tags:
       - { name: access_check }
+  access_check.node.add:
+    class: Drupal\node\Access\NodeAddAccessCheck
+    arguments: ['@plugin.manager.entity']
+    tags:
+      - { name: access_check }
diff --git a/core/modules/options/lib/Drupal/options/Tests/OptionsFieldUnitTestBase.php b/core/modules/options/lib/Drupal/options/Tests/OptionsFieldUnitTestBase.php
index 893030e3e58b..2b70634612eb 100644
--- a/core/modules/options/lib/Drupal/options/Tests/OptionsFieldUnitTestBase.php
+++ b/core/modules/options/lib/Drupal/options/Tests/OptionsFieldUnitTestBase.php
@@ -55,7 +55,7 @@ class OptionsFieldUnitTestBase extends FieldUnitTestBase {
    */
   public function setUp() {
     parent::setUp();
-    $this->installSchema('system', 'menu_router');
+    $this->installSchema('system', array('router', 'menu_router'));
 
     $this->fieldDefinition = array(
       'name' => $this->fieldName,
diff --git a/core/modules/path/lib/Drupal/path/Controller/PathController.php b/core/modules/path/lib/Drupal/path/Controller/PathController.php
new file mode 100644
index 000000000000..a8fae4afffd1
--- /dev/null
+++ b/core/modules/path/lib/Drupal/path/Controller/PathController.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\path\Controller\PathController.
+ */
+
+namespace Drupal\path\Controller;
+
+/**
+ * Controller routines for path routes.
+ */
+class PathController {
+
+  /**
+   * @todo Remove path_admin_overview().
+   */
+  public function adminOverview($keys = NULL) {
+    module_load_include('admin.inc', 'path');
+    return path_admin_overview($keys);
+  }
+
+  /**
+   * @todo Remove path_admin_edit().
+   */
+  public function adminEdit($path) {
+    $path = path_load($path);
+    module_load_include('admin.inc', 'path');
+    return path_admin_edit($path);
+  }
+
+  /**
+   * @todo Remove path_admin_edit().
+   */
+  public function adminAdd() {
+    module_load_include('admin.inc', 'path');
+    return path_admin_edit();
+  }
+
+}
diff --git a/core/modules/path/path.admin.inc b/core/modules/path/path.admin.inc
index 200dbed1b9c1..4c5b3f4a0fd9 100644
--- a/core/modules/path/path.admin.inc
+++ b/core/modules/path/path.admin.inc
@@ -12,6 +12,8 @@
  *
  * When filter key passed, perform a standard search on the given key,
  * and return the list of matching URL aliases.
+ *
+ * @deprecated Use \Drupal\path\Controller\PathController::adminOverview()
  */
 function path_admin_overview($keys = NULL) {
   // Add the filter form above the overview table.
@@ -105,6 +107,9 @@ function path_admin_overview($keys = NULL) {
  *   A form for adding or editing a URL alias.
  *
  * @see path_menu()
+ *
+ * @deprecated Use \Drupal\path\Controller\PathController::adminEdit() or
+ *   \Drupal\path\Controller\PathController::adminEdit()
  */
 function path_admin_edit($path = array()) {
   if ($path) {
diff --git a/core/modules/path/path.module b/core/modules/path/path.module
index f7e3084b5bb7..984650acf396 100644
--- a/core/modules/path/path.module
+++ b/core/modules/path/path.module
@@ -58,10 +58,8 @@ function path_menu() {
   $items['admin/config/search/path'] = array(
     'title' => 'URL aliases',
     'description' => "Change your site's URL paths by aliasing them.",
-    'page callback' => 'path_admin_overview',
-    'access arguments' => array('administer url aliases'),
+    'route_name' => 'path.admin_overview',
     'weight' => -5,
-    'file' => 'path.admin.inc',
   );
   $items['admin/config/search/path/list'] = array(
     'title' => 'List',
@@ -69,10 +67,7 @@ function path_menu() {
   );
   $items['admin/config/search/path/edit/%path'] = array(
     'title' => 'Edit alias',
-    'page callback' => 'path_admin_edit',
-    'page arguments' => array(5),
-    'access arguments' => array('administer url aliases'),
-    'file' => 'path.admin.inc',
+    'route_name' => 'path.admin_edit',
   );
   $items['admin/config/search/path/delete/%path'] = array(
     'title' => 'Delete alias',
@@ -80,10 +75,8 @@ function path_menu() {
   );
   $items['admin/config/search/path/add'] = array(
     'title' => 'Add alias',
-    'page callback' => 'path_admin_edit',
-    'access arguments' => array('administer url aliases'),
+    'route_name' => 'path.admin_add',
     'type' => MENU_LOCAL_ACTION,
-    'file' => 'path.admin.inc',
   );
 
   return $items;
diff --git a/core/modules/path/path.routing.yml b/core/modules/path/path.routing.yml
index 1dd8193e8815..969ab53c567c 100644
--- a/core/modules/path/path.routing.yml
+++ b/core/modules/path/path.routing.yml
@@ -4,3 +4,28 @@ path.delete:
     _form: '\Drupal\path\Form\DeleteForm'
   requirements:
     _permission: 'administer url aliases'
+
+path.admin_overview:
+  path: '/admin/config/search/path/{keys}'
+  defaults:
+    _title: 'URL aliases'
+    _content: '\Drupal\path\Controller\PathController::adminOverview'
+    keys: NULL
+  requirements:
+    _permission: 'administer url aliases'
+
+path.admin_add:
+  path: '/admin/config/search/path/add'
+  defaults:
+    _title: 'Add alias'
+    _content: '\Drupal\path\Controller\PathController::adminAdd'
+  requirements:
+    _permission: 'administer url aliases'
+
+path.admin_edit:
+  path: '/admin/config/search/path/edit/{path}'
+  defaults:
+    _title: 'Edit alias'
+    _content: '\Drupal\path\Controller\PathController::adminEdit'
+  requirements:
+    _permission: 'administer url aliases'
diff --git a/core/modules/search/lib/Drupal/search/Access/SearchAccessCheck.php b/core/modules/search/lib/Drupal/search/Access/SearchAccessCheck.php
new file mode 100644
index 000000000000..fb9a74e5a1ec
--- /dev/null
+++ b/core/modules/search/lib/Drupal/search/Access/SearchAccessCheck.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\search\Access\SearchAccessCheck
+ */
+
+namespace Drupal\search\Access;
+
+use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\search\SearchPluginManager;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Checks access for viewing search.
+ */
+class SearchAccessCheck implements StaticAccessCheckInterface {
+
+  /**
+   * The search plugin manager.
+   *
+   * @var \Drupal\search\SearchPluginManager
+   */
+  protected $searchManager;
+
+  /**
+   * Contructs a new search access check.
+   *
+   * @param SearchPluginManager $search_plugin_manager
+   *   The search plugin manager.
+   */
+  public function __construct(SearchPluginManager $search_plugin_manager) {
+    $this->searchManager = $search_plugin_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function appliesTo() {
+    return array('_search_access');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, Request $request) {
+    return $this->searchManager->getActiveDefinitions() ? static::ALLOW : static::DENY;
+  }
+
+}
diff --git a/core/modules/search/lib/Drupal/search/Access/SearchPluginAccessCheck.php b/core/modules/search/lib/Drupal/search/Access/SearchPluginAccessCheck.php
new file mode 100644
index 000000000000..af4b1dfe7891
--- /dev/null
+++ b/core/modules/search/lib/Drupal/search/Access/SearchPluginAccessCheck.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\search\Access\SearchPluginAccessCheck
+ */
+
+namespace Drupal\search\Access;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Route access check for search plugins.
+ */
+class SearchPluginAccessCheck extends SearchAccessCheck {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function appliesTo() {
+    return array('_search_plugin_view_access');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, Request $request) {
+    $account = \Drupal::currentUser();
+    $plugin_id = $route->getRequirement('_search_plugin_view_access');
+    return $this->searchManager->pluginAccess($plugin_id, $account) ? static::ALLOW : static::DENY;
+  }
+
+}
diff --git a/core/modules/search/lib/Drupal/search/Controller/SearchController.php b/core/modules/search/lib/Drupal/search/Controller/SearchController.php
new file mode 100644
index 000000000000..8622341af4b9
--- /dev/null
+++ b/core/modules/search/lib/Drupal/search/Controller/SearchController.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\search\Controller\SearchController
+ */
+
+namespace Drupal\search\Controller;
+
+/**
+ * Route controller for search.
+ */
+class SearchController {
+
+  /**
+   * @todo Remove search_view().
+   */
+  public function searchView($keys) {
+    module_load_include('pages.inc', 'search');
+    return search_view(NULL, $keys);
+  }
+
+  /**
+   * @todo Remove search_view().
+   */
+  public function searchViewPlugin($plugin_id, $keys) {
+    module_load_include('pages.inc', 'search');
+    return search_view($plugin_id, $keys);
+  }
+
+}
diff --git a/core/modules/search/lib/Drupal/search/Routing/SearchRouteSubscriber.php b/core/modules/search/lib/Drupal/search/Routing/SearchRouteSubscriber.php
new file mode 100644
index 000000000000..02bfd03e256c
--- /dev/null
+++ b/core/modules/search/lib/Drupal/search/Routing/SearchRouteSubscriber.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\search\Routing\SearchRouteSubscriber
+ */
+
+namespace Drupal\search\Routing;
+
+use Drupal\Core\Routing\RouteBuildEvent;
+use Drupal\Core\Routing\RoutingEvents;
+use Drupal\search\SearchPluginManager;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Provides dynamic routes for search.
+ */
+class SearchRouteSubscriber implements EventSubscriberInterface {
+
+  /**
+   * The search plugin manager.
+   *
+   * @var \Drupal\search\SearchPluginManager
+   */
+  protected $searchManager;
+
+  /**
+   * Constructs a new search route subscriber.
+   *
+   * @param \Drupal\search\SearchPluginManager $search_plugin_manager
+   *   The search plugin manager.
+   */
+  public function __construct(SearchPluginManager $search_plugin_manager) {
+    $this->searchManager = $search_plugin_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[RoutingEvents::DYNAMIC] = 'routes';
+    return $events;
+  }
+
+  /**
+   * Adds routes for search.
+   *
+   * @param \Drupal\Core\Routing\RouteBuildEvent $event
+   *   The route building event.
+   */
+  public function routes(RouteBuildEvent $event) {
+    $collection = $event->getRouteCollection();
+
+    foreach ($this->searchManager->getActiveDefinitions() as $plugin_id => $search_info) {
+      $path = 'search/' . $search_info['path'] . '/{keys}';
+      $defaults = array(
+        '_content' => 'Drupal\search\Controller\SearchController::searchViewPlugin',
+        'plugin_id' => $plugin_id,
+        'keys' => '',
+      );
+      $requirements = array(
+        'keys' => '.+',
+        '_search_plugin_view_access' => $plugin_id,
+        '_permission' => 'search content',
+      );
+      $options = array(
+        '_access_mode' => 'ALL',
+      );
+      $route = new Route($path, $defaults, $requirements, $options);
+      $collection->add('search.view_' . $plugin_id, $route);
+    }
+  }
+
+}
diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchKeywordsConditionsTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchKeywordsConditionsTest.php
index a17c1c68ca0e..15ae685af9c7 100644
--- a/core/modules/search/lib/Drupal/search/Tests/SearchKeywordsConditionsTest.php
+++ b/core/modules/search/lib/Drupal/search/Tests/SearchKeywordsConditionsTest.php
@@ -36,7 +36,7 @@ function setUp() {
     $this->drupalLogin($this->searching_user);
     // Test with all search modules enabled.
     \Drupal::config('search.settings')->set('active_plugins', array('node_search', 'user_search', 'search_extra_type_search'))->save();
-    menu_router_rebuild();
+    \Drupal::state()->set('menu_rebuild_needed', TRUE);
   }
 
   /**
diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchPageOverrideTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchPageOverrideTest.php
index b8ead260c91f..ab3f9135c866 100644
--- a/core/modules/search/lib/Drupal/search/Tests/SearchPageOverrideTest.php
+++ b/core/modules/search/lib/Drupal/search/Tests/SearchPageOverrideTest.php
@@ -38,7 +38,7 @@ function setUp() {
 
     // Enable the extra type module for searching.
     \Drupal::config('search.settings')->set('active_plugins', array('node_search', 'user_search', 'search_extra_type_search'))->save();
-    menu_router_rebuild();
+    \Drupal::state()->set('menu_rebuild_needed', TRUE);
   }
 
   function testSearchPageHook() {
diff --git a/core/modules/search/search.module b/core/modules/search/search.module
index 330e9a7c5d6c..8b6817f49c7e 100644
--- a/core/modules/search/search.module
+++ b/core/modules/search/search.module
@@ -152,11 +152,8 @@ function search_preprocess_block(&$variables) {
 function search_menu() {
   $items['search'] = array(
     'title' => 'Search',
-    'page callback' => 'search_view',
-    'page arguments' => array(NULL, '', ''),
-    'access callback' => 'search_is_active',
     'type' => MENU_SUGGESTED_ITEM,
-    'file' => 'search.pages.inc',
+    'route_name' => 'search.view',
   );
   $items['admin/config/search/settings'] = array(
     'title' => 'Search settings',
@@ -183,45 +180,15 @@ function search_menu() {
       $path = 'search/' . $search_info['path'];
       $items[$path] = array(
         'title' => $search_info['title'],
-        'page callback' => 'search_view',
-        'page arguments' => array($plugin_id, ''),
-        'access callback' => '_search_menu_access',
-        'access arguments' => array($plugin_id),
+        'route_name' => 'search.view_' . $plugin_id,
         'type' => MENU_LOCAL_TASK,
-        'file' => 'search.pages.inc',
         'weight' => $plugin_id == $default_info['id'] ? -10 : 0,
       );
-      $items["$path/%menu_tail"] = array(
-        'title' => $search_info['title'],
-        'load arguments' => array('%map', '%index'),
-        'page callback' => 'search_view',
-        'page arguments' => array($plugin_id, 2),
-        'access callback' => '_search_menu_access',
-        'access arguments' => array($plugin_id),
-        // The default local task points to its parent, but this item points to
-        // where it should so it should not be changed.
-        'type' => MENU_LOCAL_TASK,
-        'file' => 'search.pages.inc',
-        'weight' => 0,
-        // These tabs are not subtabs.
-        'tab_root' => 'search/' . $default_info['path'] . '/%',
-        // These tabs need to display at the same level.
-        'tab_parent' => 'search/' . $default_info['path'],
-      );
     }
   }
   return $items;
 }
 
-/**
- * Determines access for the 'search' path.
- */
-function search_is_active() {
-  // This path cannot be accessed if there are no active plugins.
-  $account = \Drupal::request()->attributes->get('_account');
-  return !empty($account) && $account->hasPermission('search content') && \Drupal::service('plugin.manager.search')->getActiveDefinitions();
-}
-
 /**
  * Returns information about the default search plugin.
  *
@@ -251,12 +218,7 @@ function search_get_default_plugin_info() {
  * @see search_menu()
  */
 function _search_menu_access($plugin_id) {
-  $account = \Drupal::request()->attributes->get('_account');
-  // @todo - remove the empty() check once we are more confident
-  // that the account will be populated, especially during tests.
-  // @see https://drupal.org/node/2032553
-  $access = !empty($account) && $account->hasPermission('search content');
-  return $access && \Drupal::service('plugin.manager.search')->pluginAccess($plugin_id, $account);
+  return \Drupal::service('access_manager')->checkNamedRoute('search.view_' . $plugin_id);
 }
 
 /**
diff --git a/core/modules/search/search.pages.inc b/core/modules/search/search.pages.inc
index b9e06ab4c91b..9d4aebee702d 100644
--- a/core/modules/search/search.pages.inc
+++ b/core/modules/search/search.pages.inc
@@ -15,6 +15,8 @@
  *   Search plugin_id to use for the search.
  * @param $keys
  *   Keywords to use for the search.
+ *
+ * @deprecated Use \Drupal\search\Controller\SearchController::searchView()
  */
 function search_view($plugin_id = NULL, $keys = '') {
   $info = FALSE;
diff --git a/core/modules/search/search.routing.yml b/core/modules/search/search.routing.yml
index 11719c05d36c..b71c85b79ee0 100644
--- a/core/modules/search/search.routing.yml
+++ b/core/modules/search/search.routing.yml
@@ -11,3 +11,17 @@ search.reindex_confirm:
     _form: 'Drupal\search\Form\ReindexConfirm'
   requirements:
     _permission: 'administer search'
+
+search.view:
+  path: '/search/{plugin_id}'
+  defaults:
+    _title: 'Search'
+    _content: '\Drupal\search\Controller\SearchController::searchView'
+    plugin_id: NULL
+    keys: ''
+  options:
+    _access_mode: 'ALL'
+  requirements:
+    keys: '.+'
+    _permission: 'search content'
+    _search_access: 'TRUE'
diff --git a/core/modules/search/search.services.yml b/core/modules/search/search.services.yml
index 22dc7f2e1ec6..8af9864e0740 100644
--- a/core/modules/search/search.services.yml
+++ b/core/modules/search/search.services.yml
@@ -2,3 +2,21 @@ services:
   plugin.manager.search:
     class: Drupal\search\SearchPluginManager
     arguments: ['@container.namespaces', '@config.factory']
+
+  access_check.search:
+    class: Drupal\search\Access\SearchAccessCheck
+    arguments: ['@plugin.manager.search']
+    tags:
+      - { name: access_check }
+
+  access_check.search_plugin:
+    class: Drupal\search\Access\SearchPluginAccessCheck
+    arguments: ['@plugin.manager.search']
+    tags:
+      - { name: access_check }
+
+  route_subscriber.search:
+    class: Drupal\search\Routing\SearchRouteSubscriber
+    arguments: ['@plugin.manager.search']
+    tags:
+     - { name: event_subscriber }
diff --git a/core/modules/system/lib/Drupal/system/Controller/SystemController.php b/core/modules/system/lib/Drupal/system/Controller/SystemController.php
index 470b5bbdaf4e..eaaa8a0acee8 100644
--- a/core/modules/system/lib/Drupal/system/Controller/SystemController.php
+++ b/core/modules/system/lib/Drupal/system/Controller/SystemController.php
@@ -145,4 +145,20 @@ public function systemAdminMenuBlockPage() {
     return $this->systemManager->getBlockContents();
   }
 
+  /**
+   * @todo Remove system_themes_page().
+   */
+  public function themesPage() {
+    module_load_include('admin.inc', 'system');
+    return system_themes_page();
+  }
+
+  /**
+   * @todo Remove system_theme_default().
+   */
+  public function themeSetDefault() {
+    module_load_include('admin.inc', 'system');
+    return system_theme_default();
+  }
+
 }
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 b44217125f5d..afcfde4520bf 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php
@@ -172,9 +172,6 @@ function testBreadCrumbs() {
     $this->assertBreadcrumb("node/$nid1", $trail);
     // Also verify that the node does not appear elsewhere (e.g., menu trees).
     $this->assertNoLink($node1->getTitle());
-    // 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->getTitle());
 
@@ -218,9 +215,6 @@ function testBreadCrumbs() {
         "node/$nid2" => $node2->menu['link_title'],
       );
       $this->assertBreadcrumb("node/$nid2", $trail, $node2->getTitle(), $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->getTitle(), $tree);
       $trail += array(
         "node/$nid2" => $node2->menu['link_title'],
       );
@@ -242,9 +236,6 @@ function testBreadCrumbs() {
       $nid3 = $node3->id();
 
       $this->assertBreadcrumb("node/$nid3", $trail, $node3->getTitle(), $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->getTitle(), $tree, FALSE);
       $trail += array(
         "node/$nid3" => $node3->menu['link_title'],
       );
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeOutputTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/TreeOutputTest.php
index 0bcec30dbc57..7c5948d35c52 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeOutputTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/TreeOutputTest.php
@@ -32,7 +32,7 @@ public static function getInfo() {
   function setUp() {
     parent::setUp();
 
-    $this->installSchema('system', 'menu_router');
+    $this->installSchema('system', array('router', 'menu_router'));
   }
 
   /**
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index 866b553acf78..cab80f7a3dd3 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -12,6 +12,8 @@
 
 /**
  * Menu callback; displays a listing of all themes.
+ *
+ * @deprecated Use \Drupal\system\Controller\SystemController::themesPage()
  */
 function system_themes_page() {
   // Get current list of themes.
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index c0d4da00c4ce..412138085c64 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -605,13 +605,6 @@ function system_element_info() {
  * Implements hook_menu().
  */
 function system_menu() {
-  $items['system/temporary'] = array(
-    'title' => 'Temporary files',
-    'page callback' => 'file_download',
-    'page arguments' => array('temporary'),
-    'access callback' => TRUE,
-    'type' => MENU_CALLBACK,
-  );
   $items['system/ajax'] = array(
     'title' => 'AHAH callback',
     'route_name' => 'system.ajax',
@@ -648,11 +641,9 @@ function system_menu() {
   $items['admin/appearance'] = array(
     'title' => 'Appearance',
     'description' => 'Select and configure your themes.',
-    'page callback' => 'system_themes_page',
-    'access arguments' => array('administer themes'),
+    'route_name' => 'system.themes_page',
     'position' => 'left',
     'weight' => -6,
-    'file' => 'system.admin.inc',
   );
   $items['admin/appearance/list'] = array(
     'title' => 'List',
@@ -660,13 +651,6 @@ function system_menu() {
     'type' => MENU_DEFAULT_LOCAL_TASK,
     'file' => 'system.admin.inc',
   );
-  $items['admin/appearance/default'] = array(
-    'title' => 'Set default theme',
-    'page callback' => 'system_theme_default',
-    'access arguments' => array('administer themes'),
-    'type' => MENU_CALLBACK,
-    'file' => 'system.admin.inc',
-  );
   $items['admin/appearance/settings'] = array(
     'title' => 'Settings',
     'description' => 'Configure default and theme specific settings.',
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index 6f60d0b65348..bef9a0551779 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -281,6 +281,30 @@ system.files:
   requirements:
     _access: 'TRUE'
 
+system.temporary:
+  path: '/system/temporary'
+  defaults:
+    _controller: '\Drupal\system\FileDownloadController::download'
+    scheme: temporary
+  requirements:
+    _access: 'TRUE'
+
+system.themes_page:
+  path: '/admin/appearance'
+  defaults:
+    _title: 'Appearance'
+    _content: '\Drupal\system\Controller\SystemController::themesPage'
+  requirements:
+    _permission: 'administer themes'
+
+system.theme_set_default:
+  path: '/admin/appearance/default'
+  defaults:
+    _title: 'Set default theme'
+    _content: '\Drupal\system\Controller\SystemController::themeSetDefault'
+  requirements:
+    _permission: 'administer themes'
+
 system.theme_settings:
   path: '/admin/appearance/settings'
   defaults:
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Controller/TaxonomyController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Controller/TaxonomyController.php
index 938cc7b05b69..eb57cc56ef63 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Controller/TaxonomyController.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Controller/TaxonomyController.php
@@ -8,6 +8,7 @@
 namespace Drupal\taxonomy\Controller;
 
 use Drupal\Core\Controller\ControllerBase;
+use Drupal\taxonomy\TermInterface;
 use Drupal\taxonomy\VocabularyInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -33,4 +34,20 @@ public function addForm(VocabularyInterface $taxonomy_vocabulary) {
     return $this->entityManager()->getForm($term);
   }
 
+  /**
+   * @todo Remove taxonomy_term_page().
+   */
+  public function termPage(TermInterface $taxonomy_term) {
+    module_load_include('pages.inc', 'taxonomy');
+    return taxonomy_term_page($taxonomy_term);
+  }
+
+  /**
+   * @todo Remove taxonomy_term_feed().
+   */
+  public function termFeed(TermInterface $taxonomy_term) {
+    module_load_include('pages.inc', 'taxonomy');
+    return taxonomy_term_feed($taxonomy_term);
+  }
+
 }
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index 1df57d34307b..81537b6d9a13 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -250,11 +250,7 @@ function taxonomy_menu() {
     'title' => 'Taxonomy term',
     'title callback' => 'taxonomy_term_title',
     'title arguments' => array(2),
-    'page callback' => 'taxonomy_term_page',
-    'page arguments' => array(2),
-    'access callback' => 'entity_page_access',
-    'access arguments' => array(2, 'view'),
-    'file' => 'taxonomy.pages.inc',
+    'route_name' => 'taxonomy.term_page',
   );
   $items['taxonomy/term/%taxonomy_term/view'] = array(
     'title' => 'View',
@@ -280,12 +276,8 @@ function taxonomy_menu() {
     'title' => 'Taxonomy term',
     'title callback' => 'taxonomy_term_title',
     'title arguments' => array(2),
-    'page callback' => 'taxonomy_term_feed',
-    'page arguments' => array(2),
-    'access callback' => 'entity_page_access',
-    'access arguments' => array(2, 'view'),
+    'route_name' => 'taxonomy.term_feed',
     'type' => MENU_CALLBACK,
-    'file' => 'taxonomy.pages.inc',
   );
 
   $items['admin/structure/taxonomy/manage/%taxonomy_vocabulary'] = array(
diff --git a/core/modules/taxonomy/taxonomy.pages.inc b/core/modules/taxonomy/taxonomy.pages.inc
index a2fc46733fdb..956bc71e199d 100644
--- a/core/modules/taxonomy/taxonomy.pages.inc
+++ b/core/modules/taxonomy/taxonomy.pages.inc
@@ -12,6 +12,8 @@
  *
  * @param Drupal\taxonomy\Entity\Term $term
  *   The taxonomy term entity.
+ *
+ * @deprecated Use \Drupal\taxonomy\Controller\TaxonomyController::termPage()
  */
 function taxonomy_term_page(Term $term) {
   // Assign the term name as the page title.
@@ -65,6 +67,8 @@ function taxonomy_term_page(Term $term) {
  *
  * @param Drupal\taxonomy\Entity\Term $term
  *   The taxonomy term entity.
+ *
+ * @deprecated Use \Drupal\taxonomy\Controller\TaxonomyController::termFeed()
  */
 function taxonomy_term_feed(Term $term) {
   $channel['link'] = url('taxonomy/term/' . $term->id(), array('absolute' => TRUE));
diff --git a/core/modules/taxonomy/taxonomy.routing.yml b/core/modules/taxonomy/taxonomy.routing.yml
index f1b9d8281805..8e024a0fc636 100644
--- a/core/modules/taxonomy/taxonomy.routing.yml
+++ b/core/modules/taxonomy/taxonomy.routing.yml
@@ -75,3 +75,16 @@ taxonomy.overview_terms:
   requirements:
     _entity_access: 'taxonomy_vocabulary.view'
 
+taxonomy.term_page:
+  path: '/taxonomy/term/{taxonomy_term}'
+  defaults:
+    _content: '\Drupal\taxonomy\Controller\TaxonomyController::termPage'
+  requirements:
+    _entity_access: 'taxonomy_term.view'
+
+taxonomy.term_feed:
+  path: '/taxonomy/term/{taxonomy_term}/feed'
+  defaults:
+    _content: '\Drupal\taxonomy\Controller\TaxonomyController::termFeed'
+  requirements:
+    _entity_access: 'taxonomy_term.view'
diff --git a/core/modules/translation/lib/Drupal/translation/Access/TranslationNodeOverviewAccessCheck.php b/core/modules/translation/lib/Drupal/translation/Access/TranslationNodeOverviewAccessCheck.php
new file mode 100644
index 000000000000..3b54fe15fd06
--- /dev/null
+++ b/core/modules/translation/lib/Drupal/translation/Access/TranslationNodeOverviewAccessCheck.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\translation\Access\TranslationNodeOverviewAccessCheck.
+ */
+
+namespace Drupal\translation\Access;
+
+use Drupal\Core\Access\StaticAccessCheckInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Provides an access checker for the node translation tab.
+ */
+class TranslationNodeOverviewAccessCheck implements StaticAccessCheckInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function appliesTo() {
+    return array('_access_translation_tab');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, Request $request) {
+    $key = $route->getRequirement('_access_translation_tab');
+    if ($request->attributes->has($key)) {
+      // @todo Remove _translation_tab_access().
+      return _translation_tab_access($request->attributes->get($key)) ? static::ALLOW : static::DENY;
+    }
+    return static::DENY;
+  }
+
+}
diff --git a/core/modules/translation/lib/Drupal/translation/Controller/TranslationController.php b/core/modules/translation/lib/Drupal/translation/Controller/TranslationController.php
new file mode 100644
index 000000000000..68911ec3fc76
--- /dev/null
+++ b/core/modules/translation/lib/Drupal/translation/Controller/TranslationController.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\translation\Controller\TranslationController.
+ */
+
+namespace Drupal\translation\Controller;
+
+use Drupal\node\NodeInterface;
+
+/**
+ * Controller routines for translation routes.
+ */
+class TranslationController {
+
+  /**
+   * @todo Remove translation_node_overview().
+   */
+  public function nodeOverview(NodeInterface $node) {
+    module_load_include('pages.inc', 'translation');
+    return translation_node_overview($node);
+  }
+
+}
diff --git a/core/modules/translation/translation.module b/core/modules/translation/translation.module
index f9d7a623a21b..bd1bf3134cad 100644
--- a/core/modules/translation/translation.module
+++ b/core/modules/translation/translation.module
@@ -59,14 +59,10 @@ function translation_menu() {
   $items = array();
   $items['node/%node/translate'] = array(
     'title' => 'Translate',
-    'page callback' => 'translation_node_overview',
-    'page arguments' => array(1),
-    'access callback' => '_translation_tab_access',
-    'access arguments' => array(1),
+    'route_name' => 'translation.node_overview',
     'type' => MENU_LOCAL_TASK,
     'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
     'weight' => 2,
-    'file' => 'translation.pages.inc',
   );
   return $items;
 }
diff --git a/core/modules/translation/translation.pages.inc b/core/modules/translation/translation.pages.inc
index 4fff31dd6bf0..a1f6360f0a50 100644
--- a/core/modules/translation/translation.pages.inc
+++ b/core/modules/translation/translation.pages.inc
@@ -18,6 +18,8 @@
  *   A render array for a page containing a list of content.
  *
  * @see translation_menu()
+ *
+ * @deprecated Use \Drupal\translation\Controller\TranslationController::nodeOverview()
  */
 function translation_node_overview(EntityInterface $node) {
   include_once DRUPAL_ROOT . '/core/includes/language.inc';
@@ -91,7 +93,7 @@ function translation_node_overview(EntityInterface $node) {
     $rows[] = $row;
   }
 
-  drupal_set_title(t('Translations of %title', array('%title' => $node->label())), PASS_THROUGH);
+  $build['#title'] = t('Translations of %title', array('%title' => $node->label()));
 
   $build['translation_node_overview'] = array(
     '#theme' => 'table',
diff --git a/core/modules/translation/translation.routing.yml b/core/modules/translation/translation.routing.yml
new file mode 100644
index 000000000000..712907534371
--- /dev/null
+++ b/core/modules/translation/translation.routing.yml
@@ -0,0 +1,7 @@
+translation.node_overview:
+  path: '/node/{node}/translate'
+  defaults:
+    _title: 'Translate'
+    _content: '\Drupal\translation\Controller\TranslationController::nodeOverview'
+  requirements:
+    _access_translation_tab: 'node'
diff --git a/core/modules/translation/translation.services.yml b/core/modules/translation/translation.services.yml
new file mode 100644
index 000000000000..e6520d5903dc
--- /dev/null
+++ b/core/modules/translation/translation.services.yml
@@ -0,0 +1,5 @@
+services:
+  access_check.translation.node_overview:
+    class: Drupal\translation\Access\TranslationNodeOverviewAccessCheck
+    tags:
+      - { name: access_check }
diff --git a/core/modules/update/lib/Drupal/update/Controller/UpdateController.php b/core/modules/update/lib/Drupal/update/Controller/UpdateController.php
index deef6d852844..681646657106 100644
--- a/core/modules/update/lib/Drupal/update/Controller/UpdateController.php
+++ b/core/modules/update/lib/Drupal/update/Controller/UpdateController.php
@@ -62,4 +62,12 @@ public function updateStatus() {
     return $build;
   }
 
+  /**
+   * @todo Remove update_manual_status().
+   */
+  public function updateStatusManually() {
+    module_load_include('fetch.inc', 'update');
+    return update_manual_status();
+  }
+
 }
diff --git a/core/modules/update/update.fetch.inc b/core/modules/update/update.fetch.inc
index 6cb7cb78f29c..ac3c1b5a8efd 100644
--- a/core/modules/update/update.fetch.inc
+++ b/core/modules/update/update.fetch.inc
@@ -14,6 +14,8 @@
  * Manually checks the update status without the use of cron.
  *
  * @see update_menu()
+ *
+ * @deprecated Use \Drupal\update\Controller\UpdateController::updateStatusManually()
  */
 function update_manual_status() {
   _update_refresh();
diff --git a/core/modules/update/update.module b/core/modules/update/update.module
index 0210c01377b7..692d5bc0beb0 100644
--- a/core/modules/update/update.module
+++ b/core/modules/update/update.module
@@ -173,13 +173,6 @@ function update_menu() {
     'type' => MENU_LOCAL_TASK,
     'weight' => 50,
   );
-  $items['admin/reports/updates/check'] = array(
-    'title' => 'Manual update check',
-    'page callback' => 'update_manual_status',
-    'access arguments' => array('administer site configuration'),
-    'type' => MENU_CALLBACK,
-    'file' => 'update.fetch.inc',
-  );
 
   // We want action links for updating projects at a few different locations:
   // both the module and theme administration pages, and on the available
diff --git a/core/modules/update/update.routing.yml b/core/modules/update/update.routing.yml
index f6bd12099ca4..43ae6b53bdf3 100644
--- a/core/modules/update/update.routing.yml
+++ b/core/modules/update/update.routing.yml
@@ -11,3 +11,11 @@ update.status:
     _content: '\Drupal\update\Controller\UpdateController::updateStatus'
   requirements:
     _permission: 'administer site configuration'
+
+update.manual_status:
+  path: '/admin/reports/updates/check'
+  defaults:
+    _title: 'Manual update check'
+    _content: '\Drupal\update\Controller\UpdateController::updateStatusManually'
+  requirements:
+    _permission: 'administer site configuration'
diff --git a/core/modules/user/lib/Drupal/user/Controller/UserController.php b/core/modules/user/lib/Drupal/user/Controller/UserController.php
index cd63cb062d1f..2ddf8438e3bd 100644
--- a/core/modules/user/lib/Drupal/user/Controller/UserController.php
+++ b/core/modules/user/lib/Drupal/user/Controller/UserController.php
@@ -8,6 +8,7 @@
 namespace Drupal\user\Controller;
 
 use Drupal\user\Form\UserLoginForm;
+use Drupal\user\UserInterface;
 use Symfony\Component\DependencyInjection\ContainerAware;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpFoundation\Request;
@@ -55,4 +56,12 @@ public function logout(Request $request) {
     return new RedirectResponse(url('<front>', array('absolute' => TRUE)));
   }
 
+  /**
+   * @todo Remove user_cancel_confirm().
+   */
+  public function confirmCancel(UserInterface $user, $timestamp = 0, $hashed_pass = '') {
+    module_load_include('pages.inc', 'user');
+    return user_cancel_confirm($user, $timestamp, $hashed_pass);
+  }
+
 }
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 3bc4534f70be..93c5b7722220 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -849,25 +849,18 @@ function user_menu() {
     'title' => 'My account',
     'title callback' => 'user_page_title',
     'title arguments' => array(1),
-    'page callback' => 'user_view_page',
-    'page arguments' => array(1),
-    'access callback' => 'entity_page_access',
-    'access arguments' => array(1),
+    'route_name' => 'user.view',
   );
   $items['user/%user/view'] = array(
     'title' => 'View',
     'type' => MENU_DEFAULT_LOCAL_TASK,
   );
   $items['user/%user/cancel'] = array(
-    'route_name' => 'user.cancel_confirm',
+    'route_name' => 'user.cancel',
   );
   $items['user/%user/cancel/confirm/%/%'] = array(
     'title' => 'Confirm account cancellation',
-    'page callback' => 'user_cancel_confirm',
-    'page arguments' => array(1, 4, 5),
-    'access callback' => 'entity_page_access',
-    'access arguments' => array(1, 'delete'),
-    'file' => 'user.pages.inc',
+    'route_name' => 'user.cancel_confirm',
   );
   $items['user/%user/edit'] = array(
     'title' => 'Edit',
@@ -1370,18 +1363,6 @@ function user_delete_multiple(array $uids) {
   entity_delete_multiple('user', $uids);
 }
 
-/**
- * Page callback wrapper for user_view().
- */
-function user_view_page($account) {
-  if (is_object($account)) {
-    return user_view($account);
-  }
-  // An administrator may try to view a non-existent account,
-  // so we give them a 404 (versus a 403 for non-admins).
-  throw new NotFoundHttpException();
-}
-
 /**
  * Generate an array for rendering the given user.
  *
diff --git a/core/modules/user/user.pages.inc b/core/modules/user/user.pages.inc
index 6e4f9993e143..6d59e6c53fdb 100644
--- a/core/modules/user/user.pages.inc
+++ b/core/modules/user/user.pages.inc
@@ -123,6 +123,8 @@ function template_preprocess_user(&$variables) {
  *
  * @see user_cancel_confirm_form()
  * @see user_cancel_url()
+ *
+ * @deprecated Use \Drupal\user\Controller\UserController::confirmCancel()
  */
 function user_cancel_confirm($account, $timestamp = 0, $hashed_pass = '') {
   // Time out in seconds until cancel URL expires; 24 hours = 86400 seconds.
diff --git a/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml
index 22b6c5e643f2..6a9c057bfcdd 100644
--- a/core/modules/user/user.routing.yml
+++ b/core/modules/user/user.routing.yml
@@ -111,6 +111,14 @@ user.page:
   requirements:
     _access: 'TRUE'
 
+user.view:
+  path: '/user/{user}'
+  defaults:
+    _entity_view: 'user.full'
+  requirements:
+    user: \d+
+    _entity_access: 'user.view'
+
 user.login:
   path: '/user/login'
   defaults:
@@ -125,10 +133,20 @@ user.edit:
   requirements:
     _entity_access: 'user.update'
 
-user.cancel_confirm:
+user.cancel:
   path: '/user/{user}/cancel'
   defaults:
     _title: 'Cancel account'
     _entity_form: 'user.cancel'
   requirements:
     _entity_access: 'user.delete'
+
+user.cancel_confirm:
+  path: '/user/{user}/cancel/confirm/{timestamp}/{hashed_pass}'
+  defaults:
+    _title: 'Confirm account cancellation'
+    _content: '\Drupal\user\Controller\UserController::confirmCancel'
+    timestamp: 0
+    hashed_pass: ''
+  requirements:
+    _entity_access: 'user.delete'
diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterBooleanOperatorStringTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterBooleanOperatorStringTest.php
index d3c386242c70..3617409784ba 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterBooleanOperatorStringTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterBooleanOperatorStringTest.php
@@ -49,7 +49,7 @@ public static function getInfo() {
   protected function setUp() {
     parent::setUp();
 
-    $this->installSchema('system', array('menu_router', 'variable', 'key_value_expire'));
+    $this->installSchema('system', array('router', 'menu_router', 'variable', 'key_value_expire'));
   }
 
 
diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterBooleanOperatorTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterBooleanOperatorTest.php
index 77841f37fd15..8ff7823d83fc 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterBooleanOperatorTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterBooleanOperatorTest.php
@@ -45,7 +45,7 @@ public static function getInfo() {
   protected function setUp() {
     parent::setUp();
 
-    $this->installSchema('system', array('menu_router', 'variable', 'key_value_expire'));
+    $this->installSchema('system', array('router', 'menu_router', 'variable', 'key_value_expire'));
   }
 
   /**
diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterEqualityTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterEqualityTest.php
index 9b74533ef757..4ab42ad04d26 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterEqualityTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterEqualityTest.php
@@ -38,7 +38,7 @@ public static function getInfo() {
   protected function setUp() {
     parent::setUp();
 
-    $this->installSchema('system', array('menu_router', 'variable', 'key_value_expire'));
+    $this->installSchema('system', array('router', 'menu_router', 'variable', 'key_value_expire'));
   }
 
   function viewsData() {
diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterInOperatorTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterInOperatorTest.php
index 0c1f32a9236b..6e3e4e589ed5 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterInOperatorTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterInOperatorTest.php
@@ -39,7 +39,7 @@ public static function getInfo() {
   protected function setUp() {
     parent::setUp();
 
-    $this->installSchema('system', array('menu_router', 'variable', 'key_value_expire'));
+    $this->installSchema('system', array('router', 'menu_router', 'variable', 'key_value_expire'));
   }
 
   function viewsData() {
diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterNumericTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterNumericTest.php
index a1f50ef18909..62b16d30d070 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterNumericTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterNumericTest.php
@@ -39,7 +39,7 @@ public static function getInfo() {
   protected function setUp() {
     parent::setUp();
 
-    $this->installSchema('system', array('menu_router', 'variable', 'key_value_expire'));
+    $this->installSchema('system', array('router', 'menu_router', 'variable', 'key_value_expire'));
   }
 
   function viewsData() {
diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterStringTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterStringTest.php
index 2bf0e9bf7ed1..efb19f760e9a 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterStringTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterStringTest.php
@@ -38,7 +38,7 @@ public static function getInfo() {
   protected function setUp() {
     parent::setUp();
 
-    $this->installSchema('system', array('menu_router', 'variable', 'key_value_expire'));
+    $this->installSchema('system', array('router', 'menu_router', 'variable', 'key_value_expire'));
   }
 
   function viewsData() {
diff --git a/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayPageTest.php b/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayPageTest.php
index d3ddcf08fbf4..f597199b4f5c 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayPageTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayPageTest.php
@@ -57,9 +57,7 @@ protected function setUp() {
     parent::setUp();
 
     // Setup the needed tables in order to make the drupal router working.
-    $this->installSchema('system', 'router');
-    $this->installSchema('system', 'url_alias');
-    $this->installSchema('system', 'menu_router');
+    $this->installSchema('system', array('router', 'menu_router', 'url_alias'));
     $this->installSchema('menu_link', 'menu_links');
   }
 
diff --git a/core/modules/views/lib/Drupal/views/Tests/Plugin/RowEntityTest.php b/core/modules/views/lib/Drupal/views/Tests/Plugin/RowEntityTest.php
index 8ce7a8ecd300..44861576c3c4 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Plugin/RowEntityTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/RowEntityTest.php
@@ -51,7 +51,7 @@ public static function getInfo() {
   protected function setUp() {
     parent::setUp();
 
-    $this->installSchema('system', array('menu_router'));
+    $this->installSchema('system', array('menu_router', 'router'));
     $this->installSchema('taxonomy', array('taxonomy_term_data', 'taxonomy_term_hierarchy'));
     $this->installConfig(array('taxonomy'));
   }
diff --git a/core/modules/views/lib/Drupal/views/Tests/ViewPageControllerTest.php b/core/modules/views/lib/Drupal/views/Tests/ViewPageControllerTest.php
index 892b2bea7d80..d4806f0d346f 100644
--- a/core/modules/views/lib/Drupal/views/Tests/ViewPageControllerTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/ViewPageControllerTest.php
@@ -55,7 +55,7 @@ public static function getInfo() {
   protected function setUp() {
     parent::setUp();
 
-    $this->installSchema('system', 'menu_router');
+    $this->installSchema('system', array('router', 'menu_router'));
 
     $this->pageController = new ViewPageController($this->container->get('entity.manager')->getStorageController('view'), new ViewExecutableFactory());
   }
diff --git a/core/modules/xmlrpc/lib/Drupal/xmlrpc/Controller/XmlrpcController.php b/core/modules/xmlrpc/lib/Drupal/xmlrpc/Controller/XmlrpcController.php
new file mode 100644
index 000000000000..4abbf3da24d7
--- /dev/null
+++ b/core/modules/xmlrpc/lib/Drupal/xmlrpc/Controller/XmlrpcController.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\xmlrpc\Controller\XmlrpcController.
+ */
+
+namespace Drupal\xmlrpc\Controller;
+
+/**
+ * Contains controller methods for the XML-RPC module.
+ */
+class XmlrpcController {
+
+  /**
+   * @todo Remove xmlrpc_server_page().
+   */
+  public function php() {
+    module_load_include('server.inc', 'xmlrpc');
+    return xmlrpc_server_page();
+  }
+
+}
diff --git a/core/modules/xmlrpc/xmlrpc.module b/core/modules/xmlrpc/xmlrpc.module
index fe1fcbc7a506..c0e3d448f241 100644
--- a/core/modules/xmlrpc/xmlrpc.module
+++ b/core/modules/xmlrpc/xmlrpc.module
@@ -20,20 +20,6 @@ function xmlrpc_help($path, $args) {
   }
 }
 
-/**
- * Implements hook_menu().
- */
-function xmlrpc_menu() {
-  $items['xmlrpc.php'] = array(
-    'title' => 'XML-RPC',
-    'page callback' => 'xmlrpc_server_page',
-    'access callback' => TRUE,
-    'type' => MENU_CALLBACK,
-    'file' => 'xmlrpc.server.inc',
-  );
-  return $items;
-}
-
 /**
  * Performs one or more XML-RPC request(s).
  *
diff --git a/core/modules/xmlrpc/xmlrpc.routing.yml b/core/modules/xmlrpc/xmlrpc.routing.yml
new file mode 100644
index 000000000000..f6cc78c604af
--- /dev/null
+++ b/core/modules/xmlrpc/xmlrpc.routing.yml
@@ -0,0 +1,7 @@
+xmlrpc.php:
+  path: '/xmlrpc.php'
+  defaults:
+    _title: 'XML-RPC'
+    _content: '\Drupal\xmlrpc\Controller\XmlrpcController::php'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/xmlrpc/xmlrpc.server.inc b/core/modules/xmlrpc/xmlrpc.server.inc
index 8f171a5444a0..1d210e5e65e7 100644
--- a/core/modules/xmlrpc/xmlrpc.server.inc
+++ b/core/modules/xmlrpc/xmlrpc.server.inc
@@ -13,6 +13,8 @@
  *
  * @return \Symfony\Component\HttpFoundation\Response
  *   A Response object.
+ *
+ * @deprecated Use \Drupal\xmlrpc\Controller\XmlrpcController::php()
  */
 function xmlrpc_server_page() {
   module_load_include('inc', 'xmlrpc');
-- 
GitLab