diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 3dd3a6d..069c885 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -2,7 +2,7 @@ CHANGELOG for Disqus for Drupal 7
 
 Disqus 7.x-1.x-dev
 =================================
-- #2021905 by tkngdwn: Record file usage for SSO logo.
+- #2083329 by slashrsm, thedavidmeister | dianikol: The link to get api key is lead to a 404 page.
 - #2040057 by mandar.harkare, RobLoach | steven.wichers: Hook_uninstall() removes variables not belonging to Disqus.
 - #2021905 by Grayside, pblissmt: Custom SSO logo admin.
 - #2017385 by marcingy: Fixed Disqus creates a notice when no domain is defined.
diff --git a/disqus.admin.inc b/disqus.admin.inc
deleted file mode 100644
index 0b2d912..0000000
--- a/disqus.admin.inc
+++ /dev/null
@@ -1,187 +0,0 @@
-<?php
-
-/**
- * @file
- * Administration forms for the Disqus module.
- */
-
-/**
- * Menu callback; Displays the administration settings for Disqus.
- */
-function disqus_admin_settings() {
-  $form = array();
-  $form['disqus_domain'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Shortname'),
-    '#description' => t('The website shortname that you registered Disqus with. If you registered http://example.disqus.com, you would enter "example" here.'),
-    '#default_value' => variable_get('disqus_domain', ''),
-  );
-  $form['settings'] = array(
-    '#type' => 'vertical_tabs',
-    '#weight' => 50,
-  );
-  // Visibility settings.
-  $form['visibility'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Visibility'),
-    '#group' => 'settings',
-  );
-  $types = node_type_get_types();
-  $options = array();
-  foreach ($types as $type) {
-    $options[$type->type] = $type->name;
-  }
-  $form['visibility']['disqus_nodetypes'] = array(
-    '#type' => 'checkboxes',
-    '#title' => t('Node Types'),
-    '#description' => t('Apply comments to only the following node types.'),
-    '#default_value' => variable_get('disqus_nodetypes', array()),
-    '#options' => $options,
-  );
-  $form['visibility']['disqus_location'] = array(
-    '#type' => 'select',
-    '#title' => t('Location'),
-    '#description' => t('Display the Disqus comments in the given location. When "Block" is selected, the comments will appear in the <a href="@disquscomments">Disqus Comments block</a>.', array('@disquscomments' => url('admin/structure/block'))),
-    '#default_value' => variable_get('disqus_location', 'content_area'),
-    '#options' => array(
-      'content_area' => t('Content Area'),
-      'block' => t('Block'),
-    ),
-  );
-  $form['visibility']['disqus_weight'] = array(
-    '#type' => 'select',
-    '#title' => t('Weight'),
-    '#description' => t('When the comments are displayed in the content area, you can change the position at which they will be shown.'),
-    '#default_value' => variable_get('disqus_weight', 50),
-    '#options' => drupal_map_assoc(array(-100, -75, -50, -25, 0, 25, 50, 75, 100)),
-    '#states' => array(
-      'visible' => array(
-        'select[name="disqus_location"]' => array('value' => 'content_area'),
-      ),
-    ),
-  );
-  // Behavior settings.
-  $form['behavior'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Behavior'),
-    '#group' => 'settings',
-  );
-  $form['behavior']['disqus_userapikey'] = array(
-    '#type' => 'textfield',
-    '#title' => t('User API Key'),
-    '#description' => t('The API key of the administrator account on Disqus. You can get yours <a href="@key">here</a>.', array('@key' => 'http://disqus.com/api/get_my_key/')),
-    '#default_value' => variable_get('disqus_userapikey', ''),
-  );
-  $form['behavior']['disqus_localization'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Localization support'),
-    '#description' => t("When enabled, overrides the language set by Disqus with the language provided by the site."),
-    '#default_value' => variable_get('disqus_localization', FALSE),
-  );
-  $form['behavior']['disqus_inherit_login'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Inherit User Credentials'),
-    '#description' => t("When enabled and a user is logged in, the Disqus 'Post as Guest' login form will be pre-filled with the user's name and email address."),
-    '#default_value' => variable_get('disqus_inherit_login', TRUE),
-  );
-  $form['behavior']['disqus_developer'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Testing'),
-    '#description' => t('When enabled, uses the <a href="http://docs.disqus.com/help/2/">disqus_developer</a> flag to tell Disqus that you are in a testing environment. Threads will not display on the public community page with this set.'),
-    '#default_value' => variable_get('disqus_developer', FALSE),
-  );
-  // Advanced settings.
-  $form['advanced'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Advanced'),
-    '#group' => 'settings',
-    '#description' => t('Use these settings to configure the more advanced uses of Disqus. You can find more information about these in the <a href="@applications">Applications</a> section of Disqus. To enable some of these features, you will require a <a href="@addons">Disqus Add-on Package</a>.', array(
-      '@applications' => 'http://disqus.com/api/applications/',
-      '@addons' => 'http://disqus.com/addons/',
-    )),
-  );
-  $form['advanced']['disqus_publickey'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Public Key'),
-    '#default_value' => variable_get('disqus_publickey', ''),
-  );
-  $form['advanced']['disqus_secretkey'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Secret Key'),
-    '#default_value' => variable_get('disqus_secretkey', ''),
-  );
-
-  $form['advanced']['sso'] = array(
-    '#weight' => 5,
-    '#type' => 'fieldset',
-    '#title' => t('Single Sign-on'),
-    '#collapsible' => FALSE,
-    '#collapsed' => FALSE,
-    '#states' => array(
-      'visible' => array(
-        'input[name="disqus_publickey"]' => array('empty' => FALSE),
-        'input[name="disqus_secretkey"]' => array('empty' => FALSE),
-      ),
-    ),
-  );
-  $form['advanced']['sso']['disqus_sso'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Use Single Sign-On'),
-    '#description' => t('Provide <a href="@sso">Single Sign-On</a> access to your site.', array(
-      '@sso' => 'http://disqus.com/api/sso/',
-    )),
-    '#default_value' => variable_get('disqus_sso', FALSE),
-  );
-  $form['advanced']['sso']['disqus_use_site_logo'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Use Site Logo'),
-    '#description' => t('Pass the site logo to Disqus for use as SSO login button.'),
-    '#default_value' => variable_get('disqus_use_site_logo', TRUE),
-    '#states' => array(
-      'disabled' => array(
-        'input[name="disqus_sso"]' => array('checked' => FALSE),
-      ),
-    ),
-  );
-  $form['advanced']['sso']['disqus_logo'] = array(
-    '#type' => 'managed_file',
-    '#title' => t('Custom Logo'),
-    '#upload_location' => 'public://images',
-    '#default_value' => variable_get('disqus_logo', ''),
-    '#states' => array(
-      'disabled' => array(
-        'input[name="disqus_sso"]' => array('checked' => FALSE),
-      ),
-      'visible' => array(
-        'input[name="disqus_use_site_logo"]' => array('checked' => FALSE),
-      ),
-    ),
-  );
-  // Make sure the validation is called to handle the custom sso logo.
-  $form['#submit'][] = 'disqus_admin_settings_submit';
-  return system_settings_form($form);
-}
-
-/**
- * Form callback; Make sure we process the sso logo and set it to a permanent status.
- */
-function disqus_admin_settings_submit($form, &$form_state) {
-  $old_logo = variable_get('disqus_logo', '');
-  $new_logo = (isset($form_state['values']['disqus_logo'])) ? $form_state['values']['disqus_logo'] : '';
-  // Ignore if the file hasn't changed.
-  if ($new_logo != $old_logo) {
-    // Remove the old file and usage if previously set.
-    if ($old_logo != '') {
-      $file = file_load($old_logo);
-      file_usage_delete($file, 'disqus', 'disqus');
-      file_delete($file);
-    }
-    // Update the new file and usage.
-    if ($new_logo != '') {
-      $file = file_load($new_logo);
-      file_usage_add($file, 'disqus', 'disqus', 0);
-      $file->status = FILE_STATUS_PERMANENT;
-      file_save($file);
-    }
-  }
-}
diff --git a/disqus.info b/disqus.info
deleted file mode 100644
index 1cad6d8..0000000
--- a/disqus.info
+++ /dev/null
@@ -1,12 +0,0 @@
-name = Disqus
-description = Uses the Disqus web service to enhance comments.
-core = 7.x
-php = 5
-
-files[] = disqus.admin.inc
-files[] = disqus.install
-files[] = disqus.module
-files[] = disqus.php
-files[] = disqus.views.inc
-
-configure = admin/config/services/disqus
diff --git a/disqus.info.yml b/disqus.info.yml
new file mode 100644
index 0000000..8a3a91e
--- /dev/null
+++ b/disqus.info.yml
@@ -0,0 +1,8 @@
+name: 'Disqus'
+type: 'module'
+description: 'Uses the Disqus web service to enhance comments.'
+core: 8.x
+configure: admin/config/services/disqus
+dependencies:
+  - node
+  - user
diff --git a/disqus.js b/disqus.js
index 7332e3f..956e925 100644
--- a/disqus.js
+++ b/disqus.js
@@ -15,6 +15,8 @@ var disqus_config;
 
 (function ($) {
 
+"use strict";
+
 /**
  * Drupal Disqus behavior.
  */
diff --git a/disqus.module b/disqus.module
index eee24e2..f6667f1 100644
--- a/disqus.module
+++ b/disqus.module
@@ -50,34 +50,20 @@ function disqus_permission() {
  * Implements hook_menu().
  */
 function disqus_menu() {
-  $items = array();
   $items['admin/config/services/disqus'] = array(
+    'route_name' => 'disqus.admin',
     'title' => 'Disqus',
     'description' => 'Provides configuration options for the Disqus comment system.',
-    'access arguments' => array('administer disqus'),
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('disqus_admin_settings'),
-    'file' => 'disqus.admin.inc',
   );
+
   $items['disqus/closewindow'] = array(
+    'route_name' => 'disqus.close_window',
     'title' => 'Please wait',
     'description' => 'Once the user logs in through the Disqus login workflow, they are redirected here to automatically close the popup window.',
-    'access arguments' => array('access content'),
-    'page callback' => 'disqus_closewindow',
     'type' => MENU_CALLBACK,
   );
-  return $items;
-}
 
-/**
- * Menu callback; Automatically closes the window after the user logs in.
- *
- * @return
- *   Confirmation message and link that closes overlay window.
- */
-function disqus_closewindow() {
-  drupal_add_js('window.close();', 'inline');
-  return t('Thank you for logging in. Please close this window, or <a href="@clickhere">click here</a> to continue.', array('@clickhere' => 'javascript:window.close();'));
+  return $items;
 }
 
 /**
@@ -109,10 +95,11 @@ function disqus_element_post_render($children, &$element) {
   }
 
   // If the user is logged in, we can inject the username and email for Disqus.
-  global $user;
-  if (variable_get('disqus_inherit_login', TRUE) && $user->uid > 0) {
-    $disqus['name'] = $user->name;
-    $disqus['email'] = $user->mail;
+  $account = \Drupal::currentUser();
+
+  if (variable_get('disqus_inherit_login', TRUE) && !$account->isAnonymous()) {
+    $disqus['name'] = $account->getUsername();
+    $disqus['email'] = $account->getEmail();
   }
 
   // Provide alternate language support if desired.
@@ -126,15 +113,15 @@ function disqus_element_post_render($children, &$element) {
     $data = array();
 
     // Inject the user data if it's available.
-    if ($user->uid > 0) {
-      $data['id'] = $user->uid;
-      $data['username'] = $user->name;
-      $data['email'] = $user->mail;
-      $data['url'] = url('user/' . $user->uid, array('absolute' => TRUE));
+    if (!$account->isAnonymous()) {
+      $data['id'] = $account->id();
+      $data['username'] = $account->getUsername();
+      $data['email'] = $account->getEmail();
+      $data['url'] = url('user/' . $account->id(), array('absolute' => TRUE));
 
       // Load the user's avatar.
       $user_picture_default = variable_get('user_picture_default', '');
-      if (isset($user->picture) && !empty($user->picture) && is_numeric($user->picture) && $file = file_load($user->picture)) {
+      if (isset($account->picture) && !empty($account->picture) && is_numeric($account->picture) && $file = file_load($account->picture)) {
         $data['avatar'] = !empty($file->uri) ? $file->uri : NULL;
       }
       elseif (!empty($user_picture_default)) {
@@ -200,14 +187,6 @@ function disqus_element_post_render($children, &$element) {
     $disqus['callbacks'] = $element['#disqus']['callbacks'];
   }
 
-  // Add the disqus.js and all the settings to process the JavaScript and load Disqus.
-  $element['#attached']['js'][] = drupal_get_path('module', 'disqus') . '/disqus.js';
-  $element['#attached']['js'][] = array(
-    'type' => 'setting',
-    'data' => array(
-      'disqus' => $disqus,
-    ),
-  );
   return $children;
 }
 
@@ -215,38 +194,40 @@ function disqus_element_post_render($children, &$element) {
  * Implements hook_node_load().
  */
 function disqus_node_load($nodes, $types) {
+  $disqus_config = \Drupal::config('disqus.settings');
+
   // Make sure we only load Disqus on nodes of the desired types.
-  $disqustypes = variable_get('disqus_nodetypes', array());
+  $disqustypes = $disqus_config->get('visibility.disqus_nodetypes');
 
   // Check which Disqus domain to use.
-  $domain = variable_get('disqus_domain', '');
+  $domain = $disqus_config->get('disqus_domain');
   if (!empty($domain)) {
     // Load Disqus into the nodes.
     foreach ($nodes as &$node) {
-      if (!empty($disqustypes[$node->type])) {
+      if (!empty($disqustypes[$node->getType()])) {
         // Save the data to the node object.
         $node->disqus = array('domain' => $domain);
 
         // Apply the Disqus status to the node.
-        $status = db_query("SELECT status FROM {disqus} WHERE nid = :nid", array(':nid' => $node->nid))->fetchObject();
+        $status = db_query("SELECT status FROM {disqus} WHERE nid = :nid", array(':nid' => $node->id()))->fetchObject();
         $node->disqus['status'] = isset($status->status) ? (bool)$status->status : TRUE;
 
         // Build the absolute URL without the alias for the disqus_url flag.
-        $node->disqus['url'] = url("node/$node->nid", array(
+        $node->disqus['url'] = url('node/' . $node->id(), array(
           'absolute' => TRUE,
         ));
 
         // Build the title.
-        $node->disqus['title'] = check_plain(strip_tags($node->title));
+        $node->disqus['title'] = check_plain(strip_tags($node->getTitle()));
 
         // Provide the identifier.
-        $node->disqus['identifier'] = 'node/' . $node->nid;
+        $node->disqus['identifier'] = 'node/' . $node->id();
 
         // The developer flag must always be set when the node is unpublished.
-        if ($node->status == 0) {
+        if (!$node->isPublished()) {
           $node->disqus['developer'] = 1;
         }
-        elseif ($developer = variable_get('disqus_developer', FALSE)) {
+        elseif ($developer = $disqus_config->get('behavior.disqus_developer')) {
           $node->disqus['developer'] = (int) $developer;
         }
       }
@@ -258,7 +239,7 @@ function disqus_node_load($nodes, $types) {
  * Implements hook_node_view().
  */
 function disqus_node_view($node, $view_mode) {
-  if (isset($node->disqus) && user_access('view disqus comments') && $node->disqus['status'] == 1) {
+  if (isset($node->disqus) && \Drupal::currentUser()->hasPermission('view disqus comments') && $node->disqus['status'] == 1) {
     switch ($view_mode) {
       case 'full':
         // Inject Disqus into the node object.
@@ -277,12 +258,12 @@ function disqus_node_view($node, $view_mode) {
         // Display the Disqus link.
         $links['disqus_comments_num'] = array(
           'title' => t('Comments'),
-          'href' => 'node/' . $node->nid,
+          'href' => 'node/' . $node->id(),
           'fragment' => 'disqus_thread',
           'attributes' => array(
             // Identify the node for Disqus with the unique identifier:
             // http://docs.disqus.com/developers/universal/#comment-count
-            'data-disqus-identifier' => 'node/' . $node->nid,
+            'data-disqus-identifier' => 'node/' . $node->id(),
           ),
         );
         $node->content['links']['disqus'] = array(
@@ -311,7 +292,7 @@ function disqus_node_view($node, $view_mode) {
  * Implements hook_node_delete().
  */
 function disqus_node_delete($node) {
-  db_delete('disqus')->condition('nid', $node->nid)->execute();
+  db_delete('disqus')->condition('nid', $node->id())->execute();
 }
 
 /**
@@ -321,7 +302,7 @@ function disqus_node_insert($node) {
   // Write the value only if it's disabled (default is enabled).
   if (isset($node->disqus_status) && $node->disqus_status == FALSE) {
     $data = array(
-      'nid' => $node->nid,
+      'nid' => $node->id(),
       'status' => $node->disqus_status,
     );
     drupal_write_record('disqus', $data);
@@ -351,7 +332,7 @@ function disqus_user_load($users) {
   if (!empty($domain)) {
     foreach ($users as &$account) {
       // Only show on the profile if desired. Don't show on the administrator's profile.
-      if (user_access('display disqus comments on profile', $account) && $account->uid != 1) {
+      if (\Drupal::currentUser()->hasPermission('display disqus comments on profile', $account) && $account->uid != 1) {
         // Save the data to the user object.
         $account->disqus = array('domain' => $domain);
 
@@ -385,7 +366,7 @@ function disqus_user_view($account, $view_mode, $langcode) {
           '#type' => 'disqus',
           '#disqus' => $account->disqus,
           '#weight' => variable_get('disqus_weight', 50),
-          '#access' => user_access('view disqus comments'),
+          '#access' => \Drupal::currentUser()->hasPermission('view disqus comments'),
         );
         break;
     }
@@ -393,293 +374,6 @@ function disqus_user_view($account, $view_mode, $langcode) {
 }
 
 /**
- * Implements hook_block_info().
- */
-function disqus_block_info() {
-  $blocks['disqus_recent_comments'] = array(
-    'info' => t('Disqus: Recent Comments'),
-    'cache' => DRUPAL_CACHE_GLOBAL,
-  );
-  $blocks['disqus_popular_threads'] = array(
-    'info' => t('Disqus: Popular Threads'),
-    'cache' => DRUPAL_CACHE_GLOBAL,
-  );
-  $blocks['disqus_top_commenters'] = array(
-    'info' => t('Disqus: Top Commenters'),
-    'cache' => DRUPAL_CACHE_GLOBAL,
-  );
-  $blocks['disqus_combination_widget'] = array(
-    'info' => t('Disqus: Combination Widget'),
-    'cache' => DRUPAL_CACHE_GLOBAL,
-  );
-  $blocks['disqus_comments'] = array(
-    'info' => t('Disqus: Comments'),
-    'cache' => DRUPAL_CACHE_CUSTOM,
-  );
-  return $blocks;
-}
-
-/**
- * Implements hook_block_configure().
- */
-function disqus_block_configure($delta = '') {
-  $form = array();
-  $form['disqus'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Disqus settings'),
-  );
-  if ($delta == 'disqus_comments') {
-    $form['disqus']['#description'] = t('This block will be used to display the comments from Disqus when comments are applied to the given page. Visit the <a href="@disqussettings">Disqus settings</a> to configure when this is visible.', array('@disqussettings' => url('admin/config/services/disqus')));
-  }
-  $form['disqus'][$delta . '_items'] = array(
-    '#type' => 'select',
-    '#title' => t('Number of items to show'),
-    '#options' => array(1 => 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20),
-    '#default_value' => variable_get($delta .'_items', 5),
-    '#access' => ($delta != 'disqus_comments'),
-  );
-  $form['disqus'][$delta . '_showavatars'] = array(
-    '#type' => 'select',
-    '#title' => t('Show avatars'),
-    '#options' => array(FALSE => t('No'), TRUE => t('Yes')),
-    '#default_value' => variable_get($delta .'_showavatars', TRUE),
-    '#access' => ($delta == 'disqus_recent_comments') || ($delta == 'disqus_top_commenters'),
-  );
-  $form['disqus'][$delta . '_avatarsize'] = array(
-    '#type' => 'select',
-    '#title' => t('Avatar size'),
-    '#options' => array(
-      24 => t('X-Small (24px)'),
-      32 => t('Small (32px)'),
-      48 => t('Medium (48px)'),
-      92 => t('Large (92px)'),
-      128 => t('X-Large (128px)'),
-    ),
-    '#default_value' => variable_get($delta .'_avatarsize', 32),
-    '#access' => ($delta == 'disqus_recent_comments') || ($delta == 'disqus_top_commenters'),
-  );
-  $form['disqus'][$delta . '_colortheme'] = array(
-    '#type' => 'select',
-    '#title' => t('Color Theme'),
-    '#options' => array(
-      'blue' => t('Blue'),
-      'grey' => t('Grey'),
-      'green' => t('Green'),
-      'red' => t('Red'),
-      'orange' => t('Orange'),
-    ),
-    '#default_value' => variable_get($delta .'_colortheme', 'blue'),
-    '#access' => $delta == 'disqus_combination_widget',
-  );
-  $form['disqus'][$delta . '_defaulttabview'] = array(
-    '#type' => 'select',
-    '#title' => t('Default Tab View'),
-    '#options' => array(
-      'people' => t('People'),
-      'recent' => t('Recent'),
-      'popular' => t('Popular'),
-    ),
-    '#default_value' => variable_get($delta .'_defaulttabview', 'people'),
-    '#access' => $delta == 'disqus_combination_widget',
-  );
-  $form['disqus'][$delta . '_excerpt_length'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Comment Except Length'),
-    '#default_value' => variable_get($delta .'_excerpt_length', '200'),
-    '#access' => ($delta == 'disqus_recent_comments') || ($delta == 'disqus_combination_widget'),
-    '#size' => 4,
-  );
-  $form['disqus'][$delta . '_hide_mods'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Hide moderators in ranking'),
-    '#default_value' => variable_get($delta .'_hide_mods', FALSE),
-    '#access' => ($delta == 'disqus_top_commenters') || ($delta == 'disqus_combination_widget'),
-  );
-  return $form;
-}
-
-/**
- * Implements hook_block_save().
- */
-function disqus_block_save($delta = '', $edit = array()) {
-  // The Disqus comments block doesn't have any configuration.
-  if ($delta != 'disqus_comments') {
-    variable_set($delta . '_items', $edit[$delta . '_items']);
-    // Recent comments and top commenters have avatars.
-    if (($delta == 'disqus_recent_comments') || ($delta == 'disqus_top_commenters')) {
-      variable_set($delta . '_showavatars', $edit[$delta . '_showavatars']);
-      variable_set($delta . '_avatarsize', $edit[$delta . '_avatarsize']);
-    }
-    // The excerpt length is only available for recent comments and combination.
-    if (($delta == 'disqus_recent_comments') || ($delta == 'disqus_combination_widget')) {
-      variable_set($delta . '_excerpt_length', $edit[$delta . '_excerpt_length']);
-    }
-    // Combination widget has the color theme and the default tab view.
-    if ($delta == 'disqus_combination_widget') {
-      variable_set($delta . '_colortheme', $edit[$delta . '_colortheme']);
-      variable_set($delta . '_defaulttabview', $edit[$delta . '_defaulttabview']);
-    }
-    // Hide moderators appears in top commenters and combination widget.
-    if (($delta == 'disqus_top_commenters') || ($delta == 'disqus_combination_widget')) {
-      variable_set($delta . '_hide_mods', $edit[$delta . '_hide_mods']);
-    }
-  }
-}
-
-/**
- * Implements hook_block_view().
- */
-function disqus_block_view($delta = '') {
-  $options = array(
-    'num_items' => variable_get($delta . '_items', 5),
-    'avatars' => variable_get($delta . '_showavatars', TRUE) ? array('avatar_size' => variable_get($delta . '_avatarsize', 32)) : array('hide_avatars=1'),
-    'color' => variable_get($delta . '_colortheme', 'blue'),
-    'default_tab' => variable_get($delta . '_defaulttabview', 'people'),
-    'excerpt_length' => variable_get($delta . '_excerpt_length', '200'),
-    'hide_mods' => variable_get($delta . '_hide_mods', FALSE) ? '1' : '0',
-    'domain' => variable_get('disqus_domain', ''),
-  );
-  if (!empty($options['domain'])) {
-    $subject = '';
-    $content = '';
-    switch ($delta) {
-      case 'disqus_recent_comments':
-        $subject = t('Recent Comments');
-        $content = _disqus_block_content('recent_comments_widget', $options);
-        break;
-      case 'disqus_popular_threads':
-        $subject = t('Popular Threads');
-        $content = _disqus_block_content('popular_threads_widget', $options);
-        break;
-      case 'disqus_top_commenters':
-        $subject = t('Top Commenters');
-        $content = _disqus_block_content('top_commenters_widget', $options);
-        break;
-      case 'disqus_combination_widget':
-        $subject = t('Comments');
-        $content = _disqus_block_content('combination_widget', $options);
-        break;
-      case 'disqus_comments':
-        if (variable_get('disqus_location', 'content_area') == 'block' && user_access('view disqus comments')) {
-          if ($object = menu_get_object()) {
-            // For nodes, display if the Disqus object is enabled.
-            if (isset($object->disqus) && $object->disqus['status']) {
-              $content = array(
-                'disqus' => array(
-                  '#type' => 'disqus',
-                  '#disqus' => $object->disqus,
-                ),
-                '#cache' => array(
-                  'bin' => 'cache_block',
-                  'expire' => CACHE_TEMPORARY,
-                  'keys' => array(
-                    'disqus',
-                    'disqus_comments',
-                    'node',
-                    (int) $object->nid,
-                  ),
-                ),
-              );
-            }
-          }
-          else if ($object = menu_get_object('user')) {
-            if (isset($object->disqus)) {
-              $content = array(
-                'disqus' => array(
-                  '#type' => 'disqus',
-                  '#disqus' => $object->disqus,
-                ),
-                '#cache' => array(
-                  'bin' => 'cache_block',
-                  'expire' => CACHE_TEMPORARY,
-                  'keys' => array(
-                    'disqus',
-                    'disqus_comments',
-                    'user',
-                    (int) $object->uid,
-                  ),
-                ),
-              );
-            }
-          }
-        }
-        break;
-    }
-
-    return array('subject' => $subject, 'content' => $content);
-  }
-}
-
-/**
- * Helper function for disqus widget blocks content.
- *
- * @param $function
- *   Name of the function (widget) that needs to be returned. Same as widget
- *   API call name (w/o .json suffix).
- * @param $options
- *   Options array (query variables, domain, ...).
- * @return
- *   Render array that can be directly used for block content.
- */
-function _disqus_block_content($function, $options) {
-  $configuration = array(
-    'recent_comments_widget' => array(
-      'id' => 'dsq-recentcomments',
-      'query_items' => array('num_items', 'excerpt_length', 'avatars'),
-    ),
-    'popular_threads_widget' => array(
-      'id' => 'dsq-popthreads',
-      'query_items' => array('num_items'),
-    ),
-    'top_commenters_widget' => array(
-      'id' => 'dsq-topcommenters',
-      'query_items' => array('num_items', 'hide_mods', 'avatars'),
-    ),
-    'combination_widget' => array(
-      'id' => 'dsq-combinationwidget',
-      'query_items' => array('num_items', 'hide_mods', 'excerpt_length', 'color', 'default_tab'),
-    ),
-  );
-
-  if (empty($configuration[$function])) {
-    return FALSE;
-  }
-
-  $query = array();
-  foreach ($configuration[$function]['query_items'] as $query_item) {
-    if ($query_item == 'avatars') {
-      $query += $options[$query_item];
-    }
-    else {
-      $query[$query_item] = $options[$query_item];
-    }
-  }
-
-  return array(
-    'widget' => array(
-      '#theme' => 'html_tag',
-      '#tag' => 'script',
-      '#value' => '',
-      '#attributes' => array(
-        'type' => 'text/javascript',
-        'src' => url(
-          "//disqus.com/forums/${options['domain']}/$function.js",
-          array(
-            'external' => TRUE,
-            'query' => $query,
-          )
-        ),
-      ),
-    ),
-    '#theme_wrappers' => array('container'),
-    '#attributes' => array(
-      'id' => $configuration[$function]['id'],
-      'class' => array('dsq-widget'),
-    ),
-  );
-}
-
-/**
  * Implementation of hook_form_alter().
  */
 function disqus_form_alter(&$form, $form_state, $form_id) {
@@ -689,13 +383,13 @@ function disqus_form_alter(&$form, $form_state, $form_id) {
     // Only display the toggle Disqus comments setting if comments are available
     // for the given node type.
     $types = variable_get('disqus_nodetypes', array());
-    if (isset($types[$node->type]) && !empty($types[$node->type])) {
+    if (isset($types[$node->getType()]) && !empty($types[$node->getType()])) {
       // Add a comment settings fieldset for users with "toggle disqus comments" permission
       // when Drupal core Comments module is disabled.
       if (!isset($form['comment_settings'])) {
         $form['comment_settings'] = array(
           '#type' => 'fieldset',
-          '#access' => user_access('toggle disqus comments'),
+          '#access' => \Drupal::currentUser()->hasPermission('toggle disqus comments'),
           '#title' => t('Comment settings'),
           '#collapsible' => TRUE,
           '#collapsed' => TRUE,
@@ -708,7 +402,7 @@ function disqus_form_alter(&$form, $form_state, $form_id) {
           // Ensure only core Comment administrators see Comment module settings
           $form['comment_settings']['comment']['#access'] = $form['comment_settings']['#access'];
           // But reveal parent comment settings fieldset if user has toggle permission
-          $form['comment_settings']['#access'] = user_access('toggle disqus comments');
+          $form['comment_settings']['#access'] = \Drupal::currentUser()->hasPermission('toggle disqus comments');
         }
       }
       // Add the Disqus settings into the comment settings fieldset for users with toggle permission.
@@ -717,13 +411,26 @@ function disqus_form_alter(&$form, $form_state, $form_id) {
         '#title' => t('Disqus comments'),
         '#description' => t('Users can post comments using <a href="@disqus">Disqus</a>.', array('@disqus' => 'http://disqus.com')),
         '#default_value' => isset($node->disqus['status']) ? $node->disqus['status'] : TRUE,
-        '#access' => user_access('toggle disqus comments'),
+        '#access' => \Drupal::currentUser()->hasPermission('toggle disqus comments'),
       );
     }
   }
 }
 
 /**
+ * Implements hook_views_data_alter().
+ */
+function disqus_views_data_alter(&$data) {
+  // Number of Disqus comments made on the given node.
+  $data['node']['disqus_comment_count']['field'] = array(
+    'title' => t('Disqus Comment Count'),
+    'group' => t('Content'),
+    'help' => t('The number of Disqus comments made on the post. Note that this will not work in the preview.'),
+    'id' => 'disqus_comment_count',
+  );
+}
+
+/**
  * Implements hook_theme().
  */
 function disqus_theme() {
@@ -746,8 +453,18 @@ function disqus_theme() {
  */
 function theme_disqus_noscript($variables = array()) {
   $disqus = $variables['disqus'];
-  // Return the comment markup.
-  return '<noscript><p>' . l(t('View the discussion thread.'), 'http://' . $disqus['domain'] . '.disqus.com/?url=' . urlencode($disqus['url'])) . '</p></noscript>';
+
+  $return = array(
+    '#markup' => '<noscript><p>' . l(t('View the discussion thread.'), 'http://' . $disqus['domain'] . '.disqus.com/?url=' . urlencode($disqus['url'])) . '</p></noscript>',
+    '#attached' => array(
+      'js' => array(
+        drupal_get_path('module', 'disqus') . '/disqus.js',
+        array('type' => 'setting', 'data' => array('disqus' => $disqus))
+      )
+    )
+  );
+
+  return drupal_render($return);
 }
 
 /**
@@ -762,13 +479,5 @@ function theme_disqus_noscript($variables = array()) {
  *   The instance of the Disqus API.
  */
 function disqus($user_api_key = NULL, $forum_api_key = NULL) {
-  module_load_include('php', 'disqus', 'disqus');
-  return new Disqus($user_api_key, $forum_api_key);
-}
-
-/**
- * Implementation of hook_views_api().
- */
-function disqus_views_api() {
-  return array('api' => 3);
+  return new \Drupal\disqus\Disqus($user_api_key, $forum_api_key);
 }
diff --git a/disqus.php b/disqus.php
deleted file mode 100644
index d8f12d1..0000000
--- a/disqus.php
+++ /dev/null
@@ -1,386 +0,0 @@
-<?php
-
-/**
- * @file
- * Provides the Disqus PHP API.
- */
-
-/**
- * The Disqus PHP API.
- *
- * @package    Disqus
- * @author     Rob Loach (http://www.robloach.net)
- * @version    1.1.0
- * @access     public
- * @license    GPL (http://www.opensource.org/licenses/gpl-2.0.php)
- *
- * @example
- * @code
- *   // The user API key obtained from http://disqus.com/api/get_my_key/ .
- *   $user_api_key = 'Your Key Here!';
- *
- *   // Make sure to catch any errors generated by Disqus.
- *   try {
- *     $disqus = new Disqus($user_api_key);
- *
- *     // Get the forum list.
- *     $forums = $disqus->get_forum_list();
- *
- *     foreach ($forums as $forum) {
- *       echo $forum->name;
- *     }
- *   } catch(DisqusException $exception) {
- *     // Display the error.
- *     echo $exception->getMessage();
- *   }
- * @endcode
- */
-class Disqus {
-  public $user_api_key = NULL;
-  public $forum_api_key = NULL;
-  public $api_url = 'http://disqus.com/api/';
-  public $api_version = '1.1';
-
-  /**
-   * Creates a new interface to the Disqus API.
-   *
-   * @param $user_api_key
-   *   (optional) The User API key to use.
-   * @param $forum_api_key
-   *   (optional) The Forum API key to use.
-   * @param $api_url
-   *   (optional) The prefix URL to use when calling the Disqus API.
-   */
-  function __construct($user_api_key = NULL, $forum_api_key = NULL, $api_url = 'http://disqus.com/api/') {
-    $this->user_api_key = $user_api_key;
-    $this->forum_api_key = $forum_api_key;
-    $this->$api_url = $api_url;
-  }
-
-  /**
-   * Validate API key and get username.
-   */
-  function get_user_name() {
-    return $this->call('get_user_name', array(), TRUE);
-  }
-
-  /**
-   * Get a forum API key for a specific forum.
-   *
-   * @param $forum_id
-   *   the unique id of the forum
-   * @return
-   *   A string which is the Forum Key for the given forum.
-   */
-  function get_forum_api_key($forum_id) {
-    return $this->call('get_forum_api_key', array('forum_id' => $forum_id));
-  }
-
-  /**
-   * Returns an array of hashes representing all forums the user owns.
-   *
-   * @return
-   *   An array of hashes representing all forums the user owns.
-   */
-  function get_forum_list() {
-    return $this->call('get_forum_list');
-  }
-
-  /**
-   * Get a list of comments on a website.
-   *
-   * Both filter and exclude are multivalue arguments with coma as a divider.
-   * That makes is possible to use combined requests. For example, if you want
-   * to get all deleted spam messages, your filter argument should contain
-   * 'spam,killed' string.
-   *
-   * @param $forum_id
-   *   The forum ID.
-   * @param $options
-   *   - limit: Number of entries that should be included in the response. Default is 25.
-   *   - start: Starting point for the query. Default is 0.
-   *   - filter: Type of entries that should be returned.
-   *   - exclude: Type of entries that should be excluded from the response.
-   * @return
-   *   Returns posts from a forum specified by id.
-   */
-  function get_forum_posts($forum_id, array $options = array()) {
-    $options['forum_id'] = $forum_id;
-    return $this->call('get_forum_posts', $options);
-  }
-
-  /**
-   * Count a number of comments in articles.
-   *
-   * @param $thread_ids
-   *   an array of thread IDs belonging to the given forum.
-   * @return
-   *   A hash having thread_ids as keys and 2-element arrays as values.
-   */
-  function get_num_posts(array $thread_ids = array()) {
-    $thread_ids = implode(',', $thread_ids);
-    return $this->call('get_num_posts', array('thread_ids' => $thread_ids));
-  }
-
-  /**
-   * Get a list of threads on a website.
-   *
-   * @param $forum_id
-   *   the unique id of the forum.
-   * @param $limit
-   *   Number of entries that should be included in the response.
-   * @param $start
-   *   Starting point for the query.
-   * @return
-   *   An array of hashes representing all threads belonging to the given forum.
-   */
-  function get_thread_list($forum_id, $limit = 25, $start = 0) {
-    return $this->call('get_thread_list', array('forum_id' => $forum_id, 'limit' => $limit, 'start' => 0));
-  }
-
-  /**
-   * Get a list of threads with new comments.
-   *
-   * @param $forum_id
-   *   The Forum ID.
-   * @param $since
-   *   Start date for new posts. Format: 2009-03-30T15:41, Timezone: UTC.
-   */
-  function get_updated_threads($forum_id, $since) {
-    return $this->call('get_updated_threads', array('forum_id' => $forum_id, 'since' => $since));
-  }
-
-  /**
-   * Get a list of comments in a thread.
-   *
-   * Both filter and exclude are multivalue arguments with coma as a divider.
-   * That makes is possible to use combined requests. For example, if you want
-   * to get all deleted spam messages, your filter argument should contain
-   * 'spam,killed' string. Note that values are joined by AND statement so
-   * 'spam,new' will return all messages that are new and marked as spam. It
-   * will not return messages that are new and not spam or that are spam but
-   * not new (i.e. has already been moderated).
-   *
-   * @param $thread_id
-   *   The ID of a thread belonging to the given forum
-   * @param $limit
-   *   - limit: Number of entries that should be included in the response. Default is 25.
-   *   - start: Starting point for the query. Default is 0.
-   *   - filter: Type of entries that should be returned (new, spam or killed).
-   *   - exclude: Type of entries that should be excluded from the response (new, spam or killed).
-   * @return
-   *   An array of hashes representing representing all posts belonging to the
-   *   given forum.
-   */
-  function get_thread_posts($thread_id, array $options = array()) {
-    $options['thread_id'] = $thread_id;
-    return $this->call('get_thread_posts', $options);
-  }
-
-  /**
-   * Get or create thread by identifier.
-   *
-   * This method tries to find a thread by its identifier and title. If there is
-   * no such thread, the method creates it. In either case, the output value is
-   * a thread object.
-   *
-   * @param $identifier
-   *   Unique value (per forum) for a thread that is used to keep be able to get
-   *   data even if permalink is changed.
-   * @param $title
-   *   The title of the thread to possibly be created.
-   * @return
-   *   Returns a hash with two keys:
-   *   - thread: a hash representing the thread corresponding to the identifier.
-   *   - created: indicates whether the thread was created as a result of this
-   *     method call. If created, it will have the specified title.
-   */
-  function thread_by_identifier($identifier, $title) {
-    return $this->call('thread_by_identifier', array('title' => $title, 'identifier' => $identifier), TRUE);
-  }
-
-  /**
-   * Get thread by URL.
-   *
-   * Finds a thread by its URL. Output value is a thread object.
-   *
-   * @param $url
-   *   the URL to check for an associated thread
-   * @param $partner_api_key
-   *   (optional) The Partner API key.
-   * @return
-   *   A thread object, otherwise NULL.
-   */
-  function get_thread_by_url($url, $partner_api_key = NULL) {
-    return $this->call('get_thread_by_url', array('url' => $url));
-  }
-
-  /**
-   * Updates thread.
-   *
-   * Updates thread, specified by id and forum API key, with values described in
-   * the optional arguments.
-   *
-   * @param $thread_id
-   *   the ID of a thread belonging to the given forum
-   * @param $options
-   *   - title: the title of the thread
-   *   - slug: the per-forum-unique string used for identifying this thread in
-   *           disqus.com URL’s relating to this thread. Composed of
-   *           underscore-separated alphanumeric strings.
-   *   - url: the URL this thread is on, if known.
-   *   - allow_comments: whether this thread is open to new comments
-   * @return
-   *   Returns an empty success message.
-   */
-  function update_thread($thread_id, array $options = array()) {
-    $options['thread_id'] = $thread_id;
-    return $this->call('update_thread', $options, TRUE);
-  }
-
-  /**
-   * Creates a new post.
-   *
-   * Creates a comment to the thread specified by id.
-   *
-   * @param $thread_id
-   *   the thread to post to
-   * @param $message
-   *   the content of the post
-   * @param $author_name
-   *   the post creator’s name
-   * @param $author_email
-   *   the post creator’s email address
-   * @param $options
-   *   - partner_api_key
-   *   - created_at: Format: 2009-03-30T15:41, Timezone: UTC
-   *   - ip_address: the author’s IP address
-   *   - author_url: the author's homepage
-   *   - parent_post: the id of the parent post
-   *   - state: Comment's state, must be one of the following: approved,
-   *            unapproved, spam, killed
-   */
-  function create_post($thread_id, $message, $author_name, $author_email, array $options = array()) {
-    $options['thread_id'] = $thread_id;
-    $options['message'] = $message;
-    $options['author_name'] = $author_name;
-    $options['author_email'] = $author_email;
-    return $this->call('create_post', $options, TRUE);
-  }
-
-  /**
-   * Delete a comment or mark it as spam (or not spam).
-   *
-   * @param $post_id
-   *   The Post ID.
-   * @param $action
-   *   Name of action to be performed. Value can be 'spam', 'approve' or 'kill'.
-   *
-   * @return
-   *   Returns modified version.
-   */
-  function moderate_post($post_id, $action) {
-    return $this->call('create_post', array('post_id' => $post_id, 'action' => $action), TRUE);
-  }
-
-  /**
-   * Makes a call to a Disqus API method.
-   *
-   * @return 
-   *   The Disqus object.
-   * @param $method
-   *   The Disqus API method to call.
-   * @param object $arguments
-   *   An associative array of arguments to be passed.
-   * @param $post
-   *   TRUE or FALSE, depending on whether we're making a POST call.
-   */
-  function call($function, $arguments = array(), $post = FALSE) {
-    // Construct the arguments.
-    $args = '';
-    if (!isset($arguments['user_api_key'])) {
-      $arguments['user_api_key'] = $this->user_api_key;
-    }
-    if (!isset($arguments['forum_api_key'])) {
-      $arguments['forum_api_key'] = $this->forum_api_key;
-    }
-    if (!isset($arguments['api_version'])) {
-      $arguments['api_version'] = $this->api_version;
-    }
-    foreach ($arguments as $argument => $value) {
-      if (!empty($value)) {
-        $args .= $argument .'='. urlencode($value) .'&';
-      }
-    }
-
-    // Call the Disqus API.
-    $ch = curl_init();
-    if ($post) {
-      curl_setopt($ch, CURLOPT_URL, $this->api_url . $function .'/');
-      curl_setopt($ch, CURLOPT_POSTFIELDS, $args);
-      curl_setopt($ch, CURLOPT_POST, 1);
-    }
-    else {
-      curl_setopt($ch, CURLOPT_URL, $this->api_url . $function .'/?'. $args);
-    }
-    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
-    curl_setopt($ch, CURLOPT_TIMEOUT, 5);
-    $data = curl_exec($ch);
-    $info = curl_getinfo($ch);
-    curl_close($ch);
-    
-    // Check the results for errors (200 is a successful HTTP call).
-    if ($info['http_code'] == 200) {
-      $disqus = json_decode($data);
-      if ($disqus->succeeded) {
-        // There weren't any errors, so return the results.
-        return isset($disqus->message) ? $disqus->message : NULL;
-      }
-      else {
-        throw new DisqusException(isset($disqus->message) ? $disqus->message : NULL, 0, $info, $disqus);
-      }
-    }
-    else {
-      throw new DisqusException('There was an error querying the Disqus API.', $info['http_code'], $info);
-    }
-  }
-}
-
-/**
- * Any unsucessful result that's created by Disqus API will generate a DisqusException.
- */
-class DisqusException extends Exception {
-  /**
-   * The information returned from the cURL call.
-   */
-  public $info = NULL;
-
-  /**
-   * The information returned from the Disqus call.
-   */
-  public $disqus = NULL;
-
-  /**
-   * Creates a DisqusException.
-   * @param $message
-   *   The message for the exception.
-   * @param $code
-   *   (optional) The error code.
-   * @param $info
-   *   (optional) The result from the cURL call.
-   */
-  public function __construct($message, $code = 0, $info = NULL, $disqus = NULL) {
-    $this->info = $info;
-    $this->disqus = $disqus;
-    parent::__construct($message, $code);
-  }
-
-  /**
-   * Converts the exception to a string.
-   */
-  public function __toString() {
-    $code = isset($this->disqus->code) ? $this->disqus->code : (isset($info['http_code']) ? $info['http_code'] : 0);
-    $message = $this->getMessage();
-    return __CLASS__ .": [$code]: $message\n";
-  }
-}
diff --git a/disqus.routing.yml b/disqus.routing.yml
new file mode 100644
index 0000000..0674c32
--- /dev/null
+++ b/disqus.routing.yml
@@ -0,0 +1,14 @@
+disqus.admin:
+  path: '/admin/config/services/disqus'
+  defaults:
+    _form: '\Drupal\disqus\Form\DisqusSettingsForm'
+    _title: 'Disqus'
+  requirements:
+    _permission: 'administer disqus'
+
+disqus.close_window:
+  path: '/disqus/closewindow'
+  defaults:
+    _content: 'Drupal\disqus\Controller\DisqusController::closeWindow'
+  requirements:
+    _permission: 'access content'
diff --git a/disqus.settings.js b/disqus.settings.js
new file mode 100644
index 0000000..e7155bb
--- /dev/null
+++ b/disqus.settings.js
@@ -0,0 +1,67 @@
+/**
+ * @file
+ * Javascript for disqus configuration form.
+ */
+
+(function ($) {
+
+"use strict";
+
+Drupal.behaviors.disqusSettingsForm = {
+  attach: function (context) {
+    var $context = $(context);
+
+    $context.find('#edit-visibility').drupalSetSummary(function(context) {
+      var vals = [];
+
+      $('#edit-disqus-nodetypes div.form-type-checkbox').each(function(){
+        var vals_node_types = [];
+        if ($(this).find('input').is(':checked')) {
+          vals_node_types.push(Drupal.checkPlain($(this).find('label').text()));
+        }
+
+        if (vals_node_types.length) {
+          vals.push($('label[for="edit-disqus-nodetypes"]').text() + ': ' + vals_node_types.join(', '));
+        }
+      });
+
+      vals.push(Drupal.t('Location: ') + Drupal.checkPlain($('#edit-disqus-location').val()));
+      vals.push(Drupal.t('Weight: ') + Drupal.checkPlain($('#edit-disqus-weight').val()));
+
+      return vals.join('<br />');
+    });
+
+    $context.find('#edit-behavior').drupalSetSummary(function(context) {
+      var vals = [];
+
+      if ($('#edit-disqus-userapikey').val()) {
+        vals.push($('#edit-disqus-userapikey').parent().find('label').text());
+      }
+
+      var checkboxes = ['#edit-disqus-localization', '#edit-disqus-inherit-login', '#edit-disqus-developer'];
+      for (var i in checkboxes) {
+        if ($(checkboxes[i]).is(':checked')) {
+          vals.push($(checkboxes[i]).parent().find('label').text());
+        }
+      }
+
+      return vals.join(', ');
+    });
+
+    $context.find('#edit-advanced').drupalSetSummary(function(context) {
+      var vals = [];
+
+      if ($('#edit-disqus-publickey').val()) {
+        vals.push($('#edit-disqus-publickey').parent().find('label').text());
+      }
+
+      if ($('#edit-disqus-secretkey').val()) {
+        vals.push($('#edit-disqus-secretkey').parent().find('label').text());
+      }
+
+      return vals.join(', ');
+    });
+  }
+};
+
+})(jQuery);
diff --git a/disqus.views.inc b/disqus.views.inc
deleted file mode 100644
index e836f6e..0000000
--- a/disqus.views.inc
+++ /dev/null
@@ -1,78 +0,0 @@
-<?php
-
-/**
- * @file
- * Views integration with the Disqus module.
- */
-
-/**
- * Implements hook_views_data_alter().
- */
-function disqus_views_data_alter(&$data) {
-  // Number of Disqus comments made on the given node.
-  $data['node']['disqus_comment_count'] = array(
-    'field' => array(
-      'title' => t('Disqus Comment Count'),
-      'help' => t('The number of Disqus comments made on the post. Note that this will not work in the preview.'),
-      'handler' => 'views_handler_field_node_disqus_comment_count',
-    ),
-  );
-}
-
-/**
- * Field handler to present the number of Disqus comments on a node.
- */
-class views_handler_field_node_disqus_comment_count extends views_handler_field {
-  function init(&$view, &$options) {
-    parent::init($view, $options);
-  }
-
-  function query() {
-    // Override parent::query() without altering query.
-  }
-
-  /**
-   * When rendering the field.
-   */
-  function render($values) {
-    // Ensure Disqus comments are available on the node user has access to edit this node.
-    $node = node_load($values->nid);
-    if (user_access('view disqus comments') && isset($node->disqus)) {
-      // Extract the Disqus values.
-      $disqus = $node->disqus;
-      // Build a renderable array for the link.
-      $content = array(
-        '#theme' => 'link',
-        '#text' => t('Comments'),
-        '#path' => $disqus['identifier'],
-        '#options' => array(
-          'fragment' => 'disqus_thread',
-          'attributes' => array(
-            // Identify the node for Disqus with the unique identifier:
-            // http://docs.disqus.com/developers/universal/#comment-count
-            'data-disqus-identifier' => $disqus['identifier'],
-          ),
-          'html' => FALSE,
-        ),
-      );
-
-      /**
-       * This attaches disqus.js, which will look for the DOM variable
-       * disqusComments which is set below. When found, the disqus javascript
-       * api replaces the html element with the attribute:
-       * "data-disqus-identifier" and replaces the element with the number of
-       * comments on the node.
-       */
-      $content['#attached'] = array(
-        'js' => array(
-          array('data' => drupal_get_path('module', 'disqus') . '/disqus.js'),
-          array(
-            'data' => array('disqusComments' => $disqus['domain']),
-            'type' => 'setting',
-          ),
-        ),
-      );
-      return drupal_render($content);
-    }
-  }
-}
diff --git a/lib/Drupal/disqus/Controller/DisqusController.php b/lib/Drupal/disqus/Controller/DisqusController.php
new file mode 100644
index 0000000..82fb868
--- /dev/null
+++ b/lib/Drupal/disqus/Controller/DisqusController.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Drupal\disqus\Controller;
+
+use Drupal\Core\Controller\ControllerInterface;
+
+class DisqusController implements ControllerInterface {
+  /**
+   * Menu callback; Automatically closes the window after the user logs in.
+   *
+   * @return
+   *   Confirmation message and link that closes overlay window.
+   */
+  public function closeWindow() {
+    drupal_add_js('window.close();', 'inline');
+    return t('Thank you for logging in. Please close this window, or <a href="@clickhere">click here</a> to continue.', array('@clickhere' => 'javascript:window.close();'));
+  }
+}
diff --git a/lib/Drupal/disqus/Disqus.php b/lib/Drupal/disqus/Disqus.php
new file mode 100644
index 0000000..73c2ac4
--- /dev/null
+++ b/lib/Drupal/disqus/Disqus.php
@@ -0,0 +1,349 @@
+<?php
+
+/**
+ * @file
+ * Provides the Disqus PHP API.
+ */
+
+namespace Drupal\disqus;
+
+/**
+ * The Disqus PHP API.
+ *
+ * @package    Disqus
+ * @author     Rob Loach (http://www.robloach.net)
+ * @version    1.1.0
+ * @access     public
+ * @license    GPL (http://www.opensource.org/licenses/gpl-2.0.php)
+ *
+ * @example
+ * @code
+ *   // The user API key obtained from http://disqus.com/api/get_my_key/ .
+ *   $user_api_key = 'Your Key Here!';
+ *
+ *   // Make sure to catch any errors generated by Disqus.
+ *   try {
+ *     $disqus = new Drupal\disqus\Disqus($user_api_key);
+ *
+ *     // Get the forum list.
+ *     $forums = $disqus->get_forum_list();
+ *
+ *     foreach ($forums as $forum) {
+ *       echo $forum->name;
+ *     }
+ *   } catch(Drupal\disqus\DisqusException $exception) {
+ *     // Display the error.
+ *     echo $exception->getMessage();
+ *   }
+ * @endcode
+ */
+class Disqus {
+  public $user_api_key = NULL;
+  public $forum_api_key = NULL;
+  public $api_url = 'http://disqus.com/api/';
+  public $api_version = '1.1';
+
+  /**
+   * Creates a new interface to the Disqus API.
+   *
+   * @param $user_api_key
+   *   (optional) The User API key to use.
+   * @param $forum_api_key
+   *   (optional) The Forum API key to use.
+   * @param $api_url
+   *   (optional) The prefix URL to use when calling the Disqus API.
+   */
+  function __construct($user_api_key = NULL, $forum_api_key = NULL, $api_url = 'http://disqus.com/api/') {
+    $this->user_api_key = $user_api_key;
+    $this->forum_api_key = $forum_api_key;
+    $this->$api_url = $api_url;
+  }
+
+  /**
+   * Validate API key and get username.
+   */
+  function get_user_name() {
+    return $this->call('get_user_name', array(), TRUE);
+  }
+
+  /**
+   * Get a forum API key for a specific forum.
+   *
+   * @param $forum_id
+   *   the unique id of the forum
+   * @return
+   *   A string which is the Forum Key for the given forum.
+   */
+  function get_forum_api_key($forum_id) {
+    return $this->call('get_forum_api_key', array('forum_id' => $forum_id));
+  }
+
+  /**
+   * Returns an array of hashes representing all forums the user owns.
+   *
+   * @return
+   *   An array of hashes representing all forums the user owns.
+   */
+  function get_forum_list() {
+    return $this->call('get_forum_list');
+  }
+
+  /**
+   * Get a list of comments on a website.
+   *
+   * Both filter and exclude are multivalue arguments with coma as a divider.
+   * That makes is possible to use combined requests. For example, if you want
+   * to get all deleted spam messages, your filter argument should contain
+   * 'spam,killed' string.
+   *
+   * @param $forum_id
+   *   The forum ID.
+   * @param $options
+   *   - limit: Number of entries that should be included in the response. Default is 25.
+   *   - start: Starting point for the query. Default is 0.
+   *   - filter: Type of entries that should be returned.
+   *   - exclude: Type of entries that should be excluded from the response.
+   * @return
+   *   Returns posts from a forum specified by id.
+   */
+  function get_forum_posts($forum_id, array $options = array()) {
+    $options['forum_id'] = $forum_id;
+    return $this->call('get_forum_posts', $options);
+  }
+
+  /**
+   * Count a number of comments in articles.
+   *
+   * @param $thread_ids
+   *   an array of thread IDs belonging to the given forum.
+   * @return
+   *   A hash having thread_ids as keys and 2-element arrays as values.
+   */
+  function get_num_posts(array $thread_ids = array()) {
+    $thread_ids = implode(',', $thread_ids);
+    return $this->call('get_num_posts', array('thread_ids' => $thread_ids));
+  }
+
+  /**
+   * Get a list of threads on a website.
+   *
+   * @param $forum_id
+   *   the unique id of the forum.
+   * @param $limit
+   *   Number of entries that should be included in the response.
+   * @param $start
+   *   Starting point for the query.
+   * @return
+   *   An array of hashes representing all threads belonging to the given forum.
+   */
+  function get_thread_list($forum_id, $limit = 25, $start = 0) {
+    return $this->call('get_thread_list', array('forum_id' => $forum_id, 'limit' => $limit, 'start' => 0));
+  }
+
+  /**
+   * Get a list of threads with new comments.
+   *
+   * @param $forum_id
+   *   The Forum ID.
+   * @param $since
+   *   Start date for new posts. Format: 2009-03-30T15:41, Timezone: UTC.
+   */
+  function get_updated_threads($forum_id, $since) {
+    return $this->call('get_updated_threads', array('forum_id' => $forum_id, 'since' => $since));
+  }
+
+  /**
+   * Get a list of comments in a thread.
+   *
+   * Both filter and exclude are multivalue arguments with coma as a divider.
+   * That makes is possible to use combined requests. For example, if you want
+   * to get all deleted spam messages, your filter argument should contain
+   * 'spam,killed' string. Note that values are joined by AND statement so
+   * 'spam,new' will return all messages that are new and marked as spam. It
+   * will not return messages that are new and not spam or that are spam but
+   * not new (i.e. has already been moderated).
+   *
+   * @param $thread_id
+   *   The ID of a thread belonging to the given forum
+   * @param $limit
+   *   - limit: Number of entries that should be included in the response. Default is 25.
+   *   - start: Starting point for the query. Default is 0.
+   *   - filter: Type of entries that should be returned (new, spam or killed).
+   *   - exclude: Type of entries that should be excluded from the response (new, spam or killed).
+   * @return
+   *   An array of hashes representing representing all posts belonging to the
+   *   given forum.
+   */
+  function get_thread_posts($thread_id, array $options = array()) {
+    $options['thread_id'] = $thread_id;
+    return $this->call('get_thread_posts', $options);
+  }
+
+  /**
+   * Get or create thread by identifier.
+   *
+   * This method tries to find a thread by its identifier and title. If there is
+   * no such thread, the method creates it. In either case, the output value is
+   * a thread object.
+   *
+   * @param $identifier
+   *   Unique value (per forum) for a thread that is used to keep be able to get
+   *   data even if permalink is changed.
+   * @param $title
+   *   The title of the thread to possibly be created.
+   * @return
+   *   Returns a hash with two keys:
+   *   - thread: a hash representing the thread corresponding to the identifier.
+   *   - created: indicates whether the thread was created as a result of this
+   *     method call. If created, it will have the specified title.
+   */
+  function thread_by_identifier($identifier, $title) {
+    return $this->call('thread_by_identifier', array('title' => $title, 'identifier' => $identifier), TRUE);
+  }
+
+  /**
+   * Get thread by URL.
+   *
+   * Finds a thread by its URL. Output value is a thread object.
+   *
+   * @param $url
+   *   the URL to check for an associated thread
+   * @param $partner_api_key
+   *   (optional) The Partner API key.
+   * @return
+   *   A thread object, otherwise NULL.
+   */
+  function get_thread_by_url($url, $partner_api_key = NULL) {
+    return $this->call('get_thread_by_url', array('url' => $url));
+  }
+
+  /**
+   * Updates thread.
+   *
+   * Updates thread, specified by id and forum API key, with values described in
+   * the optional arguments.
+   *
+   * @param $thread_id
+   *   the ID of a thread belonging to the given forum
+   * @param $options
+   *   - title: the title of the thread
+   *   - slug: the per-forum-unique string used for identifying this thread in
+   *           disqus.com URL’s relating to this thread. Composed of
+   *           underscore-separated alphanumeric strings.
+   *   - url: the URL this thread is on, if known.
+   *   - allow_comments: whether this thread is open to new comments
+   * @return
+   *   Returns an empty success message.
+   */
+  function update_thread($thread_id, array $options = array()) {
+    $options['thread_id'] = $thread_id;
+    return $this->call('update_thread', $options, TRUE);
+  }
+
+  /**
+   * Creates a new post.
+   *
+   * Creates a comment to the thread specified by id.
+   *
+   * @param $thread_id
+   *   the thread to post to
+   * @param $message
+   *   the content of the post
+   * @param $author_name
+   *   the post creator’s name
+   * @param $author_email
+   *   the post creator’s email address
+   * @param $options
+   *   - partner_api_key
+   *   - created_at: Format: 2009-03-30T15:41, Timezone: UTC
+   *   - ip_address: the author’s IP address
+   *   - author_url: the author's homepage
+   *   - parent_post: the id of the parent post
+   *   - state: Comment's state, must be one of the following: approved,
+   *            unapproved, spam, killed
+   */
+  function create_post($thread_id, $message, $author_name, $author_email, array $options = array()) {
+    $options['thread_id'] = $thread_id;
+    $options['message'] = $message;
+    $options['author_name'] = $author_name;
+    $options['author_email'] = $author_email;
+    return $this->call('create_post', $options, TRUE);
+  }
+
+  /**
+   * Delete a comment or mark it as spam (or not spam).
+   *
+   * @param $post_id
+   *   The Post ID.
+   * @param $action
+   *   Name of action to be performed. Value can be 'spam', 'approve' or 'kill'.
+   *
+   * @return
+   *   Returns modified version.
+   */
+  function moderate_post($post_id, $action) {
+    return $this->call('create_post', array('post_id' => $post_id, 'action' => $action), TRUE);
+  }
+
+  /**
+   * Makes a call to a Disqus API method.
+   *
+   * @return
+   *   The Disqus object.
+   * @param $method
+   *   The Disqus API method to call.
+   * @param object $arguments
+   *   An associative array of arguments to be passed.
+   * @param $post
+   *   TRUE or FALSE, depending on whether we're making a POST call.
+   */
+  function call($function, $arguments = array(), $post = FALSE) {
+    // Construct the arguments.
+    $args = '';
+    if (!isset($arguments['user_api_key'])) {
+      $arguments['user_api_key'] = $this->user_api_key;
+    }
+    if (!isset($arguments['forum_api_key'])) {
+      $arguments['forum_api_key'] = $this->forum_api_key;
+    }
+    if (!isset($arguments['api_version'])) {
+      $arguments['api_version'] = $this->api_version;
+    }
+    foreach ($arguments as $argument => $value) {
+      if (!empty($value)) {
+        $args .= $argument .'='. urlencode($value) .'&';
+      }
+    }
+
+    // Call the Disqus API.
+    $ch = curl_init();
+    if ($post) {
+      curl_setopt($ch, CURLOPT_URL, $this->api_url . $function .'/');
+      curl_setopt($ch, CURLOPT_POSTFIELDS, $args);
+      curl_setopt($ch, CURLOPT_POST, 1);
+    }
+    else {
+      curl_setopt($ch, CURLOPT_URL, $this->api_url . $function .'/?'. $args);
+    }
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+    curl_setopt($ch, CURLOPT_TIMEOUT, 5);
+    $data = curl_exec($ch);
+    $info = curl_getinfo($ch);
+    curl_close($ch);
+
+    // Check the results for errors (200 is a successful HTTP call).
+    if ($info['http_code'] == 200) {
+      $disqus = json_decode($data);
+      if ($disqus->succeeded) {
+        // There weren't any errors, so return the results.
+        return isset($disqus->message) ? $disqus->message : NULL;
+      }
+      else {
+        throw new DisqusException(isset($disqus->message) ? $disqus->message : NULL, 0, $info, $disqus);
+      }
+    }
+    else {
+      throw new DisqusException('There was an error querying the Disqus API.', $info['http_code'], $info);
+    }
+  }
+}
diff --git a/lib/Drupal/disqus/DisqusException.php b/lib/Drupal/disqus/DisqusException.php
new file mode 100644
index 0000000..e065871
--- /dev/null
+++ b/lib/Drupal/disqus/DisqusException.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Provide Disqus Exception.
+ */
+
+namespace Drupal\disqus;
+
+/**
+ * Any unsucessful result that's created by Disqus API will generate a DisqusException.
+ */
+class DisqusException extends Exception {
+  /**
+   * The information returned from the cURL call.
+   */
+  public $info = NULL;
+
+  /**
+   * The information returned from the Disqus call.
+   */
+  public $disqus = NULL;
+
+  /**
+   * Creates a DisqusException.
+   * @param $message
+   *   The message for the exception.
+   * @param $code
+   *   (optional) The error code.
+   * @param $info
+   *   (optional) The result from the cURL call.
+   */
+  public function __construct($message, $code = 0, $info = NULL, $disqus = NULL) {
+    $this->info = $info;
+    $this->disqus = $disqus;
+    parent::__construct($message, $code);
+  }
+
+  /**
+   * Converts the exception to a string.
+   */
+  public function __toString() {
+    $code = isset($this->disqus->code) ? $this->disqus->code : (isset($info['http_code']) ? $info['http_code'] : 0);
+    $message = $this->getMessage();
+    return __CLASS__ .": [$code]: $message\n";
+  }
+}
diff --git a/lib/Drupal/disqus/Form/DisqusSettingsForm.php b/lib/Drupal/disqus/Form/DisqusSettingsForm.php
new file mode 100644
index 0000000..5f6cccb
--- /dev/null
+++ b/lib/Drupal/disqus/Form/DisqusSettingsForm.php
@@ -0,0 +1,215 @@
+<?php
+
+namespace Drupal\disqus\Form;
+
+use Drupal\Core\Form\ConfigFormBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+class DisqusSettingsForm extends ConfigFormBase {
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormID() {
+    return 'disqus_settings_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state) {
+    $disqus_config = $this->configFactory->get('disqus.settings');
+
+    $form['disqus_domain'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Shortname'),
+      '#description' => t('The website shortname that you registered Disqus with. If you registered http://example.disqus.com, you would enter "example" here.'),
+      '#default_value' => $disqus_config->get('disqus_domain'),
+    );
+    $form['settings'] = array(
+      '#type' => 'vertical_tabs',
+      '#attached' => array(
+        'js' => array(
+          drupal_get_path('module', 'disqus') . '/disqus.settings.js'
+        ),
+      ),
+      '#weight' => 50,
+    );
+    // Visibility settings.
+    $form['visibility'] = array(
+      '#type' => 'details',
+      '#title' => t('Visibility'),
+      '#group' => 'settings',
+    );
+    $types = node_type_get_types();
+    $options = array();
+    foreach ($types as $type) {
+      $options[$type->type] = $type->name;
+    }
+    $form['visibility']['disqus_nodetypes'] = array(
+      '#type' => 'checkboxes',
+      '#title' => t('Node Types'),
+      '#description' => t('Apply comments to only the following node types.'),
+      '#default_value' => $disqus_config->get('visibility.disqus_nodetypes'),
+      '#options' => $options,
+    );
+    $form['visibility']['disqus_location'] = array(
+      '#type' => 'select',
+      '#title' => t('Location'),
+      '#description' => t('Display the Disqus comments in the given location. When "Block" is selected, the comments will appear in the <a href="@disquscomments">Disqus Comments block</a>.', array('@disquscomments' => url('admin/structure/block'))),
+      '#default_value' => $disqus_config->get('visibility.disqus_location'),
+      '#options' => array(
+        'content_area' => t('Content Area'),
+        'block' => t('Block'),
+      ),
+    );
+    $form['visibility']['disqus_weight'] = array(
+      '#type' => 'select',
+      '#title' => t('Weight'),
+      '#description' => t('When the comments are displayed in the content area, you can change the position at which they will be shown.'),
+      '#default_value' => $disqus_config->get('visibility.disqus_weight'),
+      '#options' => drupal_map_assoc(array(-100, -75, -50, -25, 0, 25, 50, 75, 100)),
+      '#states' => array(
+        'visible' => array(
+          'select[name="disqus_location"]' => array('value' => 'content_area'),
+        ),
+      ),
+    );
+    // Behavior settings.
+    $form['behavior'] = array(
+      '#type' => 'details',
+      '#title' => t('Behavior'),
+      '#group' => 'settings',
+    );
+    $form['behavior']['disqus_localization'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Localization support'),
+      '#description' => t("When enabled, overrides the language set by Disqus with the language provided by the site."),
+      '#default_value' => $disqus_config->get('behavior.disqus_localization'),
+    );
+    $form['behavior']['disqus_inherit_login'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Inherit User Credentials'),
+      '#description' => t("When enabled and a user is logged in, the Disqus 'Post as Guest' login form will be pre-filled with the user's name and email address."),
+      '#default_value' => $disqus_config->get('behavior.disqus_inherit_login'),
+    );
+    $form['behavior']['disqus_developer'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Testing'),
+      '#description' => t('When enabled, uses the <a href="http://docs.disqus.com/help/2/">disqus_developer</a> flag to tell Disqus that you are in a testing environment. Threads will not display on the public community page with this set.'),
+      '#default_value' => $disqus_config->get('behavior.disqus_developer'),
+    );
+    // Advanced settings.
+    $form['advanced'] = array(
+      '#type' => 'details',
+      '#title' => t('Advanced'),
+      '#group' => 'settings',
+      '#description' => t('Use these settings to configure the more advanced uses of Disqus. You can find more information about these in the <a href="@applications">Applications</a> section of Disqus. To enable some of these features, you will require a <a href="@addons">Disqus Add-on Package</a>.', array(
+        '@applications' => 'http://disqus.com/api/applications/',
+        '@addons' => 'http://disqus.com/addons/',
+      )),
+    );
+    $form['advanced']['disqus_publickey'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Public Key'),
+      '#default_value' => $disqus_config->get('advanced.disqus_publickey'),
+    );
+    $form['advanced']['disqus_secretkey'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Secret Key'),
+      '#default_value' => $disqus_config->get('advanced.disqus_secretkey'),
+    );
+
+    $form['advanced']['sso'] = array(
+      '#weight' => 5,
+      '#type' => 'fieldset',
+      '#title' => t('Single Sign-on'),
+      '#collapsible' => FALSE,
+      '#collapsed' => FALSE,
+      '#states' => array(
+        'visible' => array(
+          'input[name="disqus_publickey"]' => array('empty' => FALSE),
+          'input[name="disqus_secretkey"]' => array('empty' => FALSE),
+        ),
+      ),
+    );
+    $form['advanced']['sso']['disqus_sso'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Use Single Sign-On'),
+      '#description' => t('Provide <a href="@sso">Single Sign-On</a> access to your site.', array(
+        '@sso' => 'http://disqus.com/api/sso/',
+      )),
+      '#default_value' => $disqus_config->get('disqus_sso'),
+    );
+    $form['advanced']['sso']['disqus_use_site_logo'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Use Site Logo'),
+      '#description' => t('Pass the site logo to Disqus for use as SSO login button.'),
+      '#default_value' => $disqus_config->get('advanced.sso.disqus_use_site_logo'),
+      '#states' => array(
+        'disabled' => array(
+          'input[name="disqus_sso"]' => array('checked' => FALSE),
+        ),
+      ),
+    );
+    $form['advanced']['sso']['disqus_logo'] = array(
+      '#type' => 'managed_file',
+      '#title' => t('Custom Logo'),
+      '#upload_location' => 'public://images',
+      '#default_value' => $disqus_config->get('advanced.sso.disqus_logo'),
+      '#states' => array(
+        'disabled' => array(
+          'input[name="disqus_sso"]' => array('checked' => FALSE),
+        ),
+        'visible' => array(
+          'input[name="disqus_use_site_logo"]' => array('checked' => FALSE),
+        ),
+      ),
+    );
+
+    $form['actions'] = array('#type' => 'actions');
+    $form['actions']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => $this->t('Add'),
+    );
+
+    return $form;
+  }
+
+  public function submitForm(array &$form, array &$form_state) {
+    $this->configFactory->get('disqus.settings')
+      ->set('disqus_domain', $form_state['values']['disqus_domain'])
+      ->set('visibility.disqus_nodetypes', $form_state['values']['disqus_nodetypes'])
+      ->set('visibility.disqus_location', $form_state['values']['disqus_location'])
+      ->set('visibility.disqus_weight', $form_state['values']['disqus_weight'])
+      ->set('behavior.disqus_localization', $form_state['values']['disqus_localization'])
+      ->set('behavior.disqus_inherit_login', $form_state['values']['disqus_inherit_login'])
+      ->set('behavior.disqus_developer', $form_state['values']['disqus_developer'])
+      ->set('advanced.disqus_publickey', $form_state['values']['disqus_publickey'])
+      ->set('advanced.disqus_secretkey', $form_state['values']['disqus_secretkey'])
+      ->set('advanced.sso.disqus_sso', $form_state['values']['disqus_sso'])
+      ->set('advanced.sso.disqus_use_site_logo', $form_state['values']['disqus_use_site_logo'])
+      ->set('advanced.sso.disqus_logo', $form_state['values']['disqus_logo'])
+      ->save();
+
+    parent::submitForm($form, $form_state);
+
+    // $old_logo = variable_get('disqus_logo', '');
+    // $new_logo = (isset($form_state['values']['disqus_logo'])) ? $form_state['values']['disqus_logo'] : '';
+    // // Ignore if the file hasn't changed.
+    // if ($new_logo != $old_logo) {
+    //   // Remove the old file and usage if previously set.
+    //   if ($old_logo != '') {
+    //     $file = file_load($old_logo);
+    //     file_usage_delete($file, 'disqus', 'disqus');
+    //     file_delete($file);
+    //   }
+    //   // Update the new file and usage.
+    //   if ($new_logo != '') {
+    //     $file = file_load($new_logo);
+    //     file_usage_add($file, 'disqus', 'disqus', 0);
+    //     $file->status = FILE_STATUS_PERMANENT;
+    //     file_save($file);
+    //   }
+    // }
+  }
+}
diff --git a/lib/Drupal/disqus/Plugin/Block/DisqusBaseBlock.php b/lib/Drupal/disqus/Plugin/Block/DisqusBaseBlock.php
new file mode 100644
index 0000000..e1a3637
--- /dev/null
+++ b/lib/Drupal/disqus/Plugin/Block/DisqusBaseBlock.php
@@ -0,0 +1,207 @@
+<?php
+
+namespace Drupal\disqus\Plugin\Block;
+
+use Drupal\block\BlockBase;
+
+abstract class DisqusBaseBlock extends BlockBase {
+  /**
+   * Overrides \Drupal\block\BlockBase::settings().
+   */
+  public function settings() {
+    return array(
+      'cache' => DRUPAL_CACHE_GLOBAL,
+    );
+  }
+
+  /**
+   * Helper method to get configuration value.
+   *
+   * @param  string $key
+   * @param  mixed  $default_value
+   * @return mixed
+   */
+  protected function configuration($key, $default_value = NULL) {
+    if (isset($this->configuration[$key])) {
+      return $this->configuration[$key];
+    }
+
+    if (!is_null($default_value)) {
+      return $default_value;
+    }
+
+    throw new UnexpectedValueException('Missing default value for ' . $key);
+  }
+
+  /**
+   * Overrides \Drupal\block\BlockBase::blockForm().
+   */
+  public function blockForm($form, &$form_state) {
+    return $this->_blockForm($form, $form_state, $this->id);
+  }
+
+  /**
+   * Helper for blockForm() method.
+   */
+  public function _blockForm($form, &$form_state, $delta) {
+    $form['disqus'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Disqus settings'),
+      '#tree' => TRUE,
+    );
+
+    $form['disqus'][$delta . '_items'] = array(
+      '#type' => 'select',
+      '#title' => t('Number of items to show'),
+      '#options' => array(1 => 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20),
+      '#default_value' => $this->configuration($delta .'_items', 5),
+      '#access' => TRUE,
+    );
+    $form['disqus'][$delta . '_showavatars'] = array(
+      '#type' => 'select',
+      '#title' => t('Show avatars'),
+      '#options' => array(FALSE => t('No'), TRUE => t('Yes')),
+      '#default_value' => $this->configuration($delta .'_showavatars', TRUE),
+      '#access' => ($delta == 'disqus_recent_comments') || ($delta == 'disqus_top_commenters'),
+    );
+    $form['disqus'][$delta . '_avatarsize'] = array(
+      '#type' => 'select',
+      '#title' => t('Avatar size'),
+      '#options' => array(
+        24 => t('X-Small (24px)'),
+        32 => t('Small (32px)'),
+        48 => t('Medium (48px)'),
+        92 => t('Large (92px)'),
+        128 => t('X-Large (128px)'),
+      ),
+      '#default_value' => $this->configuration($delta .'_avatarsize', 32),
+      '#access' => ($delta == 'disqus_recent_comments') || ($delta == 'disqus_top_commenters'),
+    );
+    $form['disqus'][$delta . '_colortheme'] = array(
+      '#type' => 'select',
+      '#title' => t('Color Theme'),
+      '#options' => array(
+        'blue' => t('Blue'),
+        'grey' => t('Grey'),
+        'green' => t('Green'),
+        'red' => t('Red'),
+        'orange' => t('Orange'),
+      ),
+      '#default_value' => $this->configuration($delta .'_colortheme', 'blue'),
+      '#access' => $delta == 'disqus_combination_widget',
+    );
+    $form['disqus'][$delta . '_defaulttabview'] = array(
+      '#type' => 'select',
+      '#title' => t('Default Tab View'),
+      '#options' => array(
+        'people' => t('People'),
+        'recent' => t('Recent'),
+        'popular' => t('Popular'),
+      ),
+      '#default_value' => $this->configuration($delta .'_defaulttabview', 'people'),
+      '#access' => $delta == 'disqus_combination_widget',
+    );
+    $form['disqus'][$delta . '_excerpt_length'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Comment Except Length'),
+      '#default_value' => $this->configuration($delta .'_excerpt_length', '200'),
+      '#access' => ($delta == 'disqus_recent_comments') || ($delta == 'disqus_combination_widget'),
+      '#size' => 4,
+    );
+    $form['disqus'][$delta . '_hide_mods'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Hide moderators in ranking'),
+      '#default_value' => $this->configuration($delta .'_hide_mods', FALSE),
+      '#access' => ($delta == 'disqus_top_commenters') || ($delta == 'disqus_combination_widget'),
+    );
+    return $form;
+  }
+
+  /**
+   * Overrides \Drupal\block\BlockBase::blockSubmit().
+   */
+  public function blockSubmit($form, &$form_state) {
+    foreach ($form_state['values']['disqus'] as $k => $v) {
+      if ($form['settings']['disqus'][$k]['#access']) {
+        $this->configuration[$k] = $v;
+      }
+    }
+  }
+
+  protected function getOptions() {
+    $disqus_config = \Drupal::config('disqus.settings');
+
+    return array(
+      'num_items' => $this->configuration($this->id . '_items', 5),
+      'avatars' => $this->configuration($this->id . '_showavatars', TRUE) ? array('avatar_size' => $this->configuration($this->id . '_avatarsize', 32)) : array('hide_avatars=1'),
+      'color' => $this->configuration($this->id . '_colortheme', 'blue'),
+      'default_tab' => $this->configuration($this->id . '_defaulttabview', 'people'),
+      'excerpt_length' => $this->configuration($this->id . '_excerpt_length', '200'),
+      'hide_mods' => $this->configuration($this->id . '_hide_mods', FALSE) ? '1' : '0',
+      'domain' => $disqus_config->get('disqus_domain'),
+    );
+  }
+
+  /**
+   * Helper function for disqus widget blocks content.
+   *
+   * @param $function
+   *   Name of the function (widget) that needs to be returned. Same as widget
+   *   API call name (w/o .json suffix).
+   * @param $options
+   *   Options array (query variables, domain, ...).
+   * @return
+   *   Render array that can be directly used for block content.
+   */
+  function render($function) {
+    $options = $this->getOptions();
+
+    $configuration = array(
+      'recent_comments_widget' => array(
+        'id' => 'dsq-recentcomments',
+        'query_items' => array('num_items', 'excerpt_length', 'avatars'),
+      ),
+      'popular_threads_widget' => array(
+        'id' => 'dsq-popthreads',
+        'query_items' => array('num_items'),
+      ),
+      'top_commenters_widget' => array(
+        'id' => 'dsq-topcommenters',
+        'query_items' => array('num_items', 'hide_mods', 'avatars'),
+      ),
+      'combination_widget' => array(
+        'id' => 'dsq-combinationwidget',
+        'query_items' => array('num_items', 'hide_mods', 'excerpt_length', 'color', 'default_tab'),
+      ),
+    );
+
+    if (empty($configuration[$function])) {
+      return FALSE;
+    }
+
+    $query = array();
+    foreach ($configuration[$function]['query_items'] as $query_item) {
+      if ($query_item === 'avatars') {
+        $query += $options[$query_item];
+      }
+      else {
+        $query[$query_item] = $options[$query_item];
+      }
+    }
+
+    $url = url("//disqus.com/forums/${options['domain']}/$function.js", array('external' => TRUE, 'query' => $query));
+
+    return array(
+      'widget' => array(
+        '#prefix' => '<script type="text/javascript" src="'. $url .'">',
+        '#suffix' => '</script>',
+        '#value' => '',
+      ),
+      '#theme_wrappers' => array('container'),
+      '#attributes' => array(
+        'id' => $configuration[$function]['id'],
+        'class' => array('dsq-widget'),
+      ),
+    );
+  }
+}
diff --git a/lib/Drupal/disqus/Plugin/Block/DisqusCombinationWidgetBlock.php b/lib/Drupal/disqus/Plugin/Block/DisqusCombinationWidgetBlock.php
new file mode 100644
index 0000000..9cd4cf9
--- /dev/null
+++ b/lib/Drupal/disqus/Plugin/Block/DisqusCombinationWidgetBlock.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\disqus\Plugin\Block;
+
+use Drupal\block\Annotation\Block;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ *
+ * @Block(
+ *   id = "disqus_combination_widget",
+ *   admin_label = @Translation("Disqus: Combination Widget"),
+ *   module = "disqus"
+ * )
+ */
+class DisqusCombinationWidgetBlock extends DisqusBaseBlock {
+  protected $id = 'disqus_combination_widget';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    return array(
+      '#title' => t('Comments'),
+      $this->render('combination_widget')
+    );
+  }
+}
diff --git a/lib/Drupal/disqus/Plugin/Block/DisqusCommentsBlock.php b/lib/Drupal/disqus/Plugin/Block/DisqusCommentsBlock.php
new file mode 100644
index 0000000..2b3af13
--- /dev/null
+++ b/lib/Drupal/disqus/Plugin/Block/DisqusCommentsBlock.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace Drupal\disqus\Plugin\Block;
+
+use Drupal\block\Annotation\Block;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ *
+ * @Block(
+ *   id = "disqus_comments",
+ *   admin_label = @Translation("Disqus: Comments"),
+ *   module = "disqus"
+ * )
+ */
+class DisqusCommentsBlock extends DisqusBaseBlock {
+  protected $id = 'disqus_comments';
+
+  /**
+   * Overrides DisqusBaseBlock::settings().
+   */
+  public function settings() {
+    return array(
+      'cache' => DRUPAL_CACHE_CUSTOM,
+    );
+  }
+
+  /**
+   * Overrides DisqusBaseBlock::blockForm().
+   */
+  public function blockForm($form, &$form_state) {
+    $form['disqus'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Disqus settings'),
+      '#tree' => TRUE,
+    );
+
+    $form['disqus']['#description'] = t('This block will be used to display the comments from Disqus when comments are applied to the given page. Visit the <a href="@disqussettings">Disqus settings</a> to configure when this is visible.', array('@disqussettings' => url('admin/config/services/disqus')));
+
+    return $form;
+  }
+
+  /**
+   * Overrides DisqusBaseBlock::blockSubmit().
+   */
+  public function blockSubmit($form, &$form_state) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    $disqus_config = \Drupal::config('disqus.settings');
+
+    if ($disqus_config->get('visibility.disqus_location') === 'block' && \Drupal::currentUser()->hasPermission('view disqus comments')) {
+      if ($object = menu_get_object()) {
+        return $this->buildForNodeEntity($object);
+      }
+
+      if ($object = menu_get_object('user')) {
+        return  $this->buildForUserEntity($object);
+      }
+    }
+  }
+
+  /**
+   * Build the disqus comment block for node entity.
+   */
+  protected function buildForNodeEntity($object) {
+      // For nodes, display if the Disqus object is enabled.
+      if (isset($object->disqus) && $object->disqus['status']) {
+        return array(
+          'disqus' => array(
+            '#type' => 'disqus',
+            '#disqus' => $object->disqus,
+            '#cache' => array(
+              'bin' => 'block',
+              'keys' => array('disqus', 'disqus_comments', 'node', $object->id()),
+              'tags' => array('content' => TRUE),
+            ),
+          ),
+        );
+      }
+  }
+
+  /**
+   * Build the disqus comment block for user entity.
+   */
+  protected function buildForUserEntity($object) {
+    if (isset($object->disqus)) {
+      return array(
+        'disqus' => array(
+          '#type' => 'disqus',
+          '#disqus' => $object->disqus,
+          '#cache' => array(
+            'bin' => 'block',
+            'keys' => array('disqus', 'disqus_comments', 'user', $object->id()),
+          ),
+        ),
+      );
+    }
+  }
+}
diff --git a/lib/Drupal/disqus/Plugin/Block/DisqusPopularThreadsBlock.php b/lib/Drupal/disqus/Plugin/Block/DisqusPopularThreadsBlock.php
new file mode 100644
index 0000000..9e23558
--- /dev/null
+++ b/lib/Drupal/disqus/Plugin/Block/DisqusPopularThreadsBlock.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\disqus\Plugin\Block;
+
+use Drupal\block\Annotation\Block;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ *
+ * @Block(
+ *   id = "disqus_popular_threads",
+ *   admin_label = @Translation("Disqus: Popular Threads"),
+ *   module = "disqus"
+ * )
+ */
+class DisqusPopularThreadsBlock extends DisqusBaseBlock {
+  protected $id = 'disqus_popular_threads';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    return array(
+      '#title' => t('Popular Threads'),
+      $this->render('popular_threads_widget')
+    );
+  }
+}
diff --git a/lib/Drupal/disqus/Plugin/Block/DisqusRecentCommentBlock.php b/lib/Drupal/disqus/Plugin/Block/DisqusRecentCommentBlock.php
new file mode 100644
index 0000000..8a95fff
--- /dev/null
+++ b/lib/Drupal/disqus/Plugin/Block/DisqusRecentCommentBlock.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\disqus\Plugin\Block;
+
+use Drupal\block\Annotation\Block;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ *
+ * @Block(
+ *   id = "disqus_recent_comments",
+ *   admin_label = @Translation("Disqus: Recent Comments"),
+ *   module = "disqus"
+ * )
+ */
+class DisqusRecentCommentBlock extends DisqusBaseBlock {
+  protected $id = 'disqus_recent_comments';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    return array(
+      '#title' => t('Recent Comments'),
+      $this->render('recent_comments_widget'),
+    );
+  }
+}
diff --git a/lib/Drupal/disqus/Plugin/Block/DisqusTopCommentersBlock.php b/lib/Drupal/disqus/Plugin/Block/DisqusTopCommentersBlock.php
new file mode 100644
index 0000000..31c4bd9
--- /dev/null
+++ b/lib/Drupal/disqus/Plugin/Block/DisqusTopCommentersBlock.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\disqus\Plugin\Block;
+
+use Drupal\block\Annotation\Block;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ *
+ * @Block(
+ *   id = "disqus_top_commenters",
+ *   admin_label = @Translation("Disqus: Top Commenters"),
+ *   module = "disqus"
+ * )
+ */
+class DisqusTopCommentersBlock extends DisqusBaseBlock {
+  protected $id = 'disqus_top_commenters';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    return array(
+      '#title' => t('Top Commenters'),
+      $this->render('top_commenters_widget')
+    );
+  }
+}
diff --git a/lib/Drupal/disqus/Plugin/views/field/DisqusCommentCount.php b/lib/Drupal/disqus/Plugin/views/field/DisqusCommentCount.php
new file mode 100644
index 0000000..b110135
--- /dev/null
+++ b/lib/Drupal/disqus/Plugin/views/field/DisqusCommentCount.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Drupal\disqus\Plugin\views\field;
+
+use Drupal\views\Plugin\views\field\FieldPluginBase;
+use Drupal\Component\Annotation\PluginID;
+use Drupal\views\ResultRow;
+
+/**
+ * Field handler to present the number of Disqus comments on a node.
+ *
+ * @ingroup views_field_handlers
+ *
+ * @PluginID("disqus_comment_count")
+ */
+class DisqusCommentCount extends FieldPluginBase {
+  /**
+   * {@inheritdoc}
+   */
+  function render(ResultRow $values) {
+    // Ensure Disqus comments are available on the node user has access to edit this node.
+    $node = node_load($values->nid);
+
+    if (!\Drupal::currentUser()->hasPermission('view disqus comments') || isset($node->disqus)) {
+      return;
+    }
+
+    // Extract the Disqus values.
+    $disqus = $node->disqus;
+
+    // Build a renderable array for the link.
+    $content = array(
+      '#theme' => 'link',
+      '#text' => t('Comments'),
+      '#path' => $disqus['identifier'],
+      '#options' => array(
+        'fragment' => 'disqus_thread',
+        'attributes' => array(
+          // Identify the node for Disqus with the unique identifier:
+          // http://docs.disqus.com/developers/universal/#comment-count
+          'data-disqus-identifier' => $disqus['identifier'],
+        ),
+        'html' => FALSE,
+      ),
+    );
+
+    /**
+     * This attaches disqus.js, which will look for the DOM variable
+     * disqusComments which is set below. When found, the disqus javascript
+     * api replaces the html element with the attribute:
+     * "data-disqus-identifier" and replaces the element with the number of
+     * comments on the node.
+     */
+    $content['#attached'] = array(
+      'js' => array(
+        array('data' => drupal_get_path('module', 'disqus') . '/disqus.js'),
+        array(
+          'data' => array('disqusComments' => $disqus['domain']),
+          'type' => 'setting',
+        ),
+      ),
+    );
+
+    return drupal_render($content);
+  }
+}
