diff --git a/css/modal.css b/css/modal.css index 932fa4208f39b7d94ad18e3aa45ce5f68a7a5ddf..40a752589ac40a241540576d26830748fdb225ee 100644 --- a/css/modal.css +++ b/css/modal.css @@ -89,3 +89,9 @@ div.ctools-modal-content .modal-form fieldset, div.ctools-modal-content .modal-form .form-checkboxes { clear: left; } + +div.ctools-modal-content .resizable-textarea { + width: 90%; + margin-left: 8em; +} + diff --git a/delegator/plugins/tasks/page.admin.inc b/delegator/plugins/tasks/page.admin.inc index e355cd74c94071c58ffa70888c2129954e9e1be6..f60f0b8ef38bb814a8b5d15fe7b5a29270fd8a98 100644 --- a/delegator/plugins/tasks/page.admin.inc +++ b/delegator/plugins/tasks/page.admin.inc @@ -190,7 +190,7 @@ function delegator_page_menu_item($task, $menu, $access_arguments, $page_argumen $item = array( 'access callback' => 'ctools_access_menu', 'access arguments' => $access_arguments, - 'page callback' => isset($task['page callback']) ? $task['page callback'] : 'delegator_page_execute', + 'page callback' => 'delegator_page_execute', 'page arguments' => $page_arguments, 'load arguments' => $load_arguments, 'file' => 'plugins/tasks/page.inc', diff --git a/delegator/plugins/tasks/page.inc b/delegator/plugins/tasks/page.inc index 456f15fad6261c785f00e56eff9ece3b3b7b96f4..cf0828f412d811d8d7213b427a45675c031886f1 100644 --- a/delegator/plugins/tasks/page.inc +++ b/delegator/plugins/tasks/page.inc @@ -53,7 +53,7 @@ function delegator_page_delegator_tasks() { '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', +// 'page callback' => 'delegator_page_execute', // context only items 'handler type' => 'context', 'get arguments' => 'delegator_page_get_arguments', @@ -281,6 +281,16 @@ function delegator_page_edit_form_info() { * creating named arguments in the path. */ function delegator_page_execute($subtask_id) { + $page = delegator_page_load($subtask_id); + $task = delegator_get_task($page->task); + + if ($function = ctools_plugin_get_function($task, 'page callback')) { + $args = func_get_args(); + // overwrite the id with the actual page so it won't be loaded again. + $args[0] = $page; + return call_user_func_array($function, $args); + } + // Turn the contexts into a properly keyed array. $contexts = array(); foreach (func_get_args() as $arg) { @@ -289,8 +299,6 @@ function delegator_page_execute($subtask_id) { } } - $task = delegator_get_task('page'); - ctools_include('context-task-handler'); $output = ctools_context_handler_render($task, $subtask_id, $contexts); if ($output === FALSE) { diff --git a/help/context-content.html b/help/context-content.html new file mode 100644 index 0000000000000000000000000000000000000000..4a18e500eab97d9bd390cf8107bc6921a92a24a9 --- /dev/null +++ b/help/context-content.html @@ -0,0 +1,79 @@ +<!-- $Id $ --> +The CTools pluggable content system provides various pieces of content as discrete bits of data that can be added to other applications, such as Panels or Dashboard via the UI. Whatever the content is added to stores the configuration for that individual piece of content, and provides this to the content. + +Each content type plugin will be contained in a .inc file, with subsidiary files, if necessary, in or near the same directory. Each content type consists of some information and one or more subtypes, which all use the same renderer. Subtypes are considered to be instances of the type. For example, the 'views' content type would have each view in the system as a subtype. Many content types will have exactly one subtype. + +Because the content and forms can be provided via ajax, the plugin also provides a list of cs and js information that should be available on whatever page the content or forms may be ajaxed onto. + +For the purposes of selecting content from the UI, each content subtype will have the following information: +<ul> +<li>A title</li> +<li>A short description</li> +<li>A category [Do we want to add hierarchy categories? Do we want category to be more than just a string?]</li> +<li>An icon [do we want multiple icons? This becomes a hefty requirement]</li> +</ul> + +Each piece of content provides one or more configuration forms, if necessary, and the system that includes the content will handle the data storage. These forms can be provided in sequence as wizards or as extra forms that can be accessed through advanced administration. + +The plugin for a content type should contain: +<ul> +<li>title: For use on the content permissions screen.</li> +<li>content types: Either an array of content type definitions, or a callback that will return content type definitions. This callback will get the plugin definition as an argument.</li> +<li>content type: [Optional] Provide a single content type definition. This is only necessary if content types might be intensive.</li> +<li>render callback: The callback to render the content. Params: +<ul> +<li>$subtype: The name of the subtype being rendered. NOT the loaded subtype data.</li> +<li>$conf: The stored configuration for the content.</li> +<li>$args: Any arguments passed.</li> +<li>$context: An array of contexts requested by the required contexts and assigned by the configuration step.</li> +<li>$incoming_content: Any 'incoming content' if this is a wrapper.</li> +</ul></li> +<li>admin title: A callback to provide the administrative title. If it is not a function, then it will be counted as a string to use as the admin title.</li> +<li>admin info: A callback to provide administrative information about the content, to be displayed when manipulating the content. It should contain a summary of configuration.</li> +<li>edit form: Either a single form ID or an array of forms *keyed* by form ID with the value to be used as the title of the form. %title me be used as a placeholder for the administrative title if necessary. Example: +<pre> + array( + 'ctools_example_content_form_second' => t('Configure first form'), + 'ctools_example_content_form_first' => t('Configure second form'), + ), +</pre> +The first form will always have required configuration added to it. These forms are normal FAPI forms, but you do not need to provide buttons, these will be added by the form wizard.</li> +<li>add form: [Optional] If different from the edit forms, provide them here in the same manner. Also may be set to FALSE to not have an add form.</li> +<li>css: A file or array of CSS files that are necessary for the content.</li> +<li>js: A file or array of javascript files that are necessary for the content to be displayed.</li> +<li>admin css: A file or array of CSS files that are necessary for the forms.</li> +<li>admin js: A file or array of javascript files that are necessary for the forms.</li> +<li>extra forms: An array of form information to handle extra administrative forms.</li> +<li>no title override: Set to TRUE if the title cannot be overridden.</li> +<li>single: Set to TRUE if this content provides exactly one subtype.</li> +<li>render last: Set to true if for some reason this content needs to render after other content. This is primarily used for forms to ensure that render order is correct.</li> +</ul> + +TODO: many of the above callbacks can be assumed based upon patterns: modulename + '_' + name + '_' + function. i.e, render, admin_title, admin_info, etc. +TODO: Some kind of simple access control to easily filter out content. + +The subtype definition should contain: +<ul> +<li>title: The title of the subtype.</li> +<li>icon: The icon to display for the subtype.</li> +<li>path: The path for the icon if it is not in the same directory as the plugin.</li> +<li>description: The short description of the subtype, to be used when selecting it in the UI.</li> +<li>category: Either a text string for the category, or an array of the text string followed by the category weight.</li> +<li>required contexts: [Optional] Either a ctools_required_context or ctools_optional_context or array of contexts for this content. If omitted, no contexts are required.</li> +</ul> + +<h3>Rendered content</h3> +Rendered content is a little more than just HTML. +<ul> +<li>title: The safe to render title of the content.</li> +<li>content: The safe to render HTML content.</li> +<li>links: An array of links associated with the content suitable for theme('links').</li> +<li>more: An optional 'more' link (destination only)</li> +<li>admin_links: Administrative links associated with the content, suitable for theme('links').</li> +<li>feeds: An array of feed icons or links associated with the content. Each member of the array is rendered HTML.</li> +<li>type: The content type.</li> +<li>subtype: The content subtype. These two may be used together as module-delta for block style rendering.</li> +</ul> + +<h3>Todo: example</h3> +Todo after implementations are updated to new version. \ No newline at end of file diff --git a/includes/content.inc b/includes/content.inc new file mode 100644 index 0000000000000000000000000000000000000000..cec98ef0c867019451463db72c38cca6fbb27dcd --- /dev/null +++ b/includes/content.inc @@ -0,0 +1,574 @@ +<?php +// $Id$ + +/** + * @file + * Contains the tools to handle pluggable content that can be used by other + * applications such as Panels or Dashboard. + * + * See the context-content.html file in advanced help for documentation + * of this tool. + */ + +/** + * Implementation of hook_ctools_plugin_*. + * + * Give information to CTools about the content types plugin. + */ +function ctools_ctools_plugin_content_types() { + return array( + 'cache' => TRUE, + 'defaults' => 'ctools_content_defaults', + ); +} + +/** + * Provide defaults for a content type. + * + * Currently we check for automatically named callbacks to make life a little + * easier on the developer. + */ +function ctools_content_defaults($info, &$plugin) { + $function_base = $plugin['module'] . '_' . $plugin['name'] . '_content_type_'; + + if (empty($plugin['render callback']) && function_exists($function_base . 'render')) { + $plugin['render callback'] = $function_base . 'render'; + } + + if (empty($plugin['admin title'])) { + if (function_exists($function_base . 'admin_title')) { + $plugin['admin title'] = $function_base . 'admin_title'; + } + else { + $plugin['admin title'] = $plugin['title']; + } + } + + if (empty($plugin['admin info']) && function_exists($function_base . 'admin_info')) { + $plugin['admin info'] = $function_base . 'admin_info'; + } + + if (!isset($plugin['edit form']) && function_exists($function_base . 'edit_form')) { + $plugin['edit form'] = $function_base . 'edit_form'; + } + + if (!isset($plugin['add form']) && function_exists($function_base . 'add_form')) { + $plugin['add form'] = $function_base . 'add_form'; + } + + if (!isset($plugin['add form']) && function_exists($function_base . 'edit_form')) { + $plugin['add form'] = $function_base . 'edit_form'; + } + + // Another ease of use check: + // If a content type is set to SINGLE and *no* subtypes are defined, this rewrites + // things so that the syntax is nicer. + if (!empty($plugin['single']) && !isset($plugin['content types'])) { + $type = array( + 'title' => $plugin['title'], + 'description' => $plugin['description'], + 'icon' => $plugin['icon'], + 'category' => $plugin['category'], + ); + + if (isset($plugin['required contexts'])) { + $type['required contexts'] = $plugin['required contexts']; + } + $plugin['content types'] = array($plugin['name'] => $type); + } +} + +/** + * Fetch metadata on a specific content_type plugin. + * + * @param $content type + * Name of a panel content type. + * + * @return + * An array with information about the requested panel content type. + */ +function ctools_get_content_type($content_type) { + ctools_include('context'); + ctools_include('plugins'); + return ctools_get_plugins('ctools', 'content_types', $content_type); +} + +/** + * Fetch metadata for all content_type plugins. + * + * @return + * An array of arrays with information about all available panel content types. + */ +function ctools_get_content_types() { + ctools_include('context'); + ctools_include('plugins'); + return ctools_get_plugins('ctools', 'content_types'); +} + +/** + * Get all of the individual subtypes provided by a given content type. This + * would be all of the blocks for the block type, or all of the views for + * the view type. + * + * @param $type + * The content type to load. + * + * @return + * An array of all subtypes available. + */ +function ctools_content_get_subtypes($type) { + // @todo cache -- multiple calls on the same page can be intensive. + $subtypes = array(); + + if (is_array($type)) { + $plugin = $type; + } + else { + $plugin = ctools_get_content_type($type); + } + + if (isset($plugin['content types'])) { + $function = $plugin['content types']; + if (is_array($function)) { + $subtypes = $function; + } + else if (function_exists($function)) { + // Cast to array to prevent errors from non-array returns. + $subtypes = (array) $function($plugin); + } + } + + // Walk through the subtypes and ensure minimal settings are + // retained. + foreach ($subtypes as $id => $subtype) { + // Use exact name since this is a modify by reference. + ctools_content_prepare_subtype($subtypes[$id], $plugin); + } + + return $subtypes; +} + +/** + * Given a content type and a subtype id, return the information about that + * content subtype. + * + * @param $type + * The content type being fetched. + * @param $subtype_id + * The id of the subtype being fetched. + * + * @return + * An array of information describing the content subtype. + */ +function ctools_content_get_subtype($type, $subtype_id) { + $subtype = array(); + if (is_array($type)) { + $plugin = $type; + } + else { + $plugin = ctools_get_content_type($type); + } + + $function = ctools_plugin_get_function($plugin, 'content type'); + if ($function) { + $subtype = $function($subtype_id, $plugin); + } + else { + $subtypes = ctools_content_get_subtypes($type); + if (isset($subtypes[$subtype_id])) { + $subtype = $subtypes[$subtype_id]; + } + } + + ctools_content_prepare_subtype($subtype, $plugin); + return $subtype; +} + +/** + * Ensure minimal required settings on a content subtype exist. + */ +function ctools_content_prepare_subtype(&$subtype, $plugin) { + if (empty($subtype['path'])) { + $subtype['path'] = $plugin['path']; + } +} + +/** + * Get the content from a given content type. + * + * @param $type + * The content type. May be the name or an already loaded content type plugin. + * @param $subtype + * The name of the subtype being rendered. + * @param $conf + * The configuration for the content type. + * @param $args + * The arguments provided to the owner of the content type. Some content may + * wish to configure itself based on the arguments the panel or dashboard + * received. + * @param $context + * An array of context objects available for use. + * @param $incoming_content + * Any incoming content, if this display is a wrapper. + * + * @return + * The content as rendered by the plugin. This content should be an array + * with the following possible keys: + * - title: The safe to render title of the content. + * - content: The safe to render HTML content. + * - links: An array of links associated with the content suitable for + * theme('links'). + * - more: An optional 'more' link (destination only) + * - admin_links: Administrative links associated with the content, suitable + * for theme('links'). + * - feeds: An array of feed icons or links associated with the content. + * Each member of the array is rendered HTML. + * - type: The content type. + * - subtype: The content subtype. These two may be used together as + * module-delta for block style rendering. + */ +function ctools_content_render($type, $subtype, $conf, $args = array(), $context = array(), $incoming_content = '') { + if (is_array($type)) { + $plugin = $type; + } + else { + $plugin = ctools_get_content_type($type); + } + + if ($function = ctools_plugin_get_function($plugin, 'render callback')) { + $content = $function($subtype, $conf, $args, $context, $incoming_content); + if (!isset($content->type)) { + $content->type = $plugin['name']; + } + + if (!isset($content->subtype)) { + $content->subtype = $subtype; + } + + return $content; + } +} + +/** + * Get the administrative title from a given content type. + * + * @param $type + * The content type. May be the name or an already loaded content type object. + * @param $subtype + * The subtype being rendered. + * @param $conf + * The configuration for the content type. + * @param $context + * An array of context objects available for use. These may be placeholders. + */ +function ctools_content_admin_title($type, $subtype, $conf, $context = NULL) { + if (is_array($type)) { + $plugin = $type; + } + else if (is_string($type)) { + $plugin = ctools_get_content_type($type); + } + else { + return; + } + + if ($function = ctools_plugin_get_function($plugin, 'admin title')) { + return $function($subtype, $conf, $context); + } + else if (isset($plugin['admin title'])) { + return $plugin['admin title']; + } + else if (isset($plugin['title'])) { + return $plugin['title']; + } +} + +/** + * Get the proper icon path to use, falling back to default icons if no icon exists. + * + * $subtype + * The loaded subtype info. + */ +function ctools_content_admin_icon($subtype) { + $icon = ''; + + if (isset($subtype['icon'])) { + $icon = $subtype['icon']; + if (!file_exists($icon)) { + $icon = $subtype['path'] . '/' . $icon; + } + } + + if (empty($icon) || !file_exists($icon)) { + $icon = ctools_image_path('no-icon.png'); + } + + return $icon; +} + +/** + * Set up the default $conf for a new instance of a content type. + */ +function ctools_content_get_defaults($plugin, $subtype) { + if (isset($plugin['defaults'])) { + $defaults = $plugin['defaults']; + } + else if (isset($subtype['defaults'])) { + $defaults = $subtype['defaults']; + } + if (isset($defaults)) { + if (is_string($defaults) && function_exists($defaults)) { + if ($return = $defaults($pane)) { + return $return; + } + } + else if (is_array($defaults)) { + return $defaults; + } + } + + return array(); +} + +/** + * Get the administrative title from a given content type. + * + * @param $type + * The content type. May be the name or an already loaded content type object. + * @param $subtype + * The subtype being rendered. + * @param $conf + * The configuration for the content type. + * @param $context + * An array of context objects available for use. These may be placeholders. + */ +function ctools_content_admin_info($type, $subtype, $conf, $context = NULL) { + if (is_array($type)) { + $plugin = $type; + } + else { + $plugin = ctools_get_content_type($type); + } + + if ($function = ctools_plugin_get_function($plugin, 'admin info')) { + $output = $function($subtype, $conf, $context); + } + if (empty($output) || !is_object($output)) { + $output = new stdClass(); + $output->title = t('No info'); + $output->content =t ('No info available.'); + } + return $output; +} + +/** + * Add the default FAPI elements to the content type configuration form + */ +function ctools_content_configure_form_defaults($plugin, $subtype, $contexts, $conf) { + if (!empty($subtype['required context']) && is_array($contexts)) { + $form['context'] = ctools_context_selector($contexts, $subtype['required context'], isset($conf['context']) ? $conf['context'] : array()); + } + + // Unless we're not allowed to override the title on this content type, add this + // gadget to all panes. + if (empty($plugin['no title override'])) { + $form['aligner_start'] = array( + '#value' => '<div class="option-text-aligner">', + ); + $form['override_title'] = array( + '#type' => 'checkbox', + '#default_value' => isset($conf['override_title']) ? $conf['override_title'] : '', + '#title' => t('Override title'), + '#id' => 'override-title-checkbox', + ); + $form['override_title_text'] = array( + '#type' => 'textfield', + '#default_value' => isset($conf['override_title_text']) ? $conf['override_title_text'] : '', + '#size' => 35, + '#id' => 'override-title-textfield', + ); + $form['aligner_stop'] = array( + '#value' => '</div><div style="clear: both; padding: 0; margin: 0"></div>', + ); + $form['override_title_markup'] = array( + '#prefix' => '<div class="description">', + '#suffix' => '</div>', + '#value' => t('You may use %keywords from contexts, as well as %title to contain the original title.'), + ); + } + + return $form; +} + +/** + * Get the config form. + * + * The $form_info and $form_state need to be preconfigured with data you'll need + * such as whether or not you're using ajax, or the modal. $form_info will need + * your next/submit callbacks so that you can cache your data appropriate. This + */ +function ctools_content_form($op, $form_info, &$form_state, $plugin, $subtype, &$conf, $step = NULL) { + $form_state += array( + 'plugin' => $plugin, + 'subtype' => $subtype, + 'conf' => &$conf, + 'op' => $op, + ); + + $form_info += array( + 'show back' => TRUE, + ); + + // Turn the forms defined in the plugin into the format the wizard needs. + if ($op == 'add' && isset($plugin['add form'])) { + _ctools_content_create_add_form_info($form_info, $plugin['add form'], $plugin, $subtype, $op); + } + // Use the edit form for the add form if add form was completely left off. + else if (isset($plugin['edit form'])) { + _ctools_content_create_add_form_info($form_info, $plugin['edit form'], $plugin, $subtype, $op); + } + + ctools_include('wizard'); + return ctools_wizard_multistep_form($form_info, $step, $form_state); + +} + +function _ctools_content_create_add_form_info(&$form_info, $info, $plugin, $subtype, $op) { + if (is_string($info)) { + if ($op == 'add') { + $title = t('Configure new !subtype_title', array('!subtype_title' => $subtype['title'])); + } + else { + $title = t('Configure !subtype_title', array('!subtype_title' => $subtype['title'])); + } + $form_info['order'] = array('form' => $title); + $form_info['forms'] = array( + 'form' => array( + 'title' => $title, + 'form id' => $info, + ), + ); + } + else if (is_array($info)) { + $form_info['order'] = array(); + $form_info['forms'] = array(); + $count = 0; + $base = 'step'; + foreach ($info as $form_id => $title) { + // @todo -- docs say %title can be used to sub for the admin title. + $step = $base . ++$count; + $form_info['order'][$step] = $title; + $form_info['forms'][$step] = array( + 'title' => $title, + 'form id' => $form_id, + ); + } + } +} + +/** + * Get an array of all available content types that can be fed into the + * display editor for the add content list. + * + * @param $context + * If a context is provided, content that requires that context can apepar. + * @param $has_content + * Whether or not the display will have incoming content + * @param $allowed_types + * An array of allowed content types (pane types) keyed by content_type . '-' . sub_type + * @param $default_types + * A default allowed/denied status for content that isn't known about + */ +function ctools_get_available_content_types($contexts = NULL, $has_content = FALSE, $allowed_types = NULL, $default_types = NULL) { + $plugins = ctools_get_content_types(); + $available = array(); + + foreach ($plugins as $id => $plugin) { + foreach (ctools_content_get_subtypes($plugin) as $subtype_id => $subtype) { + // exclude items that require content if we're saying we don't + // provide it. + if (!empty($subtype['requires content']) && !$has_content) { + continue; + } + + // Check to see if the content type can be used in this context. + if (!empty($subtype['required context'])) { + if (!ctools_context_filter($contexts, $subtype['required context'])) { + continue; + } + } + + // Check to see if the passed-in allowed types allows this content. + if ($allowed_types) { + $key = $id . '-' . $subtype_id; + if (!isset($allowed_types[$key])) { + $allowed_types[$key] = isset($default_types[$id]) ? $default_types[$id] : $default_types['other']; + } + if (!$allowed_types[$key]) { + continue; + } + } + + // If we made it through all the tests, then we can use this content. + $available[$id][$subtype_id] = $subtype; + } + } + return $available; +} + +/** + * Get an array of all content types that can be fed into the + * display editor for the add content list, regardless of + * availability. + * + */ +function ctools_get_all_content_types() { + $plugins = ctools_get_content_types(); + $available = array(); + + foreach ($plugins as $id => $plugin) { + foreach (ctools_content_get_subtypes($plugin) as $subtype_id => $subtype) { + // If we made it through all the tests, then we can use this content. + $available[$id][$subtype_id] = $subtype; + } + } + return $available; +} + +/** + * Select the context to be used for a piece of content, based upon config. + * + * @param $type + * The type of the content. + * @param $subtype + * The subtype of the content. + * @param $conf + * The configuration array that should contain the context. + * @param $contexts + * A keyed array of available contexts. + * + * @return + * The matching contexts or NULL if none or necessary, or FALSE if + * requirements can't be met. + */ +function ctools_content_select_context($type, $subtype, $conf, $contexts) { + // Identify which of our possible contexts apply. + if (empty($subtype)) { + return; + } + + $subtype_info = ctools_content_get_subtype($type, $subtype); + // If the content requires a context, fetch it; if no context is returned, + // do not display the pane. + if (empty($subtype_info) || empty($subtype_info['required context'])) { + return; + } + + if (empty($conf['context'])) { + return; + } + + $context = ctools_context_select($contexts, $subtype_info['required context'], $conf['context']); + + return $context; +} diff --git a/includes/content.theme.inc b/includes/content.theme.inc new file mode 100644 index 0000000000000000000000000000000000000000..14d716c3a975bb7474963862ffc4bdc909ce5499 --- /dev/null +++ b/includes/content.theme.inc @@ -0,0 +1,22 @@ +<?php +// $Id$ + +/** + * @file + * Contains theme registry and theme implementations for the content types. + */ + +/** + * Implementation of hook_theme to load all content plugins and pass thru if + * necessary. + */ +function ctools_content_theme(&$theme) { + ctools_include('content'); + + $plugins = ctools_get_content_types(); + foreach ($plugins as $plugin) { + if (ctools_plugin_get_function($plugin, 'hook theme')) { + $function($theme); + } + } +} diff --git a/includes/plugins.inc b/includes/plugins.inc index 19526930ce2ab7cedef93916e76942e4447fde57..18a3d277eb336c356849d84d5b7009b22deebd3a 100644 --- a/includes/plugins.inc +++ b/includes/plugins.inc @@ -103,7 +103,7 @@ function ctools_get_plugins($module, $type, $id = NULL) { if ($info[$type]['cache'] && !isset($plugins['cache'])) { // @todo Maybe this should use our own table but free wiping // with content updates is convenient. - $cache = cache_get("plugins:$module:$type"); + $cache = cache_get("plugins:$module:$type", $info[$type]['cache table']); // if cache load successful, set $all_hooks and $all_files to true. if (!empty($cache->data)) { @@ -137,7 +137,7 @@ function ctools_get_plugins($module, $type, $id = NULL) { // If we were told earlier that this is cacheable and the cache was // empty, give something back. if (!empty($write_cache)) { - cache_set("plugins:$module:$type", $plugins[$type]); + cache_set("plugins:$module:$type", $plugins[$type], $info[$type]['cache table']); } // If no id was requested, we are finished here: @@ -298,7 +298,12 @@ function ctools_plugin_process($info, $module, $identifier, $path, $file = NULL, // Fill in plugin specific defaults, if they exist. if (!empty($info['defaults'])) { - $result[$name] += $info['defaults']; + if (is_array($info['defaults'])) { + $result[$name] += $info['defaults']; + } + else if (function_exists($info['defaults'])) { + $info['defaults']($info, $result[$name]); + } } // Allow the plugin owner to do additional processing. @@ -324,6 +329,7 @@ function ctools_plugin_get_info($module, $type) { 'module' => $module, 'type' => $type, 'cache' => FALSE, + 'cache table' => 'cache', 'defaults' => array(), 'hook' => $module . '_' . $type, 'load themes' => FALSE, diff --git a/includes/wizard.inc b/includes/wizard.inc index dbdd774d233bdcf1298643513c06b164cd0fe0ac..dcc84c252c408fc7b408d97272ee3d4d1b9a70f8 100644 --- a/includes/wizard.inc +++ b/includes/wizard.inc @@ -47,6 +47,11 @@ * whatever information the form(s) involved left for it. */ function ctools_wizard_multistep_form($form_info, $step, &$form_state) { + if (!isset($step)) { + $keys = array_keys($form_info['order']); + $step = array_shift($keys); + } + $form_state['step'] = $step; $form_state['form_info'] = $form_info; diff --git a/plugins/content_types/custom/custom.inc b/plugins/content_types/custom/custom.inc new file mode 100644 index 0000000000000000000000000000000000000000..c28187cb333dbe1d953b745797cc91a3003351ed --- /dev/null +++ b/plugins/content_types/custom/custom.inc @@ -0,0 +1,93 @@ +<?php +// $Id$ + + +/** + * Callback function to supply a list of content types. + */ +function ctools_custom_ctools_content_types() { + return array( + 'single' => TRUE, + 'no title override' => TRUE, + 'title' => t('New custom content'), + 'icon' => 'icon_block_custom.png', + 'description' => t('Create a completely custom piece of HTML content.'), + 'category' => t('Custom'), + 'defaults' => array('title' => '', 'body' => '', 'format' => FILTER_FORMAT_DEFAULT), + // render callback is automatically deduced: + // 'render callback' => 'ctools_custom_content_type_render', + ); +} + +/** + * Output function for the 'custom' content type. Outputs a custom + * based on the module and delta supplied in the configuration. + */ +function ctools_custom_content_type_render($subtype, $conf) { + static $delta = 0; + + $block = new stdClass(); + $block->subtype = ++$delta; + $block->title = filter_xss_admin($conf['title']); + $block->content = check_markup($conf['body'], $conf['format'], FALSE); + + return $block; +} + +/** + * Callback to provide the administrative title of the custom content. + */ +function ctools_custom_content_type_admin_title($subtype, $conf) { + $output = t('Custom'); + if (!empty($conf['title'])) { + $output .= " (" . filter_xss_admin($conf['title']) . ")"; + } + return $output; +} + +/** + * Callback to provide administrative info. In this case we'll render the + * content as long as it's not PHP, which is too risky to render here. + */ +function ctools_custom_content_type_admin_info($subtype, $conf) { + $block = new stdClass(); + $block->title = filter_xss_admin($conf['title']); + // We don't want to render php output on preview here, because if something is + // wrong the whole display will be borked. So we check to see if the php + // evaluator filter is being used, and make a temporary change to the filter + // so that we get the printed php, not the eval'ed php. + $php_filter = FALSE; + foreach (filter_list_format($conf['format']) as $filter) { + if ($filter->name == 'PHP evaluator') { // TODO stupid way to check + $php_filter = TRUE; + } + } + // If a php filter is active, pass 1 to use core's most restrictive filter. + $block->content = check_markup($conf['body'], $php_filter ? 1 : $conf['format']); + return $block; +} + +/** + * Returns an edit form for the custom type. + */ +function ctools_custom_content_type_edit_form(&$form, &$form_state) { + $conf = $form_state['conf']; + $form['title'] = array( + '#type' => 'textfield', + '#default_value' => $conf['title'], + '#title' => t('Title'), + ); + $form['body'] = array( + '#title' => t('Body'), + '#type' => 'textarea', + '#default_value' => $conf['body'], + ); + $parents[] = 'format'; + $form['format'] = filter_form($conf['format'], 1, $parents); + + return $form; +} + +function ctools_custom_content_type_edit_form_submit(&$form, &$form_state) { + $form_state['conf'] = $form_state['values']; +} diff --git a/plugins/content_types/custom/icon_block_custom.png b/plugins/content_types/custom/icon_block_custom.png new file mode 100644 index 0000000000000000000000000000000000000000..bbde4bd57c4731ddcb109da8aed4398bc587a8fd Binary files /dev/null and b/plugins/content_types/custom/icon_block_custom.png differ