From 25b0bc65097e6d4326cace2d0fc474f026de412a Mon Sep 17 00:00:00 2001 From: Earl Miles <merlin@logrus.com> Date: Tue, 31 Mar 2009 03:35:35 +0000 Subject: [PATCH] Page locking + improvements to locking notification on handlers. --- css/ctools.css | 12 ++++ delegator/css/task-handlers.css | 6 ++ delegator/delegator.admin.inc | 58 ++++++++++++---- delegator/delegator.install | 74 +++++++++++++++----- delegator/delegator.module | 8 +++ delegator/plugins/tasks/page.admin.inc | 96 +++++++++++++++++++++++--- delegator/plugins/tasks/page.inc | 27 ++++++-- includes/export.inc | 2 +- 8 files changed, 237 insertions(+), 46 deletions(-) create mode 100644 css/ctools.css diff --git a/css/ctools.css b/css/ctools.css new file mode 100644 index 00000000..5cf9c5b4 --- /dev/null +++ b/css/ctools.css @@ -0,0 +1,12 @@ +/* $Id$ */ +.ctools-locked { + color: red; + border: 1px solid red; + padding: 1em; +} + +.ctools-owns-lock { + background: #FFFFDD none repeat scroll 0 0; + border: 1px solid #F0C020; + padding: 1em; +} diff --git a/delegator/css/task-handlers.css b/delegator/css/task-handlers.css index 8c2af600..f90eed90 100644 --- a/delegator/css/task-handlers.css +++ b/delegator/css/task-handlers.css @@ -18,6 +18,12 @@ padding: 1em; } +.delegator-owns-lock { + background: #FFFFDD none repeat scroll 0 0; + border: 1px solid #F0C020; + padding: 1em; +} + .delegator-operations div { display: inline; } diff --git a/delegator/delegator.admin.inc b/delegator/delegator.admin.inc index b7ffe084..acf71647 100644 --- a/delegator/delegator.admin.inc +++ b/delegator/delegator.admin.inc @@ -132,6 +132,8 @@ function delegator_admin_set_task_cache($task, $subtask_id, $cache) { // Then write it. $key = delegator_make_task_name($task['name'], $subtask_id); ctools_include('object-cache'); + + $cache->changed = TRUE; $cache = ctools_object_cache_set('delegator_handlers', $key, $cache); } @@ -500,8 +502,12 @@ function delegator_admin_list_form(&$form_state) { '#submit' => array('delegator_admin_list_form_cancel'), ); - $form['#delegator-lock'] = $cache->locked; - $form['#task-name'] = $form_state['task_name']; + if (!empty($cache->locked)) { + $form['warning'] = array('#value' => theme('delegator_admin_lock', $cache->locked, $form_state['task_name'])); + } + else if (!empty($cache->changed)) { + $form['warning'] = array('#value' => theme('delegator_admin_changed')); + } // Set up a list of callbacks for our actions. This method allows // clever form_alter uses to add more actions easily. @@ -552,26 +558,47 @@ function delegator_admin_make_dropdown_links($actions) { return $links; } +/** + * Draw the "this task is locked from editing" box. + */ +function theme_delegator_admin_lock($lock, $task_name) { + $account = user_load($lock->uid); + $name = theme('username', $account); + $lock_age = format_interval(time() - $lock->updated); + $break = url('admin/build/delegator/' . $task_name . '/break-lock', array('query' => array('destination' => $_GET['q'], 'cancel' => $_GET['q']))); + + ctools_add_css('ctools'); + $output = '<div class="ctools-locked">'; + $output .= t('This task handler is being edited by user !user, and is therefore locked from editing by others. This lock is !age old. Click here to <a href="!break">break this lock</a>.', array('!user' => $name, '!age' => $lock_age, '!break' => $break)); + $output .= '</div>'; + return $output; +} + +/** + * Draw the "you have unsaved changes and this task is locked." message. + */ +function theme_delegator_admin_changed() { + ctools_add_css('ctools'); + $output = '<div class="ctools-owns-lock">'; + $output .= t('You have modified this task handler but the changes have not yet been permanently saved. It will be locked for editing by others until you save or cancel these changes.'); + $output .= '</div>'; + + return $output; +} + /** * Theme the form so it has a table. */ function theme_delegator_admin_list_form($form) { $output = ''; - if (!empty($form['#delegator-lock'])) { - $account = user_load($form['#delegator-lock']->uid); - $name = theme('username', $account); - $lock_age = format_interval(time() - $form['#delegator-lock']->updated); - $break = url('admin/build/delegator/' . $form['#task-name'] . '/break-lock'); - $output .= '<div class="delegator-locked">'; - $output .= t('This task is being edited by user !user, and is therefore locked from editing by others. This lock is !age old. Click here to <a href="!break">break this lock</a>.', array('!user' => $name, '!age' => $lock_age, '!break' => $break)); - $output .= '</div>'; + if (isset($form['warning'])) { + $output .= drupal_render($form['warning']); } - if (isset($form['description'])) { $output .= drupal_render($form['description']); } - if (isset($form['subatsk_description'])) { + if (isset($form['subtask_description'])) { $output .= drupal_render($form['subtask_description']); } @@ -1054,6 +1081,13 @@ function delegator_administer_task_handler_edit($task_name, $handler_id, $name, ctools_include('wizard'); $output = ctools_wizard_multistep_form($form_info, $step, $form_state); + if (!empty($cache->locked)) { + $output = theme('delegator_admin_lock', $cache->locked, $task_name) . $output; + } + else if (!empty($cache->changed)) { + $output = theme('delegator_admin_changed') . $output; + } + drupal_add_css(drupal_get_path('module', 'delegator') . '/css/task-handlers.css'); if (!empty($plugin['forms'][$step]['no blocks'])) { print theme('page', $output, FALSE); diff --git a/delegator/delegator.install b/delegator/delegator.install index 627d6123..6150fba3 100644 --- a/delegator/delegator.install +++ b/delegator/delegator.install @@ -29,39 +29,39 @@ function delegator_schema_1() { 'did' => array( 'type' => 'serial', 'not null' => TRUE, - 'description' => t('Primary ID field for the table. Not used for anything except internal lookups.'), + 'description' => 'Primary ID field for the table. Not used for anything except internal lookups.', 'no export' => TRUE, ), 'name' => array( 'type' => 'varchar', 'length' => '255', - 'description' => t('Unique ID for this task handler. Used to identify it programmatically.'), + 'description' => 'Unique ID for this task handler. Used to identify it programmatically.', ), 'task' => array( 'type' => 'varchar', 'length' => '64', - 'description' => t('ID of the task this handler is for.'), + 'description' => 'ID of the task this handler is for.', ), 'subtask' => array( 'type' => 'varchar', 'length' => '64', - 'description' => t('ID of the subtask this handler is for.'), + 'description' => 'ID of the subtask this handler is for.', 'not null' => TRUE, 'default' => '', ), 'handler' => array( 'type' => 'varchar', 'length' => '64', - 'description' => t('ID of the task handler being used.'), + 'description' => 'ID of the task handler being used.', ), 'weight' => array( 'type' => 'int', - 'description' => t('The order in which this handler appears. Lower numbers go first.'), + 'description' => 'The order in which this handler appears. Lower numbers go first.', ), 'conf' => array( 'type' => 'text', 'size' => 'big', - 'description' => t('Serialized configuration of the handler, if needed.'), + 'description' => 'Serialized configuration of the handler, if needed.', 'not null' => TRUE, 'serialize' => TRUE, ), @@ -73,18 +73,18 @@ function delegator_schema_1() { ); $schema['delegator_weights'] = array( - 'description' => t('Contains override weights for delegator handlers that are in code.'), + 'description' => 'Contains override weights for delegator handlers that are in code.', 'fields' => array( 'name' => array( 'type' => 'varchar', 'length' => '255', - 'description' => t('Unique ID for this task handler. Used to identify it programmatically.'), + 'description' => 'Unique ID for this task handler. Used to identify it programmatically.', 'not null' => TRUE, 'default' => '', ), 'weight' => array( 'type' => 'int', - 'description' => t('The order in which this handler appears. Lower numbers go first.'), + 'description' => 'The order in which this handler appears. Lower numbers go first.', ), ), 'primary key' => array('name'), @@ -94,7 +94,7 @@ function delegator_schema_1() { ); $schema['delegator_pages'] = array( - 'description' => t('Contains page subtasks for implementing pages with arbitrary tasks.'), + 'description' => 'Contains page subtasks for implementing pages with arbitrary tasks.', 'export' => array( 'identifier' => 'page', ), @@ -102,48 +102,60 @@ function delegator_schema_1() { 'pid' => array( 'type' => 'serial', 'not null' => TRUE, - 'description' => t('Primary ID field for the table. Not used for anything except internal lookups.'), + 'description' => 'Primary ID field for the table. Not used for anything except internal lookups.', 'no export' => TRUE, ), 'name' => array( 'type' => 'varchar', 'length' => '255', - 'description' => t('Unique ID for this subtask. Used to identify it programmatically.'), + 'description' => 'Unique ID for this subtask. Used to identify it programmatically.', + ), + 'type' => array( + 'type' => 'varchar', + 'length' => '255', + 'description' => 'What type of page this is, so that we can use the same mechanism for creating tighter UIs for targeted pages.', ), 'admin_title' => array( 'type' => 'varchar', 'length' => '255', - 'description' => t('Human readable title for this page subtask.'), + 'description' => 'Human readable title for this page subtask.', ), 'path' => array( 'type' => 'varchar', 'length' => '255', - 'description' => t('The menu path that will invoke this task.'), + 'description' => 'The menu path that will invoke this task.', ), 'access' => array( 'type' => 'text', 'size' => 'big', - 'description' => t('Access configuration for this path.'), + 'description' => 'Access configuration for this path.', 'not null' => TRUE, 'serialize' => TRUE, ), 'multiple' => array( 'type' => 'int', 'size' => 'tiny', - 'description' => t('True if the UI is set up to allow multiple handlers per page.'), + 'description' => 'True if the UI is set up to allow multiple handlers per page.', 'default' => 0, ), 'menu' => array( 'type' => 'text', 'size' => 'big', - 'description' => t('Serialized configuration of Drupal menu visibility settings for this item.'), + 'description' => 'Serialized configuration of Drupal menu visibility settings for this item.', 'not null' => TRUE, 'serialize' => TRUE, ), 'arguments' => array( 'type' => 'text', 'size' => 'big', - 'description' => t('Configuration of arguments for this menu item.'), + 'description' => 'Configuration of arguments for this menu item.', + 'not null' => TRUE, + 'serialize' => TRUE, + ), + 'conf' => array( + 'type' => 'text', + 'size' => 'big', + 'description' => 'Serialized configuration of the page, if needed.', 'not null' => TRUE, 'serialize' => TRUE, ), @@ -171,3 +183,27 @@ function delegator_uninstall() { drupal_uninstall_schema('delegator'); } +/** + * Update delegator page to include a little more data. + */ +function delegator_update_6100() { + $ret = array(); + $type = array( + 'type' => 'varchar', + 'length' => '255', + 'description' => 'What type of page this is, so that we can use the same mechanism for creating tighter UIs for targeted pages.', + 'initial' => 'custom', + ); + $conf = array( + 'type' => 'text', + 'size' => 'big', + 'description' => 'Serialized configuration of the page, if needed.', + 'not null' => TRUE, + 'serialize' => TRUE, + 'initial' => serialize(array()), + ); + db_add_field($ret, 'delegator_pages', 'type', $type); + db_add_field($ret, 'delegator_pages', 'conf', $conf); + + return $ret; +} diff --git a/delegator/delegator.module b/delegator/delegator.module index bf8e2c59..8ced9df4 100644 --- a/delegator/delegator.module +++ b/delegator/delegator.module @@ -35,6 +35,14 @@ function delegator_theme() { 'arguments' => array('form' => NULL), 'file' => 'delegator.admin.inc', ), + 'delegator_admin_lock' => array( + 'arguments' => array('lock' => array(), 'task_name' => NULL), + 'file' => 'delegator.admin.inc', + ), + 'delegator_admin_changed' => array( + 'arguments' => array(), + 'file' => 'delegator.admin.inc', + ), ); // Allow task plugins to have theme registrations by passing through: diff --git a/delegator/plugins/tasks/page.admin.inc b/delegator/plugins/tasks/page.admin.inc index af36f95c..f8591bec 100644 --- a/delegator/plugins/tasks/page.admin.inc +++ b/delegator/plugins/tasks/page.admin.inc @@ -156,13 +156,13 @@ function delegator_page_menu(&$items, $task) { $menu_path = implode('/', $path); - $items[$menu_path] = delegator_page_menu_item($subtask->menu, $access_arguments, $page_arguments, $load_arguments); + $items[$menu_path] = delegator_page_menu_item($task, $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); + $items[$parent_path] = delegator_page_menu_item($task, $subtask->menu['parent'], $access_arguments, $page_arguments, $load_arguments); } } } @@ -185,11 +185,11 @@ function delegator_page_menu(&$items, $task) { * @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) { +function delegator_page_menu_item($task, $menu, $access_arguments, $page_arguments, $load_arguments) { $item = array( 'access callback' => 'ctools_access_menu', 'access arguments' => $access_arguments, - 'page callback' => 'delegator_page_execute', + 'page callback' => isset($task['page callback']) ? $task['page callback'] : 'delegator_page_execute', 'page arguments' => $page_arguments, 'load arguments' => $load_arguments, 'file' => 'plugins/tasks/page.inc', @@ -238,6 +238,8 @@ function delegator_page_get_page_cache($name) { $cache = delegator_page_load($name); } + $cache->locked = ctools_object_cache_test('delegator_page', $name); + return $cache; } @@ -245,8 +247,13 @@ function delegator_page_get_page_cache($name) { * Store changes to a task handler in the object cache. */ function delegator_page_set_page_cache($page) { + if (!empty($page->locked)) { + return; + } + $name = isset($page->new) || !isset($page->name) ? '::new' : $page->name; ctools_include('object-cache'); + $page->changed = TRUE; $cache = ctools_object_cache_set('delegator_page', $name, $page); } @@ -282,6 +289,34 @@ function delegator_page_get_named_arguments($path) { return $arguments; } +/** + * Draw the "this task is locked from editing" box. + */ +function theme_delegator_page_lock($lock, $page_name) { + $account = user_load($lock->uid); + $name = theme('username', $account); + $lock_age = format_interval(time() - $lock->updated); + $break = url('admin/build/pages/break-lock/' . $page_name, array('query' => array('destination' => $_GET['q'], 'cancel' => $_GET['q']))); + + ctools_add_css('ctools'); + $output = '<div class="ctools-locked">'; + $output .= t('This page is being edited by user !user, and is therefore locked from editing by others. This lock is !age old. Click here to <a href="!break">break this lock</a>.', array('!user' => $name, '!age' => $lock_age, '!break' => $break)); + $output .= '</div>'; + return $output; +} + +/** + * Draw the "you have unsaved changes and this task is locked." message. + */ +function theme_delegator_page_changed() { + ctools_add_css('ctools'); + $output = '<div class="ctools-owns-lock">'; + $output .= t('You have modified this page but the changes have not yet been permanently saved. It will be locked for editing by others until you update or cancel these changes.'); + $output .= '</div>'; + + return $output; +} + /** * Page callback to add a subtask. */ @@ -334,6 +369,7 @@ function delegator_page_edit_subtask($page_name, $step = NULL) { 'path' => "admin/build/pages/edit/$page_name/%step", 'show trail' => FALSE, 'show return' => TRUE, + 'show cancel' => TRUE, 'return path' => 'admin/build/pages', ) + delegator_page_edit_form_info(); @@ -355,6 +391,13 @@ function delegator_page_edit_subtask($page_name, $step = NULL) { drupal_redirect_form(array(), $form_state['redirect']); } + if (!empty($page->locked)) { + $output = theme('delegator_page_lock', $page->locked, $page_name) . $output; + } + else if (!empty($page->changed)) { + $output = theme('delegator_page_changed') . $output; + } + return $output; } @@ -535,6 +578,11 @@ function delegator_page_form_basic_validate(&$form, &$form_state) { * Store the values from the basic settings form. */ function delegator_page_form_basic_submit(&$form, &$form_state) { + if ($form_state['page']->locked) { + drupal_set_message(t('Unable to update page due to lock.'), 'error'); + return; + } + if (!isset($form_state['page']->pid) && empty($form_state['page']->import)) { $form_state['page']->name = $form_state['values']['name']; } @@ -687,6 +735,11 @@ function delegator_page_form_menu(&$form, &$form_state) { * Submit handler for the menu form for add/edit page task. */ function delegator_page_form_menu_submit(&$form, &$form_state) { + if ($form_state['page']->locked) { + drupal_set_message(t('Unable to update page due to lock.'), 'error'); + return; + } + $form_state['page']->menu = $form_state['values']['menu']; } @@ -714,6 +767,11 @@ function delegator_page_form_access(&$form, &$form_state) { * Submit handler to deal with access control changes. */ function delegator_page_form_access_submit(&$form, &$form_state) { + if ($form_state['page']->locked) { + drupal_set_message(t('Unable to update page due to lock.'), 'error'); + return; + } + $form_state['page']->access['logic'] = $form_state['values']['logic']; } @@ -991,6 +1049,11 @@ function delegator_page_argument_form_change(&$form, &$form_state) { * Submit handler to change an argument. */ function delegator_page_argument_form_change_submit(&$form, &$form_state) { + if ($form_state['page']->locked) { + drupal_set_message(t('Unable to update page due to lock.'), 'error'); + return; + } + $page = &$form_state['page']; $keyword = &$form_state['keyword']; $argument = $form_state['values']['argument']; @@ -1100,13 +1163,17 @@ function delegator_page_argument_form_settings_validate(&$form, &$form_state) { * Submit handler for argument settings. */ function delegator_page_argument_form_settings_submit(&$form, &$form_state) { + if ($form_state['page']->locked) { + drupal_set_message(t('Unable to update page due to lock.'), 'error'); + return; + } + if ($function = ctools_plugin_get_function($form_state['plugin'], 'settings form submit')) { $function($form, $form_state); } $page = &$form_state['page']; $keyword = &$form_state['keyword']; - // Copy the form to our temporary location which will get moved again when // finished. Yes, finished is always next but finish can happen from other // locations so we funnel through that path rather than duplicate. @@ -1169,6 +1236,11 @@ function delegator_page_argument_form_multiple(&$form, &$form_state) { * Submit handler for the multiple form. */ function delegator_page_argument_form_multiple_submit(&$form, &$form_state) { + if ($form_state['page']->locked) { + drupal_set_message(t('Unable to update page due to lock.'), 'error'); + return; + } + $form_state['page']->multiple = $form_state['values']['multiple']; // The task handler will be created in the _finish hook. @@ -1196,9 +1268,6 @@ function delegator_page_break_lock_subtask(&$form_state, $name) { return array('message' => array('#value' => t('There is no lock on this page to break.'))); } - $task = delegator_get_task($page->task); - delegator_set_trail($task); - $cancel = 'admin/build/pages'; if (!empty($_REQUEST['cancel'])) { $cancel = $_REQUEST['cancel']; @@ -1446,12 +1515,16 @@ function delegator_page_disable_subtask($name) { function delegator_page_delete_subtask($form_state, $name) { // This ensures the task .inc file is loaded $task = delegator_get_task('page'); - $page = delegator_page_load($name); + $page = delegator_page_get_page_cache($name); if (!$page) { return drupal_not_found(); } + if (!empty($page->locked)) { + return array('locked' => array('#value' => theme('delegator_page_lock', $page->locked, $name))); + } + $form_state['page'] = $page; $form = array(); @@ -1479,6 +1552,11 @@ function delegator_page_delete_subtask($form_state, $name) { * Submit handler to delete a view. */ function delegator_page_delete_subtask_submit(&$form, &$form_state) { + if ($form_state['page']->locked) { + drupal_set_message(t('Unable to update page due to lock.'), 'error'); + return; + } + delegator_page_delete($form_state['page']); drupal_set_message(t('The page has been deleted.')); $form_state['redirect'] = 'admin/build/pages'; diff --git a/delegator/plugins/tasks/page.inc b/delegator/plugins/tasks/page.inc index bc237cfe..d5f82082 100644 --- a/delegator/plugins/tasks/page.inc +++ b/delegator/plugins/tasks/page.inc @@ -49,6 +49,10 @@ function delegator_page_delegator_tasks() { 'class' => 'delegator-page-storage', ), ), + 'page type' => 'custom', + // What page callback will execute this page. If it has handlers then + // delegator_page_execute should be the default. + 'page callback' => 'delegator_page_execute', // context only items 'handler type' => 'context', 'get arguments' => 'delegator_page_get_arguments', @@ -61,7 +65,7 @@ function delegator_page_delegator_tasks() { * Return a list of all subtasks. */ function delegator_page_subtasks($task) { - $pages = delegator_page_load_all(); + $pages = delegator_page_load_all('custom'); $return = array(); foreach ($pages as $name => $page) { $return[$name] = delegator_page_build_subtask($task, $page); @@ -191,11 +195,19 @@ function delegator_page_build_subtask($task, $page) { * Delegated implementation of hook_theme(). */ function delegator_page_theme(&$items, $task) { - $items['delegator_page_form_argument_table'] = array( - 'arguments' => array('form' => NULL), + $base = array( 'file' => 'page.admin.inc', 'path' => drupal_get_path('module', 'delegator') . '/plugins/tasks', ); + $items['delegator_page_form_argument_table'] = $base + array( + 'arguments' => array('form' => NULL), + ); + $items['delegator_page_lock'] = $base + array( + 'arguments' => array('lock' => array(), 'task_name' => NULL), + ); + $items['delegator_page_changed'] = $base + array( + 'arguments' => array(), + ); } /** @@ -370,9 +382,14 @@ function delegator_page_load($name) { /** * Load all page subtasks. */ -function delegator_page_load_all() { +function delegator_page_load_all($type = NULL) { ctools_include('export'); - return ctools_export_load_object('delegator_pages'); + if (empty($type)) { + return ctools_export_load_object('delegator_pages'); + } + else { + return ctools_export_load_object('delegator_pages', 'conditions', array('type' => $type)); + } } /** diff --git a/includes/export.inc b/includes/export.inc index 233b232b..8c141a99 100644 --- a/includes/export.inc +++ b/includes/export.inc @@ -40,7 +40,7 @@ define('EXPORT_IN_CODE', 0x02); * A string to notify the loader what the argument is * - all: load all items. This is the default. $args is unused. * - names: $args will be an array of specific named objects to load. - * - condition: $args will be a keyed array of conditions. The conditions + * - conditions: $args will be a keyed array of conditions. The conditions * must be in the schema for this table or errors will result. * @param $args * An array of arguments whose actual use is defined by the $type argument. -- GitLab