From 72c86b43f3c905f558348be3ae0d86aa336fd514 Mon Sep 17 00:00:00 2001
From: Walter Jenner <jenner@formundcode.de>
Date: Tue, 4 Feb 2014 14:13:15 +0100
Subject: [PATCH] Issue #2142903 by TravisCarden: Port to drupal 8

---
 README.txt                                         |   2 +-
 checklistapi-drupal8-2142903-10.patch              | 880 +++++++++++++++++++++
 checklistapi.admin.inc                             |  56 --
 checklistapi.css                                   |   6 +-
 checklistapi.info                                  |   7 -
 checklistapi.info.yml                              |   7 +
 checklistapi.install                               |  17 -
 checklistapi.js                                    |   2 +-
 checklistapi.module                                | 287 ++++---
 checklistapi.pages.inc                             | 258 ------
 checklistapi.routing.yml                           |  10 +
 checklistapi.services.yml                          |   5 +
 checklistapi_example/checklistapi_example.info     |   6 -
 checklistapi_example/checklistapi_example.info.yml |   6 +
 checklistapi_example/checklistapi_example.install  |  15 -
 config/checklistapi.progress.yml                   |   0
 .../Access/checklistapiAccessCheck.php             |  28 +
 lib/Drupal/checklistapi/ChecklistapiChecklist.php  |  27 +-
 .../Controller/ChecklistapiController.php          |  84 ++
 .../Form/ChecklistapiChecklistClearConfirmForm.php |  89 +++
 .../Form/ChecklistapiDefinitionForm.php            | 174 ++++
 .../Routing/ChecklistapiRouteSubscriber.php        |  69 ++
 templates/checklistapi-progress-bar.html.twig      |   5 +
 templates/checklistapi-progress-bar.tpl.php        |  22 -
 24 files changed, 1523 insertions(+), 539 deletions(-)
 create mode 100644 checklistapi-drupal8-2142903-10.patch
 delete mode 100644 checklistapi.admin.inc
 delete mode 100644 checklistapi.info
 create mode 100644 checklistapi.info.yml
 delete mode 100644 checklistapi.install
 delete mode 100644 checklistapi.pages.inc
 create mode 100644 checklistapi.routing.yml
 create mode 100644 checklistapi.services.yml
 delete mode 100644 checklistapi_example/checklistapi_example.info
 create mode 100644 checklistapi_example/checklistapi_example.info.yml
 delete mode 100644 checklistapi_example/checklistapi_example.install
 create mode 100644 config/checklistapi.progress.yml
 create mode 100644 lib/Drupal/checklistapi/Access/checklistapiAccessCheck.php
 create mode 100644 lib/Drupal/checklistapi/Controller/ChecklistapiController.php
 create mode 100644 lib/Drupal/checklistapi/Form/ChecklistapiChecklistClearConfirmForm.php
 create mode 100644 lib/Drupal/checklistapi/Form/ChecklistapiDefinitionForm.php
 create mode 100644 lib/Drupal/checklistapi/Routing/ChecklistapiRouteSubscriber.php
 create mode 100644 templates/checklistapi-progress-bar.html.twig
 delete mode 100644 templates/checklistapi-progress-bar.tpl.php

diff --git a/README.txt b/README.txt
index 921c0d3..e687877 100644
--- a/README.txt
+++ b/README.txt
@@ -21,7 +21,7 @@ INSTALLATION
 ------------
 
 Checklist API is installed in the usual way. See
-http://drupal.org/documentation/install/modules-themes/modules-7.
+http://drupal.org/documentation/install/modules-themes/modules-8.
 
 
 IMPLEMENTATION
diff --git a/checklistapi-drupal8-2142903-10.patch b/checklistapi-drupal8-2142903-10.patch
new file mode 100644
index 0000000..69b6cc2
--- /dev/null
+++ b/checklistapi-drupal8-2142903-10.patch
@@ -0,0 +1,880 @@
+diff --git a/README.txt b/README.txt
+index 921c0d3..e687877 100644
+--- a/README.txt
++++ b/README.txt
+@@ -21,7 +21,7 @@ INSTALLATION
+ ------------
+ 
+ Checklist API is installed in the usual way. See
+-http://drupal.org/documentation/install/modules-themes/modules-7.
++http://drupal.org/documentation/install/modules-themes/modules-8.
+ 
+ 
+ IMPLEMENTATION
+diff --git a/checklistapi.admin.inc b/checklistapi.admin.inc
+deleted file mode 100644
+index 382e566..0000000
+--- a/checklistapi.admin.inc
++++ /dev/null
+@@ -1,56 +0,0 @@
+-<?php
+-
+-/**
+- * @file
+- * Admin page callback file for the Checklist API module.
+- */
+-
+-/**
+- * Page callback: Form constructor for the report form.
+- *
+- * @see checklistapi_menu()
+- *
+- * @ingroup forms
+- */
+-function checklistapi_report_form() {
+-  // Define table header.
+-  $header = array(
+-    t('Checklist'),
+-    t('Progress'),
+-    t('Last updated'),
+-    t('Last updated by'),
+-    t('Operations'),
+-  );
+-
+-  // Build table rows.
+-  $rows = array();
+-  $definitions = checklistapi_get_checklist_info();
+-  foreach ($definitions as $id => $definition) {
+-    $checklist = checklistapi_checklist_load($id);
+-    $row = array();
+-    $row[] = array(
+-      'data' => ($checklist->userHasAccess()) ? l($checklist->title, $checklist->path) : drupal_placeholder($checklist->title),
+-      'title' => (!empty($checklist->description)) ? $checklist->description : '',
+-    );
+-    $row[] = t('@completed of @total (@percent%)', array(
+-      '@completed' => $checklist->getNumberCompleted(),
+-      '@total' => $checklist->getNumberOfItems(),
+-      '@percent' => round($checklist->getPercentComplete()),
+-    ));
+-    $row[] = $checklist->getLastUpdatedDate();
+-    $row[] = $checklist->getLastUpdatedUser();
+-    $row[] = ($checklist->userHasAccess('edit') && $checklist->hasSavedProgress()) ? l(t('clear saved progress'), $checklist->path . '/clear', array(
+-      'query' => array('destination' => 'admin/reports/checklistapi'),
+-    )) : '';
+-    $rows[] = $row;
+-  }
+-
+-  // Compile table.
+-  $table = array(
+-    'header' => $header,
+-    'rows' => $rows,
+-    'empty' => t('No checklists available.'),
+-  );
+-
+-  return theme('table', $table);
+-}
+diff --git a/checklistapi.css b/checklistapi.css
+index b7cc6ea..e919e07 100644
+--- a/checklistapi.css
++++ b/checklistapi.css
+@@ -19,7 +19,7 @@
+   font-weight: normal;
+   margin-bottom: 0.5em;
+ }
+-#checklistapi-checklist-form .progress .bar,
+-#checklistapi-checklist-form .progress .filled {
+-  background-image: none;
++#checklistapi-checklist-form .compact-link {
++  clear: both;
+ }
++
+diff --git a/checklistapi.info b/checklistapi.info
+deleted file mode 100644
+index a4c534b..0000000
+--- a/checklistapi.info
++++ /dev/null
+@@ -1,7 +0,0 @@
+-name = Checklist API
+-description = Provides an API for creating fillable, persistent checklists.
+-core = 7.x
+-package = Other
+-files[] = lib/Drupal/checklistapi/ChecklistapiChecklist.php
+-files[] = tests/checklistapi.test
+-configure = admin/reports/checklistapi
+diff --git a/checklistapi.install b/checklistapi.install
+deleted file mode 100644
+index a42f1b0..0000000
+--- a/checklistapi.install
++++ /dev/null
+@@ -1,17 +0,0 @@
+-<?php
+-
+-/**
+- * @file
+- * Install, update and uninstall functions for the Checklist API module.
+- */
+-
+-/**
+- * Implements hook_uninstall().
+- */
+-function checklistapi_uninstall() {
+-  // Delete all Checklist API persistent variables.
+-  db_delete('variable')
+-    ->condition('name', db_like('checklistapi_') . '%', 'LIKE')
+-    ->execute();
+-  cache_clear_all('variables', 'cache_bootstrap');
+-}
+diff --git a/checklistapi.js b/checklistapi.js
+index c4c3707..95cfad4 100644
+--- a/checklistapi.js
++++ b/checklistapi.js
+@@ -6,7 +6,7 @@
+    */
+   Drupal.behaviors.checklistapiFieldsetSummaries = {
+     attach: function (context) {
+-      $('#checklistapi-checklist-form .vertical-tabs-panes > fieldset', context).drupalSetSummary(function (context) {
++      $('#checklistapi-checklist-form .vertical-tabs-panes > details', context).drupalSetSummary(function (context) {
+         var total = $(':checkbox.checklistapi-item', context).size(), args = {};
+         if (total) {
+           args['@complete'] = $(':checkbox.checklistapi-item:checked', context).size();
+diff --git a/checklistapi.module b/checklistapi.module
+index c229177..7ef4753 100644
+--- a/checklistapi.module
++++ b/checklistapi.module
+@@ -8,6 +8,92 @@
+  * completion times and users.
+  */
+ 
++use Drupal\checklistapi\ChecklistapiChecklist;
++
++/**
++ * Implements hook_menu_link_defaults().
++ */
++function checklistapi_menu_link_defaults() {
++  $links = array();
++
++  // Checklists report.
++  $links['admin/reports/checklistapi'] = array(
++    'link_title'       => 'Checklists',
++    'description' => 'Get an overview of your installed checklists with progress details.',
++    'route_name'  => 'checklistapi.admin_index',
++    'parent' => 'system.admin.reports',
++  );
++
++  return $links;
++}
++
++/**
++ * Implements hook_permission().
++ */
++function checklistapi_permission() {
++  $perms = array();
++
++  // Universal permissions.
++  $perms['view checklistapi checklists report'] = array(
++    'title' => t(
++      'View the !name report',
++      array('!name' => (user_access('view checklistapi checklists report')) ? l(t('Checklists'), 'admin/reports/checklistapi') : drupal_placeholder('Checklists'))
++    ),
++  );
++  $perms['view any checklistapi checklist'] = array(
++    'title' => t('View any checklist'),
++    'description' => $view_checklist_perm_description = t('Read-only access: View list items and saved progress.'),
++  );
++  $perms['edit any checklistapi checklist'] = array(
++    'title' => t('Edit any checklist'),
++    'description' => $edit_checklist_perm_description = t('Check and uncheck list items and save changes, or clear saved progress.'),
++  );
++
++  // Per checklist permissions.
++  foreach (checklistapi_get_checklist_info() as $id => $definition) {
++    if (empty($id)) {
++      continue;
++    }
++    $perms['view ' . $id . ' checklistapi checklist'] = array(
++      'title' => t(
++        'View the !name checklist',
++        array('!name' => (checklistapi_checklist_access($id)) ? l($definition['#title'], $definition['#path']) : drupal_placeholder($definition['#title']))
++      ),
++      'description' => $view_checklist_perm_description,
++    );
++    $perms['edit ' . $id . ' checklistapi checklist'] = array(
++      'title' => t(
++        'Edit the !name checklist',
++        array('!name' => (checklistapi_checklist_access($id)) ? l($definition['#title'], $definition['#path']) : drupal_placeholder($definition['#title']))
++      ),
++      'description' => $edit_checklist_perm_description,
++    );
++  }
++
++  return $perms;
++}
++
++/**
++ * Implements hook_theme().
++ */
++function checklistapi_theme() {
++  return array(
++    'checklistapi_compact_link' => array(
++      'file' => 'checklistapi.pages.inc',
++    ),
++    'checklistapi_progress_bar' => array(
++      'path' => drupal_get_path('module', 'checklistapi') . '/templates',
++      'template' => 'checklistapi-progress-bar',
++      'variables' => array(
++        'message' => '&nbsp;',
++        'number_complete' => 0,
++        'number_of_items' => 0,
++        'percent_complete' => '0',
++      ),
++    ),
++  );
++}
++
+ /**
+  * Access callback: Checks the current user's access to a given checklist.
+  *
+@@ -60,10 +146,12 @@ function checklistapi_checklist_load($id) {
+  *   exists, or an array of all checklist definitions if none is specified.
+  */
+ function checklistapi_get_checklist_info($id = NULL) {
+-  $definitions = &drupal_static(__FUNCTION__);
+-  if (!isset($definitions)) {
+-    // Get definitions.
+-    $definitions = module_invoke_all('checklistapi_checklist_info');
++  static $drupal_static_definitions;
++  if (!isset($drupal_static_definitions)) {
++    $drupal_static_definitions['definitions'] = &drupal_static(__FUNCTION__);
++  }
++  if (!isset($drupal_static_definitions['definitions'])) {
++    $definitions = \Drupal::moduleHandler()->invokeAll('checklistapi_checklist_info');
+     $definitions = checklistapi_sort_array($definitions);
+     // Let other modules alter them.
+     drupal_alter('checklistapi_checklist_info', $definitions);
+@@ -80,131 +168,24 @@ function checklistapi_get_checklist_info($id = NULL) {
+ }
+ 
+ /**
+- * Implements hook_help().
+- */
+-function checklistapi_help($path, $arg) {
+-  foreach (checklistapi_get_checklist_info() as $definition) {
+-    if ($definition['#path'] == $path && !empty($definition['#help'])) {
+-      return $definition['#help'];
+-    }
+-  }
+-}
+-
+-/**
+- * Implements hook_init().
+- */
+-function checklistapi_init() {
+-  // Disable page caching on all Checklist API module paths.
+-  $module_paths = array_keys(checklistapi_menu());
+-  if (in_array(current_path(), $module_paths)) {
+-    drupal_page_is_cacheable(FALSE);
+-  }
+-}
+-
+-/**
+- * Implements hook_menu().
+- */
+-function checklistapi_menu() {
+-  $items = array();
+-
+-  // Checklists report.
+-  $items['admin/reports/checklistapi'] = array(
+-    'title' => 'Checklists',
+-    'page callback' => 'checklistapi_report_form',
+-    'access arguments' => array('view checklistapi checklists report'),
+-    'description' => 'Get an overview of your installed checklists with progress details.',
+-    'file' => 'checklistapi.admin.inc',
+-  );
+-
+-  // Individual checklists.
+-  foreach (checklistapi_get_checklist_info() as $id => $definition) {
+-    if (empty($definition['#path']) || empty($definition['#title'])) {
+-      continue;
+-    }
+-
+-    // View/edit checklist.
+-    $items[$definition['#path']] = array(
+-      'title' => $definition['#title'],
+-      'description' => (!empty($definition['#description'])) ? $definition['#description'] : '',
+-      'page callback' => 'drupal_get_form',
+-      'page arguments' => array('checklistapi_checklist_form', $id),
+-      'access callback' => 'checklistapi_checklist_access',
+-      'access arguments' => array($id),
+-      'file' => 'checklistapi.pages.inc',
+-    );
+-    if (!empty($definition['#menu_name'])) {
+-      $items[$definition['#path']]['menu_name'] = $definition['#menu_name'];
+-    }
+-
+-    // Clear saved progress.
+-    $items[$definition['#path'] . '/clear'] = array(
+-      'title' => 'Clear',
+-      'page callback' => 'drupal_get_form',
+-      'page arguments' => array('checklistapi_checklist_clear_confirm', $id),
+-      'access callback' => 'checklistapi_checklist_access',
+-      'access arguments' => array($id, 'edit'),
+-      'file' => 'checklistapi.pages.inc',
+-      'type' => MENU_CALLBACK,
+-    );
+-
+-    // Toggle compact mode.
+-    $items[$definition['#path'] . '/compact'] = array(
+-      'title' => 'Compact mode',
+-      'page callback' => 'checklistapi_compact_page',
+-      'access callback' => 'checklistapi_checklist_access',
+-      'access arguments' => array($id),
+-      'file' => 'checklistapi.pages.inc',
+-      'type' => MENU_CALLBACK,
+-    );
+-  }
+-
+-  return $items;
+-}
+-
+-/**
+- * Implements hook_permission().
++ * Determines whether the current user is in compact mode.
++ *
++ * Compact mode shows checklist forms with less description text.
++ *
++ * Whether the user is in compact mode is determined by a cookie, which is set
++ * for the user by checklistapi_compact_page().
++ *
++ * If the user does not have the cookie, the default value is given by the
++ * system variable 'checklistapi_compact_mode', which itself defaults to FALSE.
++ * This does not have a user interface to set it: it is a hidden variable which
++ * can be set in the settings.php file.
++ *
++ * @return bool
++ *   TRUE when in compact mode, or FALSE when in expanded mode.
+  */
+-function checklistapi_permission() {
+-  $perms = array();
+-
+-  // Universal permissions.
+-  $perms['view checklistapi checklists report'] = array(
+-    'title' => t(
+-      'View the !name report',
+-      array('!name' => (user_access('view checklistapi checklists report')) ? l(t('Checklists'), 'admin/reports/checklistapi') : drupal_placeholder('Checklists'))
+-    ),
+-  );
+-  $perms['view any checklistapi checklist'] = array(
+-    'title' => t('View any checklist'),
+-    'description' => $view_checklist_perm_description = t('Read-only access: View list items and saved progress.'),
+-  );
+-  $perms['edit any checklistapi checklist'] = array(
+-    'title' => t('Edit any checklist'),
+-    'description' => $edit_checklist_perm_description = t('Check and uncheck list items and save changes, or clear saved progress.'),
+-  );
+-
+-  // Per checklist permissions.
+-  foreach (checklistapi_get_checklist_info() as $id => $definition) {
+-    if (empty($id)) {
+-      continue;
+-    }
+-    $perms['view ' . $id . ' checklistapi checklist'] = array(
+-      'title' => t(
+-        'View the !name checklist',
+-        array('!name' => (checklistapi_checklist_access($id)) ? l($definition['#title'], $definition['#path']) : drupal_placeholder($definition['#title']))
+-      ),
+-      'description' => $view_checklist_perm_description,
+-    );
+-    $perms['edit ' . $id . ' checklistapi checklist'] = array(
+-      'title' => t(
+-        'Edit the !name checklist',
+-        array('!name' => (checklistapi_checklist_access($id)) ? l($definition['#title'], $definition['#path']) : drupal_placeholder($definition['#title']))
+-      ),
+-      'description' => $edit_checklist_perm_description,
+-    );
+-  }
+-
+-  return $perms;
++function checklistapi_compact_mode() {
++  $config = \Drupal::config('checklistapi.progress');
++  return isset($_COOKIE['Drupal_visitor_checklistapi_compact_mode']) ? $_COOKIE['Drupal_visitor_checklistapi_compact_mode'] : $config->get('checklistapi_compact_mode');
+ }
+ 
+ /**
+@@ -276,22 +257,36 @@ function checklistapi_strtolowercamel($string) {
+ }
+ 
+ /**
+- * Implements hook_theme().
++ * Returns HTML for a link to show or hide inline item descriptions.
++ *
++ * @ingroup themeable
+  */
+-function checklistapi_theme() {
+-  return array(
+-    'checklistapi_compact_link' => array(
+-      'file' => 'checklistapi.pages.inc',
+-    ),
+-    'checklistapi_progress_bar' => array(
+-      'path' => drupal_get_path('module', 'checklistapi') . '/templates',
+-      'template' => 'checklistapi-progress-bar',
+-      'variables' => array(
+-        'message' => '&nbsp;',
+-        'number_complete' => 0,
+-        'number_of_items' => 0,
+-        'percent_complete' => 0,
+-      ),
+-    ),
+-  );
+-}
++function theme_checklistapi_compact_link() {
++  $output = '<div class="compact-link">';
++  if (checklistapi_compact_mode()) {
++    $output .= l(
++      t('Show item descriptions'),
++      current_path() . '/compact/off',
++      array(
++        'attributes' => array(
++          'title' => t('Expand layout to include item descriptions.'),
++        ),
++        'query' => drupal_get_destination(),
++      )
++    );
++  }
++  else {
++    $output .= l(
++      t('Hide item descriptions'),
++      current_path() . '/compact/on',
++      array(
++        'attributes' => array(
++          'title' => t('Compress layout by hiding item descriptions.'),
++        ),
++        'query' => drupal_get_destination(),
++      )
++    );
++  }
++  $output .= '</div>';
++  return $output;
++}
+\ No newline at end of file
+diff --git a/checklistapi.pages.inc b/checklistapi.pages.inc
+deleted file mode 100644
+index 9b832f3..0000000
+--- a/checklistapi.pages.inc
++++ /dev/null
+@@ -1,258 +0,0 @@
+-<?php
+-
+-/**
+- * @file
+- * Page callbacks for the Checklist API module.
+- */
+-
+-/**
+- * Page callback: Form constructor for "Clear saved progress" confirmation form.
+- *
+- * @param string $id
+- *   The checklist ID.
+- *
+- * @see checklistapi_menu()
+- *
+- * @ingroup forms
+- */
+-function checklistapi_checklist_clear_confirm($form, &$form_state, $id) {
+-  $checklist = checklistapi_checklist_load($id);
+-  $form['#checklist'] = $checklist;
+-  $question = t('Are you sure you want to clear %title saved progress?', array(
+-    '%title' => $checklist->title,
+-  ));
+-  $description = t('All progress details will be erased. This action cannot be undone.');
+-  $yes = t('Clear');
+-  $no = t('Cancel');
+-  return confirm_form($form, $question, $checklist->path, $description, $yes, $no);
+-}
+-
+-/**
+- * Form submission handler for checklistapi_checklist_clear_confirm().
+- */
+-function checklistapi_checklist_clear_confirm_submit($form, &$form_state) {
+-  // If user confirmed, clear saved progress.
+-  if ($form_state['values']['confirm']) {
+-    $form['#checklist']->clearSavedProgress();
+-  }
+-
+-  // Redirect back to checklist.
+-  $form_state['redirect'] = $form['#checklist']->path;
+-}
+-
+-/**
+- * Page callback: Form constructor for the checklist form.
+- *
+- * @param string $id
+- *   The checklist ID.
+- *
+- * @see checklistapi_checklist_form_submit()
+- * @see checklistapi_menu()
+- *
+- * @ingroup forms
+- */
+-function checklistapi_checklist_form($form, &$form_state, $id) {
+-  $form['#checklist'] = $checklist = checklistapi_checklist_load($id);
+-
+-  $form['progress_bar'] = array(
+-    '#type' => 'markup',
+-    '#markup' => theme('checklistapi_progress_bar', array(
+-      'message' => ($checklist->hasSavedProgress()) ? t('Last updated @date by !user', array(
+-        '@date' => $checklist->getLastUpdatedDate(),
+-        '!user' => $checklist->getLastUpdatedUser(),
+-      )) : '&nbsp;',
+-      'number_complete' => $checklist->getNumberCompleted(),
+-      'number_of_items' => $checklist->getNumberOfItems(),
+-      'percent_complete' => round($checklist->getPercentComplete()),
+-    )),
+-  );
+-  if (checklistapi_compact_mode()) {
+-    $form['#attributes']['class'] = array('compact-mode');
+-  }
+-  $form['compact_mode_link'] = array(
+-    '#markup' => theme('checklistapi_compact_link'),
+-  );
+-
+-  $form['checklistapi'] = array(
+-    '#attached' => array(
+-      'css' => array(drupal_get_path('module', 'checklistapi') . '/checklistapi.css'),
+-      'js' => array(drupal_get_path('module', 'checklistapi') . '/checklistapi.js'),
+-    ),
+-    '#tree' => TRUE,
+-    '#type' => 'vertical_tabs',
+-  );
+-
+-  // Loop through groups.
+-  $num_autochecked_items = 0;
+-  $groups = $checklist->items;
+-  foreach (element_children($groups) as $group_key) {
+-    $group = &$groups[$group_key];
+-    $form['checklistapi'][$group_key] = array(
+-      '#title' => filter_xss($group['#title']),
+-      '#type' => 'fieldset',
+-    );
+-    if (!empty($group['#description'])) {
+-      $form['checklistapi'][$group_key]['#description'] = filter_xss_admin($group['#description']);
+-    }
+-
+-    // Loop through items.
+-    foreach (element_children($group) as $item_key) {
+-      $item = &$group[$item_key];
+-      $saved_item = !empty($checklist->savedProgress[$item_key]) ? $checklist->savedProgress[$item_key] : 0;
+-      // Build title.
+-      $title = filter_xss($item['#title']);
+-      if ($saved_item) {
+-        // Append completion details.
+-        $user = user_load($saved_item['#uid']);
+-        $title .= t(
+-          '<span class="completion-details"> - Completed @time by !user</a>',
+-          array(
+-            '@time' => format_date($saved_item['#completed'], 'short'),
+-            '!user' => theme('username', array('account' => $user)),
+-          )
+-        );
+-      }
+-      // Set default value.
+-      $default_value = FALSE;
+-      if ($saved_item) {
+-        $default_value = TRUE;
+-      }
+-      elseif (!empty($item['#default_value'])) {
+-        if ($default_value = $item['#default_value']) {
+-          $num_autochecked_items++;
+-        }
+-      }
+-      // Get description.
+-      $description = (isset($item['#description'])) ? '<p>' . filter_xss_admin($item['#description']) . '</p>' : '';
+-      // Append links.
+-      $links = array();
+-      foreach (element_children($item) as $link_key) {
+-        $link = &$item[$link_key];
+-        $options = (!empty($link['#options']) && is_array($link['#options'])) ? $link['#options'] : array();
+-        $links[] = l($link['#text'], $link['#path'], $options);
+-      }
+-      if (count($links)) {
+-        $description .= '<div class="links">' . implode(' | ', $links) . '</div>';
+-      }
+-      // Compile the list item.
+-      $form['checklistapi'][$group_key][$item_key] = array(
+-        '#attributes' => array('class' => array('checklistapi-item')),
+-        '#default_value' => $default_value,
+-        '#description' => filter_xss_admin($description),
+-        '#disabled' => !($user_has_edit_access = $checklist->userHasAccess('edit')),
+-        '#title' => filter_xss_admin($title),
+-        '#type' => 'checkbox',
+-      );
+-    }
+-  }
+-
+-  $form['actions'] = array(
+-    '#access' => $user_has_edit_access,
+-    '#type' => 'actions',
+-    '#weight' => 100,
+-    'save' => array(
+-      '#submit' => array('checklistapi_checklist_form_submit'),
+-      '#type' => 'submit',
+-      '#value' => t('Save'),
+-    ),
+-    'clear' => array(
+-      '#access' => $checklist->hasSavedProgress(),
+-      '#attributes' => array('class' => array('clear-saved-progress')),
+-      '#href' => $checklist->path . '/clear',
+-      '#title' => t('Clear saved progress'),
+-      '#type' => 'link',
+-    ),
+-  );
+-
+-  // Alert the user of autochecked items. Only set the message on GET requests
+-  // to prevent it from reappearing after saving the form. (Testing the request
+-  // method may not be the "correct" way to accomplish this.)
+-  if ($num_autochecked_items && $_SERVER['REQUEST_METHOD'] == 'GET') {
+-    $args = array(
+-      '%checklist' => $checklist->title,
+-      '@num' => $num_autochecked_items,
+-    );
+-    $message = format_plural(
+-      $num_autochecked_items,
+-      t('%checklist found 1 unchecked item that was already completed and checked it for you. Save the form to record the change.', $args),
+-      t('%checklist found @num unchecked items that were already completed and checked them for you. Save the form to record the changes.', $args)
+-    );
+-    drupal_set_message($message, 'status');
+-  }
+-
+-  return $form;
+-}
+-
+-/**
+- * Form submission handler for checklistapi_checklist_form().
+- */
+-function checklistapi_checklist_form_submit($form, &$form_state) {
+-  $form['#checklist']->saveProgress($form_state['values']['checklistapi']);
+-}
+-
+-/**
+- * Determines whether the current user is in compact mode.
+- *
+- * Compact mode shows checklist forms with less description text.
+- *
+- * Whether the user is in compact mode is determined by a cookie, which is set
+- * for the user by checklistapi_compact_page().
+- *
+- * If the user does not have the cookie, the default value is given by the
+- * system variable 'checklistapi_compact_mode', which itself defaults to FALSE.
+- * This does not have a user interface to set it: it is a hidden variable which
+- * can be set in the settings.php file.
+- *
+- * @return bool
+- *   TRUE when in compact mode, or FALSE when in expanded mode.
+- */
+-function checklistapi_compact_mode() {
+-  return isset($_COOKIE['Drupal_visitor_checklistapi_compact_mode']) ? $_COOKIE['Drupal_visitor_checklistapi_compact_mode'] : variable_get('checklistapi_compact_mode', FALSE);
+-}
+-
+-/**
+- * Menu callback: Sets whether the admin menu is in compact mode or not.
+- *
+- * @param string $mode
+- *   (optional) The mode to set compact mode to. Accepted values are "on" and
+- *   "off". Defaults to "off".
+- */
+-function checklistapi_compact_page($mode = 'off') {
+-  user_cookie_save(array('checklistapi_compact_mode' => ($mode == 'on')));
+-  drupal_goto();
+-}
+-
+-/**
+- * Returns HTML for a link to show or hide inline item descriptions.
+- *
+- * @ingroup themeable
+- */
+-function theme_checklistapi_compact_link() {
+-  $output = '<div class="compact-link">';
+-  if (checklistapi_compact_mode()) {
+-    $output .= l(
+-      t('Show item descriptions'),
+-      current_path() . '/compact/off',
+-      array(
+-        'attributes' => array(
+-          'title' => t('Expand layout to include item descriptions.'),
+-        ),
+-        'query' => drupal_get_destination(),
+-      )
+-    );
+-  }
+-  else {
+-    $output .= l(
+-      t('Hide item descriptions'),
+-      current_path() . '/compact/on',
+-      array(
+-        'attributes' => array(
+-          'title' => t('Compress layout by hiding item descriptions.'),
+-        ),
+-        'query' => drupal_get_destination(),
+-      )
+-    );
+-  }
+-  $output .= '</div>';
+-  return $output;
+-}
+diff --git a/checklistapi_example/checklistapi_example.info b/checklistapi_example/checklistapi_example.info
+deleted file mode 100644
+index 9d085dc..0000000
+--- a/checklistapi_example/checklistapi_example.info
++++ /dev/null
+@@ -1,6 +0,0 @@
+-name = Checklist API example
+-description = Provides an example implementation of the Checklist API.
+-core = 7.x
+-package = Example modules
+-dependencies[] = checklistapi
+-configure = admin/config/development/checklistapi-example
+diff --git a/checklistapi_example/checklistapi_example.install b/checklistapi_example/checklistapi_example.install
+deleted file mode 100644
+index cf4d6bb..0000000
+--- a/checklistapi_example/checklistapi_example.install
++++ /dev/null
+@@ -1,15 +0,0 @@
+-<?php
+-
+-/**
+- * @file
+- * Install, update, and uninstall functions for the Checklist API Example
+- * module.
+- */
+-
+-/**
+- * Implements hook_uninstall().
+- */
+-function checklistapi_example_uninstall() {
+-  // Remove saved progress.
+-  variable_del('checklistapi_checklist_example_checklist');
+-}
+diff --git a/lib/Drupal/checklistapi/ChecklistapiChecklist.php b/lib/Drupal/checklistapi/ChecklistapiChecklist.php
+index 6a06b33..9b9497c 100644
+--- a/lib/Drupal/checklistapi/ChecklistapiChecklist.php
++++ b/lib/Drupal/checklistapi/ChecklistapiChecklist.php
+@@ -5,6 +5,8 @@
+  * Class for Checklist API checklists.
+  */
+ 
++namespace Drupal\checklistapi;
++
+ /**
+  * Defines the checklist class.
+  */
+@@ -87,6 +89,8 @@ class ChecklistapiChecklist {
+    *   A checklist definition, as returned by checklistapi_get_checklist_info().
+    */
+   public function __construct(array $definition) {
++    $config = \Drupal::config('checklistapi.progress');
++
+     foreach (element_children($definition) as $group_key) {
+       $this->numberOfItems += count(element_children($definition[$group_key]));
+       $this->items[$group_key] = $definition[$group_key];
+@@ -96,7 +100,7 @@ class ChecklistapiChecklist {
+       $property_name = checklistapi_strtolowercamel(drupal_substr($property_key, 1));
+       $this->$property_name = $value;
+     }
+-    $this->savedProgress = variable_get($this->getSavedProgressVariableName(), array());
++    $this->savedProgress = $config->get($this->getSavedProgressVariableName());
+   }
+ 
+   /**
+@@ -166,7 +170,10 @@ class ChecklistapiChecklist {
+    * Deletes the Drupal variable containing the checklist's saved progress.
+    */
+   public function clearSavedProgress() {
+-    variable_del($this->getSavedProgressVariableName());
++    $config = \Drupal::config('checklistapi.progress');
++
++    $config->set($this->getSavedProgressVariableName(), 0)->save();
++
+     drupal_set_message(t('%title saved progress has been cleared.', array(
+       '%title' => $this->title,
+     )));
+@@ -189,7 +196,9 @@ class ChecklistapiChecklist {
+    *   TRUE if the checklist has saved progress, or FALSE if it doesn't.
+    */
+   public function hasSavedProgress() {
+-    return (bool) variable_get($this->getSavedProgressVariableName(), FALSE);
++    $config = \Drupal::config('checklistapi.progress');
++
++    return (bool) $config->get($this->getSavedProgressVariableName());
+   }
+ 
+   /**
+@@ -201,12 +210,14 @@ class ChecklistapiChecklist {
+    * @see checklistapi_checklist_form_submit()
+    */
+   public function saveProgress(array $values) {
+-    global $user;
++    $config = \Drupal::config('checklistapi.progress');
++    $user = \Drupal::currentUser();
++
+     $time = time();
+     $num_changed_items = 0;
+     $progress = array(
+       '#changed' => $time,
+-      '#changed_by' => $user->uid,
++      '#changed_by' => $user->id(),
+       '#completed_items' => 0,
+     );
+ 
+@@ -235,7 +246,7 @@ class ChecklistapiChecklist {
+             // Item is newly checked. Set new value.
+             $new_item = array(
+               '#completed' => $time,
+-              '#uid' => $user->uid,
++              '#uid' => $user->id(),
+             );
+             $num_changed_items++;
+           }
+@@ -257,13 +268,15 @@ class ChecklistapiChecklist {
+     // saved progress variable. This simplifies use with Strongarm.
+     ksort($progress);
+ 
+-    variable_set($this->getSavedProgressVariableName(), $progress);
++    $config->set($this->getSavedProgressVariableName(), $progress);
+     drupal_set_message(format_plural(
+       $num_changed_items,
+       '%title progress has been saved. 1 item changed.',
+       '%title progress has been saved. @count items changed.',
+       array('%title' => $this->title)
+     ));
++
++    $config->save();
+   }
+ 
+   /**
+diff --git a/templates/checklistapi-progress-bar.tpl.php b/templates/checklistapi-progress-bar.tpl.php
+deleted file mode 100644
+index 383dbf8..0000000
+--- a/templates/checklistapi-progress-bar.tpl.php
++++ /dev/null
+@@ -1,22 +0,0 @@
+-<?php
+-
+-/**
+- * @file
+- * Default theme implementation for the Checklist API progress bar.
+- *
+- * Available variables:
+- * - $message: The progress message.
+- * - $number_complete: The number of items complete.
+- * - $number_of_items: The total number of items.
+- * - $percent_complete: The percent of items complete.
+- *
+- * @see template_preprocess()
+- * @see template_preprocess_checklistapi_progress_bar()
+- * @see template_process()
+- */
+-?>
+-<div class="progress">
+-  <div class="bar"><div class="filled" style="width:<?php print $percent_complete; ?>%;"></div></div>
+-  <div class="percentage"><?php print $number_complete; ?> of <?php print $number_of_items; ?> (<?php print $percent_complete; ?>%)</div>
+-  <div class="message"><?php print $message; ?></div>
+-</div>
diff --git a/checklistapi.admin.inc b/checklistapi.admin.inc
deleted file mode 100644
index 382e566..0000000
--- a/checklistapi.admin.inc
+++ /dev/null
@@ -1,56 +0,0 @@
-<?php
-
-/**
- * @file
- * Admin page callback file for the Checklist API module.
- */
-
-/**
- * Page callback: Form constructor for the report form.
- *
- * @see checklistapi_menu()
- *
- * @ingroup forms
- */
-function checklistapi_report_form() {
-  // Define table header.
-  $header = array(
-    t('Checklist'),
-    t('Progress'),
-    t('Last updated'),
-    t('Last updated by'),
-    t('Operations'),
-  );
-
-  // Build table rows.
-  $rows = array();
-  $definitions = checklistapi_get_checklist_info();
-  foreach ($definitions as $id => $definition) {
-    $checklist = checklistapi_checklist_load($id);
-    $row = array();
-    $row[] = array(
-      'data' => ($checklist->userHasAccess()) ? l($checklist->title, $checklist->path) : drupal_placeholder($checklist->title),
-      'title' => (!empty($checklist->description)) ? $checklist->description : '',
-    );
-    $row[] = t('@completed of @total (@percent%)', array(
-      '@completed' => $checklist->getNumberCompleted(),
-      '@total' => $checklist->getNumberOfItems(),
-      '@percent' => round($checklist->getPercentComplete()),
-    ));
-    $row[] = $checklist->getLastUpdatedDate();
-    $row[] = $checklist->getLastUpdatedUser();
-    $row[] = ($checklist->userHasAccess('edit') && $checklist->hasSavedProgress()) ? l(t('clear saved progress'), $checklist->path . '/clear', array(
-      'query' => array('destination' => 'admin/reports/checklistapi'),
-    )) : '';
-    $rows[] = $row;
-  }
-
-  // Compile table.
-  $table = array(
-    'header' => $header,
-    'rows' => $rows,
-    'empty' => t('No checklists available.'),
-  );
-
-  return theme('table', $table);
-}
diff --git a/checklistapi.css b/checklistapi.css
index b7cc6ea..e919e07 100644
--- a/checklistapi.css
+++ b/checklistapi.css
@@ -19,7 +19,7 @@
   font-weight: normal;
   margin-bottom: 0.5em;
 }
-#checklistapi-checklist-form .progress .bar,
-#checklistapi-checklist-form .progress .filled {
-  background-image: none;
+#checklistapi-checklist-form .compact-link {
+  clear: both;
 }
+
diff --git a/checklistapi.info b/checklistapi.info
deleted file mode 100644
index a4c534b..0000000
--- a/checklistapi.info
+++ /dev/null
@@ -1,7 +0,0 @@
-name = Checklist API
-description = Provides an API for creating fillable, persistent checklists.
-core = 7.x
-package = Other
-files[] = lib/Drupal/checklistapi/ChecklistapiChecklist.php
-files[] = tests/checklistapi.test
-configure = admin/reports/checklistapi
diff --git a/checklistapi.info.yml b/checklistapi.info.yml
new file mode 100644
index 0000000..2f37097
--- /dev/null
+++ b/checklistapi.info.yml
@@ -0,0 +1,7 @@
+name: Checklist API
+type: module
+description: Provides an API for creating fillable, persistent checklists.
+package: Other
+version: 8.x-1.x
+core: 8.x
+configure: checklistapi.admin_index
\ No newline at end of file
diff --git a/checklistapi.install b/checklistapi.install
deleted file mode 100644
index a42f1b0..0000000
--- a/checklistapi.install
+++ /dev/null
@@ -1,17 +0,0 @@
-<?php
-
-/**
- * @file
- * Install, update and uninstall functions for the Checklist API module.
- */
-
-/**
- * Implements hook_uninstall().
- */
-function checklistapi_uninstall() {
-  // Delete all Checklist API persistent variables.
-  db_delete('variable')
-    ->condition('name', db_like('checklistapi_') . '%', 'LIKE')
-    ->execute();
-  cache_clear_all('variables', 'cache_bootstrap');
-}
diff --git a/checklistapi.js b/checklistapi.js
index c4c3707..95cfad4 100644
--- a/checklistapi.js
+++ b/checklistapi.js
@@ -6,7 +6,7 @@
    */
   Drupal.behaviors.checklistapiFieldsetSummaries = {
     attach: function (context) {
-      $('#checklistapi-checklist-form .vertical-tabs-panes > fieldset', context).drupalSetSummary(function (context) {
+      $('#checklistapi-checklist-form .vertical-tabs-panes > details', context).drupalSetSummary(function (context) {
         var total = $(':checkbox.checklistapi-item', context).size(), args = {};
         if (total) {
           args['@complete'] = $(':checkbox.checklistapi-item:checked', context).size();
diff --git a/checklistapi.module b/checklistapi.module
index c229177..7ef4753 100644
--- a/checklistapi.module
+++ b/checklistapi.module
@@ -8,6 +8,92 @@
  * completion times and users.
  */
 
+use Drupal\checklistapi\ChecklistapiChecklist;
+
+/**
+ * Implements hook_menu_link_defaults().
+ */
+function checklistapi_menu_link_defaults() {
+  $links = array();
+
+  // Checklists report.
+  $links['admin/reports/checklistapi'] = array(
+    'link_title'       => 'Checklists',
+    'description' => 'Get an overview of your installed checklists with progress details.',
+    'route_name'  => 'checklistapi.admin_index',
+    'parent' => 'system.admin.reports',
+  );
+
+  return $links;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function checklistapi_permission() {
+  $perms = array();
+
+  // Universal permissions.
+  $perms['view checklistapi checklists report'] = array(
+    'title' => t(
+      'View the !name report',
+      array('!name' => (user_access('view checklistapi checklists report')) ? l(t('Checklists'), 'admin/reports/checklistapi') : drupal_placeholder('Checklists'))
+    ),
+  );
+  $perms['view any checklistapi checklist'] = array(
+    'title' => t('View any checklist'),
+    'description' => $view_checklist_perm_description = t('Read-only access: View list items and saved progress.'),
+  );
+  $perms['edit any checklistapi checklist'] = array(
+    'title' => t('Edit any checklist'),
+    'description' => $edit_checklist_perm_description = t('Check and uncheck list items and save changes, or clear saved progress.'),
+  );
+
+  // Per checklist permissions.
+  foreach (checklistapi_get_checklist_info() as $id => $definition) {
+    if (empty($id)) {
+      continue;
+    }
+    $perms['view ' . $id . ' checklistapi checklist'] = array(
+      'title' => t(
+        'View the !name checklist',
+        array('!name' => (checklistapi_checklist_access($id)) ? l($definition['#title'], $definition['#path']) : drupal_placeholder($definition['#title']))
+      ),
+      'description' => $view_checklist_perm_description,
+    );
+    $perms['edit ' . $id . ' checklistapi checklist'] = array(
+      'title' => t(
+        'Edit the !name checklist',
+        array('!name' => (checklistapi_checklist_access($id)) ? l($definition['#title'], $definition['#path']) : drupal_placeholder($definition['#title']))
+      ),
+      'description' => $edit_checklist_perm_description,
+    );
+  }
+
+  return $perms;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function checklistapi_theme() {
+  return array(
+    'checklistapi_compact_link' => array(
+      'file' => 'checklistapi.pages.inc',
+    ),
+    'checklistapi_progress_bar' => array(
+      'path' => drupal_get_path('module', 'checklistapi') . '/templates',
+      'template' => 'checklistapi-progress-bar',
+      'variables' => array(
+        'message' => '&nbsp;',
+        'number_complete' => 0,
+        'number_of_items' => 0,
+        'percent_complete' => '0',
+      ),
+    ),
+  );
+}
+
 /**
  * Access callback: Checks the current user's access to a given checklist.
  *
@@ -60,10 +146,12 @@ function checklistapi_checklist_load($id) {
  *   exists, or an array of all checklist definitions if none is specified.
  */
 function checklistapi_get_checklist_info($id = NULL) {
-  $definitions = &drupal_static(__FUNCTION__);
-  if (!isset($definitions)) {
-    // Get definitions.
-    $definitions = module_invoke_all('checklistapi_checklist_info');
+  static $drupal_static_definitions;
+  if (!isset($drupal_static_definitions)) {
+    $drupal_static_definitions['definitions'] = &drupal_static(__FUNCTION__);
+  }
+  if (!isset($drupal_static_definitions['definitions'])) {
+    $definitions = \Drupal::moduleHandler()->invokeAll('checklistapi_checklist_info');
     $definitions = checklistapi_sort_array($definitions);
     // Let other modules alter them.
     drupal_alter('checklistapi_checklist_info', $definitions);
@@ -80,131 +168,24 @@ function checklistapi_get_checklist_info($id = NULL) {
 }
 
 /**
- * Implements hook_help().
- */
-function checklistapi_help($path, $arg) {
-  foreach (checklistapi_get_checklist_info() as $definition) {
-    if ($definition['#path'] == $path && !empty($definition['#help'])) {
-      return $definition['#help'];
-    }
-  }
-}
-
-/**
- * Implements hook_init().
- */
-function checklistapi_init() {
-  // Disable page caching on all Checklist API module paths.
-  $module_paths = array_keys(checklistapi_menu());
-  if (in_array(current_path(), $module_paths)) {
-    drupal_page_is_cacheable(FALSE);
-  }
-}
-
-/**
- * Implements hook_menu().
- */
-function checklistapi_menu() {
-  $items = array();
-
-  // Checklists report.
-  $items['admin/reports/checklistapi'] = array(
-    'title' => 'Checklists',
-    'page callback' => 'checklistapi_report_form',
-    'access arguments' => array('view checklistapi checklists report'),
-    'description' => 'Get an overview of your installed checklists with progress details.',
-    'file' => 'checklistapi.admin.inc',
-  );
-
-  // Individual checklists.
-  foreach (checklistapi_get_checklist_info() as $id => $definition) {
-    if (empty($definition['#path']) || empty($definition['#title'])) {
-      continue;
-    }
-
-    // View/edit checklist.
-    $items[$definition['#path']] = array(
-      'title' => $definition['#title'],
-      'description' => (!empty($definition['#description'])) ? $definition['#description'] : '',
-      'page callback' => 'drupal_get_form',
-      'page arguments' => array('checklistapi_checklist_form', $id),
-      'access callback' => 'checklistapi_checklist_access',
-      'access arguments' => array($id),
-      'file' => 'checklistapi.pages.inc',
-    );
-    if (!empty($definition['#menu_name'])) {
-      $items[$definition['#path']]['menu_name'] = $definition['#menu_name'];
-    }
-
-    // Clear saved progress.
-    $items[$definition['#path'] . '/clear'] = array(
-      'title' => 'Clear',
-      'page callback' => 'drupal_get_form',
-      'page arguments' => array('checklistapi_checklist_clear_confirm', $id),
-      'access callback' => 'checklistapi_checklist_access',
-      'access arguments' => array($id, 'edit'),
-      'file' => 'checklistapi.pages.inc',
-      'type' => MENU_CALLBACK,
-    );
-
-    // Toggle compact mode.
-    $items[$definition['#path'] . '/compact'] = array(
-      'title' => 'Compact mode',
-      'page callback' => 'checklistapi_compact_page',
-      'access callback' => 'checklistapi_checklist_access',
-      'access arguments' => array($id),
-      'file' => 'checklistapi.pages.inc',
-      'type' => MENU_CALLBACK,
-    );
-  }
-
-  return $items;
-}
-
-/**
- * Implements hook_permission().
+ * Determines whether the current user is in compact mode.
+ *
+ * Compact mode shows checklist forms with less description text.
+ *
+ * Whether the user is in compact mode is determined by a cookie, which is set
+ * for the user by checklistapi_compact_page().
+ *
+ * If the user does not have the cookie, the default value is given by the
+ * system variable 'checklistapi_compact_mode', which itself defaults to FALSE.
+ * This does not have a user interface to set it: it is a hidden variable which
+ * can be set in the settings.php file.
+ *
+ * @return bool
+ *   TRUE when in compact mode, or FALSE when in expanded mode.
  */
-function checklistapi_permission() {
-  $perms = array();
-
-  // Universal permissions.
-  $perms['view checklistapi checklists report'] = array(
-    'title' => t(
-      'View the !name report',
-      array('!name' => (user_access('view checklistapi checklists report')) ? l(t('Checklists'), 'admin/reports/checklistapi') : drupal_placeholder('Checklists'))
-    ),
-  );
-  $perms['view any checklistapi checklist'] = array(
-    'title' => t('View any checklist'),
-    'description' => $view_checklist_perm_description = t('Read-only access: View list items and saved progress.'),
-  );
-  $perms['edit any checklistapi checklist'] = array(
-    'title' => t('Edit any checklist'),
-    'description' => $edit_checklist_perm_description = t('Check and uncheck list items and save changes, or clear saved progress.'),
-  );
-
-  // Per checklist permissions.
-  foreach (checklistapi_get_checklist_info() as $id => $definition) {
-    if (empty($id)) {
-      continue;
-    }
-    $perms['view ' . $id . ' checklistapi checklist'] = array(
-      'title' => t(
-        'View the !name checklist',
-        array('!name' => (checklistapi_checklist_access($id)) ? l($definition['#title'], $definition['#path']) : drupal_placeholder($definition['#title']))
-      ),
-      'description' => $view_checklist_perm_description,
-    );
-    $perms['edit ' . $id . ' checklistapi checklist'] = array(
-      'title' => t(
-        'Edit the !name checklist',
-        array('!name' => (checklistapi_checklist_access($id)) ? l($definition['#title'], $definition['#path']) : drupal_placeholder($definition['#title']))
-      ),
-      'description' => $edit_checklist_perm_description,
-    );
-  }
-
-  return $perms;
+function checklistapi_compact_mode() {
+  $config = \Drupal::config('checklistapi.progress');
+  return isset($_COOKIE['Drupal_visitor_checklistapi_compact_mode']) ? $_COOKIE['Drupal_visitor_checklistapi_compact_mode'] : $config->get('checklistapi_compact_mode');
 }
 
 /**
@@ -276,22 +257,36 @@ function checklistapi_strtolowercamel($string) {
 }
 
 /**
- * Implements hook_theme().
+ * Returns HTML for a link to show or hide inline item descriptions.
+ *
+ * @ingroup themeable
  */
-function checklistapi_theme() {
-  return array(
-    'checklistapi_compact_link' => array(
-      'file' => 'checklistapi.pages.inc',
-    ),
-    'checklistapi_progress_bar' => array(
-      'path' => drupal_get_path('module', 'checklistapi') . '/templates',
-      'template' => 'checklistapi-progress-bar',
-      'variables' => array(
-        'message' => '&nbsp;',
-        'number_complete' => 0,
-        'number_of_items' => 0,
-        'percent_complete' => 0,
-      ),
-    ),
-  );
-}
+function theme_checklistapi_compact_link() {
+  $output = '<div class="compact-link">';
+  if (checklistapi_compact_mode()) {
+    $output .= l(
+      t('Show item descriptions'),
+      current_path() . '/compact/off',
+      array(
+        'attributes' => array(
+          'title' => t('Expand layout to include item descriptions.'),
+        ),
+        'query' => drupal_get_destination(),
+      )
+    );
+  }
+  else {
+    $output .= l(
+      t('Hide item descriptions'),
+      current_path() . '/compact/on',
+      array(
+        'attributes' => array(
+          'title' => t('Compress layout by hiding item descriptions.'),
+        ),
+        'query' => drupal_get_destination(),
+      )
+    );
+  }
+  $output .= '</div>';
+  return $output;
+}
\ No newline at end of file
diff --git a/checklistapi.pages.inc b/checklistapi.pages.inc
deleted file mode 100644
index 9b832f3..0000000
--- a/checklistapi.pages.inc
+++ /dev/null
@@ -1,258 +0,0 @@
-<?php
-
-/**
- * @file
- * Page callbacks for the Checklist API module.
- */
-
-/**
- * Page callback: Form constructor for "Clear saved progress" confirmation form.
- *
- * @param string $id
- *   The checklist ID.
- *
- * @see checklistapi_menu()
- *
- * @ingroup forms
- */
-function checklistapi_checklist_clear_confirm($form, &$form_state, $id) {
-  $checklist = checklistapi_checklist_load($id);
-  $form['#checklist'] = $checklist;
-  $question = t('Are you sure you want to clear %title saved progress?', array(
-    '%title' => $checklist->title,
-  ));
-  $description = t('All progress details will be erased. This action cannot be undone.');
-  $yes = t('Clear');
-  $no = t('Cancel');
-  return confirm_form($form, $question, $checklist->path, $description, $yes, $no);
-}
-
-/**
- * Form submission handler for checklistapi_checklist_clear_confirm().
- */
-function checklistapi_checklist_clear_confirm_submit($form, &$form_state) {
-  // If user confirmed, clear saved progress.
-  if ($form_state['values']['confirm']) {
-    $form['#checklist']->clearSavedProgress();
-  }
-
-  // Redirect back to checklist.
-  $form_state['redirect'] = $form['#checklist']->path;
-}
-
-/**
- * Page callback: Form constructor for the checklist form.
- *
- * @param string $id
- *   The checklist ID.
- *
- * @see checklistapi_checklist_form_submit()
- * @see checklistapi_menu()
- *
- * @ingroup forms
- */
-function checklistapi_checklist_form($form, &$form_state, $id) {
-  $form['#checklist'] = $checklist = checklistapi_checklist_load($id);
-
-  $form['progress_bar'] = array(
-    '#type' => 'markup',
-    '#markup' => theme('checklistapi_progress_bar', array(
-      'message' => ($checklist->hasSavedProgress()) ? t('Last updated @date by !user', array(
-        '@date' => $checklist->getLastUpdatedDate(),
-        '!user' => $checklist->getLastUpdatedUser(),
-      )) : '&nbsp;',
-      'number_complete' => $checklist->getNumberCompleted(),
-      'number_of_items' => $checklist->getNumberOfItems(),
-      'percent_complete' => round($checklist->getPercentComplete()),
-    )),
-  );
-  if (checklistapi_compact_mode()) {
-    $form['#attributes']['class'] = array('compact-mode');
-  }
-  $form['compact_mode_link'] = array(
-    '#markup' => theme('checklistapi_compact_link'),
-  );
-
-  $form['checklistapi'] = array(
-    '#attached' => array(
-      'css' => array(drupal_get_path('module', 'checklistapi') . '/checklistapi.css'),
-      'js' => array(drupal_get_path('module', 'checklistapi') . '/checklistapi.js'),
-    ),
-    '#tree' => TRUE,
-    '#type' => 'vertical_tabs',
-  );
-
-  // Loop through groups.
-  $num_autochecked_items = 0;
-  $groups = $checklist->items;
-  foreach (element_children($groups) as $group_key) {
-    $group = &$groups[$group_key];
-    $form['checklistapi'][$group_key] = array(
-      '#title' => filter_xss($group['#title']),
-      '#type' => 'fieldset',
-    );
-    if (!empty($group['#description'])) {
-      $form['checklistapi'][$group_key]['#description'] = filter_xss_admin($group['#description']);
-    }
-
-    // Loop through items.
-    foreach (element_children($group) as $item_key) {
-      $item = &$group[$item_key];
-      $saved_item = !empty($checklist->savedProgress[$item_key]) ? $checklist->savedProgress[$item_key] : 0;
-      // Build title.
-      $title = filter_xss($item['#title']);
-      if ($saved_item) {
-        // Append completion details.
-        $user = user_load($saved_item['#uid']);
-        $title .= t(
-          '<span class="completion-details"> - Completed @time by !user</a>',
-          array(
-            '@time' => format_date($saved_item['#completed'], 'short'),
-            '!user' => theme('username', array('account' => $user)),
-          )
-        );
-      }
-      // Set default value.
-      $default_value = FALSE;
-      if ($saved_item) {
-        $default_value = TRUE;
-      }
-      elseif (!empty($item['#default_value'])) {
-        if ($default_value = $item['#default_value']) {
-          $num_autochecked_items++;
-        }
-      }
-      // Get description.
-      $description = (isset($item['#description'])) ? '<p>' . filter_xss_admin($item['#description']) . '</p>' : '';
-      // Append links.
-      $links = array();
-      foreach (element_children($item) as $link_key) {
-        $link = &$item[$link_key];
-        $options = (!empty($link['#options']) && is_array($link['#options'])) ? $link['#options'] : array();
-        $links[] = l($link['#text'], $link['#path'], $options);
-      }
-      if (count($links)) {
-        $description .= '<div class="links">' . implode(' | ', $links) . '</div>';
-      }
-      // Compile the list item.
-      $form['checklistapi'][$group_key][$item_key] = array(
-        '#attributes' => array('class' => array('checklistapi-item')),
-        '#default_value' => $default_value,
-        '#description' => filter_xss_admin($description),
-        '#disabled' => !($user_has_edit_access = $checklist->userHasAccess('edit')),
-        '#title' => filter_xss_admin($title),
-        '#type' => 'checkbox',
-      );
-    }
-  }
-
-  $form['actions'] = array(
-    '#access' => $user_has_edit_access,
-    '#type' => 'actions',
-    '#weight' => 100,
-    'save' => array(
-      '#submit' => array('checklistapi_checklist_form_submit'),
-      '#type' => 'submit',
-      '#value' => t('Save'),
-    ),
-    'clear' => array(
-      '#access' => $checklist->hasSavedProgress(),
-      '#attributes' => array('class' => array('clear-saved-progress')),
-      '#href' => $checklist->path . '/clear',
-      '#title' => t('Clear saved progress'),
-      '#type' => 'link',
-    ),
-  );
-
-  // Alert the user of autochecked items. Only set the message on GET requests
-  // to prevent it from reappearing after saving the form. (Testing the request
-  // method may not be the "correct" way to accomplish this.)
-  if ($num_autochecked_items && $_SERVER['REQUEST_METHOD'] == 'GET') {
-    $args = array(
-      '%checklist' => $checklist->title,
-      '@num' => $num_autochecked_items,
-    );
-    $message = format_plural(
-      $num_autochecked_items,
-      t('%checklist found 1 unchecked item that was already completed and checked it for you. Save the form to record the change.', $args),
-      t('%checklist found @num unchecked items that were already completed and checked them for you. Save the form to record the changes.', $args)
-    );
-    drupal_set_message($message, 'status');
-  }
-
-  return $form;
-}
-
-/**
- * Form submission handler for checklistapi_checklist_form().
- */
-function checklistapi_checklist_form_submit($form, &$form_state) {
-  $form['#checklist']->saveProgress($form_state['values']['checklistapi']);
-}
-
-/**
- * Determines whether the current user is in compact mode.
- *
- * Compact mode shows checklist forms with less description text.
- *
- * Whether the user is in compact mode is determined by a cookie, which is set
- * for the user by checklistapi_compact_page().
- *
- * If the user does not have the cookie, the default value is given by the
- * system variable 'checklistapi_compact_mode', which itself defaults to FALSE.
- * This does not have a user interface to set it: it is a hidden variable which
- * can be set in the settings.php file.
- *
- * @return bool
- *   TRUE when in compact mode, or FALSE when in expanded mode.
- */
-function checklistapi_compact_mode() {
-  return isset($_COOKIE['Drupal_visitor_checklistapi_compact_mode']) ? $_COOKIE['Drupal_visitor_checklistapi_compact_mode'] : variable_get('checklistapi_compact_mode', FALSE);
-}
-
-/**
- * Menu callback: Sets whether the admin menu is in compact mode or not.
- *
- * @param string $mode
- *   (optional) The mode to set compact mode to. Accepted values are "on" and
- *   "off". Defaults to "off".
- */
-function checklistapi_compact_page($mode = 'off') {
-  user_cookie_save(array('checklistapi_compact_mode' => ($mode == 'on')));
-  drupal_goto();
-}
-
-/**
- * Returns HTML for a link to show or hide inline item descriptions.
- *
- * @ingroup themeable
- */
-function theme_checklistapi_compact_link() {
-  $output = '<div class="compact-link">';
-  if (checklistapi_compact_mode()) {
-    $output .= l(
-      t('Show item descriptions'),
-      current_path() . '/compact/off',
-      array(
-        'attributes' => array(
-          'title' => t('Expand layout to include item descriptions.'),
-        ),
-        'query' => drupal_get_destination(),
-      )
-    );
-  }
-  else {
-    $output .= l(
-      t('Hide item descriptions'),
-      current_path() . '/compact/on',
-      array(
-        'attributes' => array(
-          'title' => t('Compress layout by hiding item descriptions.'),
-        ),
-        'query' => drupal_get_destination(),
-      )
-    );
-  }
-  $output .= '</div>';
-  return $output;
-}
diff --git a/checklistapi.routing.yml b/checklistapi.routing.yml
new file mode 100644
index 0000000..5dfd68f
--- /dev/null
+++ b/checklistapi.routing.yml
@@ -0,0 +1,10 @@
+checklistapi.admin_index:
+  path: '/admin/reports/checklistapi'
+  defaults:
+    _content: '\Drupal\checklistapi\Controller\ChecklistapiController::adminOverview'
+    _title: 'Checklists'
+  requirements:
+    _permission: 'view checklistapi checklists report'
+    
+route_callbacks:
+  - '\Drupal\checklistapi\Routing\checklistapiRouteSubscriber::routes'
diff --git a/checklistapi.services.yml b/checklistapi.services.yml
new file mode 100644
index 0000000..a4cb17e
--- /dev/null
+++ b/checklistapi.services.yml
@@ -0,0 +1,5 @@
+services:
+  checklistapi.access_check:
+    class: Drupal\checklistapi\Access\checklistapiAccessCheck
+    tags:
+      - { name: access_check, applies_to: _checklistapi_access }
diff --git a/checklistapi_example/checklistapi_example.info b/checklistapi_example/checklistapi_example.info
deleted file mode 100644
index 9d085dc..0000000
--- a/checklistapi_example/checklistapi_example.info
+++ /dev/null
@@ -1,6 +0,0 @@
-name = Checklist API example
-description = Provides an example implementation of the Checklist API.
-core = 7.x
-package = Example modules
-dependencies[] = checklistapi
-configure = admin/config/development/checklistapi-example
diff --git a/checklistapi_example/checklistapi_example.info.yml b/checklistapi_example/checklistapi_example.info.yml
new file mode 100644
index 0000000..beddaf4
--- /dev/null
+++ b/checklistapi_example/checklistapi_example.info.yml
@@ -0,0 +1,6 @@
+name: Checklist API example
+type: module
+description: rovides an example implementation of the Checklist API.
+package: Other
+version: 8.x-1.x
+core: 8.x
\ No newline at end of file
diff --git a/checklistapi_example/checklistapi_example.install b/checklistapi_example/checklistapi_example.install
deleted file mode 100644
index cf4d6bb..0000000
--- a/checklistapi_example/checklistapi_example.install
+++ /dev/null
@@ -1,15 +0,0 @@
-<?php
-
-/**
- * @file
- * Install, update, and uninstall functions for the Checklist API Example
- * module.
- */
-
-/**
- * Implements hook_uninstall().
- */
-function checklistapi_example_uninstall() {
-  // Remove saved progress.
-  variable_del('checklistapi_checklist_example_checklist');
-}
diff --git a/config/checklistapi.progress.yml b/config/checklistapi.progress.yml
new file mode 100644
index 0000000..e69de29
diff --git a/lib/Drupal/checklistapi/Access/checklistapiAccessCheck.php b/lib/Drupal/checklistapi/Access/checklistapiAccessCheck.php
new file mode 100644
index 0000000..dd3569e
--- /dev/null
+++ b/lib/Drupal/checklistapi/Access/checklistapiAccessCheck.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\checklistapi\Access;
+
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+class checklistapiAccessCheck implements AccessInterface {
+
+  /**
+   * The key used by the routing requirement.
+   *
+   * @var string
+   */
+  protected $requirementsKey = '_checklistapi_access';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, Request $request, AccountInterface $account) {
+    $op = $request->attributes->get('op');
+    $op = !empty($op) ? $op : 'any';
+
+    return checklistapi_checklist_access($request->attributes->get('plugin_id'), $op) ? static::ALLOW : static::DENY;
+  }
+}
diff --git a/lib/Drupal/checklistapi/ChecklistapiChecklist.php b/lib/Drupal/checklistapi/ChecklistapiChecklist.php
index 6a06b33..9b9497c 100644
--- a/lib/Drupal/checklistapi/ChecklistapiChecklist.php
+++ b/lib/Drupal/checklistapi/ChecklistapiChecklist.php
@@ -5,6 +5,8 @@
  * Class for Checklist API checklists.
  */
 
+namespace Drupal\checklistapi;
+
 /**
  * Defines the checklist class.
  */
@@ -87,6 +89,8 @@ class ChecklistapiChecklist {
    *   A checklist definition, as returned by checklistapi_get_checklist_info().
    */
   public function __construct(array $definition) {
+    $config = \Drupal::config('checklistapi.progress');
+
     foreach (element_children($definition) as $group_key) {
       $this->numberOfItems += count(element_children($definition[$group_key]));
       $this->items[$group_key] = $definition[$group_key];
@@ -96,7 +100,7 @@ class ChecklistapiChecklist {
       $property_name = checklistapi_strtolowercamel(drupal_substr($property_key, 1));
       $this->$property_name = $value;
     }
-    $this->savedProgress = variable_get($this->getSavedProgressVariableName(), array());
+    $this->savedProgress = $config->get($this->getSavedProgressVariableName());
   }
 
   /**
@@ -166,7 +170,10 @@ class ChecklistapiChecklist {
    * Deletes the Drupal variable containing the checklist's saved progress.
    */
   public function clearSavedProgress() {
-    variable_del($this->getSavedProgressVariableName());
+    $config = \Drupal::config('checklistapi.progress');
+
+    $config->set($this->getSavedProgressVariableName(), 0)->save();
+
     drupal_set_message(t('%title saved progress has been cleared.', array(
       '%title' => $this->title,
     )));
@@ -189,7 +196,9 @@ class ChecklistapiChecklist {
    *   TRUE if the checklist has saved progress, or FALSE if it doesn't.
    */
   public function hasSavedProgress() {
-    return (bool) variable_get($this->getSavedProgressVariableName(), FALSE);
+    $config = \Drupal::config('checklistapi.progress');
+
+    return (bool) $config->get($this->getSavedProgressVariableName());
   }
 
   /**
@@ -201,12 +210,14 @@ class ChecklistapiChecklist {
    * @see checklistapi_checklist_form_submit()
    */
   public function saveProgress(array $values) {
-    global $user;
+    $config = \Drupal::config('checklistapi.progress');
+    $user = \Drupal::currentUser();
+
     $time = time();
     $num_changed_items = 0;
     $progress = array(
       '#changed' => $time,
-      '#changed_by' => $user->uid,
+      '#changed_by' => $user->id(),
       '#completed_items' => 0,
     );
 
@@ -235,7 +246,7 @@ class ChecklistapiChecklist {
             // Item is newly checked. Set new value.
             $new_item = array(
               '#completed' => $time,
-              '#uid' => $user->uid,
+              '#uid' => $user->id(),
             );
             $num_changed_items++;
           }
@@ -257,13 +268,15 @@ class ChecklistapiChecklist {
     // saved progress variable. This simplifies use with Strongarm.
     ksort($progress);
 
-    variable_set($this->getSavedProgressVariableName(), $progress);
+    $config->set($this->getSavedProgressVariableName(), $progress);
     drupal_set_message(format_plural(
       $num_changed_items,
       '%title progress has been saved. 1 item changed.',
       '%title progress has been saved. @count items changed.',
       array('%title' => $this->title)
     ));
+
+    $config->save();
   }
 
   /**
diff --git a/lib/Drupal/checklistapi/Controller/ChecklistapiController.php b/lib/Drupal/checklistapi/Controller/ChecklistapiController.php
new file mode 100644
index 0000000..4ea932f
--- /dev/null
+++ b/lib/Drupal/checklistapi/Controller/ChecklistapiController.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Form\SiteInformationForm.
+ */
+
+namespace Drupal\checklistapi\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+
+/**
+ * Configure site information settings for this site.
+ */
+class ChecklistapiController extends ControllerBase {
+
+  /**
+   * Callback for the admin overview route
+   */
+  public function adminOverview() {
+
+    // Define table header.
+    $header = array(
+      array(
+          'data' => t('Checklist')
+      ),
+      array(
+          'data' => t('Progress')
+      ),
+      array(
+          'data' => t('Last updated'),
+          'class' => array(RESPONSIVE_PRIORITY_MEDIUM),
+
+      ),
+      array(
+          'data' => t('Last updated by'),
+          'class' => array(RESPONSIVE_PRIORITY_LOW),
+      ),
+      array(
+          'data' => t('Operations'),
+          'class' => array(RESPONSIVE_PRIORITY_LOW),
+      ),
+    );
+
+    // Build table rows.
+    $rows = array();
+    $definitions = checklistapi_get_checklist_info();
+    foreach ($definitions as $id => $definition) {
+      $checklist = checklistapi_checklist_load($id);
+      $row = array();
+      $row[] = array(
+        'data' => ($checklist->userHasAccess()) ? l($checklist->title, $checklist->path) : drupal_placeholder($checklist->title),
+        'title' => (!empty($checklist->description)) ? $checklist->description : '',
+      );
+      $row[] = t('@completed of @total (@percent%)', array(
+        '@completed' => $checklist->getNumberCompleted(),
+        '@total' => $checklist->getNumberOfItems(),
+        '@percent' => round($checklist->getPercentComplete()),
+      ));
+      $row[] = $checklist->getLastUpdatedDate();
+      $row[] = $checklist->getLastUpdatedUser();
+      $row[] = ($checklist->userHasAccess('edit') && $checklist->hasSavedProgress()) ? l(t('clear saved progress'), $checklist->path . '/clear', array(
+        'query' => array('destination' => 'admin/reports/checklistapi'),
+      )) : '';
+      $rows[] = $row;
+    }
+
+    $render_array['table'] = array(
+      '#theme' => 'table',
+      '#header' => $header,
+      '#rows' => $rows,
+    );
+
+    return $render_array;
+  }
+
+  /**
+   * Callback for the checklistapi.definition_{checklist_id}_compact route
+   */
+  public function setCompact($mode) {
+    user_cookie_save(array('checklistapi_compact_mode' => ($mode == 'on')));
+    return $this->redirect('<front>');
+  }
+}
\ No newline at end of file
diff --git a/lib/Drupal/checklistapi/Form/ChecklistapiChecklistClearConfirmForm.php b/lib/Drupal/checklistapi/Form/ChecklistapiChecklistClearConfirmForm.php
new file mode 100644
index 0000000..d17aeaa
--- /dev/null
+++ b/lib/Drupal/checklistapi/Form/ChecklistapiChecklistClearConfirmForm.php
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\checklistapi\Form\ChecklistapiDefinitionForm.
+ */
+
+namespace Drupal\checklistapi\Form;
+
+use Drupal\Core\Form\ConfirmFormBase;
+
+/**
+ * todo
+ */
+class ChecklistapiChecklistClearConfirmForm extends ConfirmFormBase {
+  /**
+   * The ID of the item to delete.
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * Implements \Drupal\Core\Form\ConfirmFormBase::getQuestion().
+   */
+  public function getQuestion() {
+    return t('Are you sure you want to clear saved progress?');
+  }
+
+  /**
+   * Implements \Drupal\Core\Form\ConfirmFormBase::getCancelRoute().
+   */
+  public function getCancelRoute() { }
+
+  /**
+   * Overrides \Drupal\Core\Form\ConfirmFormBase::getDescription().
+   */
+  public function getDescription() {
+    return t('All progress details will be erased. This action cannot be undone.');
+  }
+
+  /**
+   * Overrides \Drupal\Core\Form\ConfirmFormBase::getConfirmText().
+   */
+  public function getConfirmText() {
+    return t('Clear');
+  }
+
+  /**
+   * Overrides \Drupal\Core\Form\ConfirmFormBase::getCancelText().
+   */
+  public function getCancelText() {
+    return t('Cancel');
+  }
+
+  /**
+   * Overrides \Drupal\Core\Form\ConfirmFormBase::buildForm().
+   *
+   * @param int $id
+   *   (optional) The ID of the item to be deleted.
+   */
+  public function buildForm(array $form, array &$form_state, $plugin_id = NULL) {
+    $checklist = checklistapi_checklist_load($plugin_id);
+    $form['#checklist'] = $checklist;
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * Implements \Drupal\Core\Form\FormInterface::submitForm().
+   */
+  public function submitForm(array &$form, array &$form_state) {
+     // If user confirmed, clear saved progress.
+    if ($form_state['values']['confirm']) {
+      $form['#checklist']->clearSavedProgress();
+    }
+
+    // Redirect back to checklist.
+    $form_state['redirect'] = $form['#checklist']->path;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'checklistapi_checklist_clear_confirm';
+  }
+
+}
diff --git a/lib/Drupal/checklistapi/Form/ChecklistapiDefinitionForm.php b/lib/Drupal/checklistapi/Form/ChecklistapiDefinitionForm.php
new file mode 100644
index 0000000..acc4ecf
--- /dev/null
+++ b/lib/Drupal/checklistapi/Form/ChecklistapiDefinitionForm.php
@@ -0,0 +1,174 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\checklistapi\Form\ChecklistapiDefinitionForm.
+ */
+
+namespace Drupal\checklistapi\Form;
+
+use Drupal\Core\Form\FormInterface;
+
+/**
+ * todo
+ */
+class ChecklistapiDefinitionForm implements FormInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'checklistapi_checklist_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state, $plugin_id = NULL) {
+    module_load_include('inc', 'checklistapi', 'checklistapi.pages');
+    $form['#checklist'] = $checklist = checklistapi_checklist_load($plugin_id);
+
+    $form['progress_bar'] = array(
+      '#type' => 'markup',
+      '#markup' => theme('checklistapi_progress_bar', array(
+        'message' => ($checklist->hasSavedProgress()) ? t('Last updated @date by !user', array(
+          '@date' => $checklist->getLastUpdatedDate(),
+          '!user' => $checklist->getLastUpdatedUser(),
+        )) : '&nbsp;',
+        'number_complete' => $checklist->getNumberCompleted(),
+        'number_of_items' => $checklist->getNumberOfItems(),
+        'percent_complete' => round($checklist->getPercentComplete()),
+      )),
+    );
+    if (checklistapi_compact_mode()) {
+      $form['#attributes']['class'] = array('compact-mode');
+    }
+    $form['compact_mode_link'] = array(
+      '#markup' => theme('checklistapi_compact_link'),
+    );
+    $form['checklistapi'] = array(
+      '#attached' => array(
+        'css' => array(drupal_get_path('module', 'checklistapi') . '/checklistapi.css'),
+        'js'  => array(drupal_get_path('module', 'checklistapi') . '/checklistapi.js'),
+      ),
+      '#tree' => TRUE,
+      '#type' => 'vertical_tabs',
+    );
+
+    // Loop through groups.
+    $num_autochecked_items = 0;
+    $groups = $checklist->items;
+    foreach (element_children($groups) as $group_key) {
+      $group = &$groups[$group_key];
+      $form[$group_key] = array(
+        '#title' => filter_xss($group['#title']),
+        '#type' => 'details',
+        '#group' => 'checklistapi',
+      );
+      if (!empty($group['#description'])) {
+        $form[$group_key]['#description'] = filter_xss_admin($group['#description']);
+      }
+
+      // Loop through items.
+      foreach (element_children($group) as $item_key) {
+        $item = &$group[$item_key];
+        $saved_item = !empty($checklist->savedProgress[$item_key]) ? $checklist->savedProgress[$item_key] : 0;
+        // Build title.
+        $title = filter_xss($item['#title']);
+        if ($saved_item) {
+          // Append completion details.
+          $user = user_load($saved_item['#uid']);
+          $title .= t(
+            '<span class="completion-details"> - Completed @time by !user</a>',
+            array(
+              '@time' => format_date($saved_item['#completed'], 'short'),
+              '!user' => theme('username', array('account' => $user)),
+            )
+          );
+        }
+        // Set default value.
+        $default_value = FALSE;
+        if ($saved_item) {
+          $default_value = TRUE;
+        }
+        elseif (!empty($item['#default_value'])) {
+          if ($default_value = $item['#default_value']) {
+            $num_autochecked_items++;
+          }
+        }
+        // Get description.
+        $description = (isset($item['#description'])) ? '<p>' . filter_xss_admin($item['#description']) . '</p>' : '';
+        // Append links.
+        $links = array();
+        foreach (element_children($item) as $link_key) {
+          $link = &$item[$link_key];
+          $options = (!empty($link['#options']) && is_array($link['#options'])) ? $link['#options'] : array();
+          $links[] = l($link['#text'], $link['#path'], $options);
+        }
+        if (count($links)) {
+          $description .= '<div class="links">' . implode(' | ', $links) . '</div>';
+        }
+        // Compile the list item.
+        $form[$group_key][$item_key] = array(
+          '#attributes' => array('class' => array('checklistapi-item')),
+          '#default_value' => $default_value,
+          '#description' => filter_xss_admin($description),
+          '#disabled' => !($user_has_edit_access = $checklist->userHasAccess('edit')),
+          '#title' => filter_xss_admin($title),
+          '#type' => 'checkbox',
+          '#group' => $group_key,
+          '#parents' => array('checklistapi', $group_key, $item_key),
+        );
+      }
+    }
+
+    $form['actions'] = array(
+      '#access' => $user_has_edit_access,
+      '#type' => 'actions',
+      '#weight' => 100,
+      'save' => array(
+        '#type' => 'submit',
+        '#value' => t('Save'),
+      ),
+      'clear' => array(
+        '#access' => $checklist->hasSavedProgress(),
+        '#attributes' => array('class' => array('clear-saved-progress')),
+        '#href' => $checklist->path . '/clear',
+        '#title' => t('Clear saved progress'),
+        '#type' => 'link',
+      ),
+    );
+
+    // Alert the user of autochecked items. Only set the message on GET requests
+    // to prevent it from reappearing after saving the form. (Testing the request
+    // method may not be the "correct" way to accomplish this.)
+    if ($num_autochecked_items && $_SERVER['REQUEST_METHOD'] == 'GET') {
+      $args = array(
+        '%checklist' => $checklist->title,
+        '@num' => $num_autochecked_items,
+      );
+      $message = format_plural(
+        $num_autochecked_items,
+        t('%checklist found 1 unchecked item that was already completed and checked it for you. Save the form to record the change.', $args),
+        t('%checklist found @num unchecked items that were already completed and checked them for you. Save the form to record the changes.', $args)
+      );
+      drupal_set_message($message, 'status');
+    }
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, array &$form_state) {
+
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, array &$form_state) {
+    $form['#checklist']->saveProgress($form_state['values']['checklistapi']);
+  }
+
+}
diff --git a/lib/Drupal/checklistapi/Routing/ChecklistapiRouteSubscriber.php b/lib/Drupal/checklistapi/Routing/ChecklistapiRouteSubscriber.php
new file mode 100644
index 0000000..56d2d8d
--- /dev/null
+++ b/lib/Drupal/checklistapi/Routing/ChecklistapiRouteSubscriber.php
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\checklistapi\Routing\ChecklistapiRouteSubscriber.
+ */
+
+namespace Drupal\checklistapi\Routing;
+
+use Symfony\Component\Routing\Route;
+
+/**
+ * Listens to the dynamic checklistapi route events.
+ */
+class ChecklistapiRouteSubscriber {
+
+  /**
+   * Provides dynamic routes for various checklistapi pages.
+   *
+   * @return \Symfony\Component\Routing\Route[]
+   *   An array of route objects.
+   */
+  public function routes() {
+    $routes = array();
+    foreach (checklistapi_get_checklist_info() as $id => $definition) {
+      if (empty($definition['#path']) || empty($definition['#title'])) {
+        continue;
+      }
+      $routes['checklistapi.definition_' . $id] = new Route(
+        $definition['#path'],
+        array(
+          '_title'    => $definition['#title'],
+          '_form'     => '\Drupal\checklistapi\Form\ChecklistapiDefinitionForm',
+          'plugin_id' => $id,
+        ),
+        array(
+          '_checklistapi_access' => 'TRUE',
+        )
+
+      );
+      $routes['checklistapi.definition_' . $id . '_compact'] = new Route(
+        $definition['#path'] . '/compact/{mode}',
+        array(
+          '_title'    => 'Compact mode',
+          '_content'  => '\Drupal\checklistapi\Controller\ChecklistapiController::setCompact',
+          'plugin_id' => $id,
+          'mode' => 'off',
+        ),
+        array(
+          '_checklistapi_access' => 'TRUE',
+        )
+      );
+      $routes['checklistapi.definition_' . $id . '_clear'] = new Route(
+        $definition['#path'] . '/clear',
+        array(
+          '_title'    => 'Clear',
+          '_form'     => '\Drupal\checklistapi\Form\ChecklistapiChecklistClearConfirmForm',
+          'plugin_id' => $id,
+          'op'        => 'edit',
+        ),
+        array(
+          '_checklistapi_access' => 'TRUE',
+        )
+      );
+
+      return $routes;
+    }
+  }
+}
diff --git a/templates/checklistapi-progress-bar.html.twig b/templates/checklistapi-progress-bar.html.twig
new file mode 100644
index 0000000..4f6c0e7
--- /dev/null
+++ b/templates/checklistapi-progress-bar.html.twig
@@ -0,0 +1,5 @@
+<div class="progress" aria-live="polite">
+  <div class="progress__track"><div class="progress__bar" style="width: {{ percent_complete|default('0') }}%;"></div></div>
+  <div class="progress__percentage">{{ number_complete }} of {{ number_of_items }} ({{ percent_complete|default('0') }}%)</div>
+  <div class="progress__description">{{ message }}</div>
+</div>
\ No newline at end of file
diff --git a/templates/checklistapi-progress-bar.tpl.php b/templates/checklistapi-progress-bar.tpl.php
deleted file mode 100644
index 383dbf8..0000000
--- a/templates/checklistapi-progress-bar.tpl.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-/**
- * @file
- * Default theme implementation for the Checklist API progress bar.
- *
- * Available variables:
- * - $message: The progress message.
- * - $number_complete: The number of items complete.
- * - $number_of_items: The total number of items.
- * - $percent_complete: The percent of items complete.
- *
- * @see template_preprocess()
- * @see template_preprocess_checklistapi_progress_bar()
- * @see template_process()
- */
-?>
-<div class="progress">
-  <div class="bar"><div class="filled" style="width:<?php print $percent_complete; ?>%;"></div></div>
-  <div class="percentage"><?php print $number_complete; ?> of <?php print $number_of_items; ?> (<?php print $percent_complete; ?>%)</div>
-  <div class="message"><?php print $message; ?></div>
-</div>
-- 
1.8.3.4 (Apple Git-47)

