checklistapi.module 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. <?php
  2. /**
  3. * @file
  4. * An API for creating fillable, persistent checklists.
  5. *
  6. * Provides an interface for creating checklists that track progress with
  7. * completion times and users.
  8. */
  9. /**
  10. * Access callback: Checks the current user's access to a checklist.
  11. *
  12. * @param string $id
  13. * The checklist ID.
  14. * @param string $operation
  15. * The operation to test access for. Possible values are "view", "edit", and
  16. * "any". Defaults to "any".
  17. *
  18. * @return bool
  19. * Returns TRUE if the current user has access to perform a given operation on
  20. * the specified checklist, or FALSE if not.
  21. */
  22. function checklistapi_checklist_access($id, $operation = 'any') {
  23. $access['view'] = user_access('view any checklistapi checklist') || user_access('view ' . $id . ' checklistapi checklist');
  24. $access['edit'] = user_access('edit any checklistapi checklist') || user_access('edit ' . $id . ' checklistapi checklist');
  25. $access['any'] = $access['view'] || $access['edit'];
  26. if (isset($access[$operation])) {
  27. return $access[$operation];
  28. }
  29. else {
  30. throw new Exception(t('No such operation "@operation"', array(
  31. '@operation' => $operation,
  32. )));
  33. }
  34. }
  35. /**
  36. * Loads a checklist object.
  37. *
  38. * @param string $id
  39. * The checklist ID.
  40. *
  41. * @return ChecklistapiChecklist|false
  42. * A fully-loaded checklist object, or FALSE if the checklist is not found.
  43. */
  44. function checklistapi_checklist_load($id) {
  45. $definition = checklistapi_get_checklist_info($id);
  46. return ($definition) ? new ChecklistapiChecklist($definition) : FALSE;
  47. }
  48. /**
  49. * Gets checklist definitions.
  50. *
  51. * @param string $id
  52. * (optional) A checklist ID. Defaults to NULL.
  53. *
  54. * @return array|false
  55. * The definition of the specified checklist, or FALSE if no such checklist
  56. * exists, or an array of all checklist definitions if none is specified.
  57. */
  58. function checklistapi_get_checklist_info($id = NULL) {
  59. $definitions = &drupal_static(__FUNCTION__);
  60. if (!isset($definitions)) {
  61. // Get definitions.
  62. $definitions = module_invoke_all('checklistapi_checklist_info');
  63. $definitions = checklistapi_sort_array($definitions);
  64. // Let other modules alter them.
  65. drupal_alter('checklistapi_checklist_info', $definitions);
  66. $definitions = checklistapi_sort_array($definitions);
  67. // Inject checklist IDs.
  68. foreach ($definitions as $key => $value) {
  69. $definitions[$key] = array('#id' => $key) + $definitions[$key];
  70. }
  71. }
  72. if (!empty($id)) {
  73. return (!empty($definitions[$id])) ? $definitions[$id] : FALSE;
  74. }
  75. return $definitions;
  76. }
  77. /**
  78. * Implements hook_help().
  79. */
  80. function checklistapi_help($path, $arg) {
  81. foreach (checklistapi_get_checklist_info() as $definition) {
  82. if ($definition['#path'] == $path && !empty($definition['#help'])) {
  83. return $definition['#help'];
  84. }
  85. }
  86. }
  87. /**
  88. * Implements hook_init().
  89. */
  90. function checklistapi_init() {
  91. // Disable page caching on all Checklist API module paths.
  92. $module_paths = array_keys(checklistapi_menu());
  93. if (in_array(current_path(), $module_paths)) {
  94. drupal_page_is_cacheable(FALSE);
  95. }
  96. }
  97. /**
  98. * Implements hook_menu().
  99. */
  100. function checklistapi_menu() {
  101. $items = array();
  102. // Checklists report.
  103. $items['admin/reports/checklistapi'] = array(
  104. 'title' => 'Checklists',
  105. 'page callback' => 'checklistapi_report_form',
  106. 'access arguments' => array('view checklistapi checklists report'),
  107. 'description' => 'Get an overview of your installed checklists with progress details.',
  108. 'file' => 'checklistapi.admin.inc',
  109. );
  110. // Individual checklists.
  111. foreach (checklistapi_get_checklist_info() as $id => $definition) {
  112. if (empty($definition['#path']) || empty($definition['#title'])) {
  113. continue;
  114. }
  115. // View/edit checklist.
  116. $items[$definition['#path']] = array(
  117. 'title' => $definition['#title'],
  118. 'description' => (!empty($definition['#description'])) ? $definition['#description'] : '',
  119. 'page callback' => 'drupal_get_form',
  120. 'page arguments' => array('checklistapi_checklist_form', $id),
  121. 'access callback' => 'checklistapi_checklist_access',
  122. 'access arguments' => array($id),
  123. 'file' => 'checklistapi.pages.inc',
  124. );
  125. if (!empty($definition['#menu_name'])) {
  126. $items[$definition['#path']]['menu_name'] = $definition['#menu_name'];
  127. }
  128. // Clear saved progress.
  129. $items[$definition['#path'] . '/clear'] = array(
  130. 'title' => 'Clear',
  131. 'page callback' => 'drupal_get_form',
  132. 'page arguments' => array('checklistapi_checklist_clear_confirm', $id),
  133. 'access callback' => 'checklistapi_checklist_access',
  134. 'access arguments' => array($id, 'edit'),
  135. 'file' => 'checklistapi.pages.inc',
  136. 'type' => MENU_CALLBACK,
  137. );
  138. // Toggle compact mode.
  139. $items[$definition['#path'] . '/compact'] = array(
  140. 'title' => 'Compact mode',
  141. 'page callback' => 'checklistapi_compact_page',
  142. 'access callback' => 'checklistapi_checklist_access',
  143. 'access arguments' => array($id),
  144. 'file' => 'checklistapi.pages.inc',
  145. 'type' => MENU_CALLBACK,
  146. );
  147. }
  148. return $items;
  149. }
  150. /**
  151. * Implements hook_permission().
  152. */
  153. function checklistapi_permission() {
  154. $perms = array();
  155. // Universal permissions.
  156. $perms['view checklistapi checklists report'] = array(
  157. 'title' => t(
  158. 'View the !name report',
  159. array('!name' => (user_access('view checklistapi checklists report')) ? l(t('Checklists'), 'admin/reports/checklistapi') : drupal_placeholder('Checklists'))
  160. ),
  161. );
  162. $perms['view any checklistapi checklist'] = array(
  163. 'title' => t('View any checklist'),
  164. 'description' => $view_checklist_perm_description = t('Read-only access: View list items and saved progress.'),
  165. );
  166. $perms['edit any checklistapi checklist'] = array(
  167. 'title' => t('Edit any checklist'),
  168. 'description' => $edit_checklist_perm_description = t('Check and uncheck list items and save changes, or clear saved progress.'),
  169. );
  170. // Per checklist permissions.
  171. foreach (checklistapi_get_checklist_info() as $id => $definition) {
  172. if (empty($id)) {
  173. continue;
  174. }
  175. $perms['view ' . $id . ' checklistapi checklist'] = array(
  176. 'title' => t(
  177. 'View the !name checklist',
  178. array('!name' => (checklistapi_checklist_access($id)) ? l($definition['#title'], $definition['#path']) : drupal_placeholder($definition['#title']))
  179. ),
  180. 'description' => $view_checklist_perm_description,
  181. );
  182. $perms['edit ' . $id . ' checklistapi checklist'] = array(
  183. 'title' => t(
  184. 'Edit the !name checklist',
  185. array('!name' => (checklistapi_checklist_access($id)) ? l($definition['#title'], $definition['#path']) : drupal_placeholder($definition['#title']))
  186. ),
  187. 'description' => $edit_checklist_perm_description,
  188. );
  189. }
  190. return $perms;
  191. }
  192. /**
  193. * Recursively sorts array elements by #weight.
  194. *
  195. * @param array $array
  196. * A nested array of elements and properties, such as the checklist
  197. * definitions returned by hook_checklistapi_checklist_info().
  198. *
  199. * @return array
  200. * The input array sorted recursively by #weight.
  201. *
  202. * @see checklistapi_get_checklist_info()
  203. */
  204. function checklistapi_sort_array(array $array) {
  205. $child_keys = element_children($array);
  206. if (!count($child_keys)) {
  207. // No children to sort.
  208. return $array;
  209. }
  210. $incrementer = 0;
  211. $children = array();
  212. foreach ($child_keys as $key) {
  213. // Move child to a temporary array for sorting.
  214. $children[$key] = $array[$key];
  215. unset($array[$key]);
  216. // Supply a default weight if missing or invalid.
  217. if (empty($children[$key]['#weight']) || !is_numeric($children[$key]['#weight'])) {
  218. $children[$key]['#weight'] = 0;
  219. }
  220. // Increase each weight incrementally to preserve the original order when
  221. // not overridden. This accounts for undefined behavior in PHP's uasort()
  222. // function when its comparison callback finds two values equal.
  223. $children[$key]['#weight'] += ($incrementer++ / 1000);
  224. // Descend into child.
  225. $children[$key] = checklistapi_sort_array($children[$key]);
  226. }
  227. // Sort by weight.
  228. uasort($children, 'element_sort');
  229. // Remove incremental weight hack.
  230. foreach ($children as $key => $child) {
  231. $children[$key]['#weight'] = floor($children[$key]['#weight']);
  232. }
  233. // Put children back in the main array.
  234. $array += $children;
  235. return $array;
  236. }
  237. /**
  238. * Converts a string to lowerCamel case, suitably for a class property name.
  239. *
  240. * @param string $string
  241. * The input string.
  242. *
  243. * @return string
  244. * The input string converted to camelCase.
  245. */
  246. function checklistapi_strtolowercamel($string) {
  247. $string = str_replace('_', ' ', $string);
  248. $string = ucwords($string);
  249. $string = str_replace(' ', '', $string);
  250. // Lowercase first character. lcfirst($string) would be nicer, but let's not
  251. // create a dependency on PHP 5.3 just for that.
  252. $string[0] = strtolower($string[0]);
  253. return $string;
  254. }
  255. /**
  256. * Implements hook_theme().
  257. */
  258. function checklistapi_theme() {
  259. return array(
  260. 'checklistapi_compact_link' => array(
  261. 'file' => 'checklistapi.pages.inc',
  262. ),
  263. 'checklistapi_progress_bar' => array(
  264. 'path' => drupal_get_path('module', 'checklistapi') . '/templates',
  265. 'template' => 'checklistapi-progress-bar',
  266. 'variables' => array(
  267. 'message' => '&nbsp;',
  268. 'number_complete' => 0,
  269. 'number_of_items' => 0,
  270. 'percent_complete' => 0,
  271. ),
  272. ),
  273. );
  274. }