You are here

checklistapi.module in Checklist API 8

Same filename and directory in other branches
  1. 7 checklistapi.module

An API for creating fillable, persistent checklists.

Provides an interface for creating checklists that track progress with completion times and users.

File

checklistapi.module
View source
<?php

/**
 * @file
 * An API for creating fillable, persistent checklists.
 *
 * Provides an interface for creating checklists that track progress with
 * completion times and users.
 */
use Drupal\checklistapi\ChecklistapiChecklist;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;

/**
 * Access callback: Checks the current user's access to a given checklist.
 *
 * @param string $id
 *   The checklist ID.
 * @param string $operation
 *   (optional) The operation to test access for. Accepted values are "view",
 *   "edit", and "any". Defaults to "any".
 *
 * @return bool
 *   Returns TRUE if the current user has access to perform a given operation on
 *   the specified checklist, or FALSE if not.
 *
 * @throws InvalidArgumentException
 *   Throws an exception if an unsupported operation is supplied.
 */
function checklistapi_checklist_access($id, $operation = 'any') {
  $all_operations = [
    'view',
    'edit',
    'any',
  ];
  if (!in_array($operation, $all_operations)) {
    throw new \InvalidArgumentException(sprintf('No such operation "%s"', $operation));
  }
  $current_user = \Drupal::currentUser();
  $access['view'] = $current_user
    ->hasPermission('view any checklistapi checklist') || $current_user
    ->hasPermission("view {$id} checklistapi checklist");
  $access['edit'] = $current_user
    ->hasPermission('edit any checklistapi checklist') || $current_user
    ->hasPermission("edit {$id} checklistapi checklist");
  $access['any'] = $access['view'] || $access['edit'];
  return $access[$operation];
}

/**
 * Loads a checklist object.
 *
 * @param string $id
 *   The checklist ID.
 *
 * @return Drupal\checklistapi\ChecklistapiChecklist|false
 *   A fully-loaded checklist object, or FALSE if the checklist is not found.
 */
function checklistapi_checklist_load($id) {
  $definition = checklistapi_get_checklist_info($id);
  return $definition ? new ChecklistapiChecklist($definition) : FALSE;
}

/**
 * Adds the checklist items to a given definition.
 *
 * @param array $definition
 *   A checklist definition as returned from checklistapi_get_checklist_info().
 *
 * @return array
 *   The checklist definition with checklist items added.
 */
function checklistapi_add_checklist_items(array $definition) {
  if (!empty($definition['#callback']) && is_callable($definition['#callback'])) {

    // Remove any checklist items from the original definition.
    foreach (Element::children($definition) as $child) {
      unset($definition[$child]);
    }

    // Invoke the callback function.
    $definition += call_user_func_array($definition['#callback'], $definition['#callback_arguments'] ?? []);
  }
  return $definition;
}

/**
 * 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. If the user
 * does not have the cookie, the setting defaults to off.
 *
 * @return bool
 *   TRUE when in compact mode, or FALSE when in expanded mode.
 */
function checklistapi_compact_mode_is_on() {

  // PHP converts dots into underscores in cookie names.
  return (bool) \Drupal::request()->cookies
    ->get('Drupal_visitor_checklistapi_compact_mode', FALSE);
}

/**
 * Gets checklist definitions.
 *
 * @param string $id
 *   (optional) A checklist ID. Defaults to NULL.
 *
 * @return array|false
 *   The definition of the specified checklist, or FALSE if no such checklist
 *   exists, or an array of all checklist definitions if none is specified.
 */
function checklistapi_get_checklist_info($id = NULL) {
  $definitions =& drupal_static(__FUNCTION__);
  if (!is_array($definitions)) {

    // Get definitions.
    $definitions = \Drupal::moduleHandler()
      ->invokeAll('checklistapi_checklist_info');
    foreach ($definitions as $key => $value) {
      $definitions[$key] = checklistapi_add_checklist_items($value);
    }
    $definitions = checklistapi_sort_array($definitions);

    // Let other modules alter them.
    \Drupal::moduleHandler()
      ->alter('checklistapi_checklist_info', $definitions);
    $definitions = checklistapi_sort_array($definitions);

    // Inject checklist IDs.
    foreach ($definitions as $key => $value) {
      $definitions[$key] = [
        '#id' => $key,
      ] + $definitions[$key];
    }
  }
  if (!empty($id)) {
    return !empty($definitions[$id]) ? $definitions[$id] : FALSE;
  }
  return $definitions;
}

/**
 * Implements hook_help().
 */
function checklistapi_help($route_name, RouteMatchInterface $route_match) {
  foreach (checklistapi_get_checklist_info() as $id => $definition) {
    $checklist = new ChecklistapiChecklist($definition);
    if ($checklist
      ->getRouteName() == $route_name) {

      // The checklist has help and the current user has access to view it.
      if (!empty($definition['#help']) && checklistapi_checklist_access($id)) {
        return $definition['#help'];
      }
      else {
        break;
      }
    }
  }
}

/**
 * Implements hook_module_preinstall().
 */
function checklistapi_module_preinstall($module) {
  drupal_static_reset('checklistapi_get_checklist_info');
}

/**
 * Recursively sorts array elements by #weight.
 *
 * @param array $array
 *   A nested array of elements and properties, such as the checklist
 *   definitions returned by hook_checklistapi_checklist_info().
 *
 * @return array
 *   The input array sorted recursively by #weight.
 *
 * @see checklistapi_get_checklist_info()
 */
function checklistapi_sort_array(array $array) {
  $child_keys = Element::children($array);
  if (!count($child_keys)) {

    // No children to sort.
    return $array;
  }
  $incrementer = 0;
  $children = [];
  foreach ($child_keys as $key) {

    // Move child to a temporary array for sorting.
    $children[$key] = $array[$key];
    unset($array[$key]);

    // Supply a default weight if missing or invalid.
    if (empty($children[$key]['#weight']) || !is_numeric($children[$key]['#weight'])) {
      $children[$key]['#weight'] = 0;
    }

    // Increase each weight incrementally to preserve the original order when
    // not overridden. This accounts for undefined behavior in PHP's uasort()
    // function when its comparison callback finds two values equal.
    $children[$key]['#weight'] += $incrementer++ / 1000;

    // Descend into child.
    $children[$key] = checklistapi_sort_array($children[$key]);
  }

  // Sort by weight.
  uasort($children, [
    'Drupal\\Component\\Utility\\SortArray',
    'sortByWeightProperty',
  ]);

  // Remove incremental weight hack.
  foreach ($children as $key => $child) {
    $children[$key]['#weight'] = floor($children[$key]['#weight']);
  }

  // Put children back in the main array.
  $array += $children;
  return $array;
}

/**
 * Converts a string to lowerCamel case, suitably for a class property name.
 *
 * @param string $string
 *   The input string.
 *
 * @return string
 *   The input string converted to camelCase.
 */
function checklistapi_strtolowercamel($string) {
  $string = str_replace('_', ' ', $string);
  $string = ucwords($string);
  $string = str_replace(' ', '', $string);
  $string = Unicode::lcfirst($string);
  return $string;
}

/**
 * Implements hook_theme().
 */
function checklistapi_theme() {
  return [
    'checklistapi_progress_bar' => [
      'path' => drupal_get_path('module', 'checklistapi') . '/templates',
      'template' => 'checklistapi-progress-bar',
      'variables' => [
        'message' => '',
        'number_complete' => 0,
        'number_of_items' => 0,
        'percent_complete' => 0,
      ],
    ],
  ];
}

Functions

Namesort descending Description
checklistapi_add_checklist_items Adds the checklist items to a given definition.
checklistapi_checklist_access Access callback: Checks the current user's access to a given checklist.
checklistapi_checklist_load Loads a checklist object.
checklistapi_compact_mode_is_on Determines whether the current user is in compact mode.
checklistapi_get_checklist_info Gets checklist definitions.
checklistapi_help Implements hook_help().
checklistapi_module_preinstall Implements hook_module_preinstall().
checklistapi_sort_array Recursively sorts array elements by #weight.
checklistapi_strtolowercamel Converts a string to lowerCamel case, suitably for a class property name.
checklistapi_theme Implements hook_theme().