Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.1011
diff -u -p -r1.1011 common.inc
--- includes/common.inc	9 Oct 2009 16:33:13 -0000	1.1011
+++ includes/common.inc	10 Oct 2009 03:47:43 -0000
@@ -4931,7 +4931,9 @@ function drupal_common_theme() {
       'arguments' => array('link' => NULL, 'active' => FALSE),
     ),
     'menu_local_action' => array(
-      'arguments' => array('link' => NULL),
+      // @todo drupal_render() passes $elements instead of theme function
+      //   arguments in #properties when there is a single theme argument only.
+      'arguments' => array('link' => NULL, 'dummy' => NULL),
     ),
     'menu_local_tasks' => array(
       'arguments' => array(),
Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.350
diff -u -p -r1.350 menu.inc
--- includes/menu.inc	9 Oct 2009 08:02:24 -0000	1.350
+++ includes/menu.inc	10 Oct 2009 03:46:29 -0000
@@ -1357,7 +1357,7 @@ function theme_menu_link(array $variable
  */
 function theme_menu_local_task($variables) {
   $link = $variables['link'];
-  return '<li ' . ($variables['active'] ? 'class="active" ' : '') . '>' . l($link['title'], $link['href'], $link['localized_options']) . "</li>\n";
+  return '<li' . ($variables['active'] ? ' class="active"' : '') . '>' . l($link['title'], $link['href'], $link['localized_options']) . "</li>\n";
 }
 
 /**
@@ -1564,8 +1564,8 @@ function menu_local_tasks($level = 0) {
   $data = &drupal_static(__FUNCTION__);
   $root_path = &drupal_static(__FUNCTION__ . ':root_path', '');
   $empty = array(
-    'tabs' => array('count' => 0, 'output' => ''),
-    'actions' => array('count' => 0, 'output' => ''),
+    'tabs' => array('count' => 0, 'output' => array()),
+    'actions' => array('count' => 0, 'output' => array()),
     'root_path' => &$root_path,
   );
 
@@ -1606,8 +1606,8 @@ function menu_local_tasks($level = 0) {
     // equal the depth. Thus we use the $depth counter (offset by 1000 for ksort).
     $depth = 1001;
     while (isset($children[$path])) {
-      $tabs_current = '';
-      $actions_current = '';
+      $tabs_current = array();
+      $actions_current = array();
       $next_path = '';
       $tab_count = 0;
       $action_count = 0;
@@ -1620,17 +1620,27 @@ function menu_local_tasks($level = 0) {
             for ($p = $item['tab_parent']; $tasks[$p]['type'] == MENU_DEFAULT_LOCAL_TASK; $p = $tasks[$p]['tab_parent']);
             // Use the path of the parent instead.
             $link['href'] = $tasks[$p]['href'];
-            $tabs_current .= theme('menu_local_task', array('link' => $link, 'active' => TRUE));
+            $tabs_current[] = array(
+              '#theme' => 'menu_local_task',
+              '#link' => $link,
+              '#active' => TRUE,
+            );
             $next_path = $item['path'];
             $tab_count++;
           }
           else {
             if ($item['type'] == MENU_LOCAL_TASK) {
-              $tabs_current .= theme('menu_local_task', array('link' => $link));
+              $tabs_current[] = array(
+                '#theme' => 'menu_local_task',
+                '#link' => $link,
+              );
               $tab_count++;
             }
             else {
-              $actions_current .= theme('menu_local_action', array('link' => $link));
+              $actions_current[] = array(
+                '#theme' => 'menu_local_action',
+                '#link' => $link,
+              );
               $action_count++;
             }
           }
@@ -1650,7 +1660,7 @@ function menu_local_tasks($level = 0) {
     $current = $router_item;
     $depth = 1000;
     while (isset($children[$parent])) {
-      $tabs_current = '';
+      $tabs_current = array();
       $next_path = '';
       $next_parent = '';
       $count = 0;
@@ -1672,14 +1682,21 @@ function menu_local_tasks($level = 0) {
           }
           // We check for the active tab.
           if ($item['path'] == $path) {
-            $tabs_current .= theme('menu_local_task', array('link' => $link, 'active' => TRUE));
+            $tabs_current[] = array(
+              '#theme' => 'menu_local_task',
+              '#link' => $link,
+              '#active' => TRUE,
+            );
             $next_path = $item['tab_parent'];
             if (isset($tasks[$next_path])) {
               $next_parent = $tasks[$next_path]['tab_parent'];
             }
           }
           else {
-            $tabs_current .= theme('menu_local_task', array('link' => $link));
+            $tabs_current[] = array(
+              '#theme' => 'menu_local_task',
+              '#link' => $link,
+            );
           }
         }
       }
@@ -1694,6 +1711,9 @@ function menu_local_tasks($level = 0) {
     // Remove the depth, we are interested only in their relative placement.
     $tabs = array_values($tabs);
     $data['tabs'] = $tabs;
+
+    // Allow modules to alter local tasks or dynamically append further tasks.
+    drupal_alter('menu_local_tasks', $data, $router_item, $root_path);
   }
 
   if (isset($data['tabs'][$level])) {
@@ -1741,18 +1761,22 @@ function menu_tab_root_path() {
 }
 
 /**
- * Returns the rendered local tasks. The default implementation renders them as tabs.
+ * Returns renderable local tasks.
  *
  * @ingroup themeable
  */
 function theme_menu_local_tasks() {
-  $output = '';
+  $output = array();
 
   if ($primary = menu_primary_local_tasks()) {
-    $output .= "<ul class=\"tabs primary\">\n" . $primary . "</ul>\n";
+    $primary['#prefix'] = '<ul class="tabs primary">';
+    $primary['#suffix'] = '</ul>';
+    $output[] = $primary;
   }
   if ($secondary = menu_secondary_local_tasks()) {
-    $output .= "<ul class=\"tabs secondary\">\n" . $secondary . "</ul>\n";
+    $secondary['#prefix'] = '<ul class="tabs secondary">';
+    $secondary['#suffix'] = '</ul>';
+    $output[] = $secondary;
   }
 
   return $output;
Index: modules/menu/menu.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.api.php,v
retrieving revision 1.15
diff -u -p -r1.15 menu.api.php
--- modules/menu/menu.api.php	9 Oct 2009 08:02:24 -0000	1.15
+++ modules/menu/menu.api.php	10 Oct 2009 03:30:59 -0000
@@ -430,5 +430,68 @@ function hook_menu_delete($menu) {
 }
 
 /**
+ * Alter tabs and actions displayed on the page before they are rendered.
+ *
+ * This hook is invoked by menu_local_tasks(). The system-determined tabs and
+ * actions are passed in by reference. Additional tabs or actions may be added,
+ * or existing items altered.
+ *
+ * Each tab or action is an associative array containing:
+ * - #theme: The theme function to use to render.
+ * - #link: An associative array containing:
+ *   - title: The localized title of the link.
+ *   - href: The system path to link to.
+ *   - localized_options: An array of options to pass to url().
+ * - #active: Whether the link should be marked as 'active'.
+ *
+ * @param $data
+ *   An associative array containing:
+ *   - actions: An associative array containing:
+ *     - count: The amount of actions determined by the menu system, which can
+ *       be ignored.
+ *     - output: A list of of actions, each one being an associative array
+ *       as described above.
+ *   - tabs: An indexed array (list) of tab levels (up to 2 levels), each
+ *     containing an associative array:
+ *     - count: The amount of actions determined by the menu system. This value
+ *       does not need to be altered if there is more than one action.
+ *     - output: A list of of actions, each one being an associative array
+ *       as described above.
+ */
+function hook_menu_local_tasks_alter(&$data, $router_item, $root_path) {
+  // Add an action linking to node/add to all pages.
+  $data['actions']['output'][] = array(
+    '#theme' => 'menu_local_task',
+    '#link' => array(
+      'title' => t('Add new content'),
+      'href' => 'node/add',
+      'localized_options' => array(
+        'attributes' => array(
+          'title' => t('Add new content'),
+        ),
+      ),
+    ),
+  );
+
+  // Add a tab linking to node/add to all pages.
+  $data['tabs'][0]['output'][] = array(
+    '#theme' => 'menu_local_task',
+    '#link' => array(
+      'title' => t('Example tab'),
+      'href' => 'node/add',
+      'localized_options' => array(
+        'attributes' => array(
+          'title' => t('Add new content'),
+        ),
+      ),
+    ),
+    // Define whether this link is active. This can be omitted for
+    // implementations that add links to pages outside of the current page
+    // context.
+    '#active' => ($router_item['path'] == $root_path),
+  );
+}
+
+/**
  * @} End of "addtogroup hooks".
  */
Index: modules/system/page.tpl.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/page.tpl.php,v
retrieving revision 1.36
diff -u -p -r1.36 page.tpl.php
--- modules/system/page.tpl.php	9 Oct 2009 01:00:05 -0000	1.36
+++ modules/system/page.tpl.php	10 Oct 2009 03:30:59 -0000
@@ -31,14 +31,14 @@
  * - $secondary_menu (array): An array containing the Secondary menu links for
  *   the site, if they have been configured.
  * - $breadcrumb: The breadcrumb trail for the current page.
- * - $action_links: Actions local to the page, such as 'Add menu' on the menu
- *   administration interface.
+ * - $action_links (array): Actions local to the page, such as 'Add menu' on the
+ *   menu administration interface.
  *
  * Page content (in order of occurrence in the default page.tpl.php):
  * - $title: The page title, for use in the actual HTML content.
  * - $messages: HTML for status and error messages. Should be displayed prominently.
- * - $tabs: Tabs linking to any sub-pages beneath the current page (e.g., the view
- *   and edit tabs when displaying a node).
+ * - $tabs (array): Tabs linking to any sub-pages beneath the current page
+ *   (e.g., the view and edit tabs when displaying a node).
  * - $feed_icons: A string of all feed icons for the current page.
  *
  * Regions:
@@ -107,9 +107,9 @@
       <div id="content" class="column"><div class="section">
         <?php if ($page['highlight']): ?><div id="highlight"><?php print render($page['highlight']); ?></div><?php endif; ?>
         <?php if ($title): ?><h1 class="title" id="page-title"><?php print $title; ?></h1><?php endif; ?>
-        <?php if ($tabs): ?><div class="tabs"><?php print $tabs; ?></div><?php endif; ?>
+        <?php if ($tabs): ?><div class="tabs"><?php print render($tabs); ?></div><?php endif; ?>
         <?php print render($page['help']); ?>
-        <?php if ($action_links): ?><ul class="action-links"><?php print $action_links; ?></ul><?php endif; ?>
+        <?php if ($action_links): ?><ul class="action-links"><?php print render($action_links); ?></ul><?php endif; ?>
         <?php print render($page['content']); ?>
         <?php print $feed_icons; ?>
       </div></div> <!-- /.section, /#content -->
Index: themes/garland/page.tpl.php
===================================================================
RCS file: /cvs/drupal/drupal/themes/garland/page.tpl.php,v
retrieving revision 1.35
diff -u -p -r1.35 page.tpl.php
--- themes/garland/page.tpl.php	5 Oct 2009 02:43:01 -0000	1.35
+++ themes/garland/page.tpl.php	10 Oct 2009 03:30:59 -0000
@@ -33,11 +33,11 @@
           <?php if ($page['highlight']): ?><div id="highlight"><?php render($page['highlight']); ?></div><?php endif; ?>
           <?php if ($tabs): ?><div id="tabs-wrapper" class="clearfix"><?php endif; ?>
           <?php if ($title): ?><h2<?php print $tabs ? ' class="with-tabs"' : '' ?>><?php print $title ?></h2><?php endif; ?>
-          <?php if ($tabs): ?><ul class="tabs primary"><?php print $tabs ?></ul></div><?php endif; ?>
-          <?php if ($tabs2): ?><ul class="tabs secondary"><?php print $tabs2 ?></ul><?php endif; ?>
+          <?php if ($tabs): ?><ul class="tabs primary"><?php print render($tabs) ?></ul></div><?php endif; ?>
+          <?php if ($tabs2): ?><ul class="tabs secondary"><?php print render($tabs2) ?></ul><?php endif; ?>
           <?php if ($show_messages && $messages): print $messages; endif; ?>
           <?php print render($page['help']); ?>
-          <?php if ($action_links): ?><ul class="action-links"><?php print $action_links; ?></ul><?php endif; ?>
+          <?php if ($action_links): ?><ul class="action-links"><?php print render($action_links); ?></ul><?php endif; ?>
           <div class="clearfix">
             <?php print render($page['content']); ?>
           </div>
Index: themes/seven/page.tpl.php
===================================================================
RCS file: /cvs/drupal/drupal/themes/seven/page.tpl.php,v
retrieving revision 1.4
diff -u -p -r1.4 page.tpl.php
--- themes/seven/page.tpl.php	15 Sep 2009 17:10:39 -0000	1.4
+++ themes/seven/page.tpl.php	10 Oct 2009 03:43:25 -0000
@@ -4,11 +4,11 @@
   <div id="branding" class="clearfix">
     <?php print $breadcrumb; ?>
     <?php if ($title): ?><h1 class="page-title"><?php print $title; ?></h1><?php endif; ?>
-    <?php if ($primary_local_tasks): ?><ul class="tabs primary"><?php print $primary_local_tasks; ?></ul><?php endif; ?>
+    <?php if ($primary_local_tasks): ?><ul class="tabs primary"><?php print render($primary_local_tasks); ?></ul><?php endif; ?>
   </div>
 
   <div id="page">
-    <?php if ($secondary_local_tasks): ?><ul class="tabs secondary"><?php print $secondary_local_tasks; ?></ul><?php endif; ?>
+    <?php if ($secondary_local_tasks): ?><ul class="tabs secondary"><?php print render($secondary_local_tasks); ?></ul><?php endif; ?>
 
     <div id="content" class="clearfix">
       <?php if ($show_messages && $messages): ?>
@@ -19,7 +19,7 @@
           <?php print render($page['help']); ?>
         </div>
       <?php endif; ?>
-      <?php if ($action_links): ?><ul class="action-links"><?php print $action_links; ?></ul><?php endif; ?>
+      <?php if ($action_links): ?><ul class="action-links"><?php print render($action_links); ?></ul><?php endif; ?>
       <?php print render($page['content']); ?>
     </div>
 
