diff --git a/README.md b/README.md
new file mode 100644
index 0000000..86ed1fd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,33 @@
+INTRODUCTION
+------------
+ 
+ Provides common and resuable token UI elements and missing core tokens.
+
+ * For a full description of the module, visit the project page:
+   https://drupal.org/project/token
+
+ * To submit bug reports and feature suggestions, or to track changes:
+   https://drupal.org/project/issues/token
+
+
+INSTALLATION
+------------
+
+Install as usual, see
+https://drupal.org/documentation/install/modules-themes/modules-7 for further
+information.
+
+
+TROUBLESHOOTING
+---------------
+
+Token module doesn't provide any visible functions to the user on its own, it
+just provides token handling services for other modules.
+
+
+MAINTAINERS
+-----------
+
+Current maintainers:
+
+ * Dave Reid (https://drupal.org/user/53892)
diff --git a/README.txt b/README.txt
deleted file mode 100644
index 230774e..0000000
--- a/README.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-
-Provides common and resuable token UI elements and missing core tokens.
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..aae115f
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,6 @@
+{
+  "name": "drupal/token",
+  "description": "Provides a user interface for the Token API and some missing core tokens.",
+  "type": "drupal-module",
+  "license": "GPL-2.0+"
+}
diff --git a/jquery.treeTable.css b/css/jquery.treeTable.css
similarity index 93%
rename from jquery.treeTable.css
rename to css/jquery.treeTable.css
index bec225a..db2149b 100644
--- a/jquery.treeTable.css
+++ b/css/jquery.treeTable.css
@@ -16,11 +16,11 @@
 }
 
 .treeTable tr.collapsed td .expander {
-  background-image: url(arrow-right.png);
+  background-image: url(../arrow-right.png);
 }
 
 .treeTable tr.expanded td .expander {
-  background-image: url(arrow-down.png);
+  background-image: url(../arrow-down.png);
 }
 
 /* jquery.treeTable.sortable
diff --git a/token.css b/css/token.css
similarity index 85%
rename from token.css
rename to css/token.css
index 5c3b3ba..5432f0f 100644
--- a/token.css
+++ b/css/token.css
@@ -4,6 +4,10 @@
   margin-left: 19px;
 }
 
+.ui-dialog-content .token-tree {
+  margin-left: 0;
+}
+
 .token-tree td, .token-tree th {
   padding-top: 0;
   padding-bottom: 0;
diff --git a/jquery.treeTable.js b/js/jquery.treeTable.js
similarity index 100%
rename from jquery.treeTable.js
rename to js/jquery.treeTable.js
diff --git a/token.js b/js/token.js
similarity index 88%
rename from token.js
rename to js/token.js
index 59715f4..1045221 100644
--- a/token.js
+++ b/js/token.js
@@ -13,16 +13,16 @@ Drupal.behaviors.tokenInsert = {
   attach: function (context, settings) {
     // Keep track of which textfield was last selected/focused.
     $('textarea, input[type="text"]', context).focus(function() {
-      Drupal.settings.tokenFocusedField = this;
+      drupalSettings.tokenFocusedField = this;
     });
 
     $('.token-click-insert .token-key', context).once('token-click-insert', function() {
       var newThis = $('<a href="javascript:void(0);" title="' + Drupal.t('Insert this token into your form') + '">' + $(this).html() + '</a>').click(function(){
-        if (typeof Drupal.settings.tokenFocusedField == 'undefined') {
+        if (typeof drupalSettings.tokenFocusedField == 'undefined') {
           alert(Drupal.t('First click a text field to insert your tokens into.'));
         }
         else {
-          var myField = Drupal.settings.tokenFocusedField;
+          var myField = drupalSettings.tokenFocusedField;
           var myValue = $(this).text();
 
           //IE support
@@ -52,4 +52,4 @@ Drupal.behaviors.tokenInsert = {
   }
 };
 
-})(jQuery);
+})(jQuery, drupalSettings);
diff --git a/src/Controller/TokenAutocompleteController.php b/src/Controller/TokenAutocompleteController.php
new file mode 100644
index 0000000..60e977a
--- /dev/null
+++ b/src/Controller/TokenAutocompleteController.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Controller\TokenAutocompleteController.
+ */
+
+namespace Drupal\token\Controller;
+
+use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Controller\ControllerBase;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Returns autocomplete responses for tokens.
+ */
+class TokenAutocompleteController extends ControllerBase {
+
+  /**
+   * Retrieves suggestions for block category autocompletion.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request.
+   * @param string $token_type
+   *   The token type.
+   * @param string $filter
+   *   The autocomplete filter.
+   *
+   * @return \Symfony\Component\HttpFoundation\JsonResponse
+   *   A JSON response containing autocomplete suggestions.
+   */
+  public function autocomplete($token_type, $filter, Request $request) {
+    $filter = substr($filter, strrpos($filter, '['));
+
+    $matches = array();
+
+    if (!Unicode::strlen($filter)) {
+      $matches["[{$token_type}:"] = 0;
+    }
+    else {
+      $depth = max(1, substr_count($filter, ':'));
+      $tree = token_build_tree($token_type, array('flat' => TRUE, 'depth' => $depth));
+      foreach (array_keys($tree) as $token) {
+        if (strpos($token, $filter) === 0) {
+          $matches[$token] = levenshtein($token, $filter);
+          if (isset($tree[$token]['children'])) {
+            $token = rtrim($token, ':]') . ':';
+            $matches[$token] = levenshtein($token, $filter);
+          }
+        }
+      }
+    }
+
+    asort($matches);
+
+    $keys = array_keys($matches);
+    $matches = array_combine($keys, $keys);
+
+    return new JsonResponse($matches);
+  }
+
+}
diff --git a/src/Controller/TokenCacheController.php b/src/Controller/TokenCacheController.php
new file mode 100644
index 0000000..a086906
--- /dev/null
+++ b/src/Controller/TokenCacheController.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Controller\TokenCacheController.
+ */
+
+namespace Drupal\token\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+
+/**
+ * Clears cache for tokens.
+ */
+class TokenCacheController extends ControllerBase  {
+
+  /**
+   * Clear caches and redirect back to the frontpage.
+   */
+  public function flush() {
+    token_clear_cache();
+    drupal_set_message(t('Token registry caches cleared.'));
+    return $this->redirect('<front>');
+  }
+
+}
diff --git a/src/Controller/TokenDevelController.php b/src/Controller/TokenDevelController.php
new file mode 100644
index 0000000..7f29048
--- /dev/null
+++ b/src/Controller/TokenDevelController.php
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Controller\TokenDevelController.
+ */
+
+namespace Drupal\token\Controller;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Devel integration for tokens.
+ */
+class TokenDevelController implements ContainerInjectionInterface {
+
+  /**
+   * The module handler to invoke the alter hook.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * Constructs a new TokenDevelController.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   */
+  public function __construct(ModuleHandlerInterface $module_handler) {
+    $this->moduleHandler = $module_handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('module_handler')
+    );
+  }
+
+  public function devel_token_node($node, Request $request) {
+    return $this->devel_token_object('node', $node, $request);
+  }
+
+  public function devel_token_comment($comment, Request $request) {
+    return $this->devel_token_object('comment', $comment, $request);
+  }
+
+  public function devel_token_user($user, Request $request) {
+    return $this->devel_token_object('user', $user, $request);
+  }
+
+  public function devel_token_taxonomy_term($taxonomy_term, Request $request) {
+    return $this->devel_token_object('taxonomy_term', $taxonomy_term, $request);
+  }
+
+  private function devel_token_object($entity_type, $entity_id, Request $request) {
+    $this->moduleHandler->loadInclude('token', 'pages.inc');
+    $entity = entity_load($entity_type, $entity_id);
+
+    $header = array(
+      t('Token'),
+      t('Value'),
+    );
+    $rows = array();
+
+    $options = array(
+      'flat' => TRUE,
+      'values' => TRUE,
+      'data' => array($entity_type => $entity),
+    );
+    $tree = token_build_tree($entity_type, $options);
+    foreach ($tree as $token => $token_info) {
+      if (!empty($token_info['restricted'])) {
+        continue;
+      }
+      if (!isset($token_info['value']) && !empty($token_info['parent']) && !isset($tree[$token_info['parent']]['value'])) {
+        continue;
+      }
+      $row = _token_token_tree_format_row($token, $token_info);
+      unset($row['data']['description']);
+      unset($row['data']['name']);
+      $rows[] = $row;
+    }
+
+    $build['tokens'] = array(
+      '#theme' => 'tree_table',
+      '#header' => $header,
+      '#rows' => $rows,
+      '#attributes' => array('class' => array('token-tree')),
+      '#empty' => t('No tokens available.'),
+      '#attached' => array(
+        'library' => array('token/token'),
+      ),
+    );
+
+    return $build;
+  }
+}
diff --git a/src/Controller/TokenTreeController.php b/src/Controller/TokenTreeController.php
new file mode 100644
index 0000000..3e2c39c
--- /dev/null
+++ b/src/Controller/TokenTreeController.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Controller\TokenTreeController.
+ */
+
+namespace Drupal\token\Controller;
+
+use Drupal\Component\Serialization\Json;
+use Drupal\Core\Controller\ControllerBase;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Returns tree responses for tokens.
+ */
+class TokenTreeController extends ControllerBase {
+
+  /**
+   * Page callback to output a token tree as an empty page.
+   */
+  function outputTree(Request $request) {
+    $build['#title'] = $this->t('Available tokens');
+
+    $options = $request->query->has('options') ? Json::decode($request->query->get('options')) : array();
+
+    // Force the dialog option to be false so we're not creating a dialog within
+    // a dialog.
+    $options['dialog'] = FALSE;
+
+    // Build a render array with the options.
+    foreach ($options as $key => $value) {
+      $build['tree']['#' . $key] = $value;
+    }
+    $build['tree']['#theme'] = 'token_tree';
+
+    return $build;
+  }
+
+}
diff --git a/src/Tests/TokenArrayTest.php b/src/Tests/TokenArrayTest.php
new file mode 100644
index 0000000..e4ce87f
--- /dev/null
+++ b/src/Tests/TokenArrayTest.php
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Tests\TokenArrayTest
+ */
+namespace Drupal\token\Tests;
+
+/**
+ * Tests array tokens.
+ *
+ * @group token
+ */
+class TokenArrayTest extends TokenKernelTestBase {
+
+  function testArrayTokens() {
+    // Test a simple array.
+    $array = array(0 => 'a', 1 => 'b', 2 => 'c', 4 => 'd');
+    $tokens = array(
+      'first' => 'a',
+      'last' => 'd',
+      'value:0' => 'a',
+      'value:2' => 'c',
+      'count' => 4,
+      'keys' => '0, 1, 2, 4',
+      'keys:value:3' => '4',
+      'keys:join' => '0124',
+      'reversed' => 'd, c, b, a',
+      'reversed:keys' => '4, 2, 1, 0',
+      'join:/' => 'a/b/c/d',
+      'join' => 'abcd',
+      'join:, ' => 'a, b, c, d',
+      'join: ' => 'a b c d',
+    );
+    $this->assertTokens('array', array('array' => $array), $tokens);
+
+    // Test a mixed simple and render array.
+    // 2 => c, 0 => a, 4 => d, 1 => b
+    $array = array(
+      '#property' => 'value',
+      0 => 'a',
+      1 => array('#markup' => 'b', '#weight' => 0.01),
+      2 => array('#markup' => 'c', '#weight' => -10),
+      4 => array('#markup' => 'd', '#weight' => 0),
+    );
+    $tokens = array(
+      'first' => 'c',
+      'last' => 'b',
+      'value:0' => 'a',
+      'value:2' => 'c',
+      'count' => 4,
+      'keys' => '2, 0, 4, 1',
+      'keys:value:3' => '1',
+      'keys:join' => '2041',
+      'reversed' => 'b, d, a, c',
+      'reversed:keys' => '1, 4, 0, 2',
+      'join:/' => 'c/a/d/b',
+      'join' => 'cadb',
+      'join:, ' => 'c, a, d, b',
+      'join: ' => 'c a d b',
+    );
+    $this->assertTokens('array', array('array' => $array), $tokens);
+  }
+}
diff --git a/src/Tests/TokenBlockTest.php b/src/Tests/TokenBlockTest.php
new file mode 100644
index 0000000..c34b48b
--- /dev/null
+++ b/src/Tests/TokenBlockTest.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Tests\TokenBlockTest.
+ */
+namespace Drupal\token\Tests;
+use Drupal\block_content\Entity\BlockContent;
+use Drupal\block_content\Entity\BlockContentType;
+
+/**
+ * Tests block tokens.
+ *
+ * @group token
+ */
+class TokenBlockTest extends TokenTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('path', 'token', 'token_test', 'block', 'node', 'views', 'block_content');
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp($modules = array()) {
+    parent::setUp();
+    $this->admin_user = $this->drupalCreateUser(array('access content', 'administer blocks'));
+    $this->drupalLogin($this->admin_user);
+  }
+
+  public function testBlockTitleTokens() {
+    $label = 'tokenblock';
+    $bundle = BlockContentType::create(array(
+      'id' => $label,
+      'label' => $label,
+      'revision' => FALSE
+    ));
+    $bundle->save();
+
+    $block_content = BlockContent::create(array(
+      'type' => $label,
+      'label' => '[current-page:title] block title',
+      'info' => 'Test token title block',
+      'body[value]' => 'This is the test token title block.',
+    ));
+    $block_content->save();
+
+    $block = $this->drupalPlaceBlock('block_content:' . $block_content->uuid(), array(
+      'label' => '[user:name]',
+    ));
+    $this->drupalGet($block->getSystemPath());
+    $this->drupalPostForm(NULL, array(), t('Save block'));
+    // Ensure token validation is working on the block.
+    $this->assertText('The Title is using the following invalid tokens: [user:name].');
+
+    // Create the block for real now with a valid title.
+    $settings = $block->get('settings');
+    $settings['label'] = '[current-page:title] block title';
+    $block->set('settings', $settings);
+    $block->save();
+
+    // Ensure that tokens are not double-escaped when output as a block title.
+    $this->drupalCreateContentType(array('type' => 'page'));
+    $node = $this->drupalCreateNode(array('title' => "Site's first node"));
+    $this->drupalGet('node/' . $node->id());
+    // The apostraphe should only be escaped once.
+    $this->assertRaw("Site&#039;s first node block title");
+  }
+}
diff --git a/src/Tests/TokenCommentTest.php b/src/Tests/TokenCommentTest.php
new file mode 100644
index 0000000..6286c6d
--- /dev/null
+++ b/src/Tests/TokenCommentTest.php
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Tests\TokenCommentTest.
+ */
+
+namespace Drupal\token\Tests;
+
+use Drupal\node\Entity\NodeType;
+use Drupal\node\Entity\Node;
+use Drupal\comment\Entity\Comment;
+
+/**
+ * Tests comment tokens.
+ *
+ * @group token
+ */
+class TokenCommentTest extends TokenKernelTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('path', 'token', 'token_test', 'node', 'comment', 'user', 'field', 'text', 'entity_reference', 'system');
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('node');
+    $this->installEntitySchema('user');
+    $this->installEntitySchema('comment');
+
+    $node_type = NodeType::create(['type' => 'page', 'name' => t('Page')]);
+    $node_type->save();
+
+    $this->installConfig(['comment']);
+
+    \Drupal::service('comment.manager')->addDefaultField('node', 'page');
+  }
+
+  function testCommentTokens() {
+    $node = Node::create(['type' => 'page']);
+    $node->save();
+
+    $parent_comment = Comment::create([
+      'entity_id' => $node->id(),
+      'entity_type' => 'node',
+      'field_name' => 'comment',
+      'name' => 'anonymous user',
+      'mail' => 'anonymous@example.com',
+      'subject' => $this->randomMachineName(),
+      'body' => $this->randomMachineName(),
+    ]);
+    $parent_comment->save();
+
+    // Fix http://example.com/index.php/comment/1 fails 'url:path' test.
+    $parent_comment_path = $parent_comment->url();
+    $parent_comment_path = ltrim($parent_comment_path, '/');
+
+    $tokens = array(
+      'url' => $parent_comment->urlInfo('canonical', ['fragment' => "comment-{$parent_comment->id()}"])->setAbsolute()->toString(),
+      'url:absolute' => $parent_comment->urlInfo('canonical', ['fragment' => "comment-{$parent_comment->id()}"])->setAbsolute()->toString(),
+      'url:relative' => $parent_comment->urlInfo('canonical', ['fragment' => "comment-{$parent_comment->id()}"])->toString(),
+      'url:path' => $parent_comment_path,
+      'parent:url:absolute' => NULL,
+    );
+    $this->assertTokens('comment', array('comment' => $parent_comment), $tokens);
+
+    $comment = Comment::create([
+      'entity_id' => $node->id(),
+      'pid' => $parent_comment->id(),
+      'entity_type' => 'node',
+      'field_name' => 'comment',
+      'uid' => 1,
+      'name' => 'anonymous user',
+      'mail' => 'anonymous@example.com',
+      'subject' => $this->randomMachineName(),
+      'body' => $this->randomMachineName(),
+    ]);
+    $comment->save();
+
+    // Fix http://example.com/index.php/comment/1 fails 'url:path' test.
+    $comment_path = \Drupal::url('entity.comment.canonical', array('comment' => $comment->id()));
+    $comment_path = ltrim($comment_path, '/');
+
+    $tokens = array(
+      'url' => $comment->urlInfo('canonical', ['fragment' => "comment-{$comment->id()}"])->setAbsolute()->toString(),
+      'url:absolute' => $comment->urlInfo('canonical', ['fragment' => "comment-{$comment->id()}"])->setAbsolute()->toString(),
+      'url:relative' => $comment->urlInfo('canonical', ['fragment' => "comment-{$comment->id()}"])->toString(),
+      'url:path' => $comment_path,
+      'parent:url:absolute' => $parent_comment->urlInfo('canonical', ['fragment' => "comment-{$parent_comment->id()}"])->setAbsolute()->toString(),
+    );
+    $this->assertTokens('comment', array('comment' => $comment), $tokens);
+  }
+}
diff --git a/src/Tests/TokenCurrentPageTest.php b/src/Tests/TokenCurrentPageTest.php
new file mode 100644
index 0000000..4b68976
--- /dev/null
+++ b/src/Tests/TokenCurrentPageTest.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Tests\TokenCurrentPageTest.
+ */
+
+namespace Drupal\token\Tests;
+
+/**
+ * Test the [current-page:*] tokens.
+ *
+ * @group token
+ */
+class TokenCurrentPageTest extends TokenTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('node');
+
+  function testCurrentPageTokens() {
+    $tokens = array(
+      '[current-page:title]' => t('Log in'),
+      '[current-page:url]' => \Drupal::url('user.login', [], array('absolute' => TRUE)),
+      '[current-page:url:absolute]' => \Drupal::url('user.login', [], array('absolute' => TRUE)),
+      '[current-page:url:relative]' => \Drupal::url('user.login'),
+      '[current-page:url:path]' => 'user/login',
+      '[current-page:url:args:value:0]' => 'user',
+      '[current-page:url:args:value:1]' => 'login',
+      '[current-page:url:args:value:2]' => NULL,
+      '[current-page:url:unaliased]' => \Drupal::url('user.login', [], array('absolute' => TRUE, 'alias' => TRUE)),
+      '[current-page:page-number]' => 1,
+      '[current-page:query:foo]' => NULL,
+      '[current-page:query:bar]' => NULL,
+      '[current-page:query:q]' => 'user/login',
+      // Deprecated tokens
+      '[current-page:arg:0]' => 'user',
+      '[current-page:arg:1]' => 'login',
+      '[current-page:arg:2]' => NULL,
+    );
+    $this->assertPageTokens('user/login', $tokens);
+
+    $this->drupalCreateContentType(array('type' => 'page'));
+    $node = $this->drupalCreateNode(array('title' => 'Node title', 'path' => array('alias' => 'node-alias')));
+    $tokens = array(
+      '[current-page:title]' => 'Node title',
+      '[current-page:url]' => $node->url('canonical', array('absolute' => TRUE)),
+      '[current-page:url:absolute]' => $node->url('canonical', array('absolute' => TRUE)),
+      '[current-page:url:relative]' => $node->url(),
+      '[current-page:url:alias]' => 'node-alias',
+      '[current-page:url:args:value:0]' => 'node-alias',
+      '[current-page:url:args:value:1]' => NULL,
+      '[current-page:url:unaliased]' => $node->url('canonical', array('absolute' => TRUE, 'alias' => TRUE)),
+      '[current-page:url:unaliased:args:value:0]' => 'node',
+      '[current-page:url:unaliased:args:value:1]' => $node->id(),
+      '[current-page:url:unaliased:args:value:2]' => NULL,
+      '[current-page:page-number]' => 1,
+      '[current-page:query:foo]' => 'bar',
+      '[current-page:query:bar]' => NULL,
+      '[current-page:query:q]' => 'node/1',
+      // Deprecated tokens
+      '[current-page:arg:0]' => 'node',
+      '[current-page:arg:1]' => 1,
+      '[current-page:arg:2]' => NULL,
+    );
+    $this->assertPageTokens("node/{$node->id()}", $tokens, array(), array('url_options' => array('query' => array('foo' => 'bar'))));
+  }
+}
diff --git a/src/Tests/TokenDateTest.php b/src/Tests/TokenDateTest.php
new file mode 100644
index 0000000..b299d2d
--- /dev/null
+++ b/src/Tests/TokenDateTest.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Tests\TokenDateTest.
+ */
+
+namespace Drupal\token\Tests;
+
+/**
+ * Tests date tokens.
+ *
+ * @group token
+ */
+class TokenDateTest extends TokenKernelTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['system'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->installConfig(['system', 'token_test']);
+  }
+
+  function testDateTokens() {
+    $tokens = array(
+      'token_test' => '1984',
+      'invalid_format' => NULL,
+    );
+
+    $this->assertTokens('date', array('date' => 453859200), $tokens);
+  }
+}
diff --git a/src/Tests/TokenEntityTest.php b/src/Tests/TokenEntityTest.php
new file mode 100644
index 0000000..1071358
--- /dev/null
+++ b/src/Tests/TokenEntityTest.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Tests\TokenEntityTest.
+ */
+
+namespace Drupal\token\Tests;
+
+use Drupal\Component\Utility\Unicode;
+use Drupal\node\Entity\Node;
+use Drupal\taxonomy\Entity\Vocabulary;
+use Drupal\taxonomy\VocabularyInterface;
+
+/**
+ * Tests entity tokens.
+ *
+ * @group token
+ */
+class TokenEntityTest extends TokenKernelTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('path', 'token', 'token_test', 'node', 'taxonomy', 'text');
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Create the default tags vocabulary.
+    $vocabulary = Vocabulary::create([
+      'name' => 'Tags',
+      'vid' => 'tags',
+    ]);
+    $vocabulary->save();
+
+    $this->installEntitySchema('taxonomy_term');
+    $this->installEntitySchema('user');
+    $this->installEntitySchema('node');
+
+    $this->vocab = $vocabulary;
+  }
+
+  function testEntityMapping() {
+    $this->assertIdentical(token_get_entity_mapping('token', 'node'), 'node');
+    $this->assertIdentical(token_get_entity_mapping('token', 'term'), 'taxonomy_term');
+    $this->assertIdentical(token_get_entity_mapping('token', 'vocabulary'), 'taxonomy_vocabulary');
+    $this->assertIdentical(token_get_entity_mapping('token', 'invalid'), FALSE);
+    $this->assertIdentical(token_get_entity_mapping('token', 'invalid', TRUE), 'invalid');
+    $this->assertIdentical(token_get_entity_mapping('entity', 'node'), 'node');
+    $this->assertIdentical(token_get_entity_mapping('entity', 'taxonomy_term'), 'term');
+    $this->assertIdentical(token_get_entity_mapping('entity', 'taxonomy_vocabulary'), 'vocabulary');
+    $this->assertIdentical(token_get_entity_mapping('entity', 'invalid'), FALSE);
+    $this->assertIdentical(token_get_entity_mapping('entity', 'invalid', TRUE), 'invalid');
+
+    // Test that when we send the mis-matched entity type into token_replace()
+    // that we still get the tokens replaced.
+    $vocabulary = entity_load('taxonomy_vocabulary', 'tags');
+    $term = $this->addTerm($vocabulary);
+    $this->assertIdentical(\Drupal::token()->replace('[vocabulary:name]', array('taxonomy_vocabulary' => $vocabulary)), $vocabulary->label());
+    $this->assertIdentical(\Drupal::token()->replace('[term:name][term:vocabulary:name]', array('taxonomy_term' => $term)), $term->label() . $vocabulary->label());
+  }
+
+  function addTerm(VocabularyInterface $vocabulary, array $term = array()) {
+    $term += array(
+      'name' => Unicode::strtolower($this->randomMachineName(5)),
+      'vid' => $vocabulary->id(),
+    );
+    $term = entity_create('taxonomy_term', $term);
+    $term->save();
+    return $term;
+  }
+
+  /**
+   * Test the [entity:original:*] tokens.
+   */
+  function testEntityOriginal() {
+    $node = Node::create(['type' => 'page', 'title' => 'Original title']);
+    $node->save();
+
+    $tokens = array(
+      'nid' => $node->id(),
+      'title' => 'Original title',
+      'original' => NULL,
+      'original:nid' => NULL,
+    );
+    $this->assertTokens('node', array('node' => $node), $tokens);
+
+    // Emulate the original entity property that would be available from
+    // node_save() and change the title for the node.
+    $node->original = entity_load_unchanged('node', $node->id());
+    $node->title = 'New title';
+
+    $tokens = array(
+      'nid' => $node->id(),
+      'title' => 'New title',
+      'original' => 'Original title',
+      'original:nid' => $node->id(),
+    );
+    $this->assertTokens('node', array('node' => $node), $tokens);
+  }
+}
diff --git a/src/Tests/TokenFileTest.php b/src/Tests/TokenFileTest.php
new file mode 100644
index 0000000..c51309c
--- /dev/null
+++ b/src/Tests/TokenFileTest.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Tests\TokenFileTest.
+ */
+namespace Drupal\token\Tests;
+use Drupal\taxonomy\Entity\Vocabulary;
+
+/**
+ * Tests file tokens.
+ *
+ * @group token
+ */
+class TokenFileTest extends TokenKernelTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('file');
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    $this->installEntitySchema('file');
+  }
+
+  function testFileTokens() {
+    // Create a test file object.
+    $file = entity_create('file', array(
+      'fid' => 1,
+      'filename' => 'test.png',
+      'filesize' => 100,
+      'uri' => 'public://images/test.png',
+      'filemime' => 'image/png',
+    ));
+
+    $tokens = array(
+      'basename' => 'test.png',
+      'extension' => 'png',
+      'size-raw' => 100,
+    );
+    $this->assertTokens('file', array('file' => $file), $tokens);
+
+    // Test a file with no extension and a fake name.
+    $file->filename = 'Test PNG image';
+    $file->uri = 'public://images/test';
+
+    $tokens = array(
+      'basename' => 'test',
+      'extension' => '',
+      'size-raw' => 100,
+    );
+    $this->assertTokens('file', array('file' => $file), $tokens);
+  }
+}
diff --git a/src/Tests/TokenKernelTestBase.php b/src/Tests/TokenKernelTestBase.php
new file mode 100644
index 0000000..ef9c346
--- /dev/null
+++ b/src/Tests/TokenKernelTestBase.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Tests\TokenKernelTestBase.
+ */
+
+namespace Drupal\token\Tests;
+
+use Drupal\simpletest\KernelTestBase;
+
+/**
+ * Helper test class with some added functions for testing.
+ */
+abstract class TokenKernelTestBase extends KernelTestBase {
+
+  use TokenTestTrait;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('path', 'token', 'token_test', 'system', 'user');
+  
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installSchema('system', ['router', 'url_alias']);
+    \Drupal::service('router.builder')->rebuild();
+    $this->installConfig(['system']);
+  }
+
+}
diff --git a/src/Tests/TokenMenuTest.php b/src/Tests/TokenMenuTest.php
new file mode 100644
index 0000000..9ee0381
--- /dev/null
+++ b/src/Tests/TokenMenuTest.php
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Tests\TokenMenuTest.
+ */
+namespace Drupal\token\Tests;
+
+/**
+ * Tests menu tokens.
+ *
+ * @group token
+ */
+class TokenMenuTest extends TokenTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('path', 'token', 'token_test', 'menu_ui', 'node');
+
+  function testMenuTokens() {
+    // Add a menu.
+    $menu = entity_create('menu', array(
+      'id' => 'main-menu',
+      'label' => 'Main menu',
+      'description' => 'The <em>Main</em> menu is used on many sites to show the major sections of the site, often in a top navigation bar.',
+    ));
+    $menu->save();
+    // Add a root link.
+    $root_link = entity_create('menu_link_content', array(
+      'url' => 'admin',
+      'title' => 'Administration',
+      'menu_name' => 'main-menu',
+      'bundle' => 'menu_link_content',
+    ));
+    $root_link->save();
+
+    // Add another link with the root link as the parent
+    $parent_link = entity_create('menu_link_content', array(
+      'route_name' => 'system.admin_config',
+      'title' => 'Configuration',
+      'menu_name' => 'main-menu',
+      'parent' => 'menu_link_content:' . $root_link->uuid(),
+      'bundle' => 'menu_link_content',
+    ));
+    $parent_link->save();
+
+    // Test menu link tokens.
+    $tokens = array(
+      'mlid' => $parent_link->id(),
+      'title' => 'Configuration',
+      'menu' => 'Main menu',
+      'menu:name' => 'Main menu',
+      'menu:machine-name' => 'main-menu',
+      'menu:description' => 'The <em>Main</em> menu is used on many sites to show the major sections of the site, often in a top navigation bar.',
+      'menu:menu-link-count' => 2,
+      'menu:edit-url' => \Drupal::url('entity.menu.edit_form', ['menu' => 'main-menu'], array('absolute' => TRUE)),
+      'url' => \Drupal::url('system.admin_config', [], array('absolute' => TRUE)),
+      'url:absolute' => \Drupal::url('system.admin_config', [], array('absolute' => TRUE)),
+      'url:relative' => \Drupal::url('system.admin_config', [], array('absolute' => FALSE)),
+      'url:path' => 'admin/config',
+      'url:alias' => 'admin/config',
+      'edit-url' => \Drupal::url('entity.menu_link_content.canonical', ['menu_link_content' => $parent_link->id()], array('absolute' => TRUE)),
+      'parent' => 'Administration',
+      'parent:mlid' => $root_link->id(),
+      'parent:title' => 'Administration',
+      'parent:menu' => 'Main menu',
+      'parent:parent' => NULL,
+      'parents' => 'Administration',
+      'parents:count' => 1,
+      'parents:keys' => $root_link->id(),
+      'root' => 'Administration',
+      'root:mlid' => $root_link->id(),
+      'root:parent' => NULL,
+      'root:root' => NULL,
+    );
+    $this->assertTokens('menu-link', array('menu-link' => $parent_link), $tokens);
+
+    // Add a node.
+    $node = $this->drupalCreateNode();
+
+    // Allow main menu for this node type.
+    \Drupal::config('menu.entity.node.' . $node->getType())->set('available_menus', array('main-menu'))->save();
+
+    // Add a node menu link
+    $node_link = entity_create('menu_link_content', array(
+      'link_path' => 'node/' . $node->id(),
+      'title' => 'Node link',
+      'parent' => 'menu_link_content:' . $parent_link->uuid(),
+      'menu_name' => 'main-menu',
+    ));
+    $node_link->save();
+
+    // Test [node:menu] tokens.
+    $tokens = array(
+      'menu-link' => 'Node link',
+      'menu-link:mlid' => $node_link->id(),
+      'menu-link:title' => 'Node link',
+      'menu-link:menu' => 'Main menu',
+      'menu-link:url' => $node->url('canonical', ['absolute' => TRUE]),
+      'menu-link:url:path' => 'node/' . $node->id(),
+      'menu-link:edit-url' => $node_link->url('canonical', ['absolute' => TRUE]),
+      'menu-link:parent' => 'Configuration',
+      'menu-link:parent:mlid' => $parent_link->id(),
+      'menu-link:parents' => 'Administration, Configuration',
+      'menu-link:parents:count' => 2,
+      'menu-link:parents:keys' => $root_link->id() . ', ' . $parent_link->id(),
+      'menu-link:root' => 'Administration',
+      'menu-link:root:mlid' => $root_link->id(),
+    );
+    $this->assertTokens('node', array('node' => $node), $tokens);
+
+    // Reload the node which will not have $node->menu defined and re-test.
+    $loaded_node = node_load($node->id());
+    $this->assertTokens('node', array('node' => $loaded_node), $tokens);
+
+    // Regression test for http://drupal.org/node/1317926 to ensure the
+    // original node object is not changed when calling menu_node_prepare().
+    $this->assertTrue(!isset($loaded_node->menu), t('The $node->menu property was not modified during token replacement.'), 'Regression');
+  }
+}
diff --git a/src/Tests/TokenNodeTest.php b/src/Tests/TokenNodeTest.php
new file mode 100644
index 0000000..a9cfeb4
--- /dev/null
+++ b/src/Tests/TokenNodeTest.php
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Tests\TokenNodeTest.
+ */
+
+namespace Drupal\token\Tests;
+
+use Drupal\node\Entity\NodeType;
+use Drupal\node\Entity\Node;
+
+/**
+ * Test the node and content type tokens.
+ *
+ * @group token
+ */
+class TokenNodeTest extends TokenKernelTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['node', 'field', 'text'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('user');
+    $this->installEntitySchema('node');
+
+    $node_type = NodeType::create([
+      'type' => 'page',
+      'name' => 'Basic page',
+      'description' => "Use <em>basic pages</em> for your static content, such as an 'About us' page.",
+    ]);
+    $node_type->save();
+    $node_type = NodeType::create([
+      'type' => 'article',
+      'name' => 'Article',
+      'description' => "Use <em>articles</em> for time-sensitive content like news, press releases or blog posts.",
+    ]);
+    $node_type->save();
+  }
+
+  function testNodeTokens() {
+    $page = Node::create(['type' => 'page', 'revision_log' => $this->randomMachineName(), 'path' => array('alias' => 'content/source-node')]);
+    $page->save();
+    $tokens = array(
+      'log' => $page->revision_log->value,
+      'url:path' => 'content/source-node',
+      'url:absolute' => \Drupal::url('entity.node.canonical', ['node' => $page->id()], array('absolute' => TRUE)),
+      'url:relative' => \Drupal::url('entity.node.canonical', ['node' => $page->id()], array('absolute' => FALSE)),
+      'url:unaliased:path' => "node/{$page->id()}",
+      'content-type' => 'Basic page',
+      'content-type:name' => 'Basic page',
+      'content-type:machine-name' => 'page',
+      'content-type:description' => "Use <em>basic pages</em> for your static content, such as an 'About us' page.",
+      'content-type:node-count' => 1,
+      'content-type:edit-url' => \Drupal::url('entity.node_type.edit_form', ['node_type' => 'page'], array('absolute' => TRUE)),
+      // Deprecated tokens.
+      'type' => 'page',
+      'type-name' => 'Basic page',
+      'url:alias' => 'content/source-node',
+    );
+    $this->assertTokens('node', array('node' => $page), $tokens);
+
+    $article = Node::create(['type' => 'article']);
+    $article->save();
+    $tokens = array(
+      'log' => '',
+      'url:path' => "node/{$article->id()}",
+      'url:absolute' => \Drupal::url('entity.node.canonical', ['node' => $article->id()], array('absolute' => TRUE)),
+      'url:relative' => \Drupal::url('entity.node.canonical', ['node' => $article->id()], array('absolute' => FALSE)),
+      'url:unaliased:path' => "node/{$article->id()}",
+      'content-type' => 'Article',
+      'content-type:name' => 'Article',
+      'content-type:machine-name' => 'article',
+      'content-type:description' => "Use <em>articles</em> for time-sensitive content like news, press releases or blog posts.",
+      'content-type:node-count' => 1,
+      'content-type:edit-url' => \Drupal::url('entity.node_type.edit_form', ['node_type' => 'article'], array('absolute' => TRUE)),
+      // Deprecated tokens.
+      'type' => 'article',
+      'type-name' => 'Article',
+      'url:alias' => "node/{$article->id()}",
+    );
+    $this->assertTokens('node', array('node' => $article), $tokens);
+  }
+}
diff --git a/src/Tests/TokenRandomTest.php b/src/Tests/TokenRandomTest.php
new file mode 100644
index 0000000..e806b2c
--- /dev/null
+++ b/src/Tests/TokenRandomTest.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Tests\TokenRandomTest.
+ */
+namespace Drupal\token\Tests;
+
+/**
+ * Tests random tokens.
+ *
+ * @group token
+ */
+class TokenRandomTest extends TokenKernelTestBase {
+
+  function testRandomTokens() {
+    $tokens = array(
+      'number' => '[0-9]{1,}',
+      'hash:md5' => '[0-9a-f]{32}',
+      'hash:sha1' => '[0-9a-f]{40}',
+      'hash:sha256' => '[0-9a-f]{64}',
+      'hash:invalid-algo' => NULL,
+    );
+
+    $first_set = $this->assertTokens('random', array(), $tokens, array('regex' => TRUE));
+    $second_set = $this->assertTokens('random', array(), $tokens, array('regex' => TRUE));
+    foreach ($first_set as $token => $value) {
+      $this->assertNotIdentical($first_set[$token], $second_set[$token]);
+    }
+  }
+}
diff --git a/src/Tests/TokenTaxonomyTest.php b/src/Tests/TokenTaxonomyTest.php
new file mode 100644
index 0000000..1749f38
--- /dev/null
+++ b/src/Tests/TokenTaxonomyTest.php
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Tests\TokenTaxonomyTest.
+ */
+
+namespace Drupal\token\Tests;
+
+use Drupal\Component\Utility\String;
+use Drupal\Component\Utility\Unicode;
+use Drupal\taxonomy\Entity\Vocabulary;
+
+/**
+ * Tests taxonomy tokens.
+ *
+ * @group token
+ */
+class TokenTaxonomyTest extends TokenKernelTestBase {
+  protected $vocab;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('taxonomy', 'text');
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('taxonomy_term');
+
+    // Create the default tags vocabulary.
+    $vocabulary = Vocabulary::create([
+      'name' => 'Tags',
+      'vid' => 'tags',
+    ]);
+    $vocabulary->save();
+    $this->vocab = $vocabulary;
+  }
+
+  /**
+   * Test the additional taxonomy term tokens.
+   */
+  function testTaxonomyTokens() {
+    $root_term = $this->addTerm($this->vocab, array('name' => 'Root term', 'path' => array('alias' => 'root-term')));
+    $tokens = array(
+      'url' => \Drupal::url('entity.taxonomy_term.canonical', ['taxonomy_term' => $root_term->id()], array('absolute' => TRUE)),
+      'url:absolute' => \Drupal::url('entity.taxonomy_term.canonical', ['taxonomy_term' => $root_term->id()], array('absolute' => TRUE)),
+      'url:relative' => \Drupal::url('entity.taxonomy_term.canonical', ['taxonomy_term' => $root_term->id()], array('absolute' => FALSE)),
+      'url:path' => 'root-term',
+      'url:unaliased:path' => "taxonomy/term/{$root_term->id()}",
+      'edit-url' => \Drupal::url('entity.taxonomy_term.edit_form', ['taxonomy_term' => $root_term->id()], array('absolute' => TRUE)),
+      'parents' => NULL,
+      'parents:count' => NULL,
+      'parents:keys' => NULL,
+      'root' => NULL,
+      // Deprecated tokens
+      'url:alias' => 'root-term',
+    );
+    $this->assertTokens('term', array('term' => $root_term), $tokens);
+
+    $parent_term = $this->addTerm($this->vocab, array('name' => 'Parent term', 'parent' => $root_term->id()));
+    $tokens = array(
+      'url' => \Drupal::url('entity.taxonomy_term.canonical', ['taxonomy_term' => $parent_term->id()], array('absolute' => TRUE)),
+      'url:absolute' => \Drupal::url('entity.taxonomy_term.canonical', ['taxonomy_term' => $parent_term->id()], array('absolute' => TRUE)),
+      'url:relative' => \Drupal::url('entity.taxonomy_term.canonical', ['taxonomy_term' => $parent_term->id()], array('absolute' => FALSE)),
+      'url:path' => "taxonomy/term/{$parent_term->id()}",
+      'url:unaliased:path' => "taxonomy/term/{$parent_term->id()}",
+      'edit-url' => \Drupal::url('entity.taxonomy_term.edit_form', ['taxonomy_term' => $parent_term->id()], array('absolute' => TRUE)),
+      'parents' => 'Root term',
+      'parents:count' => 1,
+      'parents:keys' => $root_term->id(),
+      'root' => String::checkPlain($root_term->label()),
+      'root:tid' => $root_term->id(),
+      // Deprecated tokens
+      'url:alias' => "taxonomy/term/{$parent_term->id()}",
+    );
+    $this->assertTokens('term', array('term' => $parent_term), $tokens);
+
+    $term = $this->addTerm($this->vocab, array('name' => 'Test term', 'parent' => $parent_term->id()));
+    $tokens = array(
+      'parents' => 'Root term, Parent term',
+      'parents:count' => 2,
+      'parents:keys' => implode(', ', array($root_term->id(), $parent_term->id())),
+    );
+    $this->assertTokens('term', array('term' => $term), $tokens);
+  }
+
+  /**
+   * Test the additional vocabulary tokens.
+   */
+  function testVocabularyTokens() {
+    $vocabulary = $this->vocab;
+    $tokens = array(
+      'machine-name' => 'tags',
+      'edit-url' => \Drupal::url('entity.taxonomy_vocabulary.edit_form', ['taxonomy_vocabulary' => $vocabulary->id()], array('absolute' => TRUE)),
+    );
+    $this->assertTokens('vocabulary', array('vocabulary' => $vocabulary), $tokens);
+  }
+
+  function addVocabulary(array $vocabulary = array()) {
+    $vocabulary += array(
+      'name' => Unicode::strtolower($this->randomMachineName(5)),
+      'nodes' => array('article' => 'article'),
+    );
+    $vocabulary = entity_create('taxonomy_vocabulary', $vocabulary)->save();
+    return $vocabulary;
+  }
+
+  function addTerm($vocabulary, array $term = array()) {
+    $term += array(
+      'name' => Unicode::strtolower($this->randomMachineName(5)),
+      'vid' => $vocabulary->id(),
+    );
+    $term = entity_create('taxonomy_term', $term);
+    $term->save();
+    return $term;
+  }
+}
diff --git a/src/Tests/TokenTestBase.php b/src/Tests/TokenTestBase.php
new file mode 100644
index 0000000..2e5be4a
--- /dev/null
+++ b/src/Tests/TokenTestBase.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Tests\TokenTestBase.
+ */
+namespace Drupal\token\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Helper test class with some added functions for testing.
+ */
+abstract class TokenTestBase extends WebTestBase {
+
+  use TokenTestTrait;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('path', 'token', 'token_test');
+
+}
diff --git a/src/Tests/TokenTestTrait.php b/src/Tests/TokenTestTrait.php
new file mode 100644
index 0000000..c40326d
--- /dev/null
+++ b/src/Tests/TokenTestTrait.php
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Tests\TokenTestTrait.
+ */
+
+namespace Drupal\token\Tests;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Language\Language;
+
+/**
+ * Helper test trait with some added functions for testing.
+ */
+trait TokenTestTrait {
+
+  function assertToken($type, array $data, $token, $expected, array $options = array()) {
+    return $this->assertTokens($type, $data, array($token => $expected), $options);
+  }
+
+  function assertTokens($type, array $data, array $tokens, array $options = array()) {
+    $input = $this->mapTokenNames($type, array_keys($tokens));
+    $replacements = \Drupal::token()->generate($type, $input, $data, $options);
+    foreach ($tokens as $name => $expected) {
+      $token = $input[$name];
+      if (!isset($expected)) {
+        $this->assertTrue(!isset($replacements[$token]), t("Token value for @token was not generated.", array('@type' => $type, '@token' => $token)));
+      }
+      elseif (!isset($replacements[$token])) {
+        $this->fail(t("Token value for @token was not generated.", array('@type' => $type, '@token' => $token)));
+      }
+      elseif (!empty($options['regex'])) {
+        $this->assertTrue(preg_match('/^' . $expected . '$/', $replacements[$token]), t("Token value for @token was '@actual', matching regular expression pattern '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $replacements[$token], '@expected' => $expected)));
+      }
+      else {
+        $this->assertIdentical($replacements[$token], $expected, t("Token value for @token was '@actual', expected value '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $replacements[$token], '@expected' => $expected)));
+      }
+    }
+
+    return $replacements;
+  }
+
+  function mapTokenNames($type, array $tokens = array()) {
+    $return = array();
+    foreach ($tokens as $token) {
+      $return[$token] = "[$type:$token]";
+    }
+    return $return;
+  }
+
+  function assertNoTokens($type, array $data, array $tokens, array $options = array()) {
+    $input = $this->mapTokenNames($type, $tokens);
+    $replacements = \Drupal::token()->generate($type, $input, $data, $options);
+    foreach ($tokens as $name) {
+      $token = $input[$name];
+      $this->assertTrue(!isset($replacements[$token]), t("Token value for @token was not generated.", array('@type' => $type, '@token' => $token)));
+    }
+    return $values;
+  }
+
+  function saveAlias($source, $alias, $language = Language::LANGCODE_NOT_SPECIFIED) {
+    $alias = array(
+      'source' => $source,
+      'alias' => $alias,
+      'language' => $language,
+    );
+    \Drupal::service('path.alias_storage')->save($alias['source'], $alias['alias']);
+    return $alias;
+  }
+
+  function saveEntityAlias($entity_type, EntityInterface $entity, $alias, $language = Language::LANGCODE_NOT_SPECIFIED) {
+    $uri = $entity->urlInfo()->toArray();
+    return $this->saveAlias($uri['path'], $alias, $language);
+  }
+
+  /**
+   * Make a page request and test for token generation.
+   */
+  function assertPageTokens($url, array $tokens, array $data = array(), array $options = array()) {
+    if (empty($tokens)) {
+      return TRUE;
+    }
+
+    $token_page_tokens = array(
+      'tokens' => $tokens,
+      'data' => $data,
+      'options' => $options,
+    );
+    \Drupal::state()->set('token_page_tokens', $token_page_tokens);
+
+    $options += array('url_options' => array());
+    $this->drupalGet($url, $options['url_options']);
+    $this->refreshVariables();
+    $result = \Drupal::state()->get('token_page_tokens', array());
+
+    if (!isset($result['values']) || !is_array($result['values'])) {
+      return $this->fail('Failed to generate tokens.');
+    }
+
+    foreach ($tokens as $token => $expected) {
+      if (!isset($expected)) {
+        $this->assertTrue(!isset($result['values'][$token]) || $result['values'][$token] === $token, t("Token value for @token was not generated.", array('@token' => $token)));
+      }
+      elseif (!isset($result['values'][$token])) {
+        $this->fail(t('Failed to generate token @token.', array('@token' => $token)));
+      }
+      else {
+        $this->assertIdentical($result['values'][$token], (string) $expected, t("Token value for @token was '@actual', expected value '@expected'.", array('@token' => $token, '@actual' => $result['values'][$token], '@expected' => $expected)));
+      }
+    }
+  }
+
+}
diff --git a/src/Tests/TokenURLTest.php b/src/Tests/TokenURLTest.php
new file mode 100644
index 0000000..e21c0f5
--- /dev/null
+++ b/src/Tests/TokenURLTest.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Tests\TokenURLTest.
+ */
+
+namespace Drupal\token\Tests;
+
+use Drupal\Core\Url;
+
+/**
+ * Tests url tokens.
+ *
+ * @group token
+ */
+class TokenURLTest extends TokenTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('node');
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    $this->saveAlias('node/1', 'first-node');
+  }
+
+  function testURLTokens() {
+    $host = \Drupal::request()->getHttpHost();
+    $tokens = array(
+      'absolute' => "http://{$host}/first-node",
+      'relative' => base_path() . 'first-node',
+      'path' => 'first-node',
+      'brief' => "{$host}/first-node",
+      'args:value:0' => 'first-node',
+      'args:value:1' => NULL,
+      'args:value:N' => NULL,
+      'unaliased' => "http://{$host}/node/1",
+      'unaliased:relative' => base_path() . 'node/1',
+      'unaliased:path' => 'node/1',
+      'unaliased:brief' => "{$host}/node/1",
+      'unaliased:args:value:0' => 'node',
+      'unaliased:args:value:1' => '1',
+      'unaliased:args:value:2' => NULL,
+      // Deprecated tokens.
+      'alias' => 'first-node',
+    );
+    $this->assertTokens('url', array('url' => new Url('entity.node.canonical', array('node' => 1))), $tokens);
+  }
+}
diff --git a/src/Tests/TokenUnitTest.php b/src/Tests/TokenUnitTest.php
new file mode 100644
index 0000000..5470684
--- /dev/null
+++ b/src/Tests/TokenUnitTest.php
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Tests\TokenUnitTest.
+ */
+namespace Drupal\token\Tests;
+
+/**
+ * Test basic, low-level token functions.
+ *
+ * @group token
+ */
+class TokenUnitTest extends TokenKernelTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('file', 'node');
+
+  /**
+   * Test token_get_invalid_tokens() and token_get_invalid_tokens_by_context().
+   */
+  public function testGetInvalidTokens() {
+    $tests = array();
+    $tests[] = array(
+      'valid tokens' => array(
+        '[node:title]',
+        '[node:created:short]',
+        '[node:created:custom:invalid]',
+        '[node:created:custom:mm-YYYY]',
+        '[site:name]',
+        '[site:slogan]',
+        '[current-date:short]',
+        '[current-user:uid]',
+        '[current-user:ip-address]',
+      ),
+      'invalid tokens' => array(
+        '[node:title:invalid]',
+        '[node:created:invalid]',
+        '[node:created:short:invalid]',
+        '[invalid:title]',
+        '[site:invalid]',
+        '[user:ip-address]',
+        '[user:uid]',
+        '[comment:cid]',
+        // Deprecated tokens
+        '[node:tnid]',
+        '[node:type]',
+        '[node:type-name]',
+        '[date:short]',
+      ),
+      'types' => array('node'),
+    );
+    $tests[] = array(
+      'valid tokens' => array(
+        '[node:title]',
+        '[node:created:short]',
+        '[node:created:custom:invalid]',
+        '[node:created:custom:mm-YYYY]',
+        '[site:name]',
+        '[site:slogan]',
+        '[user:uid]',
+        '[current-date:short]',
+        '[current-user:uid]',
+      ),
+      'invalid tokens' => array(
+        '[node:title:invalid]',
+        '[node:created:invalid]',
+        '[node:created:short:invalid]',
+        '[invalid:title]',
+        '[site:invalid]',
+        '[user:ip-address]',
+        '[comment:cid]',
+        // Deprecated tokens
+        '[node:tnid]',
+        '[node:type]',
+        '[node:type-name]',
+      ),
+      'types' => array('all'),
+    );
+
+    foreach ($tests as $test) {
+      $tokens = array_merge($test['valid tokens'], $test['invalid tokens']);
+      shuffle($tokens);
+
+      $invalid_tokens = token_get_invalid_tokens_by_context(implode(' ', $tokens), $test['types']);
+
+      sort($invalid_tokens);
+      sort($test['invalid tokens']);
+      $this->assertEqual($invalid_tokens, $test['invalid tokens'], 'Invalid tokens detected properly: ' . implode(', ', $invalid_tokens));
+    }
+  }
+}
diff --git a/src/Tests/TokenUserTest.php b/src/Tests/TokenUserTest.php
new file mode 100644
index 0000000..d207318
--- /dev/null
+++ b/src/Tests/TokenUserTest.php
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\Tests\TokenUserTest.
+ */
+
+namespace Drupal\token\Tests;
+
+use Drupal\Core\Session\AnonymousUserSession;
+use Drupal\field\Entity\FieldStorageConfig;
+
+/**
+ * Tests user tokens.
+ *
+ * @group token
+ */
+class TokenUserTest extends TokenTestBase {
+
+  /**
+   * The user account.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $account = NULL;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('token_user_picture');
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Enable user pictures.
+    \Drupal::state()->set('user_pictures', 1);
+    \Drupal::state()->set('user_picture_file_size', '');
+
+    // Set up the pictures directory.
+    $picture_path = file_default_scheme() . '://' . \Drupal::state()->get('user_picture_path', 'pictures');
+    if (!file_prepare_directory($picture_path, FILE_CREATE_DIRECTORY)) {
+      $this->fail('Could not create directory ' . $picture_path . '.');
+    }
+
+    $this->account = $this->drupalCreateUser(array('administer users'));
+    $this->drupalLogin($this->account);
+  }
+
+  function testUserTokens() {
+    // Add a user picture to the account.
+    $image = current($this->drupalGetTestFiles('image'));
+    $edit = array('files[user_picture_0]' => drupal_realpath($image->uri));
+    $this->drupalPostForm('user/' . $this->account->id() . '/edit', $edit, t('Save'));
+
+    $storage = \Drupal::entityManager()->getStorage('user');
+
+    // Load actual user data from database.
+    $storage->resetCache();
+    $this->account = $storage->load($this->account->id());
+    $this->assertTrue(!empty($this->account->user_picture->target_id), 'User picture uploaded.');
+
+    $picture = [
+      '#theme' => 'user_picture',
+      '#account' => $this->account,
+    ];
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+    $user_tokens = array(
+      'picture' => $renderer->render($picture),
+      'picture:fid' => $this->account->user_picture->target_id,
+      'picture:size-raw' => 125,
+      'ip-address' => NULL,
+      'roles' => implode(', ', $this->account->getRoles()),
+    );
+    $this->assertTokens('user', array('user' => $this->account), $user_tokens);
+
+    // Remove the simpletest-created user role.
+    $roles = $this->account->getRoles();
+    $this->account->removeRole(end($roles));
+    $this->account->save();
+
+    // Remove the user picture field and reload the user.
+    FieldStorageConfig::loadByName('user', 'user_picture')->delete();
+    $storage->resetCache();
+    $this->account = $storage->load($this->account->id());
+
+    $user_tokens = array(
+      'picture' => NULL,
+      'picture:fid' => NULL,
+      'ip-address' => NULL,
+      'roles' => 'authenticated',
+      'roles:keys' => (string) DRUPAL_AUTHENTICATED_RID,
+    );
+    $this->assertTokens('user', array('user' => $this->account), $user_tokens);
+
+    // The ip address token should work for the current user token type.
+    $tokens = array(
+      'ip-address' => \Drupal::request()->getClientIp(),
+    );
+    $this->assertTokens('current-user', array(), $tokens);
+
+    $anonymous = new AnonymousUserSession();
+    $tokens = array(
+      'roles' => 'anonymous',
+      'roles:keys' => (string) DRUPAL_ANONYMOUS_RID,
+    );
+    $this->assertTokens('user', array('user' => $anonymous), $tokens);
+  }
+}
diff --git a/tests/token_test.info b/tests/token_test.info
deleted file mode 100644
index 20dd2e2..0000000
--- a/tests/token_test.info
+++ /dev/null
@@ -1,5 +0,0 @@
-name = Token Test
-description = Testing module for token functionality.
-package = Testing
-core = 8.x
-hidden = TRUE
diff --git a/tests/token_test.module b/tests/token_test.module
deleted file mode 100644
index 3174cb6..0000000
--- a/tests/token_test.module
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-
-/**
- * @file
- * Helper module for token tests.
- */
-
-/**
- * Implements hook_exit().
- */
-function token_test_exit() {
-  if ($debug = variable_get('token_page_tokens', array())) {
-    $debug += array('tokens' => array(), 'data' => array(), 'options' => array());
-    foreach (array_keys($debug['tokens']) as $token) {
-      $debug['values'][$token] = token_replace($token, $debug['data'], $debug['options']);
-    }
-    variable_set('token_page_tokens', $debug);
-  }
-}
-
-/**
- * Implements hook_date_format_types().
- *
- * @todo Remove when http://drupal.org/node/1173706 is fixed.
- */
-function token_test_date_format_types() {
-  $info['token_test'] = t('Token test date format');
-
-  // Explicitly set the variable here as well.
-  variable_set('date_format_token_test', 'Y');
-
-  return $info;
-}
diff --git a/tests/token_test/config/install/core.date_format.token_test.yml b/tests/token_test/config/install/core.date_format.token_test.yml
new file mode 100644
index 0000000..c05b275
--- /dev/null
+++ b/tests/token_test/config/install/core.date_format.token_test.yml
@@ -0,0 +1,6 @@
+id: token_test
+label: 'Token test'
+status: true
+langcode: en
+locked: true
+pattern: Y
diff --git a/tests/token_test/token_test.info.yml b/tests/token_test/token_test.info.yml
new file mode 100644
index 0000000..0068679
--- /dev/null
+++ b/tests/token_test/token_test.info.yml
@@ -0,0 +1,6 @@
+type: module
+name: Token Test
+description: Testing module for token functionality.
+package: Testing
+core: 8.x
+hidden: TRUE
diff --git a/tests/token_test/token_test.module b/tests/token_test/token_test.module
new file mode 100644
index 0000000..5736b8d
--- /dev/null
+++ b/tests/token_test/token_test.module
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @file
+ * Helper module for token tests.
+ */
+
+/**
+ * Implements hook_page_alter().
+ */
+function token_test_page_alter() {
+  if ($debug = \Drupal::state()->get('token_page_tokens', array())) {
+    $debug += array('tokens' => array(), 'data' => array(), 'options' => array());
+    foreach (array_keys($debug['tokens']) as $token) {
+      $debug['values'][$token] = \Drupal::token()->replace($token, $debug['data'], $debug['options']);
+    }
+    \Drupal::state()->set('token_page_tokens', $debug);
+  }
+}
diff --git a/tests/token_user_picture/config/install/core.entity_form_display.user.user.default.yml b/tests/token_user_picture/config/install/core.entity_form_display.user.user.default.yml
new file mode 100644
index 0000000..a30d012
--- /dev/null
+++ b/tests/token_user_picture/config/install/core.entity_form_display.user.user.default.yml
@@ -0,0 +1,23 @@
+langcode: de
+status: true
+dependencies:
+  module:
+    - image
+    - user
+id: user.user.default
+targetEntityType: user
+bundle: user
+mode: default
+content:
+  account:
+    weight: -10
+  user_picture:
+    type: image_image
+    settings:
+      progress_indicator: throbber
+      preview_image_style: thumbnail
+    third_party_settings: {  }
+    weight: -1
+  timezone:
+    weight: 6
+hidden: {  }
diff --git a/tests/token_user_picture/config/install/field.field.user.user.user_picture.yml b/tests/token_user_picture/config/install/field.field.user.user.user_picture.yml
new file mode 100644
index 0000000..45f6e82
--- /dev/null
+++ b/tests/token_user_picture/config/install/field.field.user.user.user_picture.yml
@@ -0,0 +1,31 @@
+id: user.user.user_picture
+status: true
+langcode: en
+entity_type: user
+bundle: user
+field_name: user_picture
+label: Picture
+description: 'Your virtual face or picture.'
+required: false
+default_value: {  }
+default_value_function: ''
+settings:
+  file_extensions: 'png gif jpg jpeg'
+  file_directory: pictures
+  max_filesize: '30 KB'
+  alt_field: false
+  title_field: false
+  max_resolution: 85x85
+  min_resolution: ''
+  default_image:
+    uuid: null
+    alt: ''
+    title: ''
+    width: null
+    height: null
+  alt_field_required: false
+  title_field_required: false
+field_type: image
+dependencies:
+  config:
+    - field.storage.user.user_picture
diff --git a/tests/token_user_picture/config/install/field.storage.user.user_picture.yml b/tests/token_user_picture/config/install/field.storage.user.user_picture.yml
new file mode 100644
index 0000000..01d0939
--- /dev/null
+++ b/tests/token_user_picture/config/install/field.storage.user.user_picture.yml
@@ -0,0 +1,25 @@
+id: user.user_picture
+status: true
+langcode: en
+field_name: user_picture
+entity_type: user
+type: image
+settings:
+  uri_scheme: public
+  default_image:
+    uuid: null
+    alt: ''
+    title: ''
+    width: null
+    height: null
+module: image
+locked: false
+cardinality: 1
+translatable: false
+indexes:
+  target_id:
+    - target_id
+dependencies:
+  module:
+    - image
+    - user
diff --git a/tests/token_user_picture/token_user_picture.info.yml b/tests/token_user_picture/token_user_picture.info.yml
new file mode 100644
index 0000000..166659f
--- /dev/null
+++ b/tests/token_user_picture/token_user_picture.info.yml
@@ -0,0 +1,8 @@
+type: module
+name: Token User picture
+description: Testing module that provides user pictures field.
+package: Testing
+core: 8.x
+hidden: TRUE
+dependencies:
+  - image
diff --git a/token.drush.inc b/token.drush.inc
new file mode 100644
index 0000000..b31b8d7
--- /dev/null
+++ b/token.drush.inc
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Drush integration for the Token module.
+ */
+
+/**
+ * Implements hook_drush_cache_clear().
+ */
+function token_drush_cache_clear(&$types) {
+  $types['token'] = 'drush_token_cache_clear_token_info';
+}
+
+/**
+ * Clear caches internal to Token module.
+ */
+function drush_token_cache_clear_token_info() {
+  token_clear_cache();
+}
diff --git a/token.info b/token.info
deleted file mode 100644
index 7a18eae..0000000
--- a/token.info
+++ /dev/null
@@ -1,4 +0,0 @@
-name = Token
-description = Provides a user interface for the Token API and some missing core tokens.
-core = 8.x
-files[] = token.test
diff --git a/token.info.yml b/token.info.yml
new file mode 100644
index 0000000..cccc5b8
--- /dev/null
+++ b/token.info.yml
@@ -0,0 +1,4 @@
+type: module
+name: Token
+description: Provides a user interface for the Token API and some missing core tokens.
+core: 8.x
diff --git a/token.install b/token.install
index c2b57fd..7646b11 100644
--- a/token.install
+++ b/token.install
@@ -10,7 +10,6 @@
  */
 function token_requirements($phase = 'runtime') {
   $requirements = array();
-  $t = get_t();
 
   if ($phase == 'runtime') {
     // Check for various token definition problems.
@@ -19,8 +18,9 @@ function token_requirements($phase = 'runtime') {
     foreach ($token_problems as $problem_key => $problem) {
       if (!empty($problem['problems'])) {
         $problems = array_unique($problem['problems']);
-        $problems = array_map('check_plain', $problems);
-        $token_problems[$problem_key] = $problem['label'] . theme('item_list', array('items' => $problems));
+        $problems = array_map('\Drupal\Component\Utility\String::checkPlain', $problems);
+        // ToDo: Use render arrays. See https://drupal.org/node/2195739
+        $token_problems[$problem_key] = $problem['label'] . \Drupal::theme()->render('item_list', array('items' => $problems));
       }
       else {
         unset($token_problems[$problem_key]);
@@ -28,10 +28,10 @@ function token_requirements($phase = 'runtime') {
     }
     if (!empty($token_problems)) {
       $requirements['token_problems'] = array(
-        'title' => $t('Tokens'),
-        'value' => $t('Problems detected'),
+        'title' => t('Tokens'),
+        'value' => t('Problems detected'),
         'severity' => REQUIREMENT_WARNING,
-        'description' => '<p>' . implode('</p><p>', $token_problems) . '</p>', //theme('item_list', array('items' => $token_problems)),
+        'description' => array('#markup' => '<p>' . implode('</p><p>', $token_problems) . '</p>'),
       );
     }
   }
@@ -40,52 +40,31 @@ function token_requirements($phase = 'runtime') {
 }
 
 /**
- * Implements hook_schema().
+ * Implements hook_install().
  */
-function token_schema() {
-  $schema['cache_token'] = drupal_get_schema_unprocessed('system', 'cache');
-  $schema['cache_token']['description'] = 'Cache table for token information.';
-  return $schema;
-}
-
-/**
- * Add the cache_token table.
- */
-function token_update_7000() {
-  if (!db_table_exists('cache_token')) {
-    $schema = drupal_get_schema_unprocessed('system', 'cache');
-    $schema['description'] = 'Cache table for token information.';
-    db_create_table('cache_token', $schema);
+function token_install() {
+  // Create a token view mode for each entity type.
+  $info = \Drupal::entityManager()->getDefinitions();
+  foreach ($info as $entity_type => $entity_type_info) {
+    // We're only interested in entity types with a view builder.
+    if (!$entity_type_info->getViewBuilderClass()) {
+      continue;
+    }
+    // Try to find a token view mode for that entity type.
+    $storage = \Drupal::entityManager()->getStorage('entity_view_mode');
+    // Add a token view mode if it does not already exist.
+    if (!$storage->load("$entity_type.token")) {
+      $storage->create(array(
+        'targetEntityType' => $entity_type,
+        'id' => "$entity_type.token",
+        'status' => TRUE,
+        'label' => t('Token'),
+      ))->save();
+    }
   }
 }
 
 /**
- * Deprecate and disable the token_actions module.
- */
-function token_update_7001() {
-  module_disable(array('token_actions'));
-  return 'The Token actions module has been deprecated by the updated system module actions that support tokens.';
-}
-
-/**
- * Migrate old token_actions module actions to system actions.
- */
-//function token_update_700X() {
-//  $actions = db_query("SELECT aid, type, callback, parameters, label FROM {actions} WHERE type = 'system' AND callback IN ('token_actions_message_action', 'token_actions_send_email_action', 'token_actions_goto_action')")->fetchAll();
-//  foreach ($actions as $action) {
-//    $action->parameters = unserialize($action->parameters);
-//    foreach ($action->parameters as $key => $value) {
-//      if (is_string($value)) {
-//        $action->parameters[$key] = token_update_token_text($value);
-//      }
-//    }
-//    $action->callback = str_replace('token_actions_', '', $action->callback);
-//    actions_save($action->callback, $action->type, $action->parameters, $action->label, $action->aid);
-//  }
-//  return 'Token actions module actions migrated to system module actions. You may still want to verify that the actions were upgraded correctly.';
-//}
-
-/**
  * Build a list of Drupal 6 tokens and their Drupal 7 token names.
  */
 function _token_upgrade_token_list() {
@@ -269,13 +248,13 @@ function token_get_token_problems() {
   //  }
   //}
 
-  $token_info = token_info();
+  $token_info = \Drupal::token()->getInfo();
   $token_problems = array(
     'not-array' => array(
       'label' => t('The following tokens or token types are not defined as arrays:'),
     ),
     'missing-info' => array(
-      'label' => t('The following tokens or token types are missing required name and/or description information:'),
+      'label' => t('The following tokens or token types are missing the required name:'),
     ),
     'type-no-tokens' => array(
       'label' => t('The following token types do not have any tokens defined:'),
@@ -295,7 +274,7 @@ function token_get_token_problems() {
       $token_problems['not-array']['problems'][] = "\$info['types']['$type']";
       continue;
     }
-    elseif (!isset($type_info['name']) || !isset($type_info['description'])) {
+    elseif (!isset($type_info['name'])) {
       $token_problems['missing-info']['problems'][] = "\$info['types']['$type']";
     }
     elseif (empty($token_info['tokens'][$real_type])) {
@@ -315,10 +294,10 @@ function token_get_token_problems() {
           $token_problems['not-array']['problems'][] = "\$info['tokens']['$type']['$token']";
           continue;
         }
-        elseif (!isset($tokens[$token]['name']) || !isset($tokens[$token]['description'])) {
+        elseif (!isset($tokens[$token]['name'])) {
           $token_problems['missing-info']['problems'][] = "\$info['tokens']['$type']['$token']";
         }
-        elseif (is_array($tokens[$token]['name']) || is_array($tokens[$token]['description'])) {
+        elseif (is_array($tokens[$token]['name']) || isset($tokens[$token]['description']) && is_array($tokens[$token]['description'])) {
           $token_problems['duplicate']['problems'][] = "\$info['tokens']['$type']['$token']";
         }
       }
diff --git a/token.libraries.yml b/token.libraries.yml
new file mode 100644
index 0000000..7391211
--- /dev/null
+++ b/token.libraries.yml
@@ -0,0 +1,24 @@
+jquery.treeTable:
+  remote: 'http://plugins.jquery.com/treetable/'
+  version: 2.3.0
+  license:
+    name: MIT
+    url: https://github.com/ludo/jquery-treetable/blob/2.3.0/treeTable/MIT-LICENSE
+    gpl-compatible: true
+  js:
+    js/jquery.treeTable.js: {}
+  css:
+    component:
+      css/jquery.treeTable.css: {}
+  dependencies:
+    - core/jquery
+token:
+  version: VERSION
+  js:
+    js/token.js: {}
+  css:
+    component:
+      css/token.css: {}
+  dependencies:
+    - core/jquery
+    - core/drupal
diff --git a/token.links.task.yml b/token.links.task.yml
new file mode 100644
index 0000000..48b23ed
--- /dev/null
+++ b/token.links.task.yml
@@ -0,0 +1,20 @@
+token.devel_node_tab:
+  route_name: token.devel_node_token
+  title: 'Tokens'
+  parent_id: devel.devel_node_tab
+  weight: 20
+token.devel_comment_tab:
+  route_name: token.devel_comment_token
+  title: 'Tokens'
+  parent_id: devel.devel_comment_tab
+  weight: 20
+token.devel_term_tab:
+  route_name: token.devel_taxonomy_term_token
+  title: 'Tokens'
+  parent_id: devel.devel_taxonomy_term_tab
+  weight: 20
+token.devel_user_tab:
+  route_name: token.devel_user_token
+  title: 'Tokens'
+  parent_id: devel.devel_user_tab
+  weight: 20
diff --git a/token.module b/token.module
index 1b19d20..49c97b1 100644
--- a/token.module
+++ b/token.module
@@ -5,17 +5,29 @@
  * Enhances the token API in core: adds a browseable UI, missing tokens, etc.
  */
 
+use Drupal\Component\Utility\String;
+use Drupal\Component\Utility\Unicode;
+use Drupal\Component\Utility\Xss;
+use Drupal\Core\Block\BlockPluginInterface;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Utility\Token;
+use Drupal\Core\Url;
+
 /**
  * The maximum depth for token tree recursion.
  */
 define('TOKEN_MAX_DEPTH', 9);
 
 /**
- * Impelements hook_help().
+ * Implements hook_help().
  */
-function token_help($path, $arg) {
-  if ($path == 'admin/help#token') {
-    if (current_path() != 'admin/help/token') {
+function token_help($route_name, RouteMatchInterface $route_match) {
+  if ($route_name == 'help.page.token') {
+    $current_url = Url::fromRoute('<current>');
+    if ($current_url->toString() != 'admin/help/token') {
       // Because system_modules() executes hook_help() for each module to 'test'
       // if they will return anything, but not actually display it, we want to
       // return a TRUE value if this is not actually the help page.
@@ -23,105 +35,25 @@ function token_help($path, $arg) {
     }
     $output = '<dl>';
     $output .= '<dt>' . t('List of the currently available tokens on this site') . '</dt>';
-    $output .= '<dd>' . theme('token_tree', array('token_types' => 'all', 'click_insert' => FALSE, 'show_restricted' => TRUE)) . '</dd>';
+    $token_tree = array(
+      '#theme' => 'token_tree',
+      '#token_types' => 'all',
+      '#click_insert' => FALSE,
+      '#show_restricted' => TRUE,
+    );
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+    $output .= '<dd>' . $renderer->render($token_tree) . '</dd>';
     $output .= '</dl>';
     return $output;
   }
 }
 
 /**
- * Implements hook_system_info_alter().
- *
- * Prevent the token_actions module from being enabled since updates may have
- * left the old module files still in the directory.
- */
-function token_system_info_alter(&$info, $file, $type) {
-  if ($type == 'module' && $file->name == 'token_actions') {
-    $info['hidden'] = TRUE;
-  }
-}
-
-/**
  * Return an array of the core modules supported by token.module.
  */
 function _token_core_supported_modules() {
-  return array('book', 'field', 'menu', 'profile');
-}
-
-/**
- * Implements hook_menu().
- */
-function token_menu() {
-  /*$items['token/autocomplete/all/%menu_tail'] = array(
-    'page callback' => 'token_autocomplete',
-    'access callback' => TRUE,
-    'type' => MENU_CALLBACK,
-    'file' => 'token.pages.inc',
-  );*/
-  $items['token/autocomplete/%token_type'] = array(
-    'page callback' => 'token_autocomplete_token',
-    'page arguments' => array(2),
-    'access callback' => TRUE,
-    'type' => MENU_CALLBACK,
-    'file' => 'token.pages.inc',
-  );
-  /*$items['token/autocomplete/%token_type/%menu_tail'] = array(
-    'page callback' => 'token_autocomplete_token',
-    'page arguments' => array(2, 3),
-    'access callback' => TRUE,
-    'type' => MENU_CALLBACK,
-    'file' => 'token.pages.inc',
-  );*/
-
-  // Devel token pages.
-  if (module_exists('devel')) {
-    $items['node/%node/devel/token'] = array(
-      'title' => 'Tokens',
-      'page callback' => 'token_devel_token_object',
-      'page arguments' => array('node', 1),
-      'access arguments' => array('access devel information'),
-      'type' => MENU_LOCAL_TASK,
-      'file' => 'token.pages.inc',
-      'weight' => 5,
-    );
-    $items['comment/%comment/devel/token'] = array(
-      'title' => 'Tokens',
-      'page callback' => 'token_devel_token_object',
-      'page arguments' => array('comment', 1),
-      'access arguments' => array('access devel information'),
-      'type' => MENU_LOCAL_TASK,
-      'file' => 'token.pages.inc',
-      'weight' => 5,
-    );
-    $items['taxonomy/term/%taxonomy_term/devel/token'] = array(
-      'title' => 'Tokens',
-      'page callback' => 'token_devel_token_object',
-      'page arguments' => array('taxonomy_term', 2),
-      'access arguments' => array('access devel information'),
-      'type' => MENU_LOCAL_TASK,
-      'file' => 'token.pages.inc',
-      'weight' => 5,
-    );
-    $items['user/%user/devel/token'] = array(
-      'title' => 'Tokens',
-      'page callback' => 'token_devel_token_object',
-      'page arguments' => array('user', 1),
-      'access arguments' => array('access devel information'),
-      'type' => MENU_LOCAL_TASK,
-      'file' => 'token.pages.inc',
-      'weight' => 5,
-    );
-  }
-
-  // Admin menu callback to clear token caches.
-  $items['token/flush-cache'] = array(
-    'page callback' => 'token_flush_cache_callback',
-    'access arguments' => array('flush caches'),
-    'type' => MENU_CALLBACK,
-    'file' => 'token.pages.inc',
-  );
-
-  return $items;
+  return array('book', 'field', 'menu_ui');
 }
 
 /**
@@ -132,7 +64,7 @@ function token_admin_menu_output_alter(&$content) {
     '#title' => t('Token registry'),
     '#href' => 'token/flush-cache',
     '#options' => array(
-      'query' => drupal_get_destination() + array('token' => drupal_get_token('token/flush-cache')),
+      'query' => drupal_get_destination() + array('token' => \Drupal::csrfToken()->get('token/flush-cache')),
     ),
   );
 }
@@ -146,36 +78,40 @@ function token_type_load($token_type) {
  * Implements hook_theme().
  */
 function token_theme() {
-  return array(
-    'tree_table' => array(
-      'variables' => array('header' => array(), 'rows' => array(), 'attributes' => array(), 'empty' => '', 'caption' => ''),
-      'file' => 'token.pages.inc',
-    ),
-    'token_tree' => array(
-      'variables' => array('token_types' => array(), 'global_types' => TRUE, 'click_insert' => TRUE, 'show_restricted' => FALSE, 'recursion_limit' => 3),
-      'file' => 'token.pages.inc',
+  $info['tree_table'] = array(
+    'variables' => array(
+      'header' => array(),
+      'rows' => array(),
+      'attributes' => array(),
+      'empty' => '',
+      'caption' => '',
     ),
+    'function' => 'theme_tree_table',
+    'file' => 'token.pages.inc',
   );
-}
-
-/**
- * Implements hook_library().
- */
-function token_library() {
-  // jQuery treeTable plugin.
-  $libraries['treeTable'] = array(
-    'title' => 'jQuery treeTable',
-    'website' => 'http://plugins.jquery.com/project/treetable',
-    'version' => '2.3.0',
-    'js' => array(
-      drupal_get_path('module', 'token') . '/jquery.treeTable.js' => array(),
-    ),
-    'css' => array(
-      drupal_get_path('module', 'token') . '/jquery.treeTable.css' => array(),
+  $info['token_tree'] = array(
+    'variables' => array(
+      'token_types' => array(),
+      'global_types' => TRUE,
+      'click_insert' => TRUE,
+      'show_restricted' => FALSE,
+      'recursion_limit' => 3,
+      'dialog' => FALSE,
     ),
+    'function' => 'theme_token_tree',
+    'file' => 'token.pages.inc',
+  );
+  $info['token_tree_link'] = array(
+    'variables' => array(
+      'text' => NULL,
+      'options' => array(),
+      'dialog' => TRUE,
+    ) + $info['token_tree']['variables'],
+    'function' => 'theme_token_tree_link',
+    'file' => 'token.pages.inc',
   );
 
-  return $libraries;
+  return $info;
 }
 
 /**
@@ -204,37 +140,31 @@ function token_form_alter(&$form, $form_state, $form_id) {
 /**
  * Implements hook_block_view_alter().
  */
-function token_block_view_alter(&$data, $block) {
-  if (!empty($block->title) && $block->title != '<none>') {
-    // Perform unsanitized token replacement since _block_render_blocks() will
-    // call check_plain() on $block->title.
-    $block->title = token_replace($block->title, array(), array('sanitize' => FALSE));
+function token_block_view_alter(&$build, BlockPluginInterface $block) {
+  $config = $block->getConfiguration();
+  $label = $config['label'];
+  if ($label != '<none>') {
+    // The label is automatically escaped, avoid escaping it twice.
+    $build['#configuration']['label'] = \Drupal::token()->replace($label, array(), array('sanitize' => FALSE));
   }
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
-function token_form_block_add_block_form_alter(&$form, $form_state) {
-  token_form_block_admin_configure_alter($form, $form_state);
-}
-
-/**
- * Implements hook_form_FORM_ID_alter().
- */
-function token_form_block_admin_configure_alter(&$form, $form_state) {
-  $form['settings']['title']['#description'] .= ' ' . t('This field supports tokens.');
-  // @todo Figure out why this token validation does not seem to be working here.
-  $form['settings']['title']['#element_validate'][] = 'token_element_validate';
-  $form['settings']['title']['#token_types'] = array();
+function token_form_block_form_alter(&$form, FormStateInterface $form_state) {
+  $form['settings']['label']['#description'] = t('This field supports tokens.');
+  $form['settings']['label']['#element_validate'][] = 'token_element_validate';
+  $form['settings']['label'] += array('#token_types' => array());
 }
 
 /**
  * Implements hook_widget_form_alter().
  */
-function token_field_widget_form_alter(&$element, &$form_state, $context) {
+function token_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) {
   if (!empty($element['#description']) && is_string($element['#description'])) {
-    $element['#description'] = token_replace($element['#description']);
+    /* @var \Drupal\Core\Field\FieldItemListInterface $context['items'] */
+    $element['#description'] = Xss::filterAdmin(\Drupal::token()->replace($context['items']->getFieldDefinition()->getDescription()));
   }
 }
 
@@ -254,7 +184,7 @@ function token_field_info_alter(&$info) {
     'text_with_summary' => 'text_default',
     'list_integer' => 'list_default',
     'list_float' => 'list_default',
-    'list_text' => 'list_default',
+    'list_string' => 'list_default',
     'list_boolean' => 'list_default',
   );
   foreach ($defaults as $field_type => $default_token_formatter) {
@@ -269,19 +199,22 @@ function token_field_info_alter(&$info) {
  */
 function token_field_display_alter(&$display, $context) {
   if ($context['view_mode'] == 'token') {
-    $view_mode_settings = field_view_mode_settings($context['instance']['entity_type'], $context['instance']['bundle']);
+    $view_display = entity_get_display($context['instance']['entity_type'], $context['instance']['bundle'], $context['view_mode']);
     // If the token view mode fell back to the 'default' view mode, then
     // use the default token formatter.
-    if (empty($view_mode_settings[$context['view_mode']]['custom_settings'])) {
-      $field_type_info = field_info_field_types($context['field']['type']);
-      if (!empty($field_type_info['default_token_formatter'])) {
-        $display['type'] = $field_type_info['default_token_formatter'];
-
-        $formatter_info = field_info_formatter_types($display['type']);
-        $display['settings'] = isset($formatter_info['settings']) ? $formatter_info['settings'] : array();
-        $display['settings']['label'] = 'hidden';
-        $display['module'] = $formatter_info['module'];
-      }
+    if (!$view_display->status()) {
+      $field_type_info = \Drupal::service('plugin.manager.field.type')->getDefinition(($context['field']['type']));
+
+      // If the field has specified a specific formatter to be used by default
+      // with tokens, use that, otherwise use the default formatter.
+      $formatter = !empty($field_type_info['default_token_formatter']) ? $field_type_info['default_token_formatter'] : $field_type_info['default_formatter'];
+
+      // Now that we have a formatter, fill in all the settings.
+      $display['type'] = $formatter;
+      $formatter_info = \Drupal::service('plugin.manager.field.formatter')->getDefinition($display['type']);
+      $display['settings'] = isset($formatter_info['settings']) ? $formatter_info['settings'] : array();
+      $display['settings']['label'] = 'hidden';
+      $display['module'] = $formatter_info['module'];
     }
   }
 }
@@ -311,9 +244,7 @@ function token_field_delete_instance($instance) {
  * Clear token caches and static variables.
  */
 function token_clear_cache() {
-  if (db_table_exists('cache_token')) {
-    cache('cache_token')->flush();
-  }
+  \Drupal::token()->resetInfo();
   drupal_static_reset('token_get_info');
   drupal_static_reset('token_get_global_token_types');
   drupal_static_reset('token_get_entity_mapping');
@@ -335,28 +266,31 @@ function token_clear_cache() {
  * @see token_entity_info_alter()
  * @see http://drupal.org/node/737726
  */
-function token_get_entity_mapping($value_type = 'token', $value = NULL) {
+function token_get_entity_mapping($value_type = 'token', $value = NULL, $fallback = FALSE) {
   $mapping = &drupal_static(__FUNCTION__, array());
 
   if (empty($mapping)) {
-    foreach (entity_get_info() as $entity_type => $info) {
-      $mapping[$entity_type] = !empty($info['token type']) ? $info['token type'] : $entity_type;
+    foreach (\Drupal::entityManager()->getDefinitions() as $entity_type => $info) {
+      $mapping[$entity_type] = $info->get('token type') ? $info->get('token type') : $entity_type;
     }
+    // Allow modules to alter the mapping array.
+    \Drupal::moduleHandler()->alter('token_entity_mapping', $mapping);
   }
 
   if (!isset($value)) {
-    return $mapping;
+    return $value_type == 'token' ? array_flip($mapping) : $mapping;
   }
   elseif ($value_type == 'token') {
-    return array_search($value, $mapping);
+    $return = array_search($value, $mapping);
+    return $return !== FALSE ? $return : ($fallback ? $value : FALSE);
   }
   elseif ($value_type == 'entity') {
-    return isset($mapping[$value]) ? $mapping[$value] : FALSE;
+    return isset($mapping[$value]) ? $mapping[$value] : ($fallback ? $value : FALSE);
   }
 }
 
 /**
- * Implements hook_entity_info_alter().
+ * Implements hook_entity_type_alter().
  *
  * Because some token types to do not match their entity type names, we have to
  * map them to the proper type. This is purely for other modules' benefit.
@@ -364,17 +298,10 @@ function token_get_entity_mapping($value_type = 'token', $value = NULL) {
  * @see token_get_entity_mapping()
  * @see http://drupal.org/node/737726
  */
-function token_entity_info_alter(&$info) {
-  foreach (array_keys($info) as $entity_type) {
-    // Add a token view mode if it does not already exist.
-    if (!empty($info[$entity_type]['view modes']) && !isset($info[$entity_type]['view modes']['token'])) {
-      $info[$entity_type]['view modes']['token'] = array(
-        'label' => t('Tokens'),
-        'custom settings' => FALSE,
-      );
-    }
-
-    if (!empty($info[$entity_type]['token type'])) {
+function token_entity_type_alter(array &$entity_types) {
+  /* @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
+  foreach (array_keys($entity_types) as $entity_type) {
+    if ($entity_types[$entity_type]->get('token type')) {
       // If the entity's token type is already defined, great!
       continue;
     }
@@ -384,41 +311,41 @@ function token_entity_info_alter(&$info) {
       case 'taxonomy_term':
       case 'taxonomy_vocabulary':
         // Stupid taxonomy token types...
-        $info[$entity_type]['token type'] = str_replace('taxonomy_', '', $entity_type);
+        $entity_types[$entity_type]->set('token type', str_replace('taxonomy_', '', $entity_type));
         break;
+
       default:
         // By default the token type is the same as the entity type.
-        $info[$entity_type]['token type'] = $entity_type;
+        $entity_types[$entity_type]->set('token type', $entity_type);
         break;
     }
   }
 }
 
 /**
+ * Implements hook_entity_view_modes_info().
+ */
+
+/**
  * Implements hook_module_implements_alter().
  *
  * Adds missing token support for core modules.
  */
 function token_module_implements_alter(&$implementations, $hook) {
-  if ($hook == 'tokens' || $hook == 'token_info') {
+  module_load_include('inc', 'token', 'token.tokens');
+
+  if ($hook == 'tokens' || $hook == 'token_info' || $hook == 'token_info_alter' || $hook == 'tokens_alter') {
     foreach (_token_core_supported_modules() as $module) {
-      if (module_exists($module)) {
+      if (\Drupal::moduleHandler()->moduleExists($module) && function_exists($module . '_' . $hook)) {
         $implementations[$module] = TRUE;
       }
     }
     // Move token.module to get included first since it is responsible for
     // other modules.
-    unset($implementations['token']);
-    $implementations = array_merge(array('token' => 'tokens'), $implementations);
-  }
-}
-
-/**
- * Implements hook_flush_caches().
- */
-function token_flush_caches() {
-  if (db_table_exists('cache_token')) {
-    return array('cache_token');
+    if (isset($implementations['token'])) {
+      unset($implementations['token']);
+      $implementations = array_merge(array('token' => 'tokens'), $implementations);
+    }
   }
 }
 
@@ -440,8 +367,6 @@ function token_flush_caches() {
  * @see hook_token_info_alter()
  */
 function token_get_info($token_type = NULL, $token = NULL) {
-  global $language_interface;
-
   // Use the advanced drupal_static() pattern, since this is called very often.
   static $drupal_static_fast;
   if (!isset($drupal_static_fast)) {
@@ -450,39 +375,30 @@ function token_get_info($token_type = NULL, $token = NULL) {
   $token_info = &$drupal_static_fast['token_info'];
 
   if (empty($token_info)) {
-    $cid = "info:{$language_interface->langcode}";
-
-    if ($cache = cache('cache_token')->get($cid)) {
-      $token_info = $cache->data;
-    }
-    else {
-      $token_info = token_info();
-
-      foreach (array_keys($token_info['types']) as $type_key) {
-        if (isset($token_info['types'][$type_key]['type'])) {
-          $base_type = $token_info['types'][$type_key]['type'];
-          // If this token type extends another token type, then merge in
-          // the base token type's tokens.
-          if (isset($token_info['tokens'][$base_type])) {
-            $token_info['tokens'] += array($type_key => array());
-            $token_info['tokens'][$type_key] += $token_info['tokens'][$base_type];
-          }
-        }
-        else {
-          // Add a 'type' value to each token type so we can properly use
-          // token_type_load().
-          $token_info['types'][$type_key]['type'] = $type_key;
+    $token_info = \Drupal::token()->getInfo();
+
+    // @todo: Move this into the token service and deprecate this function.
+    foreach (array_keys($token_info['types']) as $type_key) {
+      if (isset($token_info['types'][$type_key]['type'])) {
+        $base_type = $token_info['types'][$type_key]['type'];
+        // If this token type extends another token type, then merge in
+        // the base token type's tokens.
+        if (isset($token_info['tokens'][$base_type])) {
+          $token_info['tokens'] += array($type_key => array());
+          $token_info['tokens'][$type_key] += $token_info['tokens'][$base_type];
         }
       }
-
-      // Pre-sort tokens.
-      uasort($token_info['types'], 'token_asort_tokens');
-      foreach (array_keys($token_info['tokens']) as $type) {
-        uasort($token_info['tokens'][$type], 'token_asort_tokens');
+      else {
+        // Add a 'type' value to each token type so we can properly use
+        // token_type_load().
+        $token_info['types'][$type_key]['type'] = $type_key;
       }
+    }
 
-      // Store info in cache for future use.
-      cache('cache_token')->set($cid, $token_info);
+    // Pre-sort tokens.
+    uasort($token_info['types'], 'token_asort_tokens');
+    foreach (array_keys($token_info['tokens']) as $type) {
+      uasort($token_info['tokens'][$type], 'token_asort_tokens');
     }
   }
 
@@ -567,7 +483,7 @@ function token_get_invalid_tokens_by_context($value, $valid_types = array()) {
   }
 
   $invalid_tokens = array();
-  $value_tokens = is_string($value) ? token_scan($value) : $value;
+  $value_tokens = is_string($value) ? \Drupal::token()->scan($value) : $value;
 
   foreach ($value_tokens as $type => $tokens) {
     if (!in_array($type, $valid_types)) {
@@ -618,7 +534,7 @@ function token_get_invalid_tokens($type, $tokens) {
       }
       else {
         // Resursively check the chained tokens.
-        $sub_tokens = token_find_with_prefix(array($token => $full_token), $parts[0]);
+        $sub_tokens = \Drupal::token()->findWithPrefix(array($token => $full_token), $parts[0]);
         $invalid_tokens = array_merge($invalid_tokens, token_get_invalid_tokens($sub_token_info['type'], $sub_tokens));
       }
     }
@@ -646,38 +562,38 @@ function token_get_invalid_tokens($type, $tokens) {
  * );
  * @endcode
  */
-function token_element_validate(&$element, &$form_state) {
+function token_element_validate(&$element, FormStateInterface $form_state) {
   $value = isset($element['#value']) ? $element['#value'] : $element['#default_value'];
 
-  if (!drupal_strlen($value)) {
+  if (!Unicode::strlen($value)) {
     // Empty value needs no further validation since the element should depend
     // on using the '#required' FAPI property.
     return $element;
   }
 
-  $tokens = token_scan($value);
+  $tokens = \Drupal::token()->scan($value);
   $title = empty($element['#title']) ? $element['#parents'][0] : $element['#title'];
   // @todo Find old Drupal 6 style tokens and add them to invalid tokens.
 
   // Validate if an element must have a minimum number of tokens.
   if (isset($element['#min_tokens']) && count($tokens) < $element['#min_tokens']) {
     // @todo Change this error message to include the minimum number.
-    $error = format_plural($element['#min_tokens'], 'The %element-title cannot contain fewer than one token.', 'The %element-title must contain at least @count tokens.', array('%element-title' => $title));
-    form_error($element, $error);
+    $error = \Drupal::translation()->formatPlural($element['#min_tokens'], 'The %element-title cannot contain fewer than one token.', 'The %element-title must contain at least @count tokens.', array('%element-title' => $title));
+    $form_state->setError($element, $error);
   }
 
   // Validate if an element must have a maximum number of tokens.
   if (isset($element['#max_tokens']) && count($tokens) > $element['#max_tokens']) {
     // @todo Change this error message to include the maximum number.
-    $error = format_plural($element['#max_tokens'], 'The %element-title must contain as most one token.', 'The %element-title must contain at most @count tokens.', array('%element-title' => $title));
-    form_error($element, $error);
+    $error = \Drupal::translation()->formatPlural($element['#max_tokens'], 'The %element-title must contain as most one token.', 'The %element-title must contain at most @count tokens.', array('%element-title' => $title));
+    $form_state->setError($element, $error);
   }
 
   // Check if the field defines specific token types.
-  if (!empty($element['#token_types'])) {
+  if (isset($element['#token_types'])) {
     $invalid_tokens = token_get_invalid_tokens_by_context($tokens, $element['#token_types']);
     if ($invalid_tokens) {
-      form_error($element, t('The %element-title is using the following invalid tokens: @invalid-tokens.', array('%element-title' => $title, '@invalid-tokens' => implode(', ', $invalid_tokens))));
+      $form_state->setError($element, t('The %element-title is using the following invalid tokens: @invalid-tokens.', array('%element-title' => $title, '@invalid-tokens' => implode(', ', $invalid_tokens))));
     }
   }
 
@@ -687,28 +603,36 @@ function token_element_validate(&$element, &$form_state) {
 /**
  * Deprecated. Use token_element_validate() instead.
  */
-function token_element_validate_token_context(&$element, &$form_state) {
+function token_element_validate_token_context(&$element, FormStateInterface $form_state) {
   return token_element_validate($element, $form_state);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
-function token_form_field_ui_field_edit_form_alter(&$form, $form_state) {
-  if (($form['#field']['type'] == 'file' || $form['#field']['type'] == 'image') && isset($form['instance']['settings']['file_directory']) && !module_exists('filefield_paths')) {
+function token_form_field_ui_field_instance_edit_form_alter(&$form, $form_state) {
+  if (!isset($form['instance']) || $form['#field']->isLocked()) {
+    return;
+  }
+  $field_type = $form['#field']->getType();
+  if (($field_type == 'file' || $field_type == 'image') && isset($form['instance']['settings']['file_directory'])) {
     // GAH! We can only support global tokens in the upload file directory path.
     $form['instance']['settings']['file_directory']['#element_validate'][] = 'token_element_validate';
-    $form['instance']['settings']['file_directory']['#token_types'] = array();
-    $form['instance']['settings']['token_tree'] = array(
-      '#theme' => 'token_tree',
-      '#token_types' => array(),
-      '#weight' => $form['instance']['settings']['file_directory']['#weight'] + 0.5,
-    );
+    $form['instance']['settings']['file_directory'] += array('#token_types' => array());
     $form['instance']['settings']['file_directory']['#description'] .= ' ' . t('This field supports tokens.');
   }
 
   // Note that the description is tokenized via token_field_widget_form_alter().
   $form['instance']['description']['#description'] .= '<br />' . t('This field supports tokens.');
+  $form['instance']['description']['#element_validate'][] = 'token_element_validate';
+  $form['instance']['description'] += array('#token_types' => array());
+
+  $form['instance']['settings']['token_tree'] = array(
+    '#theme' => 'token_tree',
+    '#token_types' => array(),
+    '#dialog' => TRUE,
+    '#weight' => $form['instance']['description']['#weight'] + 0.5,
+  );
 }
 
 /**
@@ -727,6 +651,7 @@ function token_form_system_actions_configure_alter(&$form, $form_state) {
       $form['token_tree'] = array(
         '#theme' => 'token_tree',
         '#token_types' => 'all',
+        '#dialog' => TRUE,
         '#weight' => 100,
       );
       // @todo Add token validation to the action fields that can use tokens.
@@ -740,10 +665,10 @@ function token_form_system_actions_configure_alter(&$form, $form_state) {
  * Alters the user e-mail fields to add token context validation and
  * adds the token tree for a better token UI and selection.
  */
-function token_form_user_admin_settings_alter(&$form, &$form_state) {
+function token_form_user_admin_settings_alter(&$form, FormStateInterface $form_state) {
   $email_token_help = t('Available variables are: [site:name], [site:url], [user:name], [user:mail], [site:login-url], [site:url-brief], [user:edit-url], [user:one-time-login-url], [user:cancel-url].');
 
-  foreach (element_children($form) as $key) {
+  foreach (Element::children($form) as $key) {
     $element = &$form[$key];
 
     // Remove the crummy default token help text.
@@ -770,7 +695,7 @@ function token_form_user_admin_settings_alter(&$form, &$form_state) {
         continue 2;
     }
 
-    foreach (element_children($element) as $sub_key) {
+    foreach (Element::children($element) as $sub_key) {
       if (!isset($element[$sub_key]['#type'])) {
         continue;
       }
@@ -788,10 +713,11 @@ function token_form_user_admin_settings_alter(&$form, &$form_state) {
   }
 
   // Add the token tree UI.
-  $form['token_tree'] = array(
+  $form['email']['token_tree'] = array(
     '#theme' => 'token_tree',
     '#token_types' => array('user'),
     '#show_restricted' => TRUE,
+    '#dialog' => TRUE,
     '#weight' => 90,
   );
 }
@@ -813,8 +739,6 @@ function token_form_user_admin_settings_alter(&$form, &$form_state) {
  *   An optional array with the current parents of the tokens.
  */
 function token_build_tree($token_type, array $options = array()) {
-  global $language_interface;
-
   // Static cache of already built token trees.
   $trees = &drupal_static(__FUNCTION__, array());
 
@@ -834,18 +758,19 @@ function token_build_tree($token_type, array $options = array()) {
     $token_type = $entity_token_type;
   }
 
-  $tree_cid = "tree:{$token_type}:{$language_interface->langcode}:{$options['depth']}";
+  $langcode = \Drupal::languageManager()->getCurrentLanguage()->getId();
+  $tree_cid = "token_tree:{$token_type}:{$langcode}:{$options['depth']}";
 
-  // If we do not have this base tree in the static cache, check {cache_token}
+  // If we do not have this base tree in the static cache, check the cache
   // otherwise generate and store it in the cache.
   if (!isset($trees[$tree_cid])) {
-    if ($cache = cache('cache_token')->get($tree_cid)) {
+    if ($cache = \Drupal::cache('data')->get($tree_cid)) {
       $trees[$tree_cid] = $cache->data;
     }
     else {
       $options['parents'] = array();
       $trees[$tree_cid] = _token_build_tree($token_type, $options);
-      cache('cache_token')->set($tree_cid, $trees[$tree_cid]);
+      \Drupal::cache('data')->set($tree_cid, $trees[$tree_cid], Cache::PERMANENT, array(Token::TOKEN_INFO_CACHE_TAG));
     }
   }
 
@@ -868,7 +793,7 @@ function token_build_tree($token_type, array $options = array()) {
       }
     }
     if (!empty($token_values)) {
-      $token_values = token_generate($token_type, $token_values, $options['data']);
+      $token_values = \Drupal::token()->generate($token_type, $token_values, $options['data']);
       foreach ($token_values as $token => $replacement) {
         $tree[$token]['value'] = $replacement;
       }
@@ -952,40 +877,6 @@ function _token_build_tree($token_type, array $options) {
 }
 
 /**
- * Get a translated menu link by its mlid, without access checking.
- *
- * This function is a copy of menu_link_load() but with its own cache and a
- * simpler query to load the link. This also skips normal menu link access
- * checking by using _token_menu_link_translate().
- *
- * @param $mlid
- *   The mlid of the menu item.
- *
- * @return
- *   A menu link translated for rendering.
- *
- * @see menu_link_load()
- * @see _token_menu_link_translate()
- */
-function token_menu_link_load($mlid) {
-  $cache = &drupal_static(__FUNCTION__, array());
-
-  if (!is_numeric($mlid)) {
-    return FALSE;
-  }
-
-  if (!isset($cache[$mlid])) {
-    $item = db_query("SELECT * FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = :mlid", array(':mlid' => $mlid))->fetchAssoc();
-    if (!empty($item)) {
-      _token_menu_link_translate($item);
-    }
-    $cache[$mlid] = $item;
-  }
-
-  return $cache[$mlid];
-}
-
-/**
  * Get a translated book menu link by its mlid, without access checking.
  *
  * This function is a copy of book_link_load() but with its own cache and a
@@ -1058,7 +949,7 @@ function _token_menu_link_translate(&$item) {
   // options array. For performance reasons we only invoke this hook if the link
   // has the 'alter' flag set in the options array.
   if (!empty($item['options']['alter'])) {
-    drupal_alter('translated_menu_link', $item, $map);
+    \Drupal::moduleHandler()->alter('translated_menu_link', $item, $map);
   }
 
   return $map;
@@ -1095,7 +986,7 @@ function token_render_array(array $array, array $options = array()) {
     $rendered[] = is_array($value) ? render($value) : (string) $value;
   }
   if (!empty($options['sanitize'])) {
-    $rendered = array_map('check_plain', $rendered);
+    $rendered = array_map('\Drupal\Component\Utility\String::checkPlain', $rendered);
   }
   $join = isset($options['join']) ? $options['join'] : ', ';
   return implode($join, $rendered);
@@ -1107,7 +998,7 @@ function token_render_array(array $array, array $options = array()) {
 function token_render_array_value($value, array $options = array()) {
   $rendered = is_array($value) ? render($value) : (string) $value;
   if (!empty($options['sanitize'])) {
-    $rendered = check_plain($rendered);
+    $rendered = String::checkPlain($rendered);
   }
   return $rendered;
 }
@@ -1119,9 +1010,9 @@ function token_render_cache_get($elements) {
   if (!$cid = drupal_render_cid_create($elements)) {
     return FALSE;
   }
-  $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'cache';
+  $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'render';
 
-  if (!empty($cid) && $cache = cache($bin)->get($cid)) {
+  if (!empty($cid) && $cache = \Drupal::cache($bin)->get($cid)) {
     // Add additional libraries, JavaScript, CSS and other data attached
     // to this element.
     if (isset($cache->data['#attached'])) {
@@ -1138,14 +1029,14 @@ function token_render_cache_get($elements) {
  */
 function token_render_cache_set(&$markup, $elements) {
   // This should only run of drupal_render_cache_set() did not.
-  if (in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD'))) {
+  if (in_array(\Drupal::request()->server->get('REQUEST_METHOD'), array('GET', 'HEAD'))) {
     return FALSE;
   }
 
-  $original_method = $_SERVER['REQUEST_METHOD'];
-  $_SERVER['REQUEST_METHOD'] = 'GET';
+  $original_method = \Drupal::request()->server->get('REQUEST_METHOD');
+  \Drupal::request()->server->set('REQUEST_METHOD', 'GET');
   drupal_render_cache_set($markup, $elements);
-  $_SERVER['REQUEST_METHOD'] = $original_method;
+  \Drupal::request()->server->set('REQUEST_METHOD', $original_method);
 }
 
 function token_menu_link_load_all_parents($mlid) {
@@ -1158,9 +1049,9 @@ function token_menu_link_load_all_parents($mlid) {
   if (!isset($cache[$mlid])) {
     $cache[$mlid] = array();
     $plid = db_query("SELECT plid FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchField();
-    while ($plid && $parent = token_menu_link_load($plid)) {
-      $cache[$mlid] = array($plid => $parent['title']) + $cache[$mlid];
-      $plid = $parent['plid'];
+    while ($plid && $parent = menu_link_load($plid)) {
+      $cache[$mlid] = array($plid => $parent->label()) + $cache[$mlid];
+      $plid = $parent->plid;
     }
   }
 
@@ -1176,11 +1067,14 @@ function token_taxonomy_term_load_all_parents($tid) {
 
   if (!isset($cache[$tid])) {
     $cache[$tid] = array();
-    $parents = taxonomy_get_parents_all($tid);
-    array_shift($parents); // Remove this term from the array.
+    /** @var \Drupal\taxonomy\TermStorageInterface $term_storage */
+    $term_storage = \Drupal::entityManager()->getStorage('taxonomy_term');
+    $parents = $term_storage->loadAllParents($tid);
+    // Remove this term from the array.
+    array_shift($parents);
     $parents = array_reverse($parents);
     foreach ($parents as $term) {
-      $cache[$tid][$term->tid] = entity_label('taxonomy_term', $term);
+      $cache[$tid][$term->id()] = $term->label();
     }
   }
 
@@ -1201,9 +1095,9 @@ function token_taxonomy_term_load_all_parents($tid) {
  * @see token_menu_link_load()
  */
 function token_node_menu_link_load($node) {
-  $cache = &drupal_static(__FUNCTIon__, array());
+  $cache = &drupal_static(__FUNCTION__, array());
 
-  if (!isset($cache[$node->nid])) {
+  if (!isset($cache[$node->id()])) {
     $mlid = FALSE;
 
     // Nodes do not have their menu links loaded via menu_node_load().
@@ -1211,17 +1105,18 @@ function token_node_menu_link_load($node) {
       // We need to clone the node as menu_node_prepare() may cause data loss.
       // @see http://drupal.org/node/1317926
       $menu_node = clone $node;
-      menu_node_prepare($menu_node);
+      $form_state = array();
+      menu_ui_node_prepare_form($menu_node, NULL, $form_state);
       $mlid = !empty($menu_node->menu['mlid']) ? $menu_node->menu['mlid'] : FALSE;
     }
     else {
       $mlid = !empty($node->menu['mlid']) ? $node->menu['mlid'] : FALSE;
     }
 
-    $cache[$node->nid] = $mlid;
+    $cache[$node->id()] = $mlid;
   }
 
-  return $cache[$node->nid] ? token_menu_link_load($cache[$node->nid]) : FALSE;
+  return $cache[$node->id()] ? menu_link_load($cache[$node->id()]) : FALSE;
 }
 
 function token_element_children(&$elements, $sort = FALSE) {
@@ -1241,7 +1136,7 @@ function token_element_children(&$elements, $sort = FALSE) {
   }
   // Sort the children if necessary.
   if ($sort && $sortable) {
-    uasort($children, 'element_sort');
+    uasort($children, 'Drupal\Component\Utility\SortArray::sortByWeightProperty');
     // Put the sorted children back into $elements in the correct order, to
     // preserve sorting if the same element is passed through
     // element_children() twice.
diff --git a/token.pages.inc b/token.pages.inc
index d2f06e9..988ec5e 100644
--- a/token.pages.inc
+++ b/token.pages.inc
@@ -4,6 +4,58 @@
  * @file
  * User page callbacks for the token module.
  */
+use Drupal\Component\Serialization\Json;
+use Drupal\Component\Utility\DiffArray;
+use Drupal\Core\Url;
+use Drupal\Core\Utility\Token;
+
+/**
+ * Theme a link to a token tree either as a regular link or a dialog.
+ */
+function theme_token_tree_link($variables) {
+  if (empty($variables['text'])) {
+    $variables['text'] = t('Browse available tokens.');
+  }
+
+  if (!empty($variables['dialog'])) {
+    $attached['#attached']['library'][] = 'core/drupal.dialog.ajax';
+    $attached['#attached']['library'][] = 'token/token';
+    drupal_render($attached);
+    $variables['options']['attributes']['class'][] = 'token-dialog';
+  }
+
+  $info = token_theme();
+  $tree_variables = array_intersect_key($variables, $info['token_tree']['variables']);
+  $tree_variables = DiffArray::diffAssocRecursive($tree_variables, $info['token_tree']['variables']);
+  if (!isset($variables['options']['query']['options'])) {
+    $variables['options']['query']['options'] = array();
+  }
+  $variables['options']['query']['options'] += $tree_variables;
+
+  // We should never pass the dialog option to theme_token_tree(). It is only
+  // used for this function.
+  unset($variables['options']['query']['options']['dialog']);
+
+  // Because PHP converts query strings with arrays into a different syntax on
+  // the next request, the options have to be encoded with JSON in the query
+  // string so that we can reliably decode it for token comparison.
+  $variables['options']['query']['options'] = Json::encode($variables['options']['query']['options']);
+
+  // Set the token tree to open in a separate window.
+  $variables['options']['attributes'] += array(
+    'data-accepts' => 'application/vnd.drupal-dialog',
+    'data-dialog-options' => json_encode(array(
+      'width' => 600,
+      'height' => 400,
+      'position' => array('my' => 'right bottom', 'at' => 'right bottom'),
+      'draggable' => TRUE,
+      'autoResize' => FALSE,
+    )),
+   );
+  $variables['options']['attributes']['class'][] = 'use-ajax';
+
+  return Drupal::l($variables['text'], Url::fromRoute('token.tree', array(), $variables['options']));
+}
 
 /**
  * Theme a tree table.
@@ -18,12 +70,18 @@ function theme_tree_table($variables) {
       unset($row['parent']);
     }
   }
-
+  $output = [];
+  $output['#attached']['library'][] = 'token/token';
   if (!empty($variables['rows'])) {
-    drupal_add_library('token', 'treeTable');
+    $output['#attached']['library'][] = 'token/jquery.treeTable';
   }
-
-  return theme('table', $variables);
+  $output['#type'] = 'table';
+  foreach ($variables as $key => $value) {
+    $output['#' . $key] = $value;
+  }
+  /** @var \Drupal\Core\Render\RendererInterface $renderer */
+  $renderer = \Drupal::service('renderer');
+  return $renderer->render($output);
 }
 
 /**
@@ -32,6 +90,16 @@ function theme_tree_table($variables) {
  * @ingroup themeable
  */
 function theme_token_tree($variables) {
+  if (!empty($variables['dialog'])) {
+    $output = [
+      '#theme' => 'token_tree_link',
+      '#variables' => $variables,
+    ];
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+    return $renderer->render($output);
+  }
+
   $token_types = $variables['token_types'];
   $info = token_get_info();
 
@@ -45,12 +113,14 @@ function theme_token_tree($variables) {
   $element = array(
     '#cache' => array(
       'cid' => 'tree-rendered:' . hash('sha256', serialize(array('token_types' => $token_types, 'global_types' => NULL) + $variables)),
-      'bin' => 'cache_token',
+      'tags' => array(Token::TOKEN_INFO_CACHE_TAG),
     ),
   );
-  if ($cached_output = token_render_cache_get($element)) {
+
+  // @todo Find a way to use the render cache for this.
+  /*if ($cached_output = token_render_cache_get($element)) {
     return $cached_output;
-  }
+  }*/
 
   $options = array(
     'flat' => TRUE,
@@ -96,9 +166,8 @@ function theme_token_tree($variables) {
     '#attributes' => array('class' => array('token-tree')),
     '#empty' => t('No tokens available'),
     '#attached' => array(
-      'js' => array(drupal_get_path('module', 'token') . '/token.js'),
-      'css' => array(drupal_get_path('module', 'token') . '/token.css'),
-      'library' => array(array('token', 'treeTable')),
+      'css' => array(drupal_get_path('module', 'token') . '/css/token.css'),
+      'library' => array('token/token', 'token/jquery.treeTable'),
     ),
   );
 
@@ -108,7 +177,7 @@ function theme_token_tree($variables) {
   }
 
   $output = drupal_render($element);
-  token_render_cache_set($output, $element);
+  //token_render_cache_set($output, $element);
   return $output;
 }
 
@@ -133,7 +202,7 @@ function _token_token_tree_format_row($token, array $token_info, $is_group = FAL
   $row = $defaults;
   $row['id'] = _token_clean_css_identifier($token);
   $row['data']['name'] = $token_info['name'];
-  $row['data']['description'] = $token_info['description'];
+  $row['data']['description'] = isset($token_info['description']) ? $token_info['description'] : '';
 
   if ($is_group) {
     // This is a token type/group.
@@ -170,7 +239,7 @@ function _token_clean_css_identifier($id) {
 /**
  * Menu callback; prints the available tokens and values for an object.
  */
-function token_devel_token_object($entity_type, $entity) {
+function token_devel_token_object($entity_type, $entity, $token_type = NULL) {
   $header = array(
     t('Token'),
     t('Value'),
@@ -182,7 +251,10 @@ function token_devel_token_object($entity_type, $entity) {
     'values' => TRUE,
     'data' => array($entity_type => $entity),
   );
-  $tree = token_build_tree($entity_type, $options);
+  if (!isset($token_type)) {
+    $token_type = $entity_type;
+  }
+  $tree = token_build_tree($token_type, $options);
   foreach ($tree as $token => $token_info) {
     if (!empty($token_info['restricted'])) {
       continue;
@@ -203,98 +275,11 @@ function token_devel_token_object($entity_type, $entity) {
     '#attributes' => array('class' => array('token-tree')),
     '#empty' => t('No tokens available.'),
     '#attached' => array(
-      'js' => array(drupal_get_path('module', 'token') . '/token.js'),
-      'css' => array(drupal_get_path('module', 'token') . '/token.css'),
+      'library' => array('token/token'),
     ),
   );
 
   return $build;
 }
 
-/**
- * Page callback to clear the token registry caches.
- */
-function token_flush_cache_callback() {
-  if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], current_path())) {
-    return MENU_NOT_FOUND;
-  }
-
-  token_clear_cache();
-  drupal_set_message(t('Token registry caches cleared.'));
-  drupal_goto();
-}
-
-function token_autocomplete() {
-  $args = func_get_args();
-  $string = implode('/', $args);
-
-  $token_info = token_info();
-
-  preg_match_all('/\[([^\s\]:]*):?([^\s\]]*)?\]?/', $string, $matches);
-  $types = $matches[1];
-  $tokens = $matches[2];
-
-  foreach ($types as $index => $type) {
-    if (!empty($tokens[$index]) || isset($token_info['types'][$type])) {
-      token_autocomplete_token($type, $tokens[$index]);
-    }
-    else {
-      token_autocomplete_type($type);
-    }
-  }
-
-}
-
-function token_autocomplete_type($string = '') {
-  $token_info = token_info();
-  $types = $token_info['types'];
-  $matches = array();
 
-  foreach ($types as $type => $info) {
-    if (!$string || strpos($type, $string) === 0) {
-      $type_key = "[{$type}:";
-      $matches[$type_key] = levenshtein($type, $string);
-    }
-  }
-
-  if ($string) {
-    asort($matches);
-  }
-  else {
-    ksort($matches);
-  }
-
-  $matches = drupal_map_assoc(array_keys($matches));
-  drupal_json_output($matches);
-}
-
-function token_autocomplete_token($token_type) {
-  $args = func_get_args();
-  array_shift($args);
-  $string = trim(implode('/', $args));
-  $string = substr($string, strrpos($string, '['));
-
-  $token_type = $token_type['type'];
-  $matches = array();
-
-  if (!drupal_strlen($string)) {
-    $matches["[{$token_type}:"] = 0;
-  }
-  else {
-    $depth = max(1, substr_count($string, ':'));
-    $tree = token_build_tree($token_type, array('flat' => TRUE, 'depth' => $depth));
-    foreach (array_keys($tree) as $token) {
-      if (strpos($token, $string) === 0) {
-        $matches[$token] = levenshtein($token, $string);
-        if (isset($tree[$token]['children'])) {
-          $token = rtrim($token, ':]') . ':';
-          $matches[$token] = levenshtein($token, $string);
-        }
-      }
-    }
-  }
-
-  asort($matches);
-  $matches = drupal_map_assoc(array_keys($matches));
-  drupal_json_output($matches);
-}
diff --git a/token.routing.yml b/token.routing.yml
new file mode 100644
index 0000000..dc61416
--- /dev/null
+++ b/token.routing.yml
@@ -0,0 +1,63 @@
+token.tree:
+  path: '/token/tree'
+  defaults:
+    _controller: '\Drupal\token\Controller\TokenTreeController::outputTree'
+  requirements:
+    _csrf_token: 'TRUE'
+
+token.autocomplete:
+  path: '/token/autocomplete/{token_type}/{filter}'
+  defaults:
+    _controller: '\Drupal\token\Controller\TokenAutocompleteController::autocomplete'
+  requirements:
+    _access: 'TRUE'
+
+token.flush_cache:
+  path: '/token/flush-cache'
+  defaults:
+    _controller: '\Drupal\token\Controller\TokenCacheController::flush'
+  requirements:
+    _permission: 'flush caches'
+    _csrf_token: 'TRUE'
+    _module_dependencies: 'admin_menu'
+
+token.devel_node_token:
+  path: '/node/{node}/devel/token'
+  defaults:
+    _controller: '\Drupal\token\Controller\TokenDevelController::devel_token_node'
+    _title: 'Tokens'
+  options:
+    _access_mode: 'ALL'
+  requirements:
+    _permission: 'access devel information'
+    _module_dependencies: 'devel'
+token.devel_comment_token:
+  path: '/comment/{comment}/devel/token'
+  defaults:
+    _controller: '\Drupal\token\Controller\TokenDevelController::devel_token_comment'
+    _title: 'Tokens'
+  options:
+    _access_mode: 'ALL'
+  requirements:
+    _permission: 'access devel information'
+    _module_dependencies: 'devel'
+token.devel_taxonomy_term_token:
+  path: '/taxonomy/term/{taxonomy_term}/devel/token'
+  defaults:
+    _controller: '\Drupal\token\Controller\TokenDevelController::devel_token_taxonomy_term'
+    _title: 'Tokens'
+  options:
+    _access_mode: 'ALL'
+  requirements:
+    _permission: 'access devel information'
+    _module_dependencies: 'devel'
+token.devel_user_token:
+  path: '/user/{user}/devel/token'
+  defaults:
+    _controller: '\Drupal\token\Controller\TokenDevelController::devel_token_user'
+    _title: 'Tokens'
+  options:
+    _access_mode: 'ALL'
+  requirements:
+    _permission: 'access devel information'
+    _module_dependencies: 'devel'
diff --git a/token.services.yml b/token.services.yml
new file mode 100644
index 0000000..0baad47
--- /dev/null
+++ b/token.services.yml
@@ -0,0 +1 @@
+services:
diff --git a/token.test b/token.test
deleted file mode 100644
index 830e12a..0000000
--- a/token.test
+++ /dev/null
@@ -1,1087 +0,0 @@
-<?php
-
-/**
- * @file
- * Test integration for the token module.
- */
-
-/**
- * Helper test class with some added functions for testing.
- */
-class TokenTestHelper extends DrupalWebTestCase {
-  protected $profile = 'testing';
-
-  public function setUp($modules = array()) {
-    $modules[] = 'path';
-    $modules[] = 'token';
-    $modules[] = 'token_test';
-    parent::setUp($modules);
-
-    variable_set('clean_url', 1);
-  }
-
-  function assertToken($type, array $data, $token, $expected, array $options = array()) {
-    return $this->assertTokens($type, $data, array($token => $expected), $options);
-  }
-
-  function assertTokens($type, array $data, array $tokens, array $options = array()) {
-    $input = $this->mapTokenNames($type, array_keys($tokens));
-    $replacements = token_generate($type, $input, $data, $options);
-    foreach ($tokens as $name => $expected) {
-      $token = $input[$name];
-      if (!isset($expected)) {
-        $this->assertTrue(!isset($values[$token]), t("Token value for @token was not generated.", array('@type' => $type, '@token' => $token)));
-      }
-      elseif (!isset($replacements[$token])) {
-        $this->fail(t("Token value for @token was not generated.", array('@type' => $type, '@token' => $token)));
-      }
-      elseif (!empty($options['regex'])) {
-        $this->assertTrue(preg_match('/^' . $expected . '$/', $replacements[$token]), t("Token value for @token was '@actual', matching regular expression pattern '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $replacements[$token], '@expected' => $expected)));
-      }
-      else {
-        $this->assertIdentical($replacements[$token], $expected, t("Token value for @token was '@actual', expected value '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $replacements[$token], '@expected' => $expected)));
-      }
-    }
-
-    return $replacements;
-  }
-
-  function mapTokenNames($type, array $tokens = array()) {
-    $return = array();
-    foreach ($tokens as $token) {
-      $return[$token] = "[$type:$token]";
-    }
-    return $return;
-  }
-
-  function assertNoTokens($type, array $data, array $tokens, array $options = array()) {
-    $input = $this->mapTokenNames($type, $tokens);
-    $replacements = token_generate($type, $input, $data, $options);
-    foreach ($tokens as $name) {
-      $token = $input[$name];
-      $this->assertTrue(!isset($replacements[$token]), t("Token value for @token was not generated.", array('@type' => $type, '@token' => $token)));
-    }
-    return $values;
-  }
-
-  function saveAlias($source, $alias, $language = LANGUAGE_NOT_SPECIFIED) {
-    $alias = array(
-      'source' => $source,
-      'alias' => $alias,
-      'language' => $language,
-    );
-    path_save($alias);
-    return $alias;
-  }
-
-  function saveEntityAlias($entity_type, $entity, $alias, $language = LANGUAGE_NOT_SPECIFIED) {
-    $uri = entity_uri($entity_type, $entity);
-    return $this->saveAlias($uri['path'], $alias, $language);
-  }
-
-  /**
-   * Make a page request and test for token generation.
-   */
-  function assertPageTokens($url, array $tokens, array $data = array(), array $options = array()) {
-    if (empty($tokens)) {
-      return TRUE;
-    }
-
-    $token_page_tokens = array(
-      'tokens' => $tokens,
-      'data' => $data,
-      'options' => $options,
-    );
-    variable_set('token_page_tokens', $token_page_tokens);
-
-    $options += array('url_options' => array());
-    $this->drupalGet($url, $options['url_options']);
-    $this->refreshVariables();
-    $result = variable_get('token_page_tokens', array());
-
-    if (!isset($result['values']) || !is_array($result['values'])) {
-      return $this->fail('Failed to generate tokens.');
-    }
-
-    foreach ($tokens as $token => $expected) {
-      if (!isset($expected)) {
-        $this->assertTrue(!isset($result['values'][$token]) || $result['values'][$token] === $token, t("Token value for @token was not generated.", array('@token' => $token)));
-      }
-      elseif (!isset($result['values'][$token])) {
-        $this->fail(t('Failed to generate token @token.', array('@token' => $token)));
-      }
-      else {
-        $this->assertIdentical($result['values'][$token], (string) $expected, t("Token value for @token was '@actual', expected value '@expected'.", array('@token' => $token, '@actual' => $result['values'][$token], '@expected' => $expected)));
-      }
-    }
-  }
-}
-
-class TokenUnitTestCase extends TokenTestHelper {
-  public static function getInfo() {
-    return array(
-      'name' => 'Token unit tests',
-      'description' => 'Test basic, low-level token functions.',
-      'group' => 'Token',
-    );
-  }
-
-  /**
-   * Test token_get_invalid_tokens() and token_get_invalid_tokens_by_context().
-   */
-  public function testGetInvalidTokens() {
-    $tests = array();
-    $tests[] = array(
-      'valid tokens' => array(
-        '[node:title]',
-        '[node:created:short]',
-        '[node:created:custom:invalid]',
-        '[node:created:custom:mm-YYYY]',
-        '[site:name]',
-        '[site:slogan]',
-        '[current-date:short]',
-        '[current-user:uid]',
-        '[current-user:ip-address]',
-      ),
-      'invalid tokens' => array(
-        '[node:title:invalid]',
-        '[node:created:invalid]',
-        '[node:created:short:invalid]',
-        '[invalid:title]',
-        '[site:invalid]',
-        '[user:ip-address]',
-        '[user:uid]',
-        '[comment:cid]',
-        // Deprecated tokens
-        '[node:tnid]',
-        '[node:type]',
-        '[node:type-name]',
-        '[date:short]',
-      ),
-      'types' => array('node'),
-    );
-    $tests[] = array(
-      'valid tokens' => array(
-        '[node:title]',
-        '[node:created:short]',
-        '[node:created:custom:invalid]',
-        '[node:created:custom:mm-YYYY]',
-        '[site:name]',
-        '[site:slogan]',
-        '[user:uid]',
-        '[current-date:short]',
-        '[current-user:uid]',
-      ),
-      'invalid tokens' => array(
-        '[node:title:invalid]',
-        '[node:created:invalid]',
-        '[node:created:short:invalid]',
-        '[invalid:title]',
-        '[site:invalid]',
-        '[user:ip-address]',
-        '[comment:cid]',
-        // Deprecated tokens
-        '[node:tnid]',
-        '[node:type]',
-        '[node:type-name]',
-      ),
-      'types' => array('all'),
-    );
-
-    foreach ($tests as $test) {
-      $tokens = array_merge($test['valid tokens'], $test['invalid tokens']);
-      shuffle($tokens);
-
-      $invalid_tokens = token_get_invalid_tokens_by_context(implode(' ', $tokens), $test['types']);
-
-      sort($invalid_tokens);
-      sort($test['invalid tokens']);
-      $this->assertEqual($invalid_tokens, $test['invalid tokens'], 'Invalid tokens detected properly: ' . implode(', ', $invalid_tokens));
-    }
-  }
-}
-
-class TokenURLTestCase extends TokenTestHelper {
-  public static function getInfo() {
-    return array(
-      'name' => 'URL token tests',
-      'description' => 'Test the URL tokens.',
-      'group' => 'Token',
-    );
-  }
-
-  public function setUp($modules = array()) {
-    parent::setUp($modules);
-    $this->saveAlias('node/1', 'first-node');
-  }
-
-  function testURLTokens() {
-    $tokens = array(
-      'absolute' => 'http://example.com/first-node',
-      'relative' => base_path() . 'first-node',
-      'path' => 'first-node',
-      'brief' => 'example.com/first-node',
-      'args:value:0' => 'first-node',
-      'args:value:1' => NULL,
-      'args:value:N' => NULL,
-      'unaliased' => 'http://example.com/node/1',
-      'unaliased:relative' => base_path() . 'node/1',
-      'unaliased:path' => 'node/1',
-      'unaliased:brief' => 'example.com/node/1',
-      'unaliased:args:value:0' => 'node',
-      'unaliased:args:value:1' => '1',
-      'unaliased:args:value:2' => NULL,
-      // Deprecated tokens.
-      'alias' => 'first-node',
-    );
-    $this->assertTokens('url', array('path' => 'node/1', 'options' => array('base_url' => 'http://example.com')), $tokens);
-  }
-}
-
-class TokenCommentTestCase extends TokenTestHelper {
-  public static function getInfo() {
-    return array(
-      'name' => 'Comment token tests',
-      'description' => 'Test the comment tokens.',
-      'group' => 'Token',
-    );
-  }
-
-  public function setUp($modules = array()) {
-    $modules[] = 'comment';
-    parent::setUp($modules);
-  }
-
-  function testCommentTokens() {
-    $node = $this->drupalCreateNode(array('comment' => COMMENT_NODE_OPEN));
-
-    $parent_comment = new stdClass;
-    $parent_comment->nid = $node->nid;
-    $parent_comment->pid = 0;
-    $parent_comment->cid = NULL;
-    $parent_comment->uid = 0;
-    $parent_comment->name = 'anonymous user';
-    $parent_comment->mail = 'anonymous@example.com';
-    $parent_comment->subject = $this->randomName();
-    $parent_comment->timestamp = mt_rand($node->created, REQUEST_TIME);
-    $parent_comment->language = LANGUAGE_NOT_SPECIFIED;
-    $parent_comment->body[LANGUAGE_NOT_SPECIFIED][0] = $this->randomName();
-    comment_save($parent_comment);
-
-    $tokens = array(
-      'url' => url('comment/' . $parent_comment->cid, array('fragment' => 'comment-' . $parent_comment->cid, 'absolute' => TRUE)),
-      'url:absolute' => url('comment/' . $parent_comment->cid, array('fragment' => 'comment-' . $parent_comment->cid, 'absolute' => TRUE)),
-      'url:relative' => url('comment/' . $parent_comment->cid, array('fragment' => 'comment-' . $parent_comment->cid, 'absolute' => FALSE)),
-      'url:path' => 'comment/' . $parent_comment->cid,
-      'parent:url:absolute' => NULL,
-    );
-    $this->assertTokens('comment', array('comment' => $parent_comment), $tokens);
-
-    $comment = new stdClass();
-    $comment->nid = $node->nid;
-    $comment->pid = $parent_comment->cid;
-    $comment->cid = NULL;
-    $comment->uid = 1;
-    $comment->subject = $this->randomName();
-    $comment->timestamp = mt_rand($parent_comment->created, REQUEST_TIME);
-    $comment->language = LANGUAGE_NOT_SPECIFIED;
-    $comment->body[LANGUAGE_NOT_SPECIFIED][0] = $this->randomName();
-    comment_save($comment);
-
-    $tokens = array(
-      'url' => url('comment/' . $comment->cid, array('fragment' => 'comment-' . $comment->cid, 'absolute' => TRUE)),
-      'url:absolute' => url('comment/' . $comment->cid, array('fragment' => 'comment-' . $comment->cid, 'absolute' => TRUE)),
-      'url:relative' => url('comment/' . $comment->cid, array('fragment' => 'comment-' . $comment->cid, 'absolute' => FALSE)),
-      'url:path' => 'comment/' . $comment->cid,
-      'parent:url:absolute' => url('comment/' . $parent_comment->cid, array('fragment' => 'comment-' . $parent_comment->cid, 'absolute' => TRUE)),
-    );
-    $this->assertTokens('comment', array('comment' => $comment), $tokens);
-  }
-}
-
-class TokenNodeTestCase extends TokenTestHelper {
-  protected $profile = 'standard';
-
-  public static function getInfo() {
-    return array(
-      'name' => 'Node and content type token tests',
-      'description' => 'Test the node and content type tokens.',
-      'group' => 'Token',
-    );
-  }
-
-  function testNodeTokens() {
-    $source_node = $this->drupalCreateNode(array('log' => $this->randomName(), 'path' => array('alias' => 'content/source-node')));
-    $tokens = array(
-      'source' => NULL,
-      'source:nid' => NULL,
-      'log' => $source_node->log,
-      'url:path' => 'content/source-node',
-      'url:absolute' => url("node/{$source_node->nid}", array('absolute' => TRUE)),
-      'url:relative' => url("node/{$source_node->nid}", array('absolute' => FALSE)),
-      'url:unaliased:path' => "node/{$source_node->nid}",
-      'content-type' => 'Basic page',
-      'content-type:name' => 'Basic page',
-      'content-type:machine-name' => 'page',
-      'content-type:description' => "Use <em>basic pages</em> for your static content, such as an 'About us' page.",
-      'content-type:node-count' => 1,
-      'content-type:edit-url' => url('admin/structure/types/manage/page', array('absolute' => TRUE)),
-      // Deprecated tokens.
-      'tnid' => 0,
-      'type' => 'page',
-      'type-name' => 'Basic page',
-      'url:alias' => 'content/source-node',
-    );
-    $this->assertTokens('node', array('node' => $source_node), $tokens);
-
-    $translated_node = $this->drupalCreateNode(array('tnid' => $source_node->nid, 'type' => 'article'));
-    $tokens = array(
-      'source' => $source_node->title,
-      'source:nid' => $source_node->nid,
-      'log' => '',
-      'url:path' => "node/{$translated_node->nid}",
-      'url:absolute' => url("node/{$translated_node->nid}", array('absolute' => TRUE)),
-      'url:relative' => url("node/{$translated_node->nid}", array('absolute' => FALSE)),
-      'url:unaliased:path' => "node/{$translated_node->nid}",
-      'content-type' => 'Article',
-      'content-type:name' => 'Article',
-      'content-type:machine-name' => 'article',
-      'content-type:description' => "Use <em>articles</em> for time-sensitive content like news, press releases or blog posts.",
-      'content-type:node-count' => 1,
-      'content-type:edit-url' => url('admin/structure/types/manage/article', array('absolute' => TRUE)),
-      // Deprecated tokens.
-      'type' => 'article',
-      'type-name' => 'Article',
-      'tnid' => $source_node->nid,
-      'url:alias' => "node/{$translated_node->nid}",
-    );
-    $this->assertTokens('node', array('node' => $translated_node), $tokens);
-  }
-}
-
-class TokenMenuTestCase extends TokenTestHelper {
-  public static function getInfo() {
-    return array(
-      'name' => 'Menu link and menu token tests',
-      'description' => 'Test the menu tokens.',
-      'group' => 'Token',
-    );
-  }
-
-  public function setUp($modules = array()) {
-    $modules[] = 'menu';
-    parent::setUp($modules);
-  }
-
-  function testMenuTokens() {
-    // Add a root link.
-    $root_link = array(
-      'link_path' => 'root',
-      'link_title' => 'Root link',
-      'menu_name' => 'main-menu',
-    );
-    menu_link_save($root_link);
-
-    // Add another link with the root link as the parent
-    $parent_link = array(
-      'link_path' => 'root/parent',
-      'link_title' => 'Parent link',
-      'menu_name' => 'main-menu',
-      'plid' => $root_link['mlid'],
-    );
-    menu_link_save($parent_link);
-
-    // Test menu link tokens.
-    $tokens = array(
-      'mlid' => $parent_link['mlid'],
-      'title' => 'Parent link',
-      'menu' => 'Main menu',
-      'menu:name' => 'Main menu',
-      'menu:machine-name' => 'main-menu',
-      'menu:description' => 'The <em>Main</em> menu is used on many sites to show the major sections of the site, often in a top navigation bar.',
-      'menu:menu-link-count' => 2,
-      'menu:edit-url' => url("admin/structure/menu/manage/main-menu", array('absolute' => TRUE)),
-      'url' => url('root/parent', array('absolute' => TRUE)),
-      'url:absolute' => url('root/parent', array('absolute' => TRUE)),
-      'url:relative' => url('root/parent', array('absolute' => FALSE)),
-      'url:path' => 'root/parent',
-      'url:alias' => 'root/parent',
-      'edit-url' => url("admin/structure/menu/item/{$parent_link['mlid']}/edit", array('absolute' => TRUE)),
-      'parent' => 'Root link',
-      'parent:mlid' => $root_link['mlid'],
-      'parent:title' => 'Root link',
-      'parent:menu' => 'Main menu',
-      'parent:parent' => NULL,
-      'parents' => 'Root link',
-      'parents:count' => 1,
-      'parents:keys' => $root_link['mlid'],
-      'root' => 'Root link',
-      'root:mlid' => $root_link['mlid'],
-      'root:parent' => NULL,
-      'root:root' => NULL,
-    );
-    $this->assertTokens('menu-link', array('menu-link' => $parent_link), $tokens);
-
-    // Add a node menu link
-    $node_link = array(
-      'enabled' => TRUE,
-      'link_title' => 'Node link',
-      'plid' => $parent_link['mlid'],
-      'customized' => 0,
-      'description' => '',
-    );
-    $node = $this->drupalCreateNode(array('menu' => $node_link));
-
-    // Test [node:menu] tokens.
-    $tokens = array(
-      'menu-link' => 'Node link',
-      'menu-link:mlid' => $node->menu['mlid'],
-      'menu-link:title' => 'Node link',
-      'menu-link:menu' => 'Main menu',
-      'menu-link:url' => url('node/' . $node->nid, array('absolute' => TRUE)),
-      'menu-link:url:path' => 'node/' . $node->nid,
-      'menu-link:edit-url' => url("admin/structure/menu/item/{$node->menu['mlid']}/edit", array('absolute' => TRUE)),
-      'menu-link:parent' => 'Parent link',
-      'menu-link:parent:mlid' => $node->menu['plid'],
-      'menu-link:parent:mlid' => $parent_link['mlid'],
-      'menu-link:parents' => 'Root link, Parent link',
-      'menu-link:parents:count' => 2,
-      'menu-link:parents:keys' => $root_link['mlid'] . ', ' . $parent_link['mlid'],
-      'menu-link:root' => 'Root link',
-      'menu-link:root:mlid' => $root_link['mlid'],
-    );
-    $this->assertTokens('node', array('node' => $node), $tokens);
-
-    // Reload the node which will not have $node->menu defined and re-test.
-    $loaded_node = node_load($node->nid);
-    $this->assertTokens('node', array('node' => $loaded_node), $tokens);
-
-    // Regression test for http://drupal.org/node/1317926 to ensure the
-    // original node object is not changed when calling menu_node_prepare().
-    $this->assertTrue(!isset($loaded_node->menu), t('The $node->menu property was not modified during token replacement.'), 'Regression');
-  }
-}
-
-class TokenTaxonomyTestCase extends TokenTestHelper {
-  protected $vocab;
-
-  public static function getInfo() {
-    return array(
-      'name' => 'Taxonomy and vocabulary token tests',
-      'description' => 'Test the taxonomy tokens.',
-      'group' => 'Token',
-    );
-  }
-
-  public function setUp($modules = array()) {
-    $modules[] = 'taxonomy';
-    parent::setUp($modules);
-
-    // Create the default tags vocabulary.
-    $vocabulary = entity_create('taxonomy_vocabulary', array(
-      'name' => 'Tags',
-      'machine_name' => 'tags',
-    ));
-    taxonomy_vocabulary_save($vocabulary);
-    $this->vocab = $vocabulary;
-  }
-
-  /**
-   * Test the additional taxonomy term tokens.
-   */
-  function testTaxonomyTokens() {
-    $root_term = $this->addTerm($this->vocab, array('name' => 'Root term', 'path' => array('alias' => 'root-term')));
-    $tokens = array(
-      'url' => url("taxonomy/term/{$root_term->tid}", array('absolute' => TRUE)),
-      'url:absolute' => url("taxonomy/term/{$root_term->tid}", array('absolute' => TRUE)),
-      'url:relative' => url("taxonomy/term/{$root_term->tid}", array('absolute' => FALSE)),
-      'url:path' => 'root-term',
-      'url:unaliased:path' => "taxonomy/term/{$root_term->tid}",
-      'edit-url' => url("taxonomy/term/{$root_term->tid}/edit", array('absolute' => TRUE)),
-      'parents' => NULL,
-      'parents:count' => NULL,
-      'parents:keys' => NULL,
-      'root' => NULL,
-      // Deprecated tokens
-      'url:alias' => 'root-term',
-    );
-    $this->assertTokens('term', array('term' => $root_term), $tokens);
-
-    $parent_term = $this->addTerm($this->vocab, array('name' => 'Parent term', 'parent' => $root_term->tid));
-    $tokens = array(
-      'url' => url("taxonomy/term/{$parent_term->tid}", array('absolute' => TRUE)),
-      'url:absolute' => url("taxonomy/term/{$parent_term->tid}", array('absolute' => TRUE)),
-      'url:relative' => url("taxonomy/term/{$parent_term->tid}", array('absolute' => FALSE)),
-      'url:path' => "taxonomy/term/{$parent_term->tid}",
-      'url:unaliased:path' => "taxonomy/term/{$parent_term->tid}",
-      'edit-url' => url("taxonomy/term/{$parent_term->tid}/edit", array('absolute' => TRUE)),
-      'parents' => 'Root term',
-      'parents:count' => 1,
-      'parents:keys' => $root_term->tid,
-      'root' => check_plain($root_term->name),
-      'root:tid' => $root_term->tid,
-      // Deprecated tokens
-      'url:alias' => "taxonomy/term/{$parent_term->tid}",
-    );
-    $this->assertTokens('term', array('term' => $parent_term), $tokens);
-
-    $term = $this->addTerm($this->vocab, array('name' => 'Test term', 'parent' => $parent_term->tid));
-    $tokens = array(
-      'parents' => 'Root term, Parent term',
-      'parents:count' => 2,
-      'parents:keys' => implode(', ', array($root_term->tid, $parent_term->tid)),
-    );
-    $this->assertTokens('term', array('term' => $term), $tokens);
-  }
-
-  /**
-   * Test the additional vocabulary tokens.
-   */
-  function testVocabularyTokens() {
-    $vocabulary = $this->vocab;
-    $tokens = array(
-      'machine-name' => 'tags',
-      'edit-url' => url("admin/structure/taxonomy/{$vocabulary->machine_name}/edit", array('absolute' => TRUE)),
-    );
-    $this->assertTokens('vocabulary', array('vocabulary' => $vocabulary), $tokens);
-  }
-
-  function addVocabulary(array $vocabulary = array()) {
-    $vocabulary += array(
-      'name' => drupal_strtolower($this->randomName(5)),
-      'nodes' => array('article' => 'article'),
-    );
-    $vocabulary = entity_create('taxonomy_vocabulary', $vocabulary);
-    taxonomy_vocabulary_save($vocabulary);
-    return $vocabulary;
-  }
-
-  function addTerm(stdClass $vocabulary, array $term = array()) {
-    $term += array(
-      'name' => drupal_strtolower($this->randomName(5)),
-      'vid' => $vocabulary->vid,
-    );
-    $term = entity_create('taxonomy_term', $term);
-    taxonomy_term_save($term);
-    return $term;
-  }
-}
-
-class TokenUserTestCase extends TokenTestHelper {
-  protected $account = NULL;
-
-  public static function getInfo() {
-    return array(
-      'name' => 'User token tests',
-      'description' => 'Test the user tokens.',
-      'group' => 'Token',
-    );
-  }
-
-  public function setUp($modules = array()) {
-    parent::setUp($modules);
-
-    // Enable user pictures.
-    variable_set('user_pictures', 1);
-    variable_set('user_picture_file_size', '');
-
-    // Set up the pictures directory.
-    $picture_path = file_default_scheme() . '://' . variable_get('user_picture_path', 'pictures');
-    if (!file_prepare_directory($picture_path, FILE_CREATE_DIRECTORY)) {
-      $this->fail('Could not create directory ' . $picture_path . '.');
-    }
-
-    $this->account = $this->drupalCreateUser(array('administer users'));
-    $this->drupalLogin($this->account);
-  }
-
-  function testUserTokens() {
-    // Add a user picture to the account.
-    $image = current($this->drupalGetTestFiles('image'));
-    $edit = array('files[picture_upload]' => drupal_realpath($image->uri));
-    $this->drupalPost('user/' . $this->account->uid . '/edit', $edit, t('Save'));
-
-    // Load actual user data from database.
-    $this->account = user_load($this->account->uid, TRUE);
-    $this->assertTrue(!empty($this->account->picture->fid), 'User picture uploaded.');
-
-    $user_tokens = array(
-      'picture' => theme('user_picture', array('account' => $this->account)),
-      'picture:fid' => $this->account->picture->fid,
-      'picture:size-raw' => 125,
-      'ip-address' => NULL,
-      'roles' => implode(', ', $this->account->roles),
-      'roles:keys' => implode(', ', array_keys($this->account->roles)),
-    );
-    $this->assertTokens('user', array('user' => $this->account), $user_tokens);
-
-    $edit = array('user_pictures' => FALSE);
-    $this->drupalPost('admin/config/people/accounts', $edit, 'Save configuration');
-    $this->assertText('The configuration options have been saved.');
-
-    // Remove the simpletest-created user role.
-    user_role_delete(end($this->account->roles));
-    $this->account = user_load($this->account->uid, TRUE);
-
-    $user_tokens = array(
-      'picture' => NULL,
-      'picture:fid' => NULL,
-      'ip-address' => NULL,
-      'roles' => 'authenticated user',
-      'roles:keys' => (string) DRUPAL_AUTHENTICATED_RID,
-    );
-    $this->assertTokens('user', array('user' => $this->account), $user_tokens);
-
-    // The ip address token should work for the current user token type.
-    $tokens = array(
-      'ip-address' => ip_address(),
-    );
-    $this->assertTokens('current-user', array(), $tokens);
-
-    $anonymous = drupal_anonymous_user();
-    // Mess with the role array to ensure we still get consistent output.
-    $anonymous->roles[DRUPAL_ANONYMOUS_RID] = DRUPAL_ANONYMOUS_RID;
-    $tokens = array(
-      'roles' => 'anonymous user',
-      'roles:keys' => (string) DRUPAL_ANONYMOUS_RID,
-    );
-    $this->assertTokens('user', array('user' => $anonymous), $tokens);
-  }
-}
-
-class TokenEntityTestCase extends TokenTestHelper {
-  public static function getInfo() {
-    return array(
-      'name' => 'Entity token tests',
-      'description' => 'Test the entity tokens.',
-      'group' => 'Token',
-    );
-  }
-
-  public function setUp($modules = array()) {
-    $modules[] = 'taxonomy';
-    parent::setUp($modules);
-
-    // Create the default tags vocabulary.
-    $vocabulary = entity_create('taxonomy_vocabulary', array(
-      'name' => 'Tags',
-      'machine_name' => 'tags',
-    ));
-    taxonomy_vocabulary_save($vocabulary);
-    $this->vocab = $vocabulary;
-  }
-
-  function testEntityMapping() {
-    $this->assertIdentical(token_get_entity_mapping('token', 'node'), 'node');
-    $this->assertIdentical(token_get_entity_mapping('token', 'term'), 'taxonomy_term');
-    $this->assertIdentical(token_get_entity_mapping('token', 'vocabulary'), 'taxonomy_vocabulary');
-    $this->assertIdentical(token_get_entity_mapping('token', 'invalid'), FALSE);
-    $this->assertIdentical(token_get_entity_mapping('entity', 'node'), 'node');
-    $this->assertIdentical(token_get_entity_mapping('entity', 'taxonomy_term'), 'term');
-    $this->assertIdentical(token_get_entity_mapping('entity', 'taxonomy_vocabulary'), 'vocabulary');
-    $this->assertIdentical(token_get_entity_mapping('entity', 'invalid'), FALSE);
-
-    // Test that when we send the mis-matched entity type into token_replace()
-    // that we still get the tokens replaced.
-    $vocabulary = taxonomy_vocabulary_machine_name_load('tags');
-    $term = $this->addTerm($vocabulary);
-    $this->assertIdentical(token_replace('[vocabulary:name]', array('taxonomy_vocabulary' => $vocabulary)), $vocabulary->name);
-    $this->assertIdentical(token_replace('[term:name][term:vocabulary:name]', array('taxonomy_term' => $term)), $term->name . $vocabulary->name);
-  }
-
-  function addTerm(TaxonomyVocabulary $vocabulary, array $term = array()) {
-    $term += array(
-      'name' => drupal_strtolower($this->randomName(5)),
-      'vid' => $vocabulary->vid,
-    );
-    $term = entity_create('taxonomy_term', $term);
-    taxonomy_term_save($term);
-    return $term;
-  }
-
-  /**
-   * Test the [entity:original:*] tokens.
-   */
-  function testEntityOriginal() {
-    $node = $this->drupalCreateNode(array('title' => 'Original title'));
-
-    $tokens = array(
-      'nid' => $node->nid,
-      'title' => 'Original title',
-      'original' => NULL,
-      'original:nid' => NULL,
-    );
-    $this->assertTokens('node', array('node' => $node), $tokens);
-
-    // Emulate the original entity property that would be available from
-    // node_save() and change the title for the node.
-    $node->original = entity_load_unchanged('node', $node->nid);
-    $node->title = 'New title';
-
-    $tokens = array(
-      'nid' => $node->nid,
-      'title' => 'New title',
-      'original' => 'Original title',
-      'original:nid' => $node->nid,
-    );
-    $this->assertTokens('node', array('node' => $node), $tokens);
-  }
-}
-
-/**
- * Test the profile tokens.
- */
-class TokenProfileTestCase extends TokenTestHelper {
-  private $account;
-
-  public static function getInfo() {
-    return array(
-      'name' => 'Profile token tests',
-      'description' => 'Test the profile tokens.',
-      'group' => 'Token',
-    );
-  }
-
-  public function setUp($modules = array()) {
-    $modules[] = 'profile';
-    parent::setUp($modules);
-    $this->account = $this->drupalCreateUser(array('administer users'));
-    $this->drupalLogin($this->account);
-  }
-
-  /**
-   * Test the profile tokens.
-   */
-  function testProfileTokens() {
-    $field_types = _profile_field_types();
-    foreach (array_keys($field_types) as $field_type) {
-      $field = array();
-      switch ($field_type) {
-        case 'checkbox':
-          $field['title'] = 'This is a checkbox';
-          break;
-        case 'selection':
-          $field['options'] = implode("\n", array('Red', 'Blue', 'Green'));
-          break;
-      }
-      $this->addProfileField($field_type, $field);
-    }
-
-    // Submit the profile fields for the user.
-    $edit = array(
-      'profile_textfield' => 'This is a text field',
-      'profile_textarea' => "First paragraph.\n\nSecond paragraph.",
-      'profile_checkbox' => TRUE,
-      'profile_selection' => 'Red',
-      'profile_list' => ' Drupal ,  Joomla ',
-      'profile_url' => 'http://www.example.com/',
-      'profile_date[month]' => 5,
-      'profile_date[day]' => 20,
-      'profile_date[year]' => 1984,
-    );
-    $this->drupalPost("user/{$this->account->uid}/edit/SimpleTest", $edit, 'Save');
-    $account = user_load($this->account->uid, TRUE);
-
-    // Test the profile token values.
-    $tokens = array(
-      'profile-textfield' => 'This is a text field',
-      'profile-textarea' => "<p>First paragraph.</p>\n<p>Second paragraph.</p>\n",
-      'profile-checkbox' => 'This is a checkbox',
-      'profile-selection' => 'Red',
-      'profile-list' => 'Drupal, Joomla',
-      'profile-url' => 'http://www.example.com/',
-      'profile-date' => format_date(453859200, 'medium', '', NULL),
-      'profile-date:raw' => '453859200',
-      'profile-date:custom:Y' => '1984',
-    );
-    $this->assertTokens('user', array('user' => $account), $tokens);
-
-    // 'Un-select' the checkbox and select profile fields.
-    $edit = array(
-      'profile_checkbox' => FALSE,
-      'profile_selection' => '0',
-    );
-    $this->drupalPost("user/{$this->account->uid}/edit/SimpleTest", $edit, 'Save');
-    $account = user_load($this->account->uid, TRUE);
-
-    // The checkbox and select profile tokens should no longer return a value.
-    $tokens = array(
-      'profile-checkbox' => NULL,
-      'profile-selection' => NULL,
-    );
-    $this->assertTokens('user', array('user' => $account), $tokens);
-  }
-
-  /**
-   * Add a profile field.
-   *
-   * @param $type
-   *   The profile field type.
-   * @param $field
-   *   (optional) An array of the profile field properties.
-   *
-   * @return
-   *   The saved profile field record object.
-   *
-   * @see drupal_form_submit()
-   */
-  function addProfileField($type, array $field = array()) {
-    $field += array(
-      'type' => $type,
-      'category' => 'SimpleTest',
-      'title' => $this->randomName(8),
-      'name' => 'profile_' . $type,
-      'explanation' => $this->randomName(50),
-      'autocomplete' => 0,
-      'required' => 0,
-      'register' => 0,
-    );
-    drupal_write_record('profile_field', $field);
-
-    // Verify the profile field was created successfully.
-    $saved_field = db_query("SELECT * FROM {profile_field} WHERE type = :type AND name = :name", array(':type' => $type, ':name' => $field['name']))->fetchObject();
-    if (empty($saved_field)) {
-      $this->fail(t('Failed to create profile field @name.', array('@name' => $saved_field->name)));
-    }
-
-    return $saved_field;
-  }
-}
-
-/**
- * Test the current page tokens.
- */
-class TokenCurrentPageTestCase extends TokenTestHelper {
-  public static function getInfo() {
-    return array(
-      'name' => 'Current page token tests',
-      'description' => 'Test the [current-page:*] tokens.',
-      'group' => 'Token',
-    );
-  }
-
-  function testCurrentPageTokens() {
-    $tokens = array(
-      '[current-page:title]' => t('Welcome to @site-name', array('@site-name' => variable_get('site_name', 'Drupal'))),
-      '[current-page:url]' => url('node', array('absolute' => TRUE)),
-      '[current-page:url:absolute]' => url('node', array('absolute' => TRUE)),
-      '[current-page:url:relative]' => url('node', array('absolute' => FALSE)),
-      '[current-page:url:path]' => 'node',
-      '[current-page:url:args:value:0]' => 'node',
-      '[current-page:url:args:value:1]' => NULL,
-      '[current-page:url:unaliased]' => url('node', array('absolute' => TRUE, 'alias' => TRUE)),
-      '[current-page:page-number]' => 1,
-      '[current-page:query:foo]' => NULL,
-      '[current-page:query:bar]' => NULL,
-      '[current-page:query:q]' => 'node',
-      // Deprecated tokens
-      '[current-page:arg:0]' => 'node',
-      '[current-page:arg:1]' => NULL,
-    );
-    $this->assertPageTokens('', $tokens);
-
-    $node = $this->drupalCreateNode(array('title' => 'Node title', 'path' => array('alias' => 'node-alias')));
-    $tokens = array(
-      '[current-page:title]' => 'Node title',
-      '[current-page:url]' => url("node/{$node->nid}", array('absolute' => TRUE)),
-      '[current-page:url:absolute]' => url("node/{$node->nid}", array('absolute' => TRUE)),
-      '[current-page:url:relative]' => url("node/{$node->nid}", array('absolute' => FALSE)),
-      '[current-page:url:alias]' => 'node-alias',
-      '[current-page:url:args:value:0]' => 'node-alias',
-      '[current-page:url:args:value:1]' => NULL,
-      '[current-page:url:unaliased]' => url("node/{$node->nid}", array('absolute' => TRUE, 'alias' => TRUE)),
-      '[current-page:url:unaliased:args:value:0]' => 'node',
-      '[current-page:url:unaliased:args:value:1]' => $node->nid,
-      '[current-page:url:unaliased:args:value:2]' => NULL,
-      '[current-page:page-number]' => 1,
-      '[current-page:query:foo]' => 'bar',
-      '[current-page:query:bar]' => NULL,
-      '[current-page:query:q]' => 'node/1',
-      // Deprecated tokens
-      '[current-page:arg:0]' => 'node',
-      '[current-page:arg:1]' => 1,
-      '[current-page:arg:2]' => NULL,
-    );
-    $this->assertPageTokens("node/{$node->nid}", $tokens, array(), array('url_options' => array('query' => array('foo' => 'bar'))));
-  }
-}
-
-class TokenArrayTestCase extends TokenTestHelper {
-  public static function getInfo() {
-    return array(
-      'name' => 'Array token tests',
-      'description' => 'Test the array tokens.',
-      'group' => 'Token',
-    );
-  }
-
-  function testArrayTokens() {
-    // Test a simple array.
-    $array = array(0 => 'a', 1 => 'b', 2 => 'c', 4 => 'd');
-    $tokens = array(
-      'first' => 'a',
-      'last' => 'd',
-      'value:0' => 'a',
-      'value:2' => 'c',
-      'count' => 4,
-      'keys' => '0, 1, 2, 4',
-      'keys:value:3' => '4',
-      'keys:join' => '0124',
-      'reversed' => 'd, c, b, a',
-      'reversed:keys' => '4, 2, 1, 0',
-      'join:/' => 'a/b/c/d',
-      'join' => 'abcd',
-      'join:, ' => 'a, b, c, d',
-      'join: ' => 'a b c d',
-    );
-    $this->assertTokens('array', array('array' => $array), $tokens);
-
-    // Test a mixed simple and render array.
-    // 2 => c, 0 => a, 4 => d, 1 => b
-    $array = array(
-      '#property' => 'value',
-      0 => 'a',
-      1 => array('#markup' => 'b', '#weight' => 0.01),
-      2 => array('#markup' => 'c', '#weight' => -10),
-      4 => array('#markup' => 'd', '#weight' => 0),
-    );
-    $tokens = array(
-      'first' => 'c',
-      'last' => 'b',
-      'value:0' => 'a',
-      'value:2' => 'c',
-      'count' => 4,
-      'keys' => '2, 0, 4, 1',
-      'keys:value:3' => '1',
-      'keys:join' => '2041',
-      'reversed' => 'b, d, a, c',
-      'reversed:keys' => '1, 4, 0, 2',
-      'join:/' => 'c/a/d/b',
-      'join' => 'cadb',
-      'join:, ' => 'c, a, d, b',
-      'join: ' => 'c a d b',
-    );
-    $this->assertTokens('array', array('array' => $array), $tokens);
-  }
-}
-
-class TokenRandomTestCase extends TokenTestHelper {
-  public static function getInfo() {
-    return array(
-      'name' => 'Random token tests',
-      'description' => 'Test the random tokens.',
-      'group' => 'Token',
-    );
-  }
-
-  function testRandomTokens() {
-    $tokens = array(
-      'number' => '[0-9]{1,}',
-      'hash:md5' => '[0-9a-f]{32}',
-      'hash:sha1' => '[0-9a-f]{40}',
-      'hash:sha256' => '[0-9a-f]{64}',
-      'hash:invalid-algo' => NULL,
-    );
-
-    $first_set = $this->assertTokens('random', array(), $tokens, array('regex' => TRUE));
-    $second_set = $this->assertTokens('random', array(), $tokens, array('regex' => TRUE));
-    foreach ($first_set as $token => $value) {
-      $this->assertNotIdentical($first_set[$token], $second_set[$token]);
-    }
-  }
-}
-
-/**
- * @todo Remove when http://drupal.org/node/1173706 is fixed.
- */
-class TokenDateTestCase extends TokenTestHelper {
-  public static function getInfo() {
-    return array(
-      'name' => 'Date token tests',
-      'description' => 'Test the date tokens.',
-      'group' => 'Token',
-    );
-  }
-
-  function testDateTokens() {
-    $tokens = array(
-      'token_test' => '1984',
-      'invalid_format' => NULL,
-    );
-
-    $this->assertTokens('date', array('date' => 453859200), $tokens);
-  }
-}
-
-class TokenFileTestCase extends TokenTestHelper {
-  public static function getInfo() {
-    return array(
-      'name' => 'File token tests',
-      'description' => 'Test the file tokens.',
-      'group' => 'Token',
-    );
-  }
-
-  function testFileTokens() {
-    // Create a test file object.
-    $file = new stdClass();
-    $file->fid = 1;
-    $file->filename = 'test.png';
-    $file->filesize = 100;
-    $file->uri = 'public://images/test.png';
-    $file->filemime = 'image/png';
-
-    $tokens = array(
-      'basename' => 'test.png',
-      'extension' => 'png',
-      'size-raw' => 100,
-    );
-    $this->assertTokens('file', array('file' => $file), $tokens);
-
-    // Test a file with no extension and a fake name.
-    $file->filename = 'Test PNG image';
-    $file->uri = 'public://images/test';
-
-    $tokens = array(
-      'basename' => 'test',
-      'extension' => '',
-      'size-raw' => 100,
-    );
-    $this->assertTokens('file', array('file' => $file), $tokens);
-  }
-}
-
-class TokenBlockTestCase extends TokenTestHelper {
-  public static function getInfo() {
-    return array(
-      'name' => 'Block token tests',
-      'description' => 'Test the block title token replacement.',
-      'group' => 'Token',
-    );
-  }
-
-  public function setUp($modules = array()) {
-    $modules[] = 'block';
-    parent::setUp($modules);
-
-    $this->admin_user = $this->drupalCreateUser(array('access content', 'administer blocks'));
-    $this->drupalLogin($this->admin_user);
-  }
-
-  public function testBlockTitleTokens() {
-    $edit['title'] = '[current-page:title] block title';
-    $edit['info'] = 'Test token title block';
-    $edit['body[value]'] = 'This is the test token title block.';
-    $edit['regions[bartik]'] = 'sidebar_first';
-    $this->drupalPost('admin/structure/block/add', $edit, 'Save block');
-
-    $this->drupalGet('node');
-    $this->assertText('Welcome to ' . variable_get('site_name', 'Drupal') . ' block title');
-
-    // Ensure that tokens are not double-escaped when output as a block title.
-    $node = $this->drupalCreateNode(array('title' => "Site's first node"));
-    $this->drupalGet('node/' . $node->nid);
-    // The apostraphe should only be escaped once via check_plain().
-    $this->assertRaw("Site&#039;s first node block title");
-  }
-}
diff --git a/token.tokens.inc b/token.tokens.inc
index 4ed44b8..f18cf0b 100644
--- a/token.tokens.inc
+++ b/token.tokens.inc
@@ -4,6 +4,18 @@
  * @file
  * Token callbacks for the token module.
  */
+use Drupal\Component\Utility\Crypt;
+use Drupal\Component\Utility\String;
+use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Render\Element;
+use Drupal\Component\Utility\Xss;
+use Drupal\Core\Url;
+use Drupal\field\FieldConfigInterface;
+use Drupal\field\FieldStorageConfigInterface;
+use Drupal\system\Entity\Menu;
+use Drupal\user\UserInterface;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 
 /**
  * Implements hook_token_info_alter().
@@ -23,7 +35,9 @@ function token_token_info_alter(&$info) {
 
   // The [file:size] may not always return in kilobytes.
   // @todo Remove when http://drupal.org/node/1193044 is fixed.
-  $info['tokens']['file']['size']['description'] = t('The size of the file.');
+  if (!empty($info['tokens']['file']['size'])) {
+    $info['tokens']['file']['size']['description'] = t('The size of the file.');
+  }
 
   // Remove deprecated tokens from being listed.
   unset($info['tokens']['node']['tnid']);
@@ -31,33 +45,36 @@ function token_token_info_alter(&$info) {
   unset($info['tokens']['node']['type-name']);
 
   // Support 'url' type tokens for core tokens.
-  if (isset($info['tokens']['comment']['url']) && module_exists('comment')) {
+  if (isset($info['tokens']['comment']['url']) && \Drupal::moduleHandler()->moduleExists('comment')) {
     $info['tokens']['comment']['url']['type'] = 'url';
   }
-  $info['tokens']['node']['url']['type'] = 'url';
-  if (isset($info['tokens']['term']['url']) && module_exists('taxonomy')) {
+  if (isset($info['tokens']['node']['url']) && \Drupal::moduleHandler()->moduleExists('node')) {
+    $info['tokens']['node']['url']['type'] = 'url';
+  }
+  if (isset($info['tokens']['term']['url']) && \Drupal::moduleHandler()->moduleExists('taxonomy')) {
     $info['tokens']['term']['url']['type'] = 'url';
   }
   $info['tokens']['user']['url']['type'] = 'url';
 
   // Add [token:url] tokens for any URI-able entities.
-  $entities = entity_get_info();
+  $entities = \Drupal::entityManager()->getDefinitions();
   foreach ($entities as $entity => $entity_info) {
-    if (!isset($entity_info['token type'])) {
+    /* @var \Drupal\Core\Entity\EntityType $entity_info */
+    if (!$entity_info->get('token type')) {
       continue;
     }
 
-    $token_type = $entity_info['token type'];
+    $token_type = $entity_info->get('token type');
     if (!isset($info['types'][$token_type]) || !isset($info['tokens'][$token_type])) {
       continue;
     }
 
     // Add [entity:url] tokens if they do not already exist.
     // @todo Support entity:label
-    if (!isset($info['tokens'][$token_type]['url']) && !empty($entity_info['uri callback'])) {
+    if (!isset($info['tokens'][$token_type]['url'])) {
       $info['tokens'][$token_type]['url'] = array(
         'name' => t('URL'),
-        'description' => t('The URL of the @entity.', array('@entity' => drupal_strtolower($entity_info['label']))),
+        'description' => t('The URL of the @entity.', array('@entity' => Unicode::strtolower($entity_info->getLabel()))),
         'module' => 'token',
         'type' => 'url',
       );
@@ -66,8 +83,8 @@ function token_token_info_alter(&$info) {
     // Add [entity:original] tokens if they do not already exist.
     if (!isset($info['tokens'][$token_type]['original'])) {
       $info['tokens'][$token_type]['original'] = array(
-        'name' => t('Original @entity', array('@entity' => drupal_strtolower($entity_info['label']))),
-        'description' => t('The original @entity data if the @entity is being updated or saved.', array('@entity' => drupal_strtolower($entity_info['label']))),
+        'name' => t('Original @entity', array('@entity' => Unicode::strtolower($entity_info->getLabel()))),
+        'description' => t('The original @entity data if the @entity is being updated or saved.', array('@entity' => Unicode::strtolower($entity_info->getLabel()))),
         'module' => 'token',
         'type' => $token_type,
       );
@@ -76,11 +93,12 @@ function token_token_info_alter(&$info) {
 
   // Add support for custom date formats.
   // @todo Remove when http://drupal.org/node/1173706 is fixed.
-  $date_format_types = system_get_date_types();
+  $date_format_types = \Drupal::entityManager()->getStorage('date_format')->loadMultiple();
   foreach ($date_format_types as $date_format_type => $date_format_type_info) {
+    /* @var \Drupal\system\Entity\DateFormat $date_format_type_info */
     if (!isset($info['tokens']['date'][$date_format_type])) {
       $info['tokens']['date'][$date_format_type] = array(
-        'name' => $date_format_type_info['title'],
+        'name' => String::checkPlain($date_format_type_info->label()),
         'description' => t("A date in '@type' format. (%date)", array('@type' => $date_format_type, '%date' => format_date(REQUEST_TIME, $date_format_type))),
         'module' => 'token',
       );
@@ -137,7 +155,7 @@ function token_token_info() {
   );
 
   // Taxonomy term and vocabulary tokens.
-  if (module_exists('taxonomy')) {
+  if (\Drupal::moduleHandler()->moduleExists('taxonomy')) {
     $info['tokens']['term']['edit-url'] = array(
       'name' => t('Edit URL'),
       'description' => t("The URL of the taxonomy term's edit page."),
@@ -193,13 +211,6 @@ function token_token_info() {
     'restricted' => TRUE,
     // 'type' => 'url',
   );
-  if (variable_get('user_pictures', 0)) {
-    $info['tokens']['user']['picture'] = array(
-      'name' => t('Picture'),
-      'description' => t('The picture of the user.'),
-      'type' => 'file',
-    );
-  }
   $info['tokens']['user']['roles'] = array(
     'name' => t('Roles'),
     'description' => t('The user roles associated with the user account.'),
@@ -364,7 +375,7 @@ function token_token_info() {
 /**
  * Implements hook_tokens().
  */
-function token_tokens($type, $tokens, array $data = array(), array $options = array()) {
+function token_tokens($type, array $tokens, array $data = array(), array $options = array()) {
   $replacements = array();
 
   $url_options = array('absolute' => TRUE);
@@ -383,7 +394,7 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
     $date = !empty($data['date']) ? $data['date'] : REQUEST_TIME;
 
     // @todo Remove when http://drupal.org/node/1173706 is fixed.
-    $date_format_types = system_get_date_types();
+    $date_format_types = \Drupal::entityManager()->getStorage('date_format')->loadMultiple();
     foreach ($tokens as $name => $original) {
       if (isset($date_format_types[$name]) && _token_module('date', $name) == 'token') {
         $replacements[$original] = format_date($date, $name, '', NULL, $language_code);
@@ -394,78 +405,76 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
   // Current date tokens.
   // @todo Remove when http://drupal.org/node/943028 is fixed.
   if ($type == 'current-date') {
-    $replacements += token_generate('date', $tokens, array('date' => REQUEST_TIME), $options);
+    $replacements += \Drupal::token()->generate('date', $tokens, array('date' => REQUEST_TIME), $options);
   }
 
   // Comment tokens.
   if ($type == 'comment' && !empty($data['comment'])) {
+    /* @var \Drupal\comment\CommentInterface $comment */
     $comment = $data['comment'];
 
     // Chained token relationships.
-    if (($url_tokens = token_find_with_prefix($tokens, 'url'))) {
-      $replacements += token_generate('url', $url_tokens, entity_uri('comment', $comment), $options);
+    if (($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url'))) {
+      // Add fragment to url options.
+      $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $comment->urlInfo('canonical', ['fragment' => "comment-{$comment->id()}"])), $options);
     }
   }
 
   // Node tokens.
   if ($type == 'node' && !empty($data['node'])) {
+    /* @var \Drupal\node\NodeInterface $node */
     $node = $data['node'];
 
     foreach ($tokens as $name => $original) {
       switch ($name) {
-        case 'source':
-          if (!empty($node->tnid) && $source_node = node_load($node->tnid)) {
-            $title = $source_node->title;
-            $replacements[$original] = $sanitize ? filter_xss($title) : $title;
-          }
-          break;
         case 'log':
-          $replacements[$original] = $sanitize ? filter_xss($node->log) : $node->log;
+          $replacements[$original] = $sanitize ? Xss::filter($node->revision_log->value) : $node->revision_log->value;
           break;
         case 'content-type':
-          $type_name = node_type_get_name($node);
-          $replacements[$original] = $sanitize ? check_plain($type_name) : $type_name;
+          $type_name = \Drupal::entityManager()->getStorage('node_type')->load($node->getType())->label();
+          $replacements[$original] = $sanitize ? String::checkPlain($type_name) : $type_name;
           break;
       }
     }
 
     // Chained token relationships.
-    if (!empty($node->tnid) && ($source_tokens = token_find_with_prefix($tokens, 'source')) && $source_node = node_load($node->tnid)) {
-      $replacements += token_generate('node', $source_tokens, array('node' => $source_node), $options);
+    if (!empty($node->tnid) && ($source_tokens = \Drupal::token()->findWithPrefix($tokens, 'source')) && $source_node = node_load($node->tnid)) {
+      $replacements += \Drupal::token()->generate('node', $source_tokens, array('node' => $source_node), $options);
     }
-    if (($node_type_tokens = token_find_with_prefix($tokens, 'content-type')) && $node_type = node_type_load($node->type)) {
-      $replacements += token_generate('content-type', $node_type_tokens, array('node_type' => $node_type), $options);
+    if (($node_type_tokens = \Drupal::token()->findWithPrefix($tokens, 'content-type')) && $node_type = node_type_load($node->bundle())) {
+      $replacements += \Drupal::token()->generate('content-type', $node_type_tokens, array('node_type' => $node_type), $options);
     }
-    if (($url_tokens = token_find_with_prefix($tokens, 'url'))) {
-      $replacements += token_generate('url', $url_tokens, entity_uri('node', $node), $options);
+    if (($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url'))) {
+      $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $node->urlInfo()), $options);
     }
   }
 
   // Content type tokens.
   if ($type == 'content-type' && !empty($data['node_type'])) {
+    /* @var \Drupal\node\NodeTypeInterface $node_type */
     $node_type = $data['node_type'];
 
     foreach ($tokens as $name => $original) {
       switch ($name) {
         case 'name':
-          $replacements[$original] = $sanitize ? check_plain($node_type->name) : $node_type->name;
+          $replacements[$original] = $sanitize ? String::checkPlain($node_type->label()) : $node_type->label();
           break;
         case 'machine-name':
           // This is a machine name so does not ever need to be sanitized.
-          $replacements[$original] = $node_type->type;
+          $replacements[$original] = $node_type->id();
           break;
         case 'description':
-          $replacements[$original] = $sanitize ? filter_xss($node_type->description) : $node_type->description;
+          $replacements[$original] = $sanitize ? Xss::filter($node_type->getDescription()) : $node_type->getDescription();
           break;
         case 'node-count':
-          $query = db_select('node');
-          $query->condition('type', $node_type->type);
-          $query->addTag('node_type_node_count');
-          $count = $query->countQuery()->execute()->fetchField();
+          $count = \Drupal::entityQueryAggregate('node')
+            ->aggregate('nid', 'COUNT')
+            ->condition('type', $node_type->id())
+            ->execute();
           $replacements[$original] = (int) $count;
           break;
         case 'edit-url':
-          $replacements[$original] = url("admin/structure/types/manage/{$node_type->type}", $url_options);
+          $replacements[$original] = $node_type->url('edit-form', $url_options);
           break;
       }
     }
@@ -473,45 +482,49 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
 
   // Taxonomy term tokens.
   if ($type == 'term' && !empty($data['term'])) {
+    /* @var \Drupal\taxonomy\TermInterface $term */
     $term = $data['term'];
 
+    /** @var \Drupal\taxonomy\TermStorageInterface $term_storage */
+    $term_storage = \Drupal::entityManager()->getStorage('taxonomy_term');
+
     foreach ($tokens as $name => $original) {
       switch ($name) {
         case 'edit-url':
-          $replacements[$original] = url("taxonomy/term/{$term->tid}/edit", $url_options);
+          $replacements[$original] = \Drupal::url('entity.taxonomy_term.edit_form', ['taxonomy_term' => $term->id()], $url_options);
           break;
 
         case 'parents':
-          if ($parents = token_taxonomy_term_load_all_parents($term->tid)) {
+          if ($parents = token_taxonomy_term_load_all_parents($term->id())) {
             $replacements[$original] = token_render_array($parents, $options);
           }
           break;
 
         case 'root':
-          $parents = taxonomy_get_parents_all($term->tid);
+          $parents = $term_storage->loadAllParents($term->id());
           $root_term = end($parents);
-          if ($root_term->tid != $term->tid) {
-            $replacements[$original] = $sanitize ? check_plain($root_term->name) : $root_term->name;
+          if ($root_term->id() != $term->id()) {
+            $replacements[$original] = $sanitize ? String::checkPlain($root_term->label()) : $root_term->label();
           }
           break;
       }
     }
 
     // Chained token relationships.
-    if (($url_tokens = token_find_with_prefix($tokens, 'url'))) {
-      $replacements += token_generate('url', $url_tokens, entity_uri('taxonomy_term', $term), $options);
+    if (($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url'))) {
+      $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $term->urlInfo()), $options);
     }
     // [term:parents:*] chained tokens.
-    if ($parents_tokens = token_find_with_prefix($tokens, 'parents')) {
-      if ($parents = token_taxonomy_term_load_all_parents($term->tid)) {
-        $replacements += token_generate('array', $parents_tokens, array('array' => $parents), $options);
+    if ($parents_tokens = \Drupal::token()->findWithPrefix($tokens, 'parents')) {
+      if ($parents = token_taxonomy_term_load_all_parents($term->id())) {
+        $replacements += \Drupal::token()->generate('array', $parents_tokens, array('array' => $parents), $options);
       }
     }
-    if ($root_tokens = token_find_with_prefix($tokens, 'root')) {
-      $parents = taxonomy_get_parents_all($term->tid);
+    if ($root_tokens = \Drupal::token()->findWithPrefix($tokens, 'root')) {
+      $parents = $term_storage->loadAllParents($term->id());
       $root_term = end($parents);
-      if ($root_term->tid != $term->tid) {
-        $replacements += token_generate('term', $root_tokens, array('term' => $root_term), $options);
+      if ($root_term->tid != $term->id()) {
+        $replacements += \Drupal::token()->generate('term', $root_tokens, array('term' => $root_term), $options);
       }
     }
   }
@@ -524,10 +537,10 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
       switch ($name) {
         case 'machine-name':
           // This is a machine name so does not ever need to be sanitized.
-          $replacements[$original] = $vocabulary->machine_name;
+          $replacements[$original] = $vocabulary->id();
           break;
         case 'edit-url':
-          $replacements[$original] = url("admin/structure/taxonomy/{$vocabulary->machine_name}/edit", $url_options);
+          $replacements[$original] = \Drupal::url('entity.taxonomy_vocabulary.edit_form', ['taxonomy_vocabulary' => $vocabulary->id()], $url_options);
           break;
       }
     }
@@ -540,15 +553,15 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
     foreach ($tokens as $name => $original) {
       switch ($name) {
         case 'basename':
-          $basename = pathinfo($file->uri, PATHINFO_BASENAME);
-          $replacements[$original] = $sanitize ? check_plain($basename) : $basename;
+          $basename = pathinfo($file->uri->value, PATHINFO_BASENAME);
+          $replacements[$original] = $sanitize ? String::checkPlain($basename) : $basename;
           break;
         case 'extension':
-          $extension = pathinfo($file->uri, PATHINFO_EXTENSION);
-          $replacements[$original] = $sanitize ? check_plain($extension) : $extension;
+          $extension = pathinfo($file->uri->value, PATHINFO_EXTENSION);
+          $replacements[$original] = $sanitize ? String::checkPlain($extension) : $extension;
           break;
         case 'size-raw':
-          $replacements[$original] = (int) $file->filesize;
+          $replacements[$original] = (int) $file->filesize->value;
           break;
       }
     }
@@ -556,38 +569,42 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
 
   // User tokens.
   if ($type == 'user' && !empty($data['user'])) {
+    /* @var \Drupal\user\UserInterface $account */
     $account = $data['user'];
 
     foreach ($tokens as $name => $original) {
       switch ($name) {
         case 'picture':
-          if (variable_get('user_pictures', 0)) {
-            $replacements[$original] = theme('user_picture', array('account' => $account));
+          if ($account instanceof UserInterface && $account->hasField('user_picture')) {
+            /** @var \Drupal\Core\Render\RendererInterface $renderer */
+            $renderer = \Drupal::service('renderer');
+            $output = [
+              '#theme' => 'user_picture',
+              '#account' => $account,
+            ];
+            $replacements[$original] = $renderer->render($output);
           }
           break;
+
         case 'roles':
-          // The roles array may be set from checkbox values so ensure it always
-          // has 'proper' data with the role names.
-          $roles = array_intersect_key(user_roles(), $account->roles);
-          $replacements[$original] = token_render_array($roles, $options);
+          $roles = $account->getRoles();
+          $roles_names = array_combine($roles, $roles);
+          $replacements[$original] = token_render_array($roles_names, $options);
           break;
       }
     }
 
     // Chained token relationships.
-    if (variable_get('user_pictures', 0) && !empty($account->picture) && ($picture_tokens = token_find_with_prefix($tokens, 'picture'))) {
-      // @todo Remove when core bug http://drupal.org/node/978028 is fixed.
-      $account->picture->description = '';
-      $replacements += token_generate('file', $picture_tokens, array('file' => $account->picture), $options);
+    if ($account instanceof UserInterface && $account->hasField('user_picture') && ($picture_tokens = \Drupal::token()->findWithPrefix($tokens, 'picture'))) {
+      $replacements += \Drupal::token()->generate('file', $picture_tokens, array('file' => $account->user_picture->entity), $options);
     }
-    if ($url_tokens = token_find_with_prefix($tokens, 'url')) {
-      $replacements += token_generate('url', $url_tokens, entity_uri('user', $account), $options);
+    if ($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) {
+      $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $account->urlInfo()), $options);
     }
-    if ($role_tokens = token_find_with_prefix($tokens, 'roles')) {
-      // The roles array may be set from checkbox values so ensure it always
-      // has 'proper' data with the role names.
-      $roles = array_intersect_key(user_roles(), $account->roles);
-      $replacements += token_generate('array', $role_tokens, array('array' => $roles), $options);
+    if ($role_tokens = \Drupal::token()->findWithPrefix($tokens, 'roles')) {
+      $roles = $account->getRoles();
+      $roles_names = array_combine($roles, $roles);
+      $replacements += \Drupal::token()->generate('array', $role_tokens, array('array' => $roles_names), $options);
     }
   }
 
@@ -596,8 +613,8 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
     foreach ($tokens as $name => $original) {
       switch ($name) {
         case 'ip-address':
-          $ip = ip_address();
-          $replacements[$original] = $sanitize ? check_plain($ip) : $ip;
+          $ip = \Drupal::request()->getClientIp();
+          $replacements[$original] = $sanitize ? String::checkPlain($ip) : $ip;
           break;
       }
     }
@@ -605,75 +622,74 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
 
   // Menu link tokens.
   if ($type == 'menu-link' && !empty($data['menu-link'])) {
-    $link = (array) $data['menu-link'];
-
-    if (!isset($link['title'])) {
-      // Re-load the link if it was not loaded via token_menu_link_load().
-      $link = token_menu_link_load($link['mlid']);
-    }
+    $link = $data['menu-link'];
 
     foreach ($tokens as $name => $original) {
       switch ($name) {
         case 'mlid':
-          $replacements[$original] = $link['mlid'];
+          $replacements[$original] = $link->id();
           break;
         case 'title':
-          $replacements[$original] = $sanitize ? check_plain($link['title']) : $link['title'];
+          $replacements[$original] = $sanitize ? String::checkPlain($link->label()) : $link->label();
           break;
         case 'url':
-          $replacements[$original] = url($link['href'], $url_options);
+          $replacements[$original] = $link->getUrlObject()->setAbsolute()->toString();
           break;
         case 'parent':
-          if (!empty($link['plid']) && $parent = token_menu_link_load($link['plid'])) {
-            $replacements[$original] = $sanitize ? check_plain($parent['title']) : $parent['title'];
+          if (!empty($link->plid) && $parent = menu_link_load($link->plid)) {
+            $replacements[$original] = $sanitize ? String::checkPlain($parent->label()) : $parent->label();
           }
           break;
         case 'parents':
-          if ($parents = token_menu_link_load_all_parents($link['mlid'])) {
+          if ($parents = token_menu_link_load_all_parents($link->id())) {
             $replacements[$original] = token_render_array($parents, $options);
           }
           break;
         case 'root';
-          if (!empty($link['p1']) && $link['p1'] != $link['mlid'] && $root = token_menu_link_load($link['p1'])) {
-            $replacements[$original] = $sanitize ? check_plain($root['title']) : $root['title'];
+          if (!empty($link->p1) && $link->p1 != $link->id() && $root = menu_link_load($link->p1)) {
+            $replacements[$original] = $sanitize ? String::checkPlain($root->label()) : $root->label();
           }
           break;
       }
     }
 
     // Chained token relationships.
-    if (!empty($link['plid']) && ($source_tokens = token_find_with_prefix($tokens, 'parent')) && $parent = token_menu_link_load($link['plid'])) {
-      $replacements += token_generate('menu-link', $source_tokens, array('menu-link' => $parent), $options);
+    if (!empty($link->plid) && ($source_tokens = \Drupal::token()->findWithPrefix($tokens, 'parent')) && $parent = menu_link_load($link->plid)) {
+      $replacements += \Drupal::token()->generate('menu-link', $source_tokens, array('menu-link' => $parent), $options);
     }
     // [menu-link:parents:*] chained tokens.
-    if ($parents_tokens = token_find_with_prefix($tokens, 'parents')) {
-      if ($parents = token_menu_link_load_all_parents($link['mlid'])) {
-        $replacements += token_generate('array', $parents_tokens, array('array' => $parents), $options);
+    if ($parents_tokens = \Drupal::token()->findWithPrefix($tokens, 'parents')) {
+      if ($parents = token_menu_link_load_all_parents($link->id())) {
+        $replacements += \Drupal::token()->generate('array', $parents_tokens, array('array' => $parents), $options);
       }
     }
-    if (!empty($link['p1']) && $link['p1'] != $link['mlid'] && ($root_tokens = token_find_with_prefix($tokens, 'root')) && $root = token_menu_link_load($link['p1'])) {
-      $replacements += token_generate('menu-link', $root_tokens, array('menu-link' => $root), $options);
+    if (!empty($link->p1) && $link->p1 != $link->id() && ($root_tokens = \Drupal::token()->findWithPrefix($tokens, 'root')) && $root = menu_link_load($link->p1)) {
+      $replacements += \Drupal::token()->generate('menu-link', $root_tokens, array('menu-link' => $root), $options);
     }
-    if ($url_tokens = token_find_with_prefix($tokens, 'url')) {
-      $replacements += token_generate('url', $url_tokens, array('path' => $link['href']), $options);
+    if ($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) {
+      $url = new Url($link->route_name, $link->route_parameters);
+      $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $url), $options);
     }
+
   }
 
   // Current page tokens.
   if ($type == 'current-page') {
-    $current_path = current_path();
-
     foreach ($tokens as $name => $original) {
       switch ($name) {
         case 'title':
-          $title = drupal_get_title();
-          $replacements[$original] = $sanitize ? $title : decode_entities($title);
+          $request = \Drupal::request();
+          $route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT);
+          $title = \Drupal::service('title_resolver')->getTitle($request, $route);
+          $replacements[$original] = $sanitize ? $title : String::decodeEntities($title);
           break;
         case 'url':
-          $replacements[$original] = url($current_path, $url_options);
+          // Strip the query string from the result of Request::getUri().
+          list($uri) = explode('?', \Drupal::request()->getUri());
+          $replacements[$original] = Url::fromUri($uri, $url_options)->toString();
           break;
         case 'page-number':
-          if ($page = filter_input(INPUT_GET, 'page')) {
+          if ($page = \Drupal::request()->query->get('page')) {
             // @see PagerDefault::execute()
             $pager_page_array = explode(',', $page);
             $page = $pager_page_array[0];
@@ -685,119 +701,124 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
 
     // @deprecated
     // [current-page:arg] dynamic tokens.
-    if ($arg_tokens = token_find_with_prefix($tokens, 'arg')) {
+    if ($arg_tokens = \Drupal::token()->findWithPrefix($tokens, 'arg')) {
+      $path = \Drupal::request()->attributes->get('_system_path');
+      // Make sure its a system path.
+      $path = \Drupal::service('path.alias_manager')->getPathByAlias($path);
       foreach ($arg_tokens as $name => $original) {
-        if (is_numeric($name) && ($arg = arg($name)) && isset($arg)) {
-          $replacements[$original] = $sanitize ? check_plain($arg) : $arg;
+        $parts = explode('/', $path);
+        if (is_numeric($name) && isset($parts[$name])) {
+          $replacements[$original] = $sanitize ? String::checkPlain($parts[$name]) : $parts[$name];
         }
       }
     }
 
     // [current-page:query] dynamic tokens.
-    if ($query_tokens = token_find_with_prefix($tokens, 'query')) {
+    if ($query_tokens = \Drupal::token()->findWithPrefix($tokens, 'query')) {
       foreach ($query_tokens as $name => $original) {
         // @todo Should this use filter_input()?
-        if (isset($_GET[$name])) {
-          $replacements[$original] = $sanitize ? check_plain($_GET[$name]) : $_GET[$name];
+        if (\Drupal::request()->query->has($name)) {
+          $value = \Drupal::request()->query->get($name);
+          $replacements[$original] = $sanitize ? String::checkPlain($value) : $value;
+        }
+        elseif ($name == 'q') {
+          $replacements[$original] = \Drupal::request()->attributes->get('_system_path');
         }
       }
     }
 
     // Chained token relationships.
-    if ($url_tokens = token_find_with_prefix($tokens, 'url')) {
-      $replacements += token_generate('url', $url_tokens, array('path' => $current_path), $options);
+    if ($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) {
+      $request = \Drupal::request();
+      $url = new Url($request->attributes->get(RouteObjectInterface::ROUTE_OBJECT), $request->attributes->get('_raw_variables')->all());
+      $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $url), $options);
     }
   }
 
   // URL tokens.
-  if ($type == 'url' && !empty($data['path'])) {
-    $path = $data['path'];
-
-    if (isset($data['options'])) {
-      // Merge in the URL options if available.
-      $url_options = $data['options'] + $url_options;
-    }
+  if ($type == 'url' && !empty($data['url'])) {
+    /** @var \Drupal\Core\Url $url */
+    $url = $data['url'];
+    // To retrieve the correct path, modify a copy of the Url object.
+    $path_url = clone $url;
+    $path = $path_url->setAbsolute(FALSE)->setOption('fragment', NULL)->toString();
+    $path = ltrim($path, '/');
 
     foreach ($tokens as $name => $original) {
       switch ($name) {
         case 'path':
-          $value = empty($url_options['alias']) ? drupal_get_path_alias($path, $language_code) : $path;
-          $replacements[$original] = $sanitize ? check_plain($value) : $value;
+          $value = !($url->getOption('alias')) ? \Drupal::service('path.alias_manager')->getAliasByPath($path, $language_code) : $path;
+          $replacements[$original] = $sanitize ? String::checkPlain($value) : $value;
           break;
         case 'alias':
           // @deprecated
-          $alias = drupal_get_path_alias($path, $language_code);
-          $replacements[$original] = $sanitize ? check_plain($alias) : $alias;
+          $alias = \Drupal::service('path.alias_manager')->getAliasByPath($path, $language_code);
+          $replacements[$original] = $sanitize ? String::checkPlain($alias) : $alias;
           break;
         case 'absolute':
-          $replacements[$original] = url($path, $url_options);
+          $replacements[$original] = $url->setAbsolute()->toString();
           break;
         case 'relative':
-          $replacements[$original] = url($path, array('absolute' => FALSE) + $url_options);
+          $replacements[$original] = $url->setAbsolute(FALSE)->toString();
           break;
         case 'brief':
-          $replacements[$original] = preg_replace(array('!^https?://!', '!/$!'), '', url($path, $url_options));
+          $replacements[$original] = preg_replace(array('!^https?://!', '!/$!'), '', $url->setAbsolute()->toString());
           break;
         case 'unaliased':
-          $replacements[$original] = url($path, array('alias' => TRUE) + $url_options);
+          $replacements[$original] = $url->setAbsolute()->setOption('alias', TRUE)->toString();
           break;
         case 'args':
-          $value = empty($url_options['alias']) ? drupal_get_path_alias($path, $language_code) : $path;
-          $replacements[$original] = token_render_array(arg(NULL, $value), $options);
+          $value = !($url->getOption('alias')) ? \Drupal::service('path.alias_manager')->getAliasByPath($path, $language_code) : $path;
+          $replacements[$original] = token_render_array(explode('/', $value), $options);
           break;
+
       }
     }
 
-    // [url:arg:*] chained tokens.
-    if ($arg_tokens = token_find_with_prefix($tokens, 'args')) {
-      $value = empty($url_options['alias']) ? drupal_get_path_alias($path, $language_code) : $path;
-      $replacements += token_generate('array', $arg_tokens, array('array' => arg(NULL, $value)), $options);
+    // [url:args:*] chained tokens.
+    if ($arg_tokens = \Drupal::token()->findWithPrefix($tokens, 'args')) {
+      $value = !($url->getOption('alias')) ? \Drupal::service('path.alias_manager')->getAliasByPath($path, $language_code) : $path;
+      $value = ltrim($value, '/');
+      $replacements += \Drupal::token()->generate('array', $arg_tokens, array('array' => explode('/', $value)), $options);
     }
 
     // [url:unaliased:*] chained tokens.
-    if ($unaliased_tokens = token_find_with_prefix($tokens, 'unaliased')) {
-      $unaliased_token_data['path'] = $path;
-      $unaliased_token_data['options'] = isset($data['options']) ? $data['options'] : array();
-      $unaliased_token_data['options']['alias'] = TRUE;
-      $replacements += token_generate('url', $unaliased_tokens, $unaliased_token_data, $options);
+    if ($unaliased_tokens = \Drupal::token()->findWithPrefix($tokens, 'unaliased')) {
+      $url->setOption('alias', TRUE);
+      $replacements += \Drupal::token()->generate('url', $unaliased_tokens, array('url' => $url), $options);
     }
   }
 
   // Entity tokens.
   if (!empty($data[$type]) && $entity_type = token_get_entity_mapping('token', $type)) {
+    /* @var \Drupal\Core\Entity\EntityInterface $entity */
     $entity = $data[$type];
 
-    // Sometimes taxonomy terms are not properly loaded.
-    // @see http://drupal.org/node/870528
-    if ($entity_type == 'taxonomy_term' && !isset($entity->vocabulary_machine_name)) {
-      $entity->vocabulary_machine_name = db_query("SELECT machine_name FROM {taxonomy_vocabulary} WHERE vid = :vid", array(':vid' => $entity->vid))->fetchField();
-    }
-
     foreach ($tokens as $name => $original) {
       switch ($name) {
         case 'url':
-          if (_token_module($type, 'url') == 'token' && $uri = entity_uri($entity_type, $entity)) {
-            $replacements[$original] = url($uri['path'], $uri['options']);
+          if (_token_module($type, 'url') == 'token' && $url = $entity->url()) {
+            $replacements[$original] = $url;
           }
           break;
 
         case 'original':
           if (_token_module($type, 'original') == 'token' && !empty($entity->original)) {
-            $label = entity_label($entity_type, $entity->original);
-            $replacements[$original] = $sanitize ? check_plain($label) : $label;
+            $label = $entity->original->label();
+            $replacements[$original] = $sanitize ? String::checkPlain($label) : $label;
           }
           break;
       }
     }
 
     // [entity:url:*] chained tokens.
-    if (($url_tokens = token_find_with_prefix($tokens, 'url')) && _token_module($type, 'url') == 'token') {
-      $replacements += token_generate('url', $url_tokens, entity_uri($entity_type, $entity), $options);
+    if (($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) && _token_module($type, 'url') == 'token') {
+      $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $entity->urlInfo()), $options);
     }
 
     // [entity:original:*] chained tokens.
-    if (($original_tokens = token_find_with_prefix($tokens, 'original')) && _token_module($type, 'original') == 'token' && !empty($entity->original)) {
-      $replacements += token_generate($type, $original_tokens, array($type => $entity->original), $options);
+    if (($original_tokens = \Drupal::token()->findWithPrefix($tokens, 'original')) && _token_module($type, 'original') == 'token' && !empty($entity->original)) {
+      $replacements += \Drupal::token()->generate($type, $original_tokens, array($type => $entity->original), $options);
     }
 
     // Pass through to an generic 'entity' token type generation.
@@ -807,7 +828,7 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
       'token_type' => $type,
     );
     // @todo Investigate passing through more data like everything from entity_extract_ids().
-    $replacements += token_generate('entity', $tokens, $entity_data, $options);
+    $replacements += \Drupal::token()->generate('entity', $tokens, $entity_data, $options);
   }
 
   // Array tokens.
@@ -822,12 +843,12 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
         case 'first':
           $value = $array[$keys[0]];
           $value = is_array($value) ? render($value) : (string) $value;
-          $replacements[$original] = $sanitize ? check_plain($value) : $value;
+          $replacements[$original] = $sanitize ? String::checkPlain($value) : $value;
           break;
         case 'last':
           $value = $array[$keys[count($keys) - 1]];
           $value = is_array($value) ? render($value) : (string) $value;
-          $replacements[$original] = $sanitize ? check_plain($value) : $value;
+          $replacements[$original] = $sanitize ? String::checkPlain($value) : $value;
           break;
         case 'count':
           $replacements[$original] = count($keys);
@@ -846,7 +867,7 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
     }
 
     // [array:value:*] dynamic tokens.
-    if ($value_tokens = token_find_with_prefix($tokens, 'value')) {
+    if ($value_tokens = \Drupal::token()->findWithPrefix($tokens, 'value')) {
       foreach ($value_tokens as $key => $original) {
         if ($key[0] !== '#' && isset($array[$key])) {
           $replacements[$original] = token_render_array_value($array[$key], $options);
@@ -855,20 +876,20 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
     }
 
     // [array:join:*] dynamic tokens.
-    if ($join_tokens = token_find_with_prefix($tokens, 'join')) {
+    if ($join_tokens = \Drupal::token()->findWithPrefix($tokens, 'join')) {
       foreach ($join_tokens as $join => $original) {
         $replacements[$original] = token_render_array($array, array('join' => $join) + $options);
       }
     }
 
     // [array:keys:*] chained tokens.
-    if ($key_tokens = token_find_with_prefix($tokens, 'keys')) {
-      $replacements += token_generate('array', $key_tokens, array('array' => $keys), $options);
+    if ($key_tokens = \Drupal::token()->findWithPrefix($tokens, 'keys')) {
+      $replacements += \Drupal::token()->generate('array', $key_tokens, array('array' => $keys), $options);
     }
 
     // [array:reversed:*] chained tokens.
-    if ($reversed_tokens = token_find_with_prefix($tokens, 'reversed')) {
-      $replacements += token_generate('array', $reversed_tokens, array('array' => array_reverse($array, TRUE)), array('array sort' => FALSE) + $options);
+    if ($reversed_tokens = \Drupal::token()->findWithPrefix($tokens, 'reversed')) {
+      $replacements += \Drupal::token()->generate('array', $reversed_tokens, array('array' => array_reverse($array, TRUE)), array('array sort' => FALSE) + $options);
     }
 
     // @todo Handle if the array values are not strings and could be chained.
@@ -885,11 +906,11 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
     }
 
     // [custom:hash:*] dynamic token.
-    if ($hash_tokens = token_find_with_prefix($tokens, 'hash')) {
+    if ($hash_tokens = \Drupal::token()->findWithPrefix($tokens, 'hash')) {
       $algos = hash_algos();
       foreach ($hash_tokens as $name => $original) {
         if (in_array($name, $algos)) {
-          $replacements[$original] = hash($name, drupal_random_bytes(55));
+          $replacements[$original] = hash($name, Crypt::randomBytes(55));
         }
       }
     }
@@ -900,7 +921,7 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
   if (empty($data[$type]) && ($entity_type = token_get_entity_mapping('token', $type)) && $entity_type != $type && !empty($data[$entity_type]) && empty($options['recursive'])) {
     $data[$type] = $data[$entity_type];
     $options['recursive'] = TRUE;
-    $replacements += module_invoke_all('tokens', $type, $tokens, $data, $options);
+    $replacements += \Drupal::moduleHandler()->invokeAll('tokens', array($type, $tokens, $data, $options));
   }
 
   // If the token type specifics a 'needs-data' value, and the value is not
@@ -917,77 +938,6 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
 }
 
 /**
- * Implements hook_tokens_alter().
- *
- * Fix existing core tokens that do not work correctly.
- */
-function token_tokens_alter(array &$replacements, array $context) {
-  $options = $context['options'];
-
-  $sanitize = !empty($options['sanitize']);
-  $langcode = !empty($options['language']->language) ? $options['language']->language : NULL;
-
-  // Comment token fixes.
-  if ($context['type'] == 'comment' && !empty($context['data']['comment'])) {
-    $comment = $context['data']['comment'];
-
-    foreach ($context['tokens'] as $name => $original) {
-      switch ($name) {
-        case 'name':
-        case 'author':
-          // @todo Remove when http://drupal.org/node/920056 is fixed.
-          if (!empty($comment->uid)) {
-            $account = user_load($comment->uid);
-          }
-          else {
-            $account = drupal_anonymous_user();
-            $account->name = $comment->name;
-          }
-          $name = format_username($account);
-          $replacements[$original] = $sanitize ? check_plain($name) : $name;
-          break;
-      }
-    }
-  }
-
-  // Node token fixes.
-  if ($context['type'] == 'node' && !empty($context['data']['node'])) {
-    $node = $context['data']['node'];
-
-    foreach ($context['tokens'] as $name => $original) {
-      switch ($name) {
-        case 'author':
-          // http://drupal.org/node/1185842 was fixed in core release 7.9.
-          if (version_compare(VERSION, '7.9', '<')) {
-            $account = user_load($node->uid);
-            $name = format_username($account);
-            $replacements[$original] = $sanitize ? check_plain($name) : $name;
-          }
-          break;
-      }
-    }
-  }
-
-  // File token fixes.
-  if ($context['type'] == 'file' && !empty($context['data']['file'])) {
-    $file = $context['data']['file'];
-
-    foreach ($context['tokens'] as $name => $original) {
-      switch ($name) {
-        case 'owner':
-          // http://drupal.org/node/978028 was fixed in core release 7.7.
-          if (version_compare(VERSION, '7.7', '<')) {
-            $account = user_load($file->uid);
-            $name = format_username($account);
-            $replacements[$original] = $sanitize ? check_plain($name) : $name;
-          }
-          break;
-      }
-    }
-  }
-}
-
-/**
  * Implements hook_token_info() on behalf of book.module.
  */
 function book_token_info() {
@@ -1016,14 +966,14 @@ function book_tokens($type, $tokens, array $data = array(), array $options = arr
       foreach ($tokens as $name => $original) {
         switch ($name) {
           case 'book':
-            $replacements[$original] = $sanitize ? check_plain($link['title']) : $link['title'];
+            $replacements[$original] = $sanitize ? String::checkPlain($link['title']) : $link['title'];
             break;
         }
       }
 
       // Chained token relationships.
-      if ($book_tokens = token_find_with_prefix($tokens, 'book')) {
-        $replacements += token_generate('menu-link', $book_tokens, array('menu-link' => $link), $options);
+      if ($book_tokens = \Drupal::token()->findWithPrefix($tokens, 'book')) {
+        $replacements += \Drupal::token()->generate('menu-link', $book_tokens, array('menu-link' => $link), $options);
       }
     }
   }
@@ -1032,9 +982,9 @@ function book_tokens($type, $tokens, array $data = array(), array $options = arr
 }
 
 /**
- * Implements hook_token_info() on behalf of menu.module.
+ * Implements hook_token_info() on behalf of menu_ui.module.
  */
-function menu_token_info() {
+function menu_ui_token_info() {
   // Menu tokens.
   $info['types']['menu'] = array(
     'name' => t('Menus'),
@@ -1081,9 +1031,9 @@ function menu_token_info() {
 }
 
 /**
- * Implements hook_tokens() on behalf of menu.module.
+ * Implements hook_tokens() on behalf of menu_ui.module.
  */
-function menu_tokens($type, $tokens, array $data = array(), array $options = array()) {
+function menu_ui_tokens($type, $tokens, array $data = array(), array $options = array()) {
   $replacements = array();
 
   $url_options = array('absolute' => TRUE);
@@ -1105,15 +1055,15 @@ function menu_tokens($type, $tokens, array $data = array(), array $options = arr
       switch ($name) {
         case 'menu-link':
           if ($link = token_node_menu_link_load($node)) {
-            $replacements[$original] = $sanitize ? check_plain($link['title']) : $link['title'];
+            $replacements[$original] = $sanitize ? String::checkPlain($link->label()) : $link->label();
           }
           break;
       }
 
       // Chained token relationships.
-      if ($menu_tokens = token_find_with_prefix($tokens, 'menu-link')) {
+      if ($menu_tokens = \Drupal::token()->findWithPrefix($tokens, 'menu-link')) {
         if ($link = token_node_menu_link_load($node)) {
-          $replacements += token_generate('menu-link', $menu_tokens, array('menu-link' => $link), $options);
+          $replacements += \Drupal::token()->generate('menu-link', $menu_tokens, array('menu-link' => $link), $options);
         }
       }
     }
@@ -1121,52 +1071,52 @@ function menu_tokens($type, $tokens, array $data = array(), array $options = arr
 
   // Menu link tokens.
   if ($type == 'menu-link' && !empty($data['menu-link'])) {
-    $link = (array) $data['menu-link'];
+    $link = $data['menu-link'];
 
     foreach ($tokens as $name => $original) {
       switch ($name) {
         case 'menu':
-          if ($menu = menu_load($link['menu_name'])) {
-            $replacements[$original] = $sanitize ? check_plain($menu['title']) : $menu['title'];
+          if ($menu = Menu::load($link->bundle())) {
+            $replacements[$original] = $sanitize ? String::checkPlain($menu->label()) : $menu->label();
           }
           break;
         case 'edit-url':
-          $replacements[$original] = url("admin/structure/menu/item/{$link['mlid']}/edit", $url_options);
+          $replacements[$original] = $link->url($url_options);
           break;
       }
     }
 
     // Chained token relationships.
-    if (($menu_tokens = token_find_with_prefix($tokens, 'menu')) && $menu = menu_load($link['menu_name'])) {
-      $replacements += token_generate('menu', $menu_tokens, array('menu' => $menu), $options);
+    if (($menu_tokens = \Drupal::token()->findWithPrefix($tokens, 'menu')) && $menu = Menu::load($link->bundle())) {
+      $replacements += \Drupal::token()->generate('menu', $menu_tokens, array('menu' => $menu), $options);
     }
   }
 
   // Menu tokens.
   if ($type == 'menu' && !empty($data['menu'])) {
-    $menu = (array) $data['menu'];
+    $menu = $data['menu'];
 
     foreach ($tokens as $name => $original) {
       switch ($name) {
         case 'name':
-          $replacements[$original] = $sanitize ? check_plain($menu['title']) : $menu['title'];
+          $replacements[$original] = $sanitize ? String::checkPlain($menu->label()) : $menu->label();
           break;
         case 'machine-name':
           // This is a machine name so does not ever need to be sanitized.
-          $replacements[$original] = $menu['menu_name'];
+          $replacements[$original] = $menu->id();
           break;
         case 'description':
-          $replacements[$original] = $sanitize ? filter_xss($menu['description']) : $menu['description'];
+          $replacements[$original] = $sanitize ? Xss::filter($menu->description) : $menu->description;
           break;
         case 'menu-link-count':
           $query = db_select('menu_links');
-          $query->condition('menu_name', $menu['menu_name']);
+          $query->condition('menu_name', $menu->id());
           $query->addTag('menu_menu_link_count');
           $count = $query->countQuery()->execute()->fetchField();
           $replacements[$original] = (int) $count;
           break;
         case 'edit-url':
-          $replacements[$original] = url("admin/structure/menu/manage/" . $menu['menu_name'], $url_options);
+          $replacements[$original] = \Drupal::url('entity.menu.edit_form', ['menu' => $menu->id()], $url_options);
           break;
       }
     }
@@ -1176,192 +1126,31 @@ function menu_tokens($type, $tokens, array $data = array(), array $options = arr
 }
 
 /**
- * Implements hook_token_info() on behalf of profile.module.
- */
-function profile_token_info() {
-  $info = array();
-
-  foreach (_token_profile_fields() as $field) {
-    $info['tokens']['user'][$field->token_name] = array(
-      'name' => check_plain($field->title),
-      'description' => t('@category @type field.', array('@category' => drupal_ucfirst($field->category), '@type' => $field->type)),
-    );
-
-    switch ($field->type) {
-      case 'date':
-        $info['tokens']['user'][$field->token_name]['type'] = 'date';
-        break;
-    }
-  }
-
-  return $info;
-}
-
-/**
- * Implements hook_tokens() on behalf of profile.module.
- */
-function profile_tokens($type, $tokens, array $data = array(), array $options = array()) {
-  $replacements = array();
-  $sanitize = !empty($options['sanitize']);
-  $language_code = isset($options['language']) ? $options['language']->language : NULL;
-
-  if ($type == 'user' && !empty($data['user'])) {
-    $account = $data['user'];
-
-    // Load profile fields if this is the global user account.
-    // @see http://drupal.org/node/361471
-    // @see http://drupal.org/node/967330
-    if ($account->uid == $GLOBALS['user']->uid && isset($account->timestamp)) {
-      $profile_users = array($account->uid => $account);
-      profile_user_load($profile_users);
-      $account = $profile_users[$account->uid];
-    }
-
-    $profile_fields = _token_profile_fields();
-    foreach ($tokens as $name => $original) {
-      if (isset($profile_fields[$name]) && !empty($account->{$profile_fields[$name]->name})) {
-        $value = $account->{$profile_fields[$name]->name};
-        switch ($profile_fields[$name]->type) {
-          case 'textarea':
-            $replacements[$original] = $sanitize ? check_markup($value, filter_default_format($account), '', TRUE) : $value;
-            break;
-          case 'date':
-            $timestamp = gmmktime(0, 0, 0, $value['month'], $value['day'], $value['year']);
-            $replacements[$original] = format_date($timestamp, 'medium', '', NULL, $language_code);
-            break;
-          case 'url':
-            $replacements[$original] = $sanitize ? check_url($value) : $value;
-            break;
-          case 'checkbox':
-            // Checkbox field if checked should return the text.
-            $replacements[$original] = $sanitize ? check_plain($profile_fields[$name]->title) : $profile_fields[$name]->title;
-            break;
-          case 'list':
-            $value = preg_split("/[,\n\r]/", $value);
-            $value = array_map('trim', $value);
-            $value = implode(', ', $value);
-            // Intentionally fall through to the default condition.
-          default:
-            $replacements[$original] = $sanitize ? check_plain($value) : $value;
-            break;
-        }
-      }
-    }
-
-    // Chained token relationships.
-    foreach ($profile_fields as $field) {
-      if ($field->type == 'date' && isset($account->{$field->name}) && $field_tokens = token_find_with_prefix($tokens, $field->token_name)) {
-        $date = $account->{$field->name};
-        $replacements += token_generate('date', $field_tokens, array('date' => gmmktime(0, 0, 0, $date['month'], $date['day'], $date['year'])), $options);
-      }
-    }
-  }
-
-  return $replacements;
-}
-
-/**
- * Fetch an array of profile field objects, keyed by token name.
- */
-function _token_profile_fields() {
-  $fields = &drupal_static(__FUNCTION__);
-
-  if (!isset($fields)) {
-    $fields = array();
-    $results = db_query("SELECT name, title, category, type FROM {profile_field}");
-    foreach ($results as $field) {
-      $field->token_name = token_clean_token_name($field->name);
-      $fields[$field->token_name] = $field;
-    }
-  }
-
-  return $fields;
-}
-
-/**
- * Fetch an array of field data used for tokens.
- */
-function _token_field_info($field_name = NULL) {
-  $info = &drupal_static(__FUNCTION__);
-
-  if (!isset($fields)) {
-    if ($cached = cache('cache_token')->get('field:info')) {
-      $info = $cached->data;
-    }
-    else {
-      $info = array();
-
-      $fields = field_info_fields();
-      $instances = field_info_instances();
-      $type_info = field_info_field_types();
-      $entity_info = entity_get_info();
-
-      foreach ($fields as $field) {
-        $key = $field['field_name'];
-        if (!empty($field['bundles'])) {
-          foreach (array_keys($field['bundles']) as $entity) {
-            // Make sure a token type exists for this entity.
-            $token_type = token_get_entity_mapping('entity', $entity);
-            if (empty($token_type)) {
-              continue;
-            }
-
-            $info[$key]['token types'][] = $token_type;
-            $info[$key] += array('labels' => array(), 'bundles' => array());
-
-            // Find which label is most commonly used.
-            foreach ($field['bundles'][$entity] as $bundle) {
-              // Field information will included fields attached to disabled
-              // bundles, so check that the bundle exists before provided a
-              // token for it.
-              // @see http://drupal.org/node/1252566
-              if (!isset($entity_info[$entity]['bundles'][$bundle])) {
-                continue;
-              }
-
-              $info[$key]['labels'][] = $instances[$entity][$bundle][$key]['label'];
-              $info[$key]['bundles'][$token_type][$bundle] = $entity_info[$entity]['bundles'][$bundle]['label'];
-            }
-          }
-        }
-
-        if (isset($info[$key])) {
-          $labels = array_count_values($info[$key]['labels']);
-          arsort($labels);
-          $info[$key]['label'] = check_plain(key($labels));
-
-          // Generate a description for the token.
-          $info[$key]['description'] = t('@type field.', array('@type' => $type_info[$field['type']]['label']));
-          if ($also_known_as = array_unique(array_diff($info[$key]['labels'], array($info[$key]['label'])))) {
-            $info[$key]['description'] .= ' ' . t('Also known as %labels.', array('%labels' => implode(', ', $also_known_as)));
-          }
-        }
-      }
-
-      drupal_alter('token_field_info', $info);
-      cache('cache_token')->set('field:info', $info);
-    }
-  }
-
-  if (isset($field_name)) {
-    return isset($info[$field_name]) ? $info[$field_name] : FALSE;
-  }
-
-  return $info;
-}
-
-/**
  * Implements hook_token_info_alter() on behalf of field.module.
  *
  * We use hook_token_info_alter() rather than hook_token_info() as other
  * modules may already have defined some field tokens.
  */
 function field_token_info_alter(&$info) {
-  $fields = _token_field_info();
+  $type_info = \Drupal::service('plugin.manager.field.field_type')->getDefinitions();
 
   // Attach field tokens to their respecitve entity tokens.
-  foreach ($fields as $field_name => $field) {
-    foreach (array_keys($field['bundles']) as $token_type) {
+  foreach (\Drupal::entityManager()->getDefinitions() as $entity_type_id => $entity_type) {
+    if (!$entity_type->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface')) {
+      continue;
+    }
+
+    // Make sure a token type exists for this entity.
+    $token_type = token_get_entity_mapping('entity', $entity_type_id);
+    if (empty($token_type)) {
+      continue;
+    }
+
+    $fields = \Drupal::entityManager()->getFieldStorageDefinitions($entity_type_id);
+    foreach ($fields as $field_name => $field) {
+      if (!($field instanceof FieldStorageConfigInterface)) {
+        continue;
+      }
       // If a token already exists for this field, then don't add it.
       if (isset($info['tokens'][$token_type][$field_name])) {
         continue;
@@ -1377,10 +1166,18 @@ function field_token_info_alter(&$info) {
         continue;
       }
 
+      // Generate a description for the token.
+
+      list($label, $labels) = _token_field_label($entity_type_id, $field_name);
+      $description = t('@type field.', array('@type' => $type_info[$field->getType()]['label']));
+      if (count($labels) > 1) {
+        unset($labels[$label]);
+        $description .= ' ' . t('Also known as %labels.', array('%labels' => implode(', ', $labels)));
+      }
+
       $info['tokens'][$token_type][$field_name] = array(
-        // Note that label and description have already been sanitized by _token_field_info().
-        'name' => $field['label'],
-        'description' => $field['description'],
+        'name' => String::checkPlain($label),
+        'description' => $description,
         'module' => 'token',
       );
     }
@@ -1388,6 +1185,37 @@ function field_token_info_alter(&$info) {
 }
 
 /**
+ * Returns the label of a certain field.
+ *
+ * Therefore it looks up in all bundles to find the most used instance.
+ *
+ * Based on field_views_field_label().
+ */
+function _token_field_label($entity_type, $field_name) {
+  $label_counter = array();
+  $all_labels = array();
+  // Count the amount of instances per label per field.
+  foreach (array_keys(\Drupal::entityManager()->getBundleInfo($entity_type)) as $bundle) {
+    $bundle_instances = array_filter(\Drupal::entityManager()->getFieldDefinitions($entity_type, $bundle), function ($field_definition) {
+        return $field_definition instanceof FieldConfigInterface;
+      });
+    if (isset($bundle_instances[$field_name])) {
+      $instance = $bundle_instances[$field_name];
+      $label = $instance->getLabel();
+      $label_counter[$label] = isset($label_counter[$label]) ? ++$label_counter[$label] : 1;
+      $all_labels[$label] = TRUE;
+    }
+  }
+  if (empty($label_counter)) {
+    return array($field_name, $all_labels);
+  }
+  // Sort the field labels by it most used label and return the most used one.
+  arsort($label_counter);
+  $label_counter = array_keys($label_counter);
+  return array($label_counter[0], $all_labels);
+}
+
+/**
  * Implements hook_tokens() on behalf of field.module.
  */
 function field_tokens($type, $tokens, array $data = array(), array $options = array()) {
@@ -1397,35 +1225,28 @@ function field_tokens($type, $tokens, array $data = array(), array $options = ar
 
   // Entity tokens.
   if ($type == 'entity' && !empty($data['entity_type']) && !empty($data['entity']) && !empty($data['token_type'])) {
-    $entity_type = $data['entity_type'];
-
     // The field API does weird stuff to the entity, so let's clone it.
+    /* @var \Drupal\Core\Entity\ContentEntityInterface $entity */
     $entity = clone $data['entity'];
+    if (!($entity instanceof ContentEntityInterface)) {
+      return $replacements;
+    }
 
     // Reset the prepared view flag in case token generation is called from
     // inside field_attach_view().
     unset($entity->_field_view_prepared);
 
-    list(, , $bundle) = entity_extract_ids($entity_type, $entity);
-    $fields = field_info_instances($entity_type, $bundle);
-
-    foreach (array_keys($fields) as $field_name) {
-      // Do not continue if the field is empty.
-      if (empty($entity->{$field_name})) {
-        continue;
-      }
-
-      // Replace the [entity:field-name] token.
-      if (isset($tokens[$field_name])) {
-        $original = $tokens[$field_name];
+    foreach ($tokens as $name => $original) {
+      // Replace the [entity:field-name] token only if token module added this
+      // token.
+      if ($entity->hasField($name) && _token_module($data['token_type'], $name) == 'token') {
 
-        // Assert that this field was added by token.module.
-        $token_info = token_get_info($data['token_type'], $field_name);
-        if (!isset($token_info['module']) || $token_info['module'] != 'token') {
+        // Do not continue if the field is empty or not a configurable field.
+        if ($entity->get($name)->isEmpty() || !($entity->getFieldDefinition($name) instanceof FieldConfigInterface)) {
           continue;
         }
 
-        $field_output = field_view_field($entity_type, $entity, $field_name, 'token', $langcode);
+        $field_output = $entity->$name->view('token');
         $field_output['#token_options'] = $options;
         $field_output['#pre_render'][] = 'token_pre_render_field_token';
         $replacements[$original] = drupal_render($field_output);
@@ -1450,7 +1271,7 @@ function token_pre_render_field_token(&$elements) {
 
   // Prevent multi-value fields from appearing smooshed together by appending
   // a join suffix to all but the last value.
-  $deltas = element_get_visible_children($elements);
+  $deltas = Element::getVisibleChildren($elements);
   $count = count($deltas);
   if ($count > 1) {
     $join = isset($elements['#token_options']['join']) ? $elements['#token_options']['join'] : ", ";
