diff --git a/delegator/delegator.admin.inc b/delegator/delegator.admin.inc index 30c3e70911b765c89021913b905814f997a16c40..9a9f294609b830ce74af523b4247bec7a96a764f 100644 --- a/delegator/delegator.admin.inc +++ b/delegator/delegator.admin.inc @@ -23,13 +23,17 @@ define('DGA_CHANGED_DELETED', 0x04); /** * Page callback to administer a particular task. - * - * Since not all tasks actually have subtask ids, the subtask id is - * optional. However, the menu system should ensure that both of these - * arguments are filled so that additional url sections do not end up - * appearing as subtask ids. */ -function delegator_administer_task($task_id, $subtask_id = '') { +function delegator_administer_task($task_name) { + // Determine if the task id came in the form of TASK-SUBTASK or just TASK + if (strpos($task_name, '-') !== FALSE) { + list($task_id, $subtask_id) = explode('-', $task_name, 2); + } + else { + $task_id = $task_name; + $subtask_id = NULL; + } + $task = delegator_get_task($task_id); if (!$task) { return drupal_not_found(); @@ -585,7 +589,16 @@ function delegator_admin_list_form_config($form, &$form_state) { /** * Entry point to edit a task handler. */ -function delegator_administer_task_handler_edit($task_id, $handler_id, $name, $form_id) { +function delegator_administer_task_handler_edit($task_name, $handler_id, $name, $form_id) { + // Determine if the task id came in the form of TASK-SUBTASK or just TASK + if (strpos($task_name, '-') !== FALSE) { + list($task_id, $subtask_id) = explode('-', $task_name, 2); + } + else { + $task_id = $task_name; + $subtask_id = NULL; + } + $handler = delegator_admin_get_task_handler_cache($name); if (!$handler) { $handler = delegator_load_task_handler($name); @@ -608,9 +621,6 @@ function delegator_administer_task_handler_edit($task_id, $handler_id, $name, $f return drupal_not_found(); } - // @todo FIXME - $subtask_id = ''; - $title = delegator_get_handler_title($plugin, $handler, $task, $subtask_id); drupal_set_title(t('Edit task handler "@title"', array('@title' => $title))); @@ -638,20 +648,35 @@ function delegator_administer_task_handler_edit($task_id, $handler_id, $name, $f ); if (!empty($plugin['forms'][$form_id]['alternate next'])) { - $form_state['next'] = "admin/build/delegator/$task_id/$handler_id/$name/" . $plugin['forms'][$form_id]['alternate next']; + $form_state['next'] = "admin/build/delegator/$task_name/$handler_id/$name/" . $plugin['forms'][$form_id]['alternate next']; } elseif ($next_info) { - $form_state['next'] = "admin/build/delegator/$task_id/$handler_id/$name/$next_info[key]"; + $form_state['next'] = "admin/build/delegator/$task_name/$handler_id/$name/$next_info[key]"; } ctools_include('form'); - return ctools_build_form('delegator_admin_edit_task_handler', $form_state); + $output = ctools_build_form('delegator_admin_edit_task_handler', $form_state); + if ($output && !empty($plugin['forms'][$form_id]['no blocks'])) { + print theme('page', $output, FALSE); + } + else { + return $output; + } } /** * Entry point to add a task handler. */ function delegator_administer_task_handler_add($task_id, $name, $form_id) { + // Determine if the task id came in the form of TASK-SUBTASK or just TASK + if (strpos($task_name, '-') !== FALSE) { + list($task_id, $subtask_id) = explode('-', $task_name, 2); + } + else { + $task_id = $task_name; + $subtask_id = NULL; + } + $handler = delegator_admin_get_task_handler_cache($name); if (!$handler) { @@ -670,9 +695,6 @@ function delegator_administer_task_handler_add($task_id, $name, $form_id) { return drupal_not_found(); } - // @todo FIXME - $subtask_id = ''; - $title = delegator_get_handler_title($plugin, $handler, $task, $subtask_id); drupal_set_title(t('Add task handler "@title"', array('@title' => $title))); @@ -702,7 +724,7 @@ function delegator_administer_task_handler_add($task_id, $name, $form_id) { else { $class = 'delegator-next'; if (!isset($form_state['next'])) { - $form_state['next'] = "admin/build/delegator/$task_id/add/$name/$id"; + $form_state['next'] = "admin/build/delegator/$task_name/add/$name/$id"; } } $crumbs[] = '<span class="' . $class . '">' . $title . '</span>'; diff --git a/delegator/delegator.module b/delegator/delegator.module index 56600afa48dd6610ef14b64616d3ca70cfd386ce..64f3abdb80ea00fa88be4e0ca5d7ff761c675db6 100644 --- a/delegator/delegator.module +++ b/delegator/delegator.module @@ -72,6 +72,8 @@ function delegator_menu() { $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'); + // @todo -- support subtasks + if (isset($task['admin title'])) { $items['admin/build/delegator/' . $id] = array( 'title' => $task['admin title'], @@ -226,8 +228,13 @@ function _delegator_sort_task_handlers($a, $b) { /** * Write a task handler to the database. */ -function delegator_save_task_handler($handler) { +function delegator_save_task_handler(&$handler) { $update = (isset($handler->did)) ? array('did') : array(); + // Let the task handler respond to saves: + if ($function = ctools_plugin_load_function('delegator', 'task_handlers', $handler->handler, 'save')) { + $function($handler, $update); + } + drupal_write_record('delegator_handlers', $handler, $update); return $handler; } @@ -306,7 +313,7 @@ function delegator_get_handler_title($plugin, $handler, $task, $subtask_id) { } /** - * Implementation of hook_ctools_plugin_directory() to let the system know + * Implementation of hook_ctools_plugin_dierctory() to let the system know * we implement task and task_handler plugins. */ function delegator_ctools_plugin_directory($plugin) { diff --git a/delegator/help/api-task-handler.html b/delegator/help/api-task-handler.html index 0370c0d8cbee4295469a9b5aa92aba9a7d0a16a7..6904079bacb28a57ef4d7a48a2f62141fc741d58 100644 --- a/delegator/help/api-task-handler.html +++ b/delegator/help/api-task-handler.html @@ -8,6 +8,7 @@ task handler definition: params: $handler, $task, $subtask_id default conf -- either an array() of default conf data or a callback that returns an array. params: $handler, $task, $subtask_id + save -- callback to call just prior to the task handler being saved so it can adjust its data. forms => array( 'id' => array( @@ -17,6 +18,7 @@ task handler definition: 'include' => an optional file to include to get functionality for this form. Must include full path. 'no return' => hide the 'return' button, meaning that the form requires extra steps if submitted 'alternate next' => an alternate next form. Used for hidden edit forms that don't have tabs. + 'no blocks' => if TRUE, use Drupal's mechanism to not render blocks for this form. ) ) ), diff --git a/delegator/help/api-task.html b/delegator/help/api-task.html index a9b1eb3a4a220624d3c6394bee77048d97862592..50e3a81aab096ed812347cbddf64df7ced29b43b 100644 --- a/delegator/help/api-task.html +++ b/delegator/help/api-task.html @@ -9,3 +9,5 @@ task definition: type -- The type of the task, used to determine which handlers can service it. subtasks -- can be TRUE in which case it supports subtasks with the default configuration or a string (array?) with callbacks to fetch subtask data. + +task names must not contain a - as that is used to separate the task name from the subtask ID. \ No newline at end of file diff --git a/delegator/plugins/tasks/node_view.inc b/delegator/plugins/tasks/node_view.inc index f2a7ce8d7c7917c6d5dc0719f4957b57812cc221..380081e5cfd0fd4bd33e55b0f1d5d94d1283343d 100644 --- a/delegator/plugins/tasks/node_view.inc +++ b/delegator/plugins/tasks/node_view.inc @@ -50,6 +50,12 @@ function delegator_node_view($node) { if ($function = ctools_plugin_load_function('delegator', 'task_handlers', $handler->handler, 'render')) { $output = $function($handler, $node); 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; } } diff --git a/includes/css.inc b/includes/css.inc new file mode 100644 index 0000000000000000000000000000000000000000..39cceac0a7cf0a5fc87eeb9d6683b557205ca08b --- /dev/null +++ b/includes/css.inc @@ -0,0 +1,440 @@ +<?php +/* $Id$ */ +// Original author: Dmitri Gaskin +// Most of the rest of the work: Sam Boyer + +/* + * @file + * CSS filtering functions. Contains a disassembler, filter, compressor, and + * decompressor. + * + * The general usage of this tool is: + * + * To simply filter CSS: + * @code + * $filtered_css = ctools_css_filter($css, TRUE); + * @endcode + * + * In the above, if the second argument is TRUE, the returned CSS will + * be compressed. Otherwise it will be returned in a well formatted + * syntax. + * + * To cache unfiltered CSS in a file, which will be filtered: + * + * @code + * $filename = ctools_css_cache($css, TRUE); + * @endcode + * + * In the above, if the second argument is FALSE, the CSS will not be filtered. + * + * This file will be cached within the Drupal files system. This system cannot + * detect when this file changes, so it is YOUR responsibility to remove and + * re-cache this file when the CSS is changed. Your system should also contain + * a backup method of re-generating the CSS cache in case it is removed, so + * that it is easy to force a re-cache by simply deleting the contents of the + * directory. + */ + +/** + * Write a chunk of CSS to a temporary cache file and return the file name. + * + * This function optionally filters the CSS (always compressed, if so) and + * generates a unique filename based upon md5. It returns that filename that + * can be used with drupal_add_css. Note that as a cache file, technically + * this file is volatile so it should be checked before it is used to ensure + * that it exists. + * + * You can use file_exists() to test for the file and file_delete() to remove + * it if it needs to be cleared. + * + * @param $css + * A chunk of well-formed CSS text to cache. + * @param $filter + * If TRUE the css will be filtered. If FALSE the text will be cached + * as-is. + */ +function ctools_css_cache($css, $filter = TRUE) { + if ($filter) { + $css = ctools_css_filter($css); + } + + // Create the css/ within the files folder. + $path = file_create_path('ctools/css'); + if (!$path) { + $path = file_directory_path() . '/ctools'; + file_check_directory($path, FILE_CREATE_DIRECTORY); + $path .= '/css'; + file_check_directory($path, FILE_CREATE_DIRECTORY); + } + + // @todo Is this slow? DOes it matter if it is? + $filename = $path . '/' . md5($css) . '.css'; + + // This will do renames if the file already exists, ensuring we don't + // accidentally overwrite other files who share the same md5. Yes this + // is a very miniscule chance but it's safe. + $filename = file_save_data($css, $filename); + + return $filename; +} + +/** + * Filter a chunk of CSS text. + * + * This function disassembles the CSS into a raw format that makes it easier + * for our tool to work, then runs it through the filter and reassembles it. + * If you find that you want the raw data for some reason or another, you + * can use the disassemble/assemble functions yourself. + * + * @param $css + * The CSS text to filter. + * @param $compressed + * If true, generate compressed output; if false, generate pretty output. + * Defaults to TRUE. + */ +function ctools_css_filter($css, $compressed = TRUE) { + $css_data = ctools_css_disassemble($css); + + // Note: By using this function yourself you can control the allowed + // properties and values list. + $filtered = ctools_css_filter_css_data($css_data); + + return $compressed ? ctools_css_compress($filtered) : ctools_css_assemble($filtered); +} + +/** + * Re-assemble a css string and format it nicely. + * + * @param array $css_data + * An array of css data, as produced by @see ctools_css_disassemble() + * disassembler and the @see ctools_css_filter_css_data() filter. + * @return string $css + * css optimized for human viewing. + */ +function ctools_css_assemble($css_data) { + // Initialize the output. + $css = ''; + // Iterate through all the statements. + foreach ($css_data as $selector_str => $declaration) { + // Add the selectors, separating them with commas and line feeds. + $css .= strpos($selector_str, ',') === FALSE ? $selector_str : preg_replace(", ", ",\n", $selector_str); + // Add the opening curly brace. + $css .= " {\n"; + // Iterate through all the declarations. + foreach ($declaration as $property => $value) { + $css .= " " . $property . ": " . $value . ";\n"; + } + // Add the closing curly brace. + $css .= "}\n\n"; + } + // Return the output. + return $css; +} + +/** + * Compress css data (filter it first!) to optimize for use on view. + * + * @param array $css_data + * An array of css data, as produced by @see ctools_css_disassemble() + * disassembler and the @see ctools_css_filter_css_data() filter. + * @return string $css + * css optimized for use. + */ +function ctools_css_compress($css_data) { + // Initialize the output. + $css = ''; + // Iterate through all the statements. + foreach ($css_data as $selector_str => $declaration) { + if (empty($declaration)) { + // Skip this statement if filtering removed all parts of the declaration. + continue; + } + // Add the selectors, separating them with commas. + $css .= $selector_str; + // And, the opening curly brace. + $css .= "{"; + // Iterate through all the statement properties. + foreach ($declaration as $property => $value) { + $css .= $property . ':' . $value . ';'; + } + // Add the closing curly brace. + $css .= "}"; + } + // Return the output. + return $css; +} + +/** + * Disassemble the css string. + * + * Strip the css of irrelevant characters, invalid/malformed selectors and + * declarations, and otherwise prepare it for processing. + * + * @param string $css + * A string containing the css to be disassembled. + * @return array $disassembled_css + * An array of disassembled, slightly cleaned-up/formatted css statements. + */ +function ctools_css_disassemble($css) { + $disassembled_css = array(); + // Remove comments. + $css = preg_replace("/\/\*(.*)?\*\//Usi", "", $css); + // Split out each statement + $statements = explode("}", $css); + // If we have any statements, parse them. + if (!empty($statements)) { + // Iterate through all of the statements. + foreach ($statements as $statement) { + // Get the selector(s) and declaration. + if (empty($statement) || !strpos($statement, '{')) { + continue; + } + + list($selector_str, $declaration) = explode('{', $statement); + + // If the selector exists, then disassemble it, check it, and regenerate + // the selector string. + $selector_str = empty($selector_str) ? FALSE : _ctools_css_disassemble_selector($selector_str); + if (empty($selector_str)) { + // No valid selectors. Bomb out and start the next item. + continue; + } + + // Disassemble the declaration, check it and tuck it into an array. + $disassembled_css[$selector_str] = _ctools_css_disassemble_declaration(strtolower($declaration)); + } + } + return $disassembled_css; +} + +function _ctools_css_disassemble_selector($selector_str) { + // Get all selectors individually. + $selectors = explode(",", trim($selector_str)); + // Iterate through all the selectors, sanity check them and return if they + // pass. Note that this handles 0, 1, or more valid selectors gracefully. + foreach ($selectors as $key => $selector) { + // Replace un-needed characters and do a little cleanup. + $selector = preg_replace("/[\n|\t|\\|\s]+/", ' ', strtolower(trim($selector))); + // Make sure this is still a real selector after cleanup. + if (!empty($selector)) { + $selectors[$key] = $selector; + } + else { + // Selector is no good, so we scrap it. + unset ($selectors[$key]); + } + } + // Check for malformed selectors; if found, we skip this declaration. + if (empty($selectors)) { + return FALSE; + } + return implode(', ', $selectors); +} + + +function _ctools_css_disassemble_declaration($declaration) { + $formatted_statement = array(); + $propval_pairs = explode(";", $declaration); + // Make sure we actually have some properties to work with. + if (!empty($propval_pairs)) { + // Iterate through the remains and parse them. + foreach ($propval_pairs as $key => $propval_pair) { + // Check that we have a ':', otherwise it's an invalid pair. + if (strpos($propval_pair, ':') === FALSE) { + continue; + } + // Clean up the current property-value pair. + $propval_pair = preg_replace("/[\n|\t|\\|\s]+/", ' ', strtolower(trim($propval_pair))); + // Explode the remaining fragements some more, but clean them up first. + list($property, $value) = explode(':', $propval_pair); + // If the property survived, toss it onto the stack. + if(!empty($property)) { + $formatted_statement[trim($property)] = trim($value); + } + } + } + return $formatted_statement; +} + +/** + * Run disassembled $css through the filter. + * + * @param $css + * CSS code disassembled by ctools_dss_disassemble(). + * @param $allowed_properties + * A list of properties that are allowed by the filter. If empty + * ctools_css_filter_default_allowed_properties() will provide the + * list. + * @param $allowed_values + * A list of values that are allowed by the filter. If empty + * ctools_css_filter_default_allowed_values() will provide the + * list. + * + * @return + * An array of disassembled, filtered CSS. + */ +function ctools_css_filter_css_data($css, $allowed_properties = array(), $allowed_values = array(), $allowed_values_regex = '', $disallowed_values_regex = '') { +// function ctools_css_filter_css_data($css, &$filtered = NULL, $allowed_properties = array(), $allowed_values = array(), $allowed_values_regex = '', $disallowed_values_regex = '') { + // Retrieve the default list of allowed properties if none is provided. + $allowed_properties = !empty($allowed_properties) ? $allowed_properties : ctools_css_filter_default_allowed_properties(); + // Retrieve the default list of allowed values if none is provided. + $allowed_values = !empty($allowed_values) ? $allowed_values : ctools_css_filter_default_allowed_values(); + // Define allowed values regex if none is provided. + $allowed_values_regex = !empty($allowed_values_regex) ? $allowed_values_regex : '/(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)/'; + // Define disallowed url() value contents, if none is provided. + // $disallowed_values_regex = !empty($disallowed_values_regex) ? $disallowed_values_regex : '/[url|expression]\s*\(\s*[^\s)]+?\s*\)\s*/'; + $disallowed_values_regex = !empty($disallowed_values_regex) ? $disallowed_values_regex : '/(url|expression)/'; + + foreach ($css as $selector_str => $declaration) { + foreach ($declaration as $property => $value) { + if (!in_array($property, $allowed_properties)) { + // $filtered['properties'][$selector_str][$property] = $value; + unset($css[$selector_str][$property]); + continue; + } + $value = str_replace('!important', '', $value); + if (preg_match($disallowed_values_regex, $value) || !(in_array($value, $allowed_values) || preg_match($allowed_values_regex, $value))) { + // $filtered['values'][$selector_str][$property] = $value; + unset($css[$selector_str][$property]); + continue; + } + } + } + return $css; +} + +/** + * Provide a deafult list of allowed properties by the filter. + */ +function ctools_css_filter_default_allowed_properties() { + return array( + 'azimuth', + 'background', + 'background-color', + 'background-image', + 'background-repeat', + 'background-attachment', + 'background-position', + 'border', + 'border-top-width', + 'border-right-width', + 'border-bottom-width', + 'border-left-width', + 'border-width', + 'border-top-color', + 'border-right-color', + 'border-bottom-color', + 'border-left-color', + 'border-color', + 'border-top-style', + 'border-right-style', + 'border-bottom-style', + 'border-left-style', + 'border-style', + 'border-top', + 'border-right', + 'border-bottom', + 'border-left', + 'clear', + 'color', + 'cursor', + 'direction', + 'display', + 'elevation', + 'float', + 'font', + 'font-family', + 'font-size', + 'font-style', + 'font-variant', + 'font-weight', + 'height', + 'letter-spacing', + 'line-height', + 'margin', + 'margin-top', + 'margin-right', + 'margin-bottom', + 'margin-left', + 'overflow', + 'padding', + 'padding-top', + 'padding-right', + 'padding-bottom', + 'padding-left', + 'pause', + 'pause-after', + 'pause-before', + 'pitch', + 'pitch-range', + 'richness', + 'speak', + 'speak-header', + 'speak-numeral', + 'speak-punctuation', + 'speech-rate', + 'stress', + 'text-align', + 'text-decoration', + 'text-indent', + 'unicode-bidi', + 'vertical-align', + 'voice-family', + 'volume', + 'white-space', + 'width', + 'fill', + 'fill-opacity', + 'fill-rule', + 'stroke', + 'stroke-width', + 'stroke-linecap', + 'stroke-linejoin', + 'stroke-opacity', + ); +} + +/** + * Provide a default list of allowed values by the filter. + */ +function ctools_css_filter_default_allowed_values() { + return array( + 'auto', + 'aqua', + 'black', + 'block', + 'blue', + 'bold', + 'both', + 'bottom', + 'brown', + 'center', + 'collapse', + 'dashed', + 'dotted', + 'fuchsia', + 'gray', + 'green', + 'italic', + 'left', + 'lime', + 'maroon', + 'medium', + 'navy', + 'normal', + 'nowrap', + 'olive', + 'pointer', + 'purple', + 'red', + 'right', + 'solid', + 'silver', + 'teal', + 'top', + 'transparent', + 'underline', + 'white', + 'yellow', + ); +} diff --git a/includes/form.inc b/includes/form.inc index 479999a6f58672361faac8e322bd7f2280ccf5cf..48be9f141d1650b00e472c19fac3cbf26167e4d2 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -3,8 +3,15 @@ /** * @file - * ctools' replacements for Drupal's form functions. + * CTools' replacements for Drupal's form functions. * + * Primarily, ctools_build_form is meant to be a replacement for drupal_get_form(). + * + * Instead of sending arguments through to the form builder, a form state array + * is passed through. This form_state can contain any arguments needed, and it can + * also be used to return data to the calling function. + * + * This can allow cleaner separation of the form from the storage mechanism. */ /**