Commit 3ebd8bc7 authored by TravisCarden's avatar TravisCarden
Browse files

Initial commit.

parents
CONTENTS OF THIS FILE
---------------------
* Introduction
* Installation
* Implementation
* TODO
INTRODUCTION
------------
Current Maintainer: Travis Carden <http://drupal.org/user/236758>
Checklist API Provides a simple interface for modules to create fillable,
persistent checklists that track progress with completion times and users. See
checklistapi_example.module for an example implementation.
INSTALLATION
------------
Checklist API is installed in the usual way. See
http://drupal.org/documentation/install/modules-themes/modules-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. See checklistapi.api.php for more details.
TODO
----
These development tasks need to be completed for Checklist API:
* Implement component sorting by weight.
* Add tests.
<?php
/**
* @file
* Hooks provided by the Checklist API module.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Alter checklist definitions after hook_checklistapi_checklist_info() is
* invoked.
*
* This hook is invoked by checklistapi_get_checklist_info(). The checklist
* definitions are passed in by reference. Each element of the $checklists array
* is one returned by a module from checklistapi_get_checklist_info().
* Additional checklists may be added, or existing checklists may be altered or
* removed.
*
* @param array $checklists
* A multidimensional array of checklists definitions returned from
* hook_checklistapi_checklist_info().
*
* For a detailed usage example, see checklistapi_example.module.
*
* @see checklistapi_get_checklist_info()
* @see hook_checklistapi_checklist_info()
*/
function hook_checklistapi_checklist_info_alter(&$checklists) {
// Add an item.
$checklists['example']['groups']['example_group']['items']['sample_item'] = array(
'title' => t('Sample item'),
);
// Remove an item.
unset($checklists['example']['groups']['example_group']['items']['example_item']);
}
/**
* Define all checklists provided by the module.
*
* Any number of checklists can be defined in an implementation of this hook.
* Checklist API will register a menu item and create a permission for each one.
*
* @return array
* An array of checklists. Each checklist is keyed by an arbitrary unique
* identifier. The corresponding multidimensional array describing the
* checklist may contain the following key-value pairs:
* - title: Required. The title of the checklist.
* - description: A brief description of the checklist for its corresponding
* menu item.
* - path: Required. The Drupal path where the checklist will be accessed.
* - menu_name: Set this to a custom menu if you don't want your item to be
* placed in Navigation.
* - weight: An integer used to sort the list of checklists before being
* output; lower numbers appear before higher numbers.
* - groups: An array of containers for grouping items, presented as vertical
* tabs. Each group is keyed by an arbitrary unique identifier. The
* corresponding multimensional array describing the group may contain the
* following key-value pairs:
* - title: Required. The title of the group used as the vertical tab label.
* - description: A description of the group.
* - weight: An integer used to sort the list of groups before being output;
* lower numbers appear before higher numbers.
* - items: An array of checklist items. Each item is keyed by an arbitrary
* unique identifier. The corresponding multimensional array describing
* the item may contain the following key-value pairs:
* - title: Required. The title of the item.
* - description: A description of the item.
* - default_value: The default checked state of the item--TRUE for
* checked or FALSE for unchecked. Defaults to FALSE. This is useful for
* automatically checking off tasks that can be programmatically tested
* (e.g. a module is installed or a setting set).
* - weight: An integer used to sort the list of items before being
* output; lower numbers appear before higher numbers.
* - links: An array of links. Each link is keyed by an arbitrary unique
* identifier. The corresponding multimensional array describing the
* link may contain the following key-value pairs:
* - text: The link text.
* - path: The link path.
* - context: The context in which the link may appear. May be one of
* the following:
* - CHECKLISTAPI_LINK_CONTEXT_ANY: Default. The link will always
* appear.
* - CHECKLISTAPI_LINK_CONTEXT_ITEM_CHECKED: The link will appear if
* the item it belongs to has been checked off.
* - CHECKLISTAPI_LINK_CONTEXT_ITEM_UNCHECKED: The link will appear if
* the item it belongs to has not checked off.
* - options: An associative array of additional options used by the l()
* function.
*
* For a detailed usage example, see checklistapi_example.module.
*
* @see checklistapi_example.module
* @see hook_checklistapi_checklist_info_alter()
*/
function hook_checklistapi_checklist_info() {
$checklists = array();
$checklists['example'] = array(
'title' => t('Example checklist'),
'description' => t('An example checklist.'),
'path' => 'example-checklist',
'help' => t('<p>This is an example checklist.</p>'),
'groups' => array(
'example_group' => array(
'title' => t('Example group'),
'description' => t('<p>Here are some example items.</p>'),
'items' => array(
'example_item' => array(
'title' => t('Example item'),
'links' => array(
'example_link' => array(
'text' => t('Example.com'),
'path' => 'http://www.example.com/',
),
),
),
),
),
),
);
return $checklists;
}
/**
* @} End of "addtogroup hooks".
*/
#checklistapi-form div.description p {
margin: .5em 0;
}
name = Checklist API
description = Provides an API for creating fillable, persistent checklists.
core = 7.x
package = Other
(function ($) {
Drupal.behaviors.checklistapiFieldsetSummaries = {
attach: function (context) {
$('#checklistapi-form .vertical-tabs-panes > fieldset', context).drupalSetSummary(function (context) {
var args = {};
args['@complete'] = $(':checkbox:checked', context).size();
args['@total'] = $(':checkbox', context).size();
args['@percent'] = Math.round(args['@complete'] / args['@total'] * 100);
return Drupal.t('@complete of @total (@percent%) complete', args);
});
}
};
})(jQuery);
<?php
/**
* @file
* An API for creating fillable, persistent checklists.
*
* Provides an interface for creating checklists that track progress with
* completion times and users.
*/
/**
* Link should always be shown.
*/
define('CHECKLISTAPI_LINK_CONTEXT_ANY', 1);
/**
* Link should only be shown if the item it belongs to has been checked off.
*/
define('CHECKLISTAPI_LINK_CONTEXT_ITEM_CHECKED', 2);
/**
* Link should only be shown if the item it belongs to has not been checked off.
*/
define('CHECKLISTAPI_LINK_CONTEXT_ITEM_UNCHECKED', 3);
/**
* Access callback: Checks access for a checklist.
*
* @param string $checklist_id
* The unique ID of the checklist to be displayed, which is its array key from
* hook_checklistapi_checklist_info().
*/
function checklistapi_checklist_access($checklist_id) {
return user_access('edit any checklistapi checklist') || user_access('edit ' . $checklist_id . ' checklistapi checklist');
}
/**
* Get all defined checklists.
*
* @return array
* An associative array representing the defined checklists.
*/
function checklistapi_get_checklist_info() {
$checklists = &drupal_static(__FUNCTION__);
if (!isset($checklists)) {
$checklists = module_invoke_all('checklistapi_checklist_info');
// checklistapi_prepare_checklists($checklists);
drupal_alter('checklistapi_checklist_info', $checklists);
// checklistapi_prepare_checklists($checklists);
}
return $checklists;
}
/**
* Implements hook_help().
*/
function checklistapi_help($path, $arg) {
$checklists = checklistapi_get_checklist_info();
foreach ($checklists as $checklist) {
if ($checklist['path'] == $path && !empty($checklist['help'])) {
return $checklist['help'];
}
}
}
/**
* Implements hook_menu().
*/
function checklistapi_menu() {
$items = array();
foreach (checklistapi_get_checklist_info() as $checklist_id => $checklist) {
if (!empty($checklist['path']) && !empty($checklist['title'])) {
$path = $checklist['path'];
$items[$path] = array(
'title' => $checklist['title'],
'page callback' => 'drupal_get_form',
'page arguments' => array('checklistapi_form', $checklist_id),
'access callback' => 'checklistapi_checklist_access',
'access arguments' => array($checklist_id),
'file' => 'checklistapi.pages.inc',
);
if (!empty($checklist['description'])) {
$items[$path]['description'] = $checklist['description'];
}
if (!empty($checklist['menu_name'])) {
$items[$path]['menu_name'] = $checklist['menu_name'];
}
}
}
return $items;
}
/**
* Implements hook_permission().
*/
function checklistapi_permission() {
$perms = array();
$perms['edit any checklistapi checklist'] = array(
'title' => t('Edit any checklist'),
);
foreach (checklistapi_get_checklist_info() as $key => $checklist) {
if (!empty($key)) {
$permission = 'edit ' . $key . ' checklistapi checklist';
// Only link to the checklist if the active user has access to it.
$checklist_name_replacement = user_access($permission) ? l($checklist['title'], $checklist['path']) : drupal_placeholder($checklist['title']);
$perms[$permission] = array(
'title' => t('Edit the !checklist checklist', array('!checklist' => $checklist_name_replacement)),
);
}
}
return $perms;
}
/**
* Sorts checklist components by weight.
*
* @param array $checklists
* A multidimensional array of checklist definitions returned from
* hook_checklistapi_checklist_info().
*
* @see checklistapi_get_checklist_info()
*/
function checklistapi_prepare_checklists(&$checklists) {
if (!empty($checklists)) {
// Sort checklists by weight.
uasort($checklists, 'drupal_sort_weight');
// Loop through checklists.
foreach ($checklists as $checklist_id => $checklist) {
if (!empty($checklist['groups']) && is_array($checklist['groups'])) {
// Sort groups by weight.
uasort($checklists[$checklist_id]['groups'], 'drupal_sort_weight');
// Loop through groups.
foreach ($checklist['groups'] as $group_id => $group) {
if (!empty($group['items']) && is_array($group['items'])) {
// Sort checklist items by weight.
uasort($checklists[$checklist_id]['groups'][$group_id]['items'], 'drupal_sort_weight');
// Loop through checklist items.
foreach ($group['items'] as $item_id => $item) {
if (!empty($item['links']) && is_array($item['links'])) {
// Sort links by weight.
uasort($checklists[$checklist_id]['groups'][$group_id]['items'][$item_id]['links'], 'drupal_sort_weight');
}
}
}
}
}
}
}
}
<?php
/**
* @file
* Page callbacks for the Checklist API module.
*/
/**
* Form constructor for the checklist form.
*
* @param string $checklist_id
* The unique ID of the checklist to be displayed, which is its array key from
* hook_checklistapi_checklist_info().
*
* @see checklistapi_form_submit()
* @ingroup forms
*/
function checklistapi_form($form, &$form_state, $checklist_id) {
$checklists = checklistapi_get_checklist_info();
$checklist = $checklists[$checklist_id];
$saved_values = variable_get('checklistapi_checklist_' . $checklist_id, array());
$form['save_above'] = array(
'#type' => 'submit',
'#value' => t('Save'),
'#attributes' => array('class' => array('js-hide')),
'#weight' => '-100',
);
$form['checklistapi'] = array(
'#tree' => TRUE,
'#type' => 'vertical_tabs',
'#attached' => array(
'css' => array(drupal_get_path('module', 'checklistapi') . '/checklistapi.css'),
'js' => array(drupal_get_path('module', 'checklistapi') . '/checklistapi.js'),
),
);
// Loop through groups.
foreach ($checklist['groups'] as $group_key => $group) {
$form['checklistapi'][$group_key] = array(
'#type' => 'fieldset',
'#title' => filter_xss($group['title']),
);
if (isset($group['description'])) {
$form['checklistapi'][$group_key]['#description'] = filter_xss_admin($group['description']);
}
// Loop through items.
foreach ($group['items'] as $item_key => $item) {
// Get saved state.
$saved_item = !empty($saved_values[$group_key][$item_key]) ? $saved_values[$group_key][$item_key] : 0;
// Set default value.
$default_value = FALSE;
if ($saved_item) {
$default_value = TRUE;
}
elseif (!empty($item['default_value'])) {
$default_value = $item['default_value'];
}
// Get description.
$description = (isset($item['description'])) ? '<p>' . $item['description'] . '</p>' : '';
// Build links.
$links = array();
if ($saved_item) {
// Prepend completion details.
$user = user_load($saved_item['uid']);
$links[] = t(
'Completed @time by !user',
array(
'@time' => format_date($saved_item['completed'], 'short'),
'!user' => theme('username', array('account' => $user)),
)
);
}
if (!empty($item['links']) && is_array($item['links'])) {
foreach ($item['links'] as $link_key => $link) {
$context = (!empty($link['context'])) ? $link['context'] : CHECKLISTAPI_LINK_CONTEXT_ANY;
$show_link = FALSE;
if ($context == CHECKLISTAPI_LINK_CONTEXT_ANY) {
$show_link = TRUE;
}
elseif ($saved_item && $context == CHECKLISTAPI_LINK_CONTEXT_ITEM_CHECKED) {
$show_link = TRUE;
}
elseif (!$saved_item && $context == CHECKLISTAPI_LINK_CONTEXT_ITEM_UNCHECKED) {
$show_link = TRUE;
}
if ($show_link) {
$options = (!empty($link['options']) && is_array($link['options'])) ? $link['options'] : array();
$links[] = l($link['text'], $link['path'], $options);
}
}
}
$description .= '<div class="links">' . implode(' | ', $links) . '</div>';
// Build the list item.
$form['checklistapi'][$group_key][$item_key] = array(
'#type' => 'checkbox',
'#title' => filter_xss($item['title']),
'#description' => filter_xss_admin($description),
'#default_value' => $default_value,
);
}
}
$form['save'] = array(
'#type' => 'submit',
'#value' => t('Save'),
'#weight' => 100,
);
// Stash the checklist title for use in the submission handler.
$form['#checklist_title'] = check_plain($checklist['title']);
return $form;
}
/**
* Form submission handler for checklistapi_form().
*/
function checklistapi_form_submit($form, &$form_state) {
global $user;
$checklist_id = $form_state['build_info']['args'][0];
$saved_values = variable_get('checklistapi_checklist_' . $checklist_id, array());
$new_values = array_slice($form_state['values']['checklistapi'], 0, -1);
$time = time();
$changed_items = 0;
// Loop through groups.
foreach ($new_values as $group_key => $group) {
// Loop through items.
foreach ($group as $item_key => $item) {
$old_item = &$saved_values[$group_key][$item_key];
$new_item = &$new_values[$group_key][$item_key];
// Item is checked.
if ($item == 1) {
// Item was previously checked off. Use saved value.
if ($old_item) {
$new_item = $old_item;
}
// Item is newly checked off. Set new value.
else {
$new_item = array(
'completed' => $time,
'uid' => $user->uid,
);
// Increment changed items counter.
$changed_items++;
}
}
// Item is unchecked.
else {
// Item was previously checked off. Increment changed items counter.
if ($old_item) {
$changed_items++;
}
}
}
}
variable_set('checklistapi_checklist_' . $checklist_id, $new_values);
drupal_set_message(format_plural(
$changed_items,
'Checklist %checklist has been updated. 1 item changed.',
'Checklist %checklist has been updated. @count items changed.',
array('%checklist' => $form['#checklist_title'])
));
}
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
* An example implementation of the Checklist API.
*
* Creates a checklist based on
* @link http://buytaert.net/drupal-learning-curve Dries Buytaert's Drupal learning curve @endlink
* and applies
* @link http://www.unleashedmind.com/files/drupal-learning-curve.png sun's modifications @endlink
* to it.
*/
/**
* Implements hook_checklistapi_checklist_info_alter().
*/
function checklistapi_example_checklistapi_checklist_info_alter(&$checklists) {
$checklists['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>');
$checklists['example_checklist']['groups']['i_kick_butt']['items']['advanced_tasks']['title'] = t('jQuery, Form API, theme and module development');
$checklists['example_checklist']['groups']['i_kick_butt']['items']['advanced_tasks']['links'] += $checklists['example_checklist']['groups']['i_kick_butt']['items']['development']['links'];
unset($checklists['example_checklist']['groups']['i_kick_butt']['items']['development']);
$checklists['example_checklist']['groups']['i_kick_butt']['items']['contribute_code']['title'] = t('Contributing code, designs and patches back to Drupal contrib');
unset($checklists['example_checklist']['groups']['i_kick_butt']['items']['chx_or_unconed']);
$checklists['example_checklist']['groups']['core_contributor'] = array(
'title' => t("I'm a core contributor"),
'items' => array(
'contribute_core_code' => array(
'title' => t('Contribute code and patches to Drupal core'),
'links' => array(
'handbook_page' => array(
'text' => t('Core contribution mentoring (core office hours)'),
'path' => 'http://drupal.org/core-office-hours',
),
'issue_queue' => array(
'text' => t('Core issue queue'),
'path' => 'http://drupal.org/project/issues/drupal',
),
),
),
'unit_tests' => array(
'title' => t('Write unit tests to get own patch committed.'),
'links' => array(
'handbook_page' => array(
'text' => t('Unit Testing with Simpletest'),
'path' => 'http://drupal.org/node/811254',
),
),
),
'review_core_patches' => array(
'title' => t("Review other people's core patches, understanding coding standards."),
'links' => array(
'pending_patches' => array(
'text' => t('Pending patches'),
'path' => 'http://drupal.org/project/issues/search/drupal?status[]=8&status[]=13&status[]=14',
),
'handbook_page' => array(
'text' => t('Coding standards'),
'path' => 'http://drupal.org/coding-standards',
),
),
),
'security_performance' => array(
'title' => t('Security audits, performance tuning.'),
'links' => array(
'handbook_page_security' => $checklists['example_checklist']['groups']['i_kick_butt']['items']['advanced_tasks']['links']['handbook_page_security'],
'handbook_page_performance' => $checklists['example_checklist']['groups']['i_kick_butt']['items']['advanced_tasks']['links']['handbook_page_performance'],
),
),
),
);
unset($checklists['example_checklist']['groups']['i_kick_butt']['items']['advanced_tasks']['links']['handbook_page_security']);
unset($checklists['example_checklist']['groups']['i_kick_butt']['items']['advanced_tasks']['links']['handbook_page_performance']);
$checklists['example_checklist']['groups']['core_maintainer'] = array(
'title' => t("I'm trustworthy for core maintainership"),
'items' => array(
'add_sub_system' => array(
'title' => t('Rewrite or add a Drupal core sub-system.'),
),
'sub_system_maintainer' => array(
'title' => t('Sub-system maintainer.'),
),
'core_branch_maintainer' => array(
'title' => t('Core branch maintainer'),
),
),
);
$checklists['example_checklist']['groups']['know_every_bit_of_core'] = array(
'title' => t('I know every bit of core'),
'items' => array(
'im_chx' => array(
'title' => t(
"I'm !chx.",
array('!chx' => l(t('chx'), 'http://drupal.org/user/9446'))
),
),