Commit 7c1442c4 authored by TravisCarden's avatar TravisCarden Committed by TravisCarden

Issue #2142903 by TravisCarden, valderama, blesss: Port to Drupal 8.

parent c4c795ab
......@@ -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
......@@ -30,8 +30,7 @@ IMPLEMENTATION
Checklists are declared as multidimensional arrays using
hook_checklistapi_checklist_info(). They can be altered using
hook_checklistapi_checklist_info_alter(). Checklist API handles creation of menu
items and permissions. Progress details are saved in one Drupal variable per
checklist. (Note: it is the responsibility of implementing modules to remove
their own variables on hook_uninstall().)
items and permissions. Progress details are saved in one config file per
checklist.
See checklistapi.api.php for more details.
<?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);
}
......@@ -27,7 +27,7 @@
* - #help: (optional) User help to be displayed in the "System help" block
* via hook_help().
* - #menu_name: (optional) The machine name of a menu to place the checklist
* into (e.g. "main-menu" or "navigation"). If this is omitted, Drupal will
* into (e.g., "main-menu" or "navigation"). If this is omitted, Drupal will
* try to infer the correct menu placement from the specified path.
* - #weight: (optional) A floating point number used to sort the list of
* checklists before being output. Lower numbers appear before higher
......@@ -50,8 +50,8 @@
* - #default_value: (optional) The default checked state of the
* item--TRUE for checked or FALSE for unchecked. Defaults to FALSE.
* This is useful for automatically checking items that can be
* programmatically tested (e.g. a module is installed or a variable has
* a certain value).
* programmatically tested (e.g., a module is installed or a
* configuration setting has a certain value).
* - #weight: (optional) A floating point number used to sort the list of
* items before being output. Lower numbers appear before higher
* numbers.
......
#checklistapi-checklist-form div.description p {
margin: .5em 0;
}
......@@ -19,7 +18,11 @@
font-weight: normal;
margin-bottom: 0.5em;
}
#checklistapi-checklist-form .progress .bar,
#checklistapi-checklist-form .progress .filled {
#checklistapi-checklist-form .progress__bar {
animation-name: none;
background-image: none;
}
#checklistapi-checklist-form .compact-link {
clear: both;
padding-top: 1em;
}
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
name: Checklist API
type: module
description: Provides an API for creating fillable, persistent checklists.
version: VERSION
package: Other
core: 8.x
configure: checklistapi.report
<?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');
}
(function ($) {
"use strict";
/**
* Updates the progress bar as checkboxes are changed.
*/
Drupal.behaviors.checklistapiUpdateProgressBar = {
attach: function (context) {
var total_items = $(':checkbox.checklistapi-item', context).size(),
progress_bar = $('#checklistapi-checklist-form .progress__bar', context),
progress_percentage = $('#checklistapi-checklist-form .progress__percentage', context);
$(':checkbox.checklistapi-item', context).change(function () {
var num_items_checked = $(':checkbox.checklistapi-item:checked', context).size(),
percent_complete = Math.round(num_items_checked / total_items * 100),
args = {};
progress_bar.css('width', percent_complete + '%');
args['@complete'] = num_items_checked;
args['@total'] = total_items;
args['@percent'] = percent_complete;
progress_percentage.html(Drupal.t('@complete of @total (@percent%)', args));
});
}
};
/**
* Provides the summary information for the checklist form vertical tabs.
*/
Drupal.behaviors.checklistapiFieldsetSummaries = {
attach: function (context) {
$('#checklistapi-checklist-form .vertical-tabs-panes > fieldset', context).drupalSetSummary(function (context) {
var total = $(':checkbox.checklistapi-item', context).size(), args = {};
$('#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();
args['@total'] = total;
......@@ -53,10 +75,10 @@
return Drupal.t('Your changes will be lost if you leave the page without saving.');
}
});
$('#checklistapi-checklist-form').submit(function() {
$('#checklistapi-checklist-form').submit(function () {
$(window).unbind('beforeunload');
});
$('#checklistapi-checklist-form .clear-saved-progress').click(function() {
$('#checklistapi-checklist-form .clear-saved-progress').click(function () {
$(window).unbind('beforeunload');
});
}
......
checklistapi.report:
title: Checklists
description: Get an overview of your installed checklists with progress details.
route_name: checklistapi.report
parent: system.admin_reports
This diff is collapsed.
<?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;
}
checklistapi.report:
path: /admin/reports/checklistapi
defaults:
_title: Checklists
_content: \Drupal\checklistapi\Controller\ChecklistapiController::report
requirements:
_permission: view checklistapi checklists report
route_callbacks:
- \Drupal\checklistapi\Routing\ChecklistapiRouteSubscriber::routes
services:
checklistapi.access_check:
class: Drupal\checklistapi\Access\ChecklistapiAccessCheck
tags:
- { name: access_check, applies_to: _checklistapi_access }
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
<?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');
}
name: Checklist API Example
type: module
description: Provides an example implementation of the Checklist API.
version: VERSION
package: Example modules
core: 8.x
dependencies:
- checklistapi
configure: checklistapi.checklists.example_checklist
checklistapiexample.checklist:
title: Checklist API example
description: An example implementation of the Checklist API.
route_name: checklistapi.checklists.example_checklist
parent: system.admin_config_development
......@@ -12,7 +12,7 @@
* @link http://buytaert.net/drupal-learning-curve Dries Buytaert's Drupal learning curve @endlink
* .
*/
function checklistapi_example_checklistapi_checklist_info() {
function checklistapiexample_checklistapi_checklist_info() {
$definitions = array();
$definitions['example_checklist'] = array(
'#title' => t('Checklist API example'),
......@@ -206,14 +206,14 @@ function checklistapi_example_checklistapi_checklist_info() {
/**
* Implements hook_checklistapi_checklist_info_alter().
*
* Alters the checklist from checklistapi_example_checklistapi_checklist_info()
* Alters the checklist from checklistapiexample_checklistapi_checklist_info()
* according to
* @link http://www.unleashedmind.com/files/drupal-learning-curve.png sun's modifications @endlink
* of
* @link http://buytaert.net/drupal-learning-curve Dries Buytaert's Drupal learning curve @endlink
* .
*/
function checklistapi_example_checklistapi_checklist_info_alter(&$definitions) {
function checklistapiexample_checklistapi_checklist_info_alter(&$definitions) {
$definitions['example_checklist']['#help'] = t('<p>This checklist based on <a href="http://www.unleashedmind.com/files/drupal-learning-curve.png">sun\'s modification</a> of <a href="http://buytaert.net/drupal-learning-curve">Dries Buytaert\'s Drupal learning curve</a> is an example implementation of the <a href="http://drupal.org/project/checklistapi">Checklist API</a>.</p>');
$definitions['example_checklist']['i_kick_butt']['advanced_tasks']['#title'] = t('jQuery, Form API, theme and module development');
$definitions['example_checklist']['i_kick_butt']['advanced_tasks'] += $definitions['example_checklist']['i_kick_butt']['development'];
......
<?php
/**
* @file
* Contains \Drupal\checklistapi\Access\ChecklistapiAccessCheck.
*/
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 {
/**
* {@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('checklist_id'), $op) ? static::ALLOW : static::DENY;
}
}
......@@ -2,14 +2,24 @@
/**
* @file
* Class for Checklist API checklists.
* Contains \Drupal\checklistapi\ChecklistapiChecklist.
*/
namespace Drupal\checklistapi;
use Drupal\Core\Render\Element;
use Drupal\Core\Url;
/**
* Defines the checklist class.
*/
class ChecklistapiChecklist {
/**
* The configuration key for saved progress.
*/
const PROGRESS_CONFIG_KEY = 'progress';
/**
* The checklist ID.
*
......@@ -80,6 +90,13 @@ class ChecklistapiChecklist {
*/
public $savedProgress;
/**
* The configuration object for saving progress.
*
* @var \Drupal\Core\Config\Config
*/
public $config;
/**
* Constructs a ChecklistapiChecklist object.
*
......@@ -87,8 +104,8 @@ class ChecklistapiChecklist {
* A checklist definition, as returned by checklistapi_get_checklist_info().
*/
public function __construct(array $definition) {
foreach (element_children($definition) as $group_key) {
$this->numberOfItems += count(element_children($definition[$group_key]));
foreach (Element::children($definition) as $group_key) {
$this->numberOfItems += count(Element::children($definition[$group_key]));
$this->items[$group_key] = $definition[$group_key];
unset($definition[$group_key]);
}
......@@ -96,7 +113,23 @@ class ChecklistapiChecklist {
$property_name = checklistapi_strtolowercamel(drupal_substr($property_key, 1));
$this->$property_name = $value;
}
$this->savedProgress = variable_get($this->getSavedProgressVariableName(), array());
$this->config = \Drupal::config("checklistapi.progress.{$this->id}");