diff --git a/ctools.module b/ctools.module index dc46173457c363d60cd841362c7f471f8c99419c..92315a64edcecee516aa4284aa77ba9fce7574f7 100644 --- a/ctools.module +++ b/ctools.module @@ -106,3 +106,76 @@ function ctools_get_roles() { return $roles; } + +/** + * Determine if the current user has access via a plugin. + * + * This function is meant to be embedded in the Drupal menu system, and + * therefore is in the .module file since sub files can't be loaded, and + * takes arguments a little bit more haphazardly than ctools_access(). + * + * @param $plugin_name + * The access plugin to use. If empty or 'none' then no access control + * is being used and this function returns TRUE. If the plugin can't be + * found otherwise, this function automatically returns FALSE. + * @param $settings + * An array of settings theoretically set by the user. + * @param ... + * zero or more context arguments generated from argument plugins. These + * contexts must have an 'id' attached to them so that they can be + * properly associated. The argument plugin system should set this, but + * if the context is coming from elsewhere it will need to be set manually. + * + * @return + * TRUE if access is granted, false if otherwise. + */ +function ctools_access_menu($plugin_name, $settings) { + $contexts = array(); + foreach (func_get_args() as $arg) { + if (is_object($arg) && get_class($arg) == 'ctools_context') { + $contexts[$arg->id] = $arg; + } + } + + global $user; + return ctools_access($plugin_name, $settings, $contexts, $user); +} + +/** + * Determine if the current user has access via plugin. + * + * @param $plugin_name + * The access plugin to use. If empty or 'none' then no access control + * is being used and this function returns TRUE. If the plugin can't be + * found otherwise, this function automatically returns FALSE. + * @param $settings + * An array of settings theoretically set by the user. + * @param $contexts + * An array of zero or more contexts that may be used to determine if + * the user has access. + * @param $account + * The account to test against. If NULL the logged in user will be tested. + * + * @return + * TRUE if access is granted, false if otherwise. + */ +function ctools_access($plugin_name, $settings, $contexts = array(), $account = NULL) { + if (!$account) { + global $user; + $account = $user; + } + + if (empty($plugin_name) || $plugin_name == 'none') { + return TRUE; + } + + ctools_include('context'); + $plugin = ctools_get_access_plugin($plugin_name); + if (!$plugin) { + return FALSE; + } + + if ($function = ctools_plugin_get_function($plugin, 'callback')) { + return $function($settings, $contexts, $account); + } +} diff --git a/delegator/css/page-task.css b/delegator/css/page-task.css new file mode 100644 index 0000000000000000000000000000000000000000..f45792353fbaefb1f4a1a25092a3b4c0386eaa4d --- /dev/null +++ b/delegator/css/page-task.css @@ -0,0 +1,10 @@ +/* $Id$ */ + +.delegator-page-operations { + text-align: right; +} + +td.delegator-page-operations { + width: 175px; +} + diff --git a/delegator/delegator.module b/delegator/delegator.module index 4eda9a649deaa1a951bf14a83446dde0f040428c..213ac7f7d3c187dc133c4a7c82a555befa2dfad3 100644 --- a/delegator/delegator.module +++ b/delegator/delegator.module @@ -539,3 +539,12 @@ function delegator_make_task_name($task_id, $subtask_id) { } } +/** + * Delegator for arg load function because menu system will not load extra + * files for these; they must be in a .module. + */ +function dp_arg_load($value, $subtask, $argument) { + require_once './' . drupal_get_path('module', 'delegator') . '/plugins/tasks/page.inc'; + return _dp_arg_load($value, $subtask, $argument); +} + diff --git a/delegator/help/api-task-type.html b/delegator/help/api-task-type.html new file mode 100644 index 0000000000000000000000000000000000000000..144846e0f6b152ccf2d5099df30fcb5c70122831 --- /dev/null +++ b/delegator/help/api-task-type.html @@ -0,0 +1,3 @@ +<!-- $Id$ --> + +defines a task type, grouping tasks together and providing a common UI for them. \ No newline at end of file diff --git a/delegator/help/page-task-type.html b/delegator/help/page-task-type.html new file mode 100644 index 0000000000000000000000000000000000000000..c382c76ff2e76fde8653bc3ea72304e55ee92cba --- /dev/null +++ b/delegator/help/page-task-type.html @@ -0,0 +1,4 @@ + +Additional 'task' keys support: + +operations -- a list of operations suitable for theme('links') \ No newline at end of file diff --git a/delegator/plugins/task_types/page.admin.inc b/delegator/plugins/task_types/page.admin.inc new file mode 100644 index 0000000000000000000000000000000000000000..79ca9f38fdbc5f7d2b5acb07c0f98718085dc956 --- /dev/null +++ b/delegator/plugins/task_types/page.admin.inc @@ -0,0 +1,88 @@ +<?php +// $Id$ + +/** + * @file + * Administrative functions for the page system + */ +function delegator_page_type_list() { + $tasks = delegator_get_tasks_by_type('page'); + + $tables = array( + 'singles' => array( + 'title' => t('System pages'), + 'description' => t('Pages provided by the system that can be overridden to provide alternate content or layout.'), + 'rows' => array(), + ), + 'page' => array(), // give this one special weighting, + ); + + delegator_page_type_sort_tasks($tasks, $tables, 'singles'); + + $output = ''; + $header = array( + array('data' => t('Title'), 'class' => 'delegator-page-title'), + array('data' => t('Path'), 'class' => 'delegator-page-path'), + array('data' => t('Operations'), 'class' => 'delegator-page-operations'), + ); + + foreach ($tables as $bucket => $info) { + if (!empty($info['rows'])) { + $output .= '<h3>' . check_plain($info['title']) . '</h3>'; + $output .= '<div class="description">' . check_plain($info['description']) . '</div>'; + if (isset($info['operations'])) { + $info['rows'][] = array('data' => array(array('data' => theme('links', $info['operations']), 'colspan' => 3)), 'class' => 'delegator-page-operations'); + } + } + + $output .= theme('table', $header, $info['rows']); + } + + drupal_add_css(drupal_get_path('module', 'delegator') . '/css/page-task.css'); + return $output; +} + +/** + * Sort tasks into buckets based upon whether or not they have subtasks. + */ +function delegator_page_type_sort_tasks($tasks, &$tables, $bucket, $task_id = NULL) { + foreach ($tasks as $id => $task) { + // If a type has subtasks, add its subtasks in its own table. + if (!empty($task['subtasks'])) { + $tables[$id]['title'] = $task['title']; + $tables[$id]['description'] = $task['description']; + if (isset($task['operations'])) { + $tables[$id]['operations'] = $task['operations']; + } + + delegator_page_type_sort_tasks(delegator_get_task_subtasks($task), $tables, $id, $task['name']); + continue; + } + + if (isset($task_id)) { + $task_name = delegator_make_task_name($task_id, $task['name']); + } + else { + $task_name = $task['name']; + } + + $row = array('data' => array(), 'class' => 'page-task-' . $id); + $row['data'][] = array('data' => $task['admin title'], 'class' => 'delegator-page-title'); + $row['data'][] = array('data' => $task['admin path'], 'class' => 'delegator-page-path'); + if (isset($task['operations'])) { + $operations = $task['operations']; + } + else { + $operations = array( + array( + 'title' => t('Task handlers'), + 'href' => "admin/build/delegator/$task_name" + ), + ); + } + $row['data'][] = array('data' => theme('ctools_dropdown', t('Operations'), $operations), 'class' => 'delegator-page-operations'); + + $tables[$bucket]['rows'][] = $row; + } +} + diff --git a/delegator/plugins/task_types/page.inc b/delegator/plugins/task_types/page.inc new file mode 100644 index 0000000000000000000000000000000000000000..5425933303a449ae14d6deb26f6a94c28cd807df --- /dev/null +++ b/delegator/plugins/task_types/page.inc @@ -0,0 +1,56 @@ +<?php +// $Id$ +/** + * @file + * Groups tasks that provide pages and provides a common administrative UI for them. + * + * The page task supports any task that defines itself as type 'page' and it also + * includes special handling for the 'page' task to provide a nice location to + * place administer the list of pages, including adding removing and editing + * the handlers of pages alongside the less generic tasks such as node and + * user viewing. + */ + +/** + * Specialized implementation of hook_delegator_tasks(). See api-task.html for + * more information. + */ +function delegator_page_delegator_task_types() { + return array( + 'page' => array( + 'title' => t('Pages'), + 'admin path' => 'admin/build/pages', + 'hook menu' => 'delegator_page_type_menu', + ), + ); +} + +/** + * Delegated implementation of hook_menu(). + */ +function delegator_page_type_menu(&$items, $task) { + // Set up access permissions. + $access_callback = isset($task['admin access callback']) ? $task['admin access callback'] : 'user_access'; + + // @todo use 'administer pages' perm instead? + $access_arguments = isset($task['admin access arguments']) ? $task['admin access arguments'] : array('administer delegator'); + + $base = array( + 'access callback' => $access_callback, + 'access arguments' => $access_arguments, + 'file' => 'plugins/task_types/page.admin.inc', + ); + + $items['admin/build/pages'] = array( + 'title' => 'Pages', + 'description' => 'Add, edit and remove overridden system pages and user defined pages from the system.', + 'page callback' => 'delegator_page_type_list', + ) + $base; + + $items['admin/build/pages/list'] = array( + 'title' => 'List', + 'page callback' => 'delegator_page_type_list', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + ); +} diff --git a/delegator/plugins/tasks/node_view.inc b/delegator/plugins/tasks/node_view.inc index 059bbd0b0feab0ac36f138b52b824cebcbbcbcd4..a5d03e1f932ed6ac8cfcdcc8b7cb181bb6ae5be5 100644 --- a/delegator/plugins/tasks/node_view.inc +++ b/delegator/plugins/tasks/node_view.inc @@ -53,6 +53,10 @@ function delegator_node_view($node) { if ($function = ctools_plugin_load_function('delegator', 'task_handlers', $handler->handler, 'render')) { $output = $function($handler, $node); if ($output) { + // Since we're not using node_show() we need to emulate what it used to do. + // Update the history table, stating that this user viewed this node. + node_tag_new($node->nid); + // TRUE is a special return used to let us know that it handled the // task but does not wish us to render anything, as it already did. // This is needed for the 'no blocks' functionality. diff --git a/delegator/plugins/tasks/page.admin.inc b/delegator/plugins/tasks/page.admin.inc index e4c1fb6baf3ca353dbb7a01d8bd28572c0aa38fc..07881067e134d7b2e90b579d23242babd02db2e6 100644 --- a/delegator/plugins/tasks/page.admin.inc +++ b/delegator/plugins/tasks/page.admin.inc @@ -9,6 +9,161 @@ * only when needed. */ +/** + * Delegated implementation of hook_menu(). + */ +function delegator_page_menu(&$items, $task) { + // Set up access permissions. + $access_callback = isset($task['admin access callback']) ? $task['admin access callback'] : 'user_access'; + $access_arguments = isset($task['admin access arguments']) ? $task['admin access arguments'] : array('administer delegator'); + + $base = array( + 'access callback' => $access_callback, + 'access arguments' => $access_arguments, + 'file' => 'plugins/tasks/page.admin.inc', + ); + + $items['admin/build/pages/add'] = array( + 'title' => 'Add page', + 'description' => 'Add a delegator page subtask.', + 'page callback' => 'delegator_page_add_subtask', + 'type' => MENU_LOCAL_TASK, + ) + $base; + + $form_info = delegator_page_edit_form_info(); + $default_task = FALSE; + $weight = 0; + foreach ($form_info['order'] as $form_id => $form_title) { + // The first edit form is the default for tabs, so it gets a bit + // of special treatment here. + if (!$default_task) { + $default_task = TRUE; + // Add the callback for the default tab. + $items["admin/build/pages/edit/%"] = array( + 'title' => t('Edit'), + 'page callback' => 'delegator_page_edit_subtask', + 'page arguments' => array(4, $form_id), + ) + $base; + + // And make sure it's the default local task. + $type = MENU_DEFAULT_LOCAL_TASK; + } + else { + // This allows an empty form title to provide a hidden form + // which is useful for doing more branch-like multi-step + // functionality. + $type = $form_title ? MENU_LOCAL_TASK : MENU_CALLBACK; + } + + // Handler to edit delegator task handlers. May exist in its own UI. + $items["admin/build/pages/edit/%/$form_id"] = array( + 'title' => $form_title, + 'page callback' => 'delegator_page_edit_subtask', + 'page arguments' => array(4, 5), + 'type' => $type, + 'weight' => $weight++, + ) + $base; + } + + // AJAX callbacks for argument modal. + $items['admin/build/delegator/argument'] = array( + 'page callback' => 'delegator_page_subtask_argument_ajax', + ) + $base; + + // Add menu entries for each subtask + foreach (delegator_page_load_all() as $subtask_id => $subtask) { + if (!isset($subtask->access['settings'])) { + $subtask->access['settings'] = NULL; + } + + $path = array(); + $page_arguments = array($subtask_id); + $access_arguments = array($subtask->access['type'], $subtask->access['settings']); + $load_arguments = array($subtask_id, '%index'); + + // Replace named placeholders with our own placeholder to load contexts. + foreach (explode('/', $subtask->path) as $position => $bit) { + if ($bit[0] == '%' && $bit != '%') { + // If an argument, swap it out with our argument loader and make sure + // the argument gets passed through to the page callback. + $path[] = '%dp_arg'; + $page_arguments[] = $position; + $access_arguments[] = $position; + } + else { + $path[] = $bit; + } + } + + $menu_path = implode('/', $path); + + $items[$menu_path] = delegator_page_menu_item($subtask->menu, $access_arguments, $page_arguments, $load_arguments); + + // Add a parent menu item if one is configured. + if ($subtask->menu['type'] == 'default tab' && $subtask->menu['parent']['type'] != 'none') { + array_pop($path); + $parent_path = implode('/', $path); + $items[$parent_path] = delegator_page_menu_item($subtask->menu['parent'], $access_arguments, $page_arguments, $load_arguments); + } + } +} + +/** + * Create a menu item for delegator pages. + * + * @param $menu + * The configuration to use. It will contain a type, and depending on the + * type may also contain weight, title and name. These are presumed to have + * been configured from the UI. + * @param $access_arguments + * Arguments that go with ctools_access_menu; it should be loaded with + * the access plugin type, settings, and positions of any arguments that + * may produce contexts. + * @param $page_arguments + * This should be seeded with the subtask name for easy loading and like + * the access arguments above should contain positions of arguments so + * that the menu system passes contexts through. + * @param $load_arguments + * Arguments to send to the arg loader; should be the subtask id and '%index'. + */ +function delegator_page_menu_item($menu, $access_arguments, $page_arguments, $load_arguments) { + $item = array( + 'access callback' => 'ctools_access_menu', + 'access arguments' => $access_arguments, + 'page callback' => 'delegator_page_execute', + 'page arguments' => $page_arguments, + 'load arguments' => $load_arguments, + 'file' => 'plugins/tasks/page.admin.inc', + ); + + if (isset($menu['title'])) { + $item['title'] = $menu['title']; + } + if (isset($menu['weight'])) { + $item['weight'] = $menu['weight']; + } + + switch ($menu['type']) { + case 'none': + default: + $item['type'] = MENU_CALLBACK; + break; + case 'normal': + $item['type'] = MENU_NORMAL_ITEM; + // Insert item into the proper menu + $item['menu_name'] = $menu['name']; + break; + case 'tab': + $item['type'] = MENU_LOCAL_TASK; + break; + case 'default tab': + $item['type'] = MENU_DEFAULT_LOCAL_TASK; + break; + } + + return $item; +} + /** * Get the cached changes to a given task handler. */ @@ -147,7 +302,7 @@ function delegator_page_edit_subtask($page_name, $step = NULL) { function delegator_page_add_subtask_finish(&$form_state) { $page = &$form_state['page']; // Ensure $page->arguments contains only real arguments: - $arguments = delegator_page_get_arguments($page->path); + $arguments = delegator_page_get_named_arguments($page->path); $args = array(); foreach ($arguments as $keyword => $position) { if (isset($page->arguments[$keyword])) { @@ -240,7 +395,7 @@ function delegator_page_form_basic_validate_filter($value) { */ function delegator_page_form_basic_validate(&$form, &$form_state) { // Ensure name is properly formed. - $args = delegator_page_get_arguments($form_state['values']['path']); + $args = delegator_page_get_named_arguments($form_state['values']['path']); if ($invalid_args = array_filter($args, 'delegator_page_form_basic_validate_filter')) { foreach ($invalid_args as $arg => $position) { form_error($form['path'], t('Duplicated argument %arg', array('%arg' => $arg))); @@ -428,14 +583,7 @@ function delegator_page_form_access(&$form, &$form_state) { $contexts = array(); // Load contexts based on argument data: - if ($page->arguments) { - $arguments = array(); - foreach ($page->arguments as $keyword => $argument) { - if (isset($argument['name'])) { - $argument['keyword'] = $keyword; - $arguments[$argument['id']] = $argument; - } - } + if ($arguments = delegator_page_get_arguments($page)) { $contexts = ctools_context_get_placeholders_from_argument($arguments); } @@ -549,7 +697,7 @@ function delegator_page_form_argument(&$form, &$form_state) { $path = $form_state['page']->path; $page = &$form_state['page']; - $arguments = delegator_page_get_arguments($path); + $arguments = delegator_page_get_named_arguments($path); $form['table'] = array( '#theme' => 'delegator_page_form_argument_table', @@ -671,7 +819,7 @@ function delegator_page_subtask_argument_ajax($step = NULL, $cache_name = NULL, } $path = $page->path; - $arguments = delegator_page_get_arguments($path); + $arguments = delegator_page_get_named_arguments($path); // Load stored object from cache. if (!isset($arguments[$keyword])) { diff --git a/delegator/plugins/tasks/page.inc b/delegator/plugins/tasks/page.inc index 84630943c3856f7d5425b3f3227e229eb645fcd9..f86658b27820f4a22bb0f9d28bd45fe474daf3a8 100644 --- a/delegator/plugins/tasks/page.inc +++ b/delegator/plugins/tasks/page.inc @@ -7,6 +7,9 @@ * * This creates subtasks and stores them in the delegator_pages table. These * are exportable objects, too. + * + * The render callback for this task type has $handler, $page, $contexts as + * parameters. */ /** @@ -19,8 +22,13 @@ function delegator_page_delegator_tasks() { 'title' => t('User pages'), 'description' => t('Administrator created pages that have a URL path, access control and entries in the Drupal menu system.'), 'subtasks' => TRUE, + 'subtask callback' => 'delegator_page_subtask', 'subtasks callback' => 'delegator_page_subtasks', - 'hook menu' => 'delegator_page_menu', + 'hook menu' => array( + 'file' => 'page.admin.inc', + 'path' => drupal_get_path('module', 'delegator') . '/plugins/tasks', + 'function' => 'delegator_page_menu', + ), 'hook theme' => 'delegator_page_theme', // page only items @@ -35,6 +43,11 @@ function delegator_page_delegator_tasks() { 'href' => 'admin/build/pages/add', ), ), + + // context only items + 'type' => 'context', + 'get arguments' => 'delegator_page_get_argument_callback', + 'get context placeholders' => 'delegator_page_get_contexts', ), ); } @@ -43,131 +56,92 @@ function delegator_page_delegator_tasks() { * Return a list of all subtasks. */ function delegator_page_subtasks($task) { - $subtasks = delegator_page_load_all(); + $pages = delegator_page_load_all(); $return = array(); - foreach ($subtasks as $name => $subtask) { - $form_info = delegator_page_edit_form_info(); - $edit_links = array(); - foreach ($form_info['order'] as $form_id => $form_title) { - $edit_links[] = array( - 'title' => $form_title, - 'href' => "admin/build/pages/edit/$name/$form_id", - ); - } - - $operations = array(); - - if (empty($subtask->disabled)) { - $operations[] = array( - 'title' => '<span class="text">' . t('Edit page') . '</span>' . theme('links', $edit_links), - 'html' => TRUE, - ); - $operations[] = array( - 'title' => t('Clone'), - 'href' => "admin/build/pages/clone/$name", - ); - $operations[] = array( - 'title' => t('Export'), - 'href' => "admin/build/pages/export/$name", - ); - if ($subtask->export_type == (EXPORT_IN_CODE | EXPORT_IN_DATABASE)) { - $operations[] = array( - 'title' => t('Revert'), - 'href' => "admin/build/pages/delete/$name", - ); - } - else if ($subtask->export_type == EXPORT_IN_CODE) { - $operations[] = array( - 'title' => t('Disable'), - 'href' => "admin/build/pages/disable/$name", - ); - } - else { - $operations[] = array( - 'title' => t('Delete'), - 'href' => "admin/build/pages/delete/$name", - ); - } - } - else { - $operations[] = array( - 'title' => t('Enable'), - 'href' => "admin/build/pages/enable/$name", - ); - } - $return[$name] = array( - 'name' => $name, - 'admin title' => $subtask->admin_title, - 'admin description' => t('TODO'), - 'admin path' => $subtask->path, - 'subtask' => $subtask, - 'operations' => $operations, - ); + foreach ($pages as $name => $page) { + $return[$name] = delegator_page_build_subtask($task, $page); } return $return; } /** - * Delegated implementation of hook_menu(). + * Callback to return a single subtask. */ -function delegator_page_menu(&$items, $task) { - // Set up access permissions. - $access_callback = isset($task['admin access callback']) ? $task['admin access callback'] : 'user_access'; - $access_arguments = isset($task['admin access arguments']) ? $task['admin access arguments'] : array('administer delegator'); - - $base = array( - 'access callback' => $access_callback, - 'access arguments' => $access_arguments, - 'file' => 'plugins/tasks/page.admin.inc', - ); - - $items['admin/build/pages/add'] = array( - 'title' => 'Add page', - 'description' => 'Add a delegator page subtask.', - 'page callback' => 'delegator_page_add_subtask', - 'type' => MENU_LOCAL_TASK, - ) + $base; +function delegator_page_subtask($task, $subtask_id) { + $page = delegator_page_load($subtask_id); + if ($page) { + return delegator_page_build_subtask($task, $page); + } +} +/** + * Build a subtask array for a given page. + */ +function delegator_page_build_subtask($task, $page) { $form_info = delegator_page_edit_form_info(); - $default_task = FALSE; - $weight = 0; + $edit_links = array(); + $name = $page->name; + foreach ($form_info['order'] as $form_id => $form_title) { - // The first edit form is the default for tabs, so it gets a bit - // of special treatment here. - if (!$default_task) { - $default_task = TRUE; - // Add the callback for the default tab. - $items["admin/build/pages/edit/%"] = array( - 'title' => t('Edit'), - 'page callback' => 'delegator_page_edit_subtask', - 'page arguments' => array(4, $form_id), - ) + $base; - - // And make sure it's the default local task. - $type = MENU_DEFAULT_LOCAL_TASK; + $edit_links[] = array( + 'title' => $form_title, + 'href' => "admin/build/pages/edit/$name/$form_id", + ); + } + + $operations = array(); + + if (empty($page->disabled)) { + $operations[] = array( + 'title' => t('Task handlers'), + 'href' => "admin/build/delegator/" . delegator_make_task_name($task['name'], $name), + ); + $operations[] = array( + 'title' => '<span class="text">' . t('Edit page') . '</span>' . theme('links', $edit_links), + 'html' => TRUE, + ); + $operations[] = array( + 'title' => t('Clone'), + 'href' => "admin/build/pages/clone/$name", + ); + $operations[] = array( + 'title' => t('Export'), + 'href' => "admin/build/pages/export/$name", + ); + if ($page->export_type == (EXPORT_IN_CODE | EXPORT_IN_DATABASE)) { + $operations[] = array( + 'title' => t('Revert'), + 'href' => "admin/build/pages/delete/$name", + ); + } + else if ($page->export_type == EXPORT_IN_CODE) { + $operations[] = array( + 'title' => t('Disable'), + 'href' => "admin/build/pages/disable/$name", + ); } else { - // This allows an empty form title to provide a hidden form - // which is useful for doing more branch-like multi-step - // functionality. - $type = $form_title ? MENU_LOCAL_TASK : MENU_CALLBACK; + $operations[] = array( + 'title' => t('Delete'), + 'href' => "admin/build/pages/delete/$name", + ); } - - // Handler to edit delegator task handlers. May exist in its own UI. - $items["admin/build/pages/edit/%/$form_id"] = array( - 'title' => $form_title, - 'page callback' => 'delegator_page_edit_subtask', - 'page arguments' => array(4, 5), - 'type' => $type, - 'weight' => $weight++, - ) + $base; } - - // AJAX callbacks for argument modal. - $items['admin/build/delegator/argument'] = array( - 'page callback' => 'delegator_page_subtask_argument_ajax', - ) + $base; + else { + $operations[] = array( + 'title' => t('Enable'), + 'href' => "admin/build/pages/enable/$name", + ); + } + return array( + 'name' => $name, + 'admin title' => $page->admin_title, + 'admin description' => t('TODO'), + 'admin path' => $page->path, + 'subtask' => $page, + 'operations' => $operations, + ); } /** @@ -221,6 +195,139 @@ function delegator_page_edit_form_info() { ); } +// -------------------------------------------------------------------------- +// Page execution functions + +/** + * Load a context from an argument for a given page task. + * + * @param $value + * The incoming argument value. + * @param $subtask + * The subtask id. + * @param $argument + * The numeric position of the argument in the path, counting from 0. + * + * @return + * A context item if one is configured, the argument if one is not, or + * FALSE if restricted or invalid. + */ +function _dp_arg_load($value, $subtask, $argument) { + $page = delegator_page_load($subtask); + if (!$page) { + return FALSE; + } + + $path = explode('/', $page->path); + if (empty($path[$argument])) { + return FALSE; + } + + $keyword = substr($path[$argument], 1); + if (empty($page->arguments[$keyword])) { + return $value; + } + + $page->arguments[$keyword]['keyword'] = $keyword; + + ctools_include('context'); + $context = ctools_context_get_context_from_argument($page->arguments[$keyword], $value); + + // convert false equivalents to false. + return $context ? $context : FALSE; +} + +/** + * Execute a page task. + * + * This is the callback to entries in the Drupal menu system created by the + * page task. + * + * @param $subtask_id + * The name of the page task used. + * @param ... + * A number of context objects as specified by the user when + * creating named arguments in the path. + */ +function delegator_page_execute($subtask_id) { + // Turn the contexts into a properly keyed array. + $contexts = array(); + foreach (func_get_args() as $arg) { + if (is_object($arg) && get_class($arg) == 'ctools_context') { + $contexts[$arg->id] = $arg; + } + } + + $task = delegator_get_task('page'); + $handlers = delegator_load_sorted_handlers($task, $subtask_id); + + $page = delegator_page_load($subtask_id); + + // Try each handler. + foreach ($handlers as $handler) { + if ($function = ctools_plugin_load_function('delegator', 'task_handlers', $handler->handler, 'render')) { + $output = $function($handler, $page, $contexts); + if ($output) { + // TRUE is a special return used to let us know that it handled the + // task but does not wish us to render anything, as it already did. + // This is needed for the 'no blocks' functionality. + if ($output === TRUE) { + return; + } + return $output; + } + } + } + + return drupal_access_denied(); +} + +// -------------------------------------------------------------------------- +// Context type callbacks + +/** + * Return a list of arguments used by this task. + */ +function delegator_page_get_argument_callback($task, $subtask_id) { + $page = delegator_page_load($subtask_id); + + if ($page) { + return delegator_page_get_arguments($page); + } +} + +/** + * Get a group of context placeholders for the arguments. + */ +function delegator_page_get_contexts($task, $subtask_id) { + $page = delegator_page_load($subtask_id); + + if ($page && $arguments = delegator_page_get_arguments($page)) { + ctools_include('context'); + return ctools_context_get_placeholders_from_argument($arguments); + } +} + +/** + * Return a list of arguments used by this page. + * + * This provides a list of arguments suitable for using in the context + * system, which is slightly more data than we store in the database. + * This also filters out arguments that have no contexts. + */ +function delegator_page_get_arguments($page) { + if ($page->arguments) { + $arguments = array(); + foreach ($page->arguments as $keyword => $argument) { + if (isset($argument['name'])) { + $argument['keyword'] = $keyword; + $arguments[$keyword] = $argument; + } + } + return $arguments; + } +} + // -------------------------------------------------------------------------- // Page task database info. diff --git a/help/context-arguments.html b/help/context-arguments.html index fca3a01744432e750d65904b9b66f4a249c397af..f59b4a644df267f3b5653d47c0e56607c60acfc6 100644 --- a/help/context-arguments.html +++ b/help/context-arguments.html @@ -13,3 +13,8 @@ string as though it came from a URL element. 'settings form' => params: $form, $form_state, $conf -- gets the whole form. Should put anything it wants to keep automatically in $form['settings'] 'settings form validate' => params: $form, $form_state 'settings form submit' => params: $form, $form_state + + 'criteria form' => params: $form, &$form_state, $conf, $argument, $id -- gets the whole argument. It should only put form widgets in $form[$id]. $conf may not be properly initialized so always guard against this due to arguments being changed and handlers not being updated to match. + + submit + validate + + 'criteria select' => returns true if the selected criteria matches the context. params: $context, $conf diff --git a/includes/context.inc b/includes/context.inc index 23d6a1e44c74328c146af2f942c2b41232c38139..0a3bfcda3023a29022bed24f2deb3887fcbfb156 100644 --- a/includes/context.inc +++ b/includes/context.inc @@ -480,6 +480,8 @@ function ctools_context_get_context_from_argument($argument, $arg, $empty = FALS $context->identifier = $argument['identifier']; $context->page_title = isset($argument['title']) ? $argument['title'] : ''; $context->keyword = $argument['keyword']; + $context->id = ctools_context_id($argument, 'argument'); + $context->original_argument = $arg; } return $context; } diff --git a/includes/dropdown.theme.inc b/includes/dropdown.theme.inc index c52f6b7c90a495ddcb284575fed35065269669c8..54b42b58bdb5bad0d2db5367da20149a6135f37b 100644 --- a/includes/dropdown.theme.inc +++ b/includes/dropdown.theme.inc @@ -3,7 +3,34 @@ /** * @file - * Theme registry for dropdown-div tool. + * Provide a javascript based dropdown menu. + * + * The dropdown menu will show up as a clickable link; when clicked, + * a small menu will slide down beneath it, showing the list of links. + * + * The dropdown will stay open until either the user has moved the mouse + * away from the box for > .5 seconds, or can be immediately closed by + * clicking the link again. The code is smart enough that if the mouse + * moves away and then back within the .5 second window, it will not + * re-close. + * + * Multiple dropdowns can be placed per page. + * + * If the user does not have javascript enabled, the link will not appear, + * and instead by default the list of links will appear as a normal inline + * list. + * + * The menu is heavily styled by default, and to make it look different + * will require a little bit of CSS. You can apply your own class to the + * dropdown to help ensure that your CSS can override the dropdown's CSS. + * + * In particular, the text, link, background and border colors may need to + * be changed. Please see dropdown.css for information about the existing + * styling. + */ + +/** + * Delegated implementation of hook_theme() */ function ctools_dropdown_theme(&$items) { $items['ctools_dropdown'] = array( @@ -14,6 +41,16 @@ function ctools_dropdown_theme(&$items) { /** * Create a dropdown menu. + * + * @param $title + * The text to place in the clickable area to activate the dropdown. + * @param $links + * A list of links to provide within the dropdown, suitable for use + * in via Drupal's theme('links'). + * @param $class + * An optional class to add to the dropdown's container div to allow you + * to style a single dropdown however you like without interfering with + * other dropdowns. */ function theme_ctools_dropdown($title, $links, $class = '') { // Provide a unique identifier for every dropdown on the page. diff --git a/includes/plugins.inc b/includes/plugins.inc index 4c910603e379483732b8ab9aca11e921b389a582..61235643f0cd21f71ecb8979ffeaef9cf64d0f07 100644 --- a/includes/plugins.inc +++ b/includes/plugins.inc @@ -258,7 +258,6 @@ function ctools_plugin_get_function($plugin, $function_name) { // If cached the .inc file may not have been loaded. require_once is quite safe // and fast so it's okay to keep calling it. if (isset($plugin['file'])) { - if (!is_array($plugin)) { vpr_trace(); } require_once './' . $plugin['path'] . '/' . $plugin['file']; } diff --git a/js/collapsible-div.js b/js/collapsible-div.js index a51035c1992b0c7af17a221e7d2b450606920f6b..36b54f2ba803e3c9585c8b48e8436b63efaa302b 100644 --- a/js/collapsible-div.js +++ b/js/collapsible-div.js @@ -1,11 +1,8 @@ // $Id$ - -// All CTools tools begin with this: -if (!Drupal.CTools) { - Drupal.CTools = {}; -} - /** + * @file + * Javascript required for a simple collapsible div. + * * Creating a collapsible div with this doesn't take too much. There are * three classes necessary: * @@ -21,6 +18,12 @@ if (!Drupal.CTools) { * a class, which will cause the container to draw collapsed. */ +// All CTools tools begin with this if they need to use the CTools namespace. +if (!Drupal.CTools) { + Drupal.CTools = {}; +} + + // Set up an array for callbacks. Drupal.CTools.CollapsibleCallbacks = []; Drupal.CTools.CollapsibleCallbacksAfterToggle = []; diff --git a/js/dependent.js b/js/dependent.js index 5f2be0020007b861567428e47aab90ccc8a02b89..8c800c4f377e7131d756f5a6aa58de7c66b2ba59 100644 --- a/js/dependent.js +++ b/js/dependent.js @@ -1,6 +1,6 @@ // $Id$ /** - * @file dependent.js + * @file * * Written by dmitrig01 (Dmitri Gaskin) for CTools; this provides dependent * visibility for form items in CTools' ajax forms. diff --git a/js/dropdown.js b/js/dropdown.js index 67fcd6ae6722dabb57e68e2fae008d8f0708be81..17a514996115ecbc2e5c6bc600bdc669196b8b0e 100644 --- a/js/dropdown.js +++ b/js/dropdown.js @@ -1,5 +1,26 @@ // $Id$ - +/** + * @file + * Implement a simple, clickable dropdown menu. + * + * See dropdown.theme.inc for primary documentation. + * + * The javascript relies on four classes: + * - The dropdown must be fully contained in a div with the class + * ctools-dropdown. It must also contain the class ctools-dropdown-no-js + * which will be immediately removed by the javascript; this allows for + * graceful degradation. + * - The trigger that opens the dropdown must be an a tag wit hthe class + * ctools-dropdown-link. The href should just be '#' as this will never + * be allowed to complete. + * - The part of the dropdown that will appear when the link is clicked must + * be a div with class ctools-dropdown-container. + * - Finally, ctools-dropdown-hover will be placed on any link that is being + * hovered over, so that the browser can restyle the links. + * + * This tool isn't meant to replace click-tips or anything, it is specifically + * meant to work well presenting menus. + */ Drupal.behaviors.CToolsDropdown = function() { $('div.ctools-dropdown:not(.ctools-dropdown-processed)') .removeClass('ctools-dropdown-no-js') diff --git a/plugins/arguments/nid.inc b/plugins/arguments/nid.inc index 2dc9009b2934f9eb4c23f3aa91cefdbced9da44c..7fc115037fe017b5ecf40c5a037440f13485b790 100644 --- a/plugins/arguments/nid.inc +++ b/plugins/arguments/nid.inc @@ -15,7 +15,9 @@ function ctools_nid_ctools_arguments() { 'title' => t("Node ID"), 'keyword' => 'node', 'description' => t('Creates a node context from a node ID argument.'), - 'context' => 'ctools_nid_context', + 'context' => 'ctools_argument_nid_context', + 'criteria form' => 'ctools_argument_nid_criteria_form', + 'criteria select' => 'ctools_argument_nid_criteria_select', ); return $args; } @@ -23,19 +25,65 @@ function ctools_nid_ctools_arguments() { /** * Discover if this argument gives us the node we crave. */ -function ctools_nid_context($node = NULL, $conf = NULL, $empty = FALSE) { +function ctools_argument_nid_context($arg = NULL, $conf = NULL, $empty = FALSE) { // If unset it wants a generic, unfilled context. if ($empty) { return ctools_context_create_empty('node'); } - if (!is_object($node)) { - return NULL; + if (!is_numeric($arg)) { + return FALSE; } - if (array_filter($conf['types']) && empty($conf['types'][$node->type])) { - return NULL; + $node = node_load($arg); + if (!$node) { + return FALSE; } return ctools_context_create('node', $node); } + +/** + * Provide a criteria form for selecting a node. + */ +function ctools_argument_nid_criteria_form(&$form, &$form_state, $conf, $argument, $id) { + // Ensure $conf has valid defaults: + if (!is_array($conf)) { + $conf = array(); + } + + $conf += array( + 'type' => array(), + ); + + $types = node_get_types(); + foreach ($types as $type => $info) { + $options[$type] = check_plain($info->name); + } + + $form[$id]['type'] = array( + '#title' => t('Select types for %identifier', array('%identifier' => $argument['identifier'])), + '#type' => 'checkboxes', + '#options' => $options, + '#description' => t('This item will only be selected for nodes having the selected node types. If no node types are selected, it will be selected for all node types.'), + '#default_value' => $conf['type'], + ); +} + + +/** + * Provide a criteria form for selecting a node. + */ +function ctools_argument_nid_criteria_select($conf, $context) { + // As far as I know there should always be a context at this point, but this + // is safe. + if (empty($context) || empty($context->data) || empty($context->data->type)) { + return FALSE; + } + + if (array_filter($conf['type']) && empty($conf['type'][$context->data->type])) { + return FALSE; + } + + return TRUE; +} diff --git a/plugins/arguments/uid.inc b/plugins/arguments/uid.inc index 8e76d75e9cafb89cc9dad2d9b1bfd16355a0762b..c310b04a42e11f2449494c772f7870b19b5443ef 100644 --- a/plugins/arguments/uid.inc +++ b/plugins/arguments/uid.inc @@ -16,7 +16,9 @@ function ctools_uid_ctools_arguments() { // keyword to use for %substitution 'keyword' => 'user', 'description' => t('Creates a user context from a user ID argument.'), - 'context' => 'ctools_uid_context', + 'context' => 'ctools_argument_uid_context', + 'criteria form' => 'ctools_argument_uid_criteria_form', + 'criteria select' => 'ctools_argument_uid_criteria_select', ); return $args; } @@ -24,7 +26,7 @@ function ctools_uid_ctools_arguments() { /** * Discover if this argument gives us the user we crave. */ -function ctools_uid_context($arg = NULL, $conf = NULL, $empty = FALSE) { +function ctools_argument_uid_context($arg = NULL, $conf = NULL, $empty = FALSE) { // If unset it wants a generic, unfilled context. if ($empty) { return ctools_context_create_empty('user'); @@ -41,3 +43,47 @@ function ctools_uid_context($arg = NULL, $conf = NULL, $empty = FALSE) { return ctools_context_create('user', $account); } + +/** + * Provide a criteria form for selecting a node. + */ +function ctools_argument_uid_criteria_form(&$form, &$form_state, $conf, $argument, $id) { + // Ensure $conf has valid defaults: + if (!is_array($conf)) { + $conf = array(); + } + + $conf += array( + 'rids' => array(), + ); + + + $form[$id]['rids'] = array( + '#type' => 'checkboxes', + '#title' => t('Select roles for %identifier', array('%identifier' => $argument['identifier'])), + '#default_value' => $conf['rids'], + '#options' => ctools_get_roles(), + '#description' => t('This item will only be selected for users having the selected roles. If no roles are selected, it will be selected for all users.'), + ); +} + +/** + * Provide a criteria form for selecting a node. + */ +function ctools_argument_uid_criteria_select($conf, $context) { + // As far as I know there should always be a context at this point, but this + // is safe. + if (empty($context) || empty($context->data) || !isset($context->data->roles)) { + return FALSE; + } + + $rids = array_filter($conf['rids']); + if (!$rids) { + return TRUE; + } + + $roles = array_keys($context->data->roles); + $roles[] = $context->data->uid ? DRUPAL_AUTHENTICATED_RID : DRUPAL_ANONYMOUS_RID; + + return array_intersect($rids, $roles); +}