diff --git install.php install.php index 8a3daa9..8287634 100644 --- install.php +++ install.php @@ -1423,6 +1423,14 @@ function install_finished(&$install_state) { $output .= '

' . st('For more information on configuring Drupal, please refer to the help section.', array('@help' => url('admin/help'))) . '

'; } + // Rebuild the module and theme data, in case any newly-installed modules + // need to modify it via hook_system_info_alter(). We need to clear the + // theme static cache first, to make sure that the theme data is actually + // rebuilt. + drupal_static_reset('_system_get_theme_data'); + system_get_module_data(); + system_get_theme_data(); + // Rebuild menu and registry to get content type links registered by the // profile, and possibly any other menu items created through the tasks. menu_rebuild(); diff --git modules/block/block.module modules/block/block.module index d2287f4..6e4a0b0 100644 --- modules/block/block.module +++ modules/block/block.module @@ -224,20 +224,34 @@ function block_page_build(&$page) { * The requested region. */ function block_get_blocks_by_region($region) { - $weight = 0; $build = array(); if ($list = block_list($region)) { - foreach ($list as $key => $block) { - $build[$key] = $block->content; - unset($block->content); - $build[$key] += array( - '#block' => $block, - '#weight' => ++$weight, - ); - $build[$key]['#theme_wrappers'][] ='block'; - } - $build['#sorted'] = TRUE; + $build = _block_get_renderable_array($list); + } + return $build; +} + +/** + * Get an array of blocks suitable for drupal_render(). + * + * @param $list + * A list of blocks such as that returned by block_list(). + * @return + * A renderable array. + */ +function _block_get_renderable_array($list = array()) { + $weight = 0; + $build = array(); + foreach ($list as $key => $block) { + $build[$key] = $block->content; + unset($block->content); + $build[$key] += array( + '#block' => $block, + '#weight' => ++$weight, + ); + $build[$key]['#theme_wrappers'][] ='block'; } + $build['#sorted'] = TRUE; return $build; } diff --git modules/dashboard/dashboard.css modules/dashboard/dashboard.css new file mode 100644 index 0000000..8a3a099 --- /dev/null +++ modules/dashboard/dashboard.css @@ -0,0 +1,83 @@ +/* $Id$ */ + +#dashboard div.dashboard-region { + float: left; + min-height: 1px; +} + +#dashboard div#dashboard_main { + width: 65%; +} + +#dashboard div#dashboard_sidebar { + width: 35%; +} + +#dashboard div.block { + margin-bottom: 20px; +} + +#dashboard .dashboard-region .block { + clear: both; +} + +#dashboard div.block h2 { + background-color:#e2e1dc; + padding: 3px 5px; +} + +#dashboard div.block div.content, #dashboard .block-placeholder { + padding: 10px 5px 5px 5px; +} + +#dashboard div.block div.content ul.menu { + margin-left:20px; +} + +#dashboard #disabled-blocks .block, #dashboard .block-placeholder { + background: #e2e1dc; + padding: 4px; + margin: 3px; + float: left; + -moz-border-radius: 4px; +} + +#dashboard .ui-sortable { + border: 3px dashed #ccc; + padding: 10px; +} + +#dashboard .section { + margin: 5px; +} + +#dashboard div.dragging { + width: 30% !important; +} + +#dashboard #disabled-blocks h2 { + display: inline; + font-weight: normal; + white-space: nowrap; +} + +#dashboard #disabled-blocks .block .content, #dashboard .ui-sortable-helper .content { + display: none; +} + +#dashboard .ui-sortable .block { + cursor: move; +} + +#dashboard .dashboard-region .block-placeholder { + margin: 0 0 20px 0; + padding: 0; + display: block; + height: 1.6em; + width: 100%; +} + +#dashboard #disabled-blocks .block-placeholder { + width: 30px; + height: 1.6em; +} diff --git modules/dashboard/dashboard.info modules/dashboard/dashboard.info new file mode 100644 index 0000000..c49d445 --- /dev/null +++ modules/dashboard/dashboard.info @@ -0,0 +1,8 @@ +; $Id$ +name = Dashboard +description = A module that provides a dashboard interface for organizing and tracking various information within your site. +core = 7.x +package = Core +version = VERSION +files[] = dashboard.module +dependencies[] = block diff --git modules/dashboard/dashboard.js modules/dashboard/dashboard.js new file mode 100644 index 0000000..87ec246 --- /dev/null +++ modules/dashboard/dashboard.js @@ -0,0 +1,128 @@ +// $Id: toolbar.js,v 1.1 2009/07/04 05:37:30 dries Exp $ +(function ($) { + +/** + * Implementation of Drupal.behaviors for dashboard. + */ +Drupal.behaviors.dashboard = { + attach: function () { + $('#dashboard').prepend('
'); + $('#dashboard .customize input').click(Drupal.behaviors.dashboard.enterCustomizeMode); + }, + + /** + * Enter "customize" mode by displaying disabled blocks. + */ + enterCustomizeMode: function () { + $('#dashboard .customize input').unbind("click").click(Drupal.behaviors.dashboard.exitCustomizeMode).attr('value', Drupal.t('Done')); + $('div.customize .canvas').load(Drupal.settings.dashboard.customize, Drupal.behaviors.dashboard.makeDraggable); + }, + + /** + * Exit "customize" mode by simply forcing a page refresh. + */ + exitCustomizeMode: function () { + location.reload(); + }, + + /** + * Helper function for enterCustomizeMode; sets up drag-and-drop. + */ + makeDraggable: function () { + // Initialize drag-and-drop. + var regions = $('div.region'); + regions.sortable({ + connectWith: regions, + cursor: 'move', + cursorAt: 'left', + dropOnEmpty: true, + items: '>div.block, div.disabled-block', + opacity: 0.8, + helper: 'block-dragging', + placeholder: 'block-placeholder clearfix', + start: Drupal.behaviors.dashboard.start, + update: Drupal.behaviors.dashboard.update + }); + }, + + /** + * While dragging, make the block appear as a disabled block + * + * This function is called on the jQuery UI Sortable "start" event. + * + * @param event + * The event that triggered this callback. + * @param ui + * An object containing information about the item that is being dragged. + */ + start: function (event, ui) { + var item = $(ui.item); + + // If the block is already in disabled state, don't do anything. + if (!item.hasClass('disabled-block')) { + item.css({height: 'auto'}); + } + }, + + /** + * Send block order to the server, and expand previously disabled blocks. + * + * This function is called on the jQuery UI Sortable "update" event. + * + * @param event + * The event that triggered this callback. + * @param ui + * An object containing information about the item that was just dropped. + */ + update: function (event, ui) { + var item = $(ui.item); + + // If the user dragged a disabled block, load the block contents. + if (item.hasClass('disabled-block')) { + var module, delta, itemClass; + itemClass = item.attr('class'); + // Determine the block module and delta. + module = itemClass.match(/\bmodule-(\S+)\b/)[1]; + delta = itemClass.match(/\bdelta-(\S+)\b/)[1]; + + // Load the newly enabled block's content. + $.get(Drupal.settings.dashboard.blockContent + '/' + module + '/' + delta, {}, + function (block) { + var blockContent = $("div.content", $(block)); + $("div.content", item).after(blockContent).remove(); + }, + 'html' + ); + // Remove the "disabled-block" class, so we don't reload its content the + // next time it's dragged. + item.removeClass("disabled-block"); + } + + // Let the server know what the new block order is. + $.post(Drupal.settings.dashboard.updatePath, { + 'form_token': Drupal.settings.dashboard.formToken, + 'regions': Drupal.behaviors.dashboard.getOrder + } + ); + }, + + /** + * Return the current order of the blocks in each of the sortable regions, + * in query string format. + */ + getOrder: function () { + var order = []; + $('div.region').each(function () { + var region = $(this).parent().attr('id').replace(/-/g, '_'); + var blocks = $(this).sortable('toArray'); + var i; + for (i = 0; i < blocks.length; i++) { + order.push(region + '[]=' + blocks[i]); + } + }); + order = order.join('&'); + return order; + } +}; + +})(jQuery); diff --git modules/dashboard/dashboard.module modules/dashboard/dashboard.module new file mode 100644 index 0000000..b0f69de --- /dev/null +++ modules/dashboard/dashboard.module @@ -0,0 +1,339 @@ + 'Dashboard', + 'page callback' => 'dashboard_admin', + 'access arguments' => array('access dashboard'), + ); + $items['admin/dashboard/customize'] = array( + 'page callback' => 'dashboard_show_disabled', + 'access arguments' => array('administer blocks'), + 'type' => MENU_CALLBACK, + ); + $items['admin/dashboard/block-content/%/%'] = array( + 'page callback' => 'dashboard_show_block_content', + 'page arguments' => array(3, 4), + 'access arguments' => array('administer blocks'), + 'type' => MENU_CALLBACK, + ); + $items['admin/dashboard/update'] = array( + 'page callback' => 'dashboard_update', + 'access arguments' => array('administer blocks'), + 'type' => MENU_CALLBACK, + ); + return $items; +} + +/** + * Implement hook_block_info_alter(). + * + * Skip rendering dashboard blocks when not on the dashboard page itself. This + * prevents expensive dashboard blocks from causing performance issues on pages + * where they will never be displayed. + */ +function dashboard_block_info_alter(&$blocks) { + if (!dashboard_is_visible()) { + foreach ($blocks as $key => $block) { + if (in_array($block->region, dashboard_regions())) { + unset($blocks[$key]); + } + } + } +} + +/** + * Implement hook_page_alter(). + * + * Display dashboard blocks in the main content region. + */ +function dashboard_page_alter(&$page) { + if (dashboard_is_visible()) { + // Create a wrapper for the dashboard itself, then insert each dashboard + // region into it. + $page['content']['dashboard'] = array('#theme_wrappers' => array('dashboard')); + foreach (dashboard_regions() as $region) { + // Insert regions even when they are empty, so that they will be + // displayed when the dashboard is being configured. + $page['content']['dashboard'][$region] = !empty($page[$region]) ? $page[$region] : array(); + $page['content']['dashboard'][$region]['#dashboard_region'] = $region; + // Allow each dashboard region to be themed differently, or fall back on + // the generic theme wrapper function for dashboard regions. + $page['content']['dashboard'][$region]['#theme_wrappers'][] = array($region, 'dashboard_region'); + unset($page[$region]); + } + } +} + +/** + * Implement hook_permission(). + */ +function dashboard_permission() { + return array( + 'access dashboard' => array( + 'title' => t('View the administrative dashboard'), + 'description' => t('Access the site-wide dashboard. Modifying the dashboard requires the "Administer blocks" permission.'), + ), + ); +} + +/** + * Implement hook_system_info_alter(). + * + * Add regions to each theme to store the dashboard blocks. + */ +function dashboard_system_info_alter(&$info, $file, $type) { + if ($type == 'theme') { + $info['regions'] += dashboard_region_descriptions(); + } +} + +/** + * Implement hook_theme(). + */ +function dashboard_theme() { + return array( + 'dashboard' => array( + 'arguments' => array('element' => NULL), + ), + 'dashboard_region' => array( + 'arguments' => array('element' => NULL), + ), + 'dashboard_disabled_blocks' => array( + 'arguments' => array('blocks' => NULL), + ), + 'dashboard_disabled_block' => array( + 'arguments' => array('block' => NULL), + ), + ); +} + +/** + * Dashboard page callback. + */ +function dashboard_admin() { + $output = ''; + if (user_access('administer blocks')) { + $output .= '
' . t('To customize the dashboard page, move blocks to the dashboard regions on !block-admin, or enable JavaScript on this page to use the drag-and-drop interface.', array('!block-admin' => l('the block administration page', 'admin/structure/block'))) . '
'; + drupal_add_js(drupal_get_path('module', 'dashboard') . '/dashboard.js'); + $settings = array( + 'dashboard' => array( + 'customize' => url('admin/dashboard/customize'), + 'blockContent' => url('admin/dashboard/block-content'), + 'updatePath' => url('admin/dashboard/update'), + 'formToken' => drupal_get_token('dashboard-update'), + ), + ); + drupal_add_js($settings, array('type' => 'setting')); + drupal_add_library('system', 'ui.sortable'); + } + // We do not return any main page content, because the content of the page + // will be populated via the dashboard regions in dashboard_page_alter(). + return $output; +} + +/** + * Returns TRUE if the user is currently viewing the dashboard. + */ +function dashboard_is_visible() { + $menu_item = menu_get_item(); + return isset($menu_item['page_callback']) && $menu_item['page_callback'] == 'dashboard_admin'; +} + +/** + * Return an array of dashboard region descriptions, keyed by region name. + */ +function dashboard_region_descriptions() { + $default_regions = array( + 'dashboard_main' => 'Dashboard main', + 'dashboard_sidebar' => 'Dashboard sidebar', + ); + return variable_get('dashboard_region_descriptions', $default_regions); +} + +/** + * Return an array of dashboard region names. + */ +function dashboard_regions() { + return array_keys(dashboard_region_descriptions()); +} + +/** + * AJAX callback to show disabled blocks in the dashboard customization mode. + */ +function dashboard_show_disabled() { + global $theme_key; + + // Blocks are not necessarily initialized at this point. + $blocks = _block_rehash(); + + // Limit the list to disabled blocks for the current theme. + foreach ($blocks as $key => $block) { + if ($block['theme'] != $theme_key || (!empty($block['status']) && !empty($block['region']))) { + unset($blocks[$key]); + } + } + + // Theme the output and end the page request. + print theme('dashboard_disabled_blocks', $blocks); + exit(); +} + +/** + * AJAX callback to display the rendered contents of a specific block. + * + * @param $module + * The block's module name. + * @param $delta + * The block's delta. + */ +function dashboard_show_block_content($module, $delta) { + drupal_theme_initialize(); + global $theme_key; + + $blocks = array(); + $block_object = db_query("SELECT * FROM {block} WHERE theme = :theme AND module = :module AND delta = :delta", array( + ":theme" => $theme_key, + ":module" => $module, + ":delta" => $delta, + )) + ->fetchObject(); + $block_object->enabled = $block_object->page_match = TRUE; + $blocks[$module . "_" . $delta] = $block_object; + $block_content = _block_render_blocks($blocks); + $build = _block_get_renderable_array($block_content); + $rendered_block = drupal_render($build); + print $rendered_block; + exit; +} + +/** + * Set the new weight of each region according to the drag-and-drop order. + */ +function dashboard_update() { + drupal_theme_initialize(); + global $theme_key; + // Check the form token to make sure we have a valid request. + if (!empty($_REQUEST['form_token']) && drupal_valid_token($_REQUEST['form_token'], 'dashboard-update')) { + parse_str($_REQUEST['regions'], $regions); + foreach ($regions as $region_name => $blocks) { + if ($region_name == 'disabled_blocks') { + $region_name = ''; + } + foreach ($blocks as $weight => $block_string) { + // Parse the query string to determine the block's module and delta. + preg_match('/block-([^-]+)-(.+)/', $block_string, $matches); + $block = new stdClass; + $block->module = $matches[1]; + $block->delta = $matches[2]; + + $block->region = $region_name; + $block->weight = $weight; + if (empty($region_name)) { + $block->status = 0; + } + else { + $block->status = 1; + } + + db_merge('block') + ->key(array( + 'module' => $block->module, + 'delta' => $block->delta, + 'theme' => $theme_key, + )) + ->fields(array( + 'status' => $block->status, + 'weight' => $block->weight, + 'region' => $block->region, + 'pages' => '', + )) + ->execute(); + } + } + } + exit; +} + +/** + * Theme the entire dashboard. + * + * @param $element + * An associative array containing the properties of the dashboard element. + * Properties used: #children + * @return + * A string representing the themed dashboard. + * + * @ingroup themeable + */ +function theme_dashboard($element) { + drupal_add_css(drupal_get_path('module', 'dashboard') . '/dashboard.css'); + return '
' . $element['#children'] . '
'; +} + +/** + * Theme a generic dashboard region. + * + * @param $element + * An associative array containing the properties of the dashboard region + * element. Properties used: #dashboard_region, #children + * @return + * A string representing the themed dashboard region. + * + * @ingroup themeable + */ +function theme_dashboard_region($element) { + $output = '
'; + $output .= $element['#children']; + $output .= '
'; + return $output; +} + +/** + * Theme a set of disabled blocks, for display in dashboard customization mode. + * + * @param $blocks + * An array of block objects from _block_rehash(). + * @return + * A string representing the disabled blocks region of the dashboard + * customization page. + * + * @ingroup themeable + */ +function theme_dashboard_disabled_blocks($blocks) { + $output = '

' . t('Drag and drop dashboard widgets to their place. Changes are automatically saved.') . '

'; + $output .= '
'; + $output .= '

' . t('Available blocks') . '

'; + foreach ($blocks as $block) { + $output .= theme('dashboard_disabled_block', $block); + } + $output .= '
'; + return $output; +} + +/** + * Theme a disabled block, for display in dashboard customization mode. + * + * @param $block + * A block object from _block_rehash(). + * @return + * A string representing the disabled block. + * + * @ingroup themeable + */ +function theme_dashboard_disabled_block($block) { + $output = ""; + if (isset($block)) { + $output .= '
' + . '

'.(!empty($block['title']) ? $block['title'] : $block['info']).'

' + . '
' + . '
'; + } + return $output; +} diff --git modules/simpletest/tests/system_test.module modules/simpletest/tests/system_test.module index 49b34e0..2171b27 100644 --- modules/simpletest/tests/system_test.module +++ modules/simpletest/tests/system_test.module @@ -182,7 +182,7 @@ function system_test_exit() { /** * Implement hook_system_info_alter(). */ -function system_test_system_info_alter(&$info, $file) { +function system_test_system_info_alter(&$info, $file, $type) { // We need a static otherwise the last test will fail to alter common_test. static $test; if (($dependencies = variable_get('dependencies', array())) || $test) { diff --git modules/system/system.api.php modules/system/system.api.php index df2ff32..bda4a0f 100644 --- modules/system/system.api.php +++ modules/system/system.api.php @@ -713,8 +713,11 @@ function hook_mail_alter(&$message) { * @param $file * Full information about the module or theme, including $file->name, and * $file->filename + * @param $type + * Either 'module' or 'theme', depending on the type of .info file that was + * passed. */ -function hook_system_info_alter(&$info, $file) { +function hook_system_info_alter(&$info, $file, $type) { // Only fill this in if the .info file does not define a 'datestamp'. if (empty($info['datestamp'])) { $info['datestamp'] = filemtime($file->filename); diff --git modules/system/system.module modules/system/system.module index 3eb3656..14ab2ff 100644 --- modules/system/system.module +++ modules/system/system.module @@ -1906,7 +1906,7 @@ function _system_get_module_data() { // Invoke hook_system_info_alter() to give installed modules a chance to // modify the data in the .info files if necessary. - drupal_alter('system_info', $modules[$key]->info, $modules[$key]); + drupal_alter('system_info', $modules[$key]->info, $modules[$key], 'module'); } // The install profile is required. @@ -1999,7 +1999,7 @@ function _system_get_theme_data() { // Invoke hook_system_info_alter() to give installed modules a chance to // modify the data in the .info files if necessary. - drupal_alter('system_info', $themes[$key]->info, $themes[$key]); + drupal_alter('system_info', $themes[$key]->info, $themes[$key], 'theme'); if (!empty($themes[$key]->info['base theme'])) { $sub_themes[] = $key; @@ -2161,11 +2161,13 @@ function system_region_list($theme_key, $show = REGIONS_ALL) { /** * Implement hook_system_info_alter(). */ -function system_system_info_alter(&$info, $file) { +function system_system_info_alter(&$info, $file, $type) { // Remove page-top from the blocks UI since it is reserved for modules to // populate from outside the blocks system. - $info['regions_hidden'][] = 'page_top'; - $info['regions_hidden'][] = 'page_bottom'; + if ($type == 'theme') { + $info['regions_hidden'][] = 'page_top'; + $info['regions_hidden'][] = 'page_bottom'; + } } /** diff --git modules/toolbar/toolbar.install modules/toolbar/toolbar.install index 628893a..a4a48ab 100644 --- modules/toolbar/toolbar.install +++ modules/toolbar/toolbar.install @@ -27,7 +27,7 @@ function toolbar_install() { $items = array( 'node/add' => 'Add content', 'admin/content' => 'Find content', - 'admin' => 'Dashboard', + 'admin/dashboard' => 'Dashboard', ); $weight = -20; foreach ($items as $path => $title) { diff --git profiles/default/default.info profiles/default/default.info index 9b0ed6c..34eb20c 100644 --- profiles/default/default.info +++ profiles/default/default.info @@ -6,6 +6,7 @@ core = 7.x dependencies[] = block dependencies[] = color dependencies[] = comment +dependencies[] = dashboard dependencies[] = help dependencies[] = image dependencies[] = menu diff --git profiles/default/default.install profiles/default/default.install index b692c14..9ccd167 100644 --- profiles/default/default.install +++ profiles/default/default.install @@ -100,6 +100,26 @@ function default_install() { 'pages' => '', 'cache' => -1, ), + array( + 'module' => 'system', + 'delta' => 'management', + 'theme' => 'seven', + 'status' => 1, + 'weight' => 0, + 'region' => 'dashboard_main', + 'pages' => '', + 'cache' => -1, + ), + array( + 'module' => 'user', + 'delta' => 'new', + 'theme' => 'seven', + 'status' => 1, + 'weight' => 0, + 'region' => 'dashboard_sidebar', + 'pages' => '', + 'cache' => -1, + ), ); $query = db_insert('block')->fields(array('module', 'delta', 'theme', 'status', 'weight', 'region', 'pages', 'cache')); foreach ($values as $record) {