Index: wp-includes/version.php
===================================================================
--- wp-includes/version.php	(revision 6704)
+++ wp-includes/version.php	(working copy)
@@ -16,6 +16,6 @@
  *
  * @global int $wp_db_version
  */
-$wp_db_version = 6124;
+$wp_db_version = 6689;
 
 ?>
\ No newline at end of file
Index: wp-includes/widgets.php
===================================================================
--- wp-includes/widgets.php	(revision 6704)
+++ wp-includes/widgets.php	(working copy)
@@ -221,10 +221,11 @@
 
 	$did_one = false;
 	foreach ( $sidebars_widgets[$index] as $id ) {
-		$callback = $wp_registered_widgets[$id]['callback'];
+		$params = array_merge(
+			array( array_merge( $sidebar, array('widget_id' => $id, 'widget_name' => $wp_registered_widgets[$id]['name']) ) ),
+			(array) $wp_registered_widgets[$id]['params']
+		);
 
-		$params = array_merge(array($sidebar), (array) $wp_registered_widgets[$id]['params']);
-
 		// Substitute HTML id and class attributes into before_widget
 		$classname_ = '';
 		foreach ( (array) $wp_registered_widgets[$id]['classname'] as $cn ) {
@@ -236,6 +237,10 @@
 		$classname_ = ltrim($classname_, '_');
 		$params[0]['before_widget'] = sprintf($params[0]['before_widget'], $id, $classname_);
 
+		$params = apply_filters( 'dynamic_sidebar_params', $params );
+
+		$callback = $wp_registered_widgets[$id]['callback'];
+
 		if ( is_callable($callback) ) {
 			call_user_func_array($callback, $params);
 			$did_one = true;
@@ -1003,9 +1008,6 @@
 	if ( isset($options[$number]['error']) && $options[$number]['error'] )
 		return;
 
-	$num_items = (int) $options[$number]['items'];
-	$show_summary = $options[$number]['show_summary'];
-	if ( empty($num_items) || $num_items < 1 || $num_items > 10 ) $num_items = 10;
 	$url = $options[$number]['url'];
 	while ( strstr($url, 'http') != $url )
 		$url = substr($url, 1);
@@ -1032,12 +1034,40 @@
 	else
 		$icon = get_option('siteurl').'/wp-includes/images/rss.png';
 	$title = "<a class='rsswidget' href='$url' title='" . attribute_escape(__('Syndicate this content')) ."'><img style='background:orange;color:white;border:none;' width='14' height='14' src='$icon' alt='RSS' /></a> <a class='rsswidget' href='$link' title='$desc'>$title</a>";
-?>
-		<?php echo $before_widget; ?>
-			<?php $title ? print($before_title . $title . $after_title) : null; ?>
-<?php
+
+	echo $before_widget;
+	echo $before_title . $title . $after_title;
+
+	wp_widget_rss_output( $rss, $options[$number] );
+
+	echo $after_widget;
+}
+
+function wp_widget_rss_output( $rss, $args = null ) {
+	if ( is_string( $rss ) ) {
+		require_once(ABSPATH . WPINC . '/rss.php');
+		if ( !$rss = fetch_rss($rss) )
+			return;
+	} elseif ( is_array($rss) && isset($rss['url']) ) {
+		require_once(ABSPATH . WPINC . '/rss.php');
+		$args = $rss;
+		if ( !$rss = fetch_rss($rss['url']) )
+			return;
+	} elseif ( !is_object($rss) ) {
+		return;
+	}
+
+	extract( $args, EXTR_SKIP );
+
+	$items = (int) $items;
+	if ( $items < 1 || 20 < $items )
+		$items = 10;
+	$show_summary  = (int) $show_summary;
+	$show_author   = (int) $show_author;
+	$show_date     = (int) $show_date;
+
 	if ( is_array( $rss->items ) && !empty( $rss->items ) ) {
-		$rss->items = array_slice($rss->items, 0, $num_items);
+		$rss->items = array_slice($rss->items, 0, $items);
 		echo '<ul>';
 		foreach ($rss->items as $item ) {
 			while ( strstr($item['link'], 'http') != $item['link'] )
@@ -1047,21 +1077,56 @@
 			if ( empty($title) )
 				$title = __('Untitled');
 			$desc = '';
+				if ( isset( $item['description'] ) && is_string( $item['description'] ) )
+					$desc = str_replace(array("\n", "\r"), ' ', attribute_escape(strip_tags(html_entity_decode($item['description'], ENT_QUOTES))));
+				elseif ( isset( $item['summary'] ) && is_string( $item['summary'] ) )
+					$desc = str_replace(array("\n", "\r"), ' ', attribute_escape(strip_tags(html_entity_decode($item['summary'], ENT_QUOTES))));
+
+			$summary = '';
+			if ( isset( $item['description'] ) && is_string( $item['description'] ) )
+				$summary = $item['description'];
+			elseif ( isset( $item['summary'] ) && is_string( $item['summary'] ) )
+				$summary = $item['summary'];
+
+			$desc = str_replace(array("\n", "\r"), ' ', attribute_escape(strip_tags(html_entity_decode($summary, ENT_QUOTES))));
+
 			if ( $show_summary ) {
-				$summary = '<div class="rssSummary">' . $item['description'] . '</div>';
+				$desc = '';
+				$summary = wp_specialchars( $summary );
+				$summary = "<div class='rssSummary'>$summary</div>";
 			} else {
-				if ( isset( $item['description'] ) && is_string( $item['description'] ) )
-					$desc = str_replace(array("\n", "\r"), ' ', attribute_escape(strip_tags(html_entity_decode($item['description'], ENT_QUOTES))));
 				$summary = '';
 			}
-			echo "<li><a class='rsswidget' href='$link' title='$desc'>$title</a>$summary</li>";
+
+			$date = '';
+			if ( $show_date ) {
+				if ( isset($item['pubdate']) )
+					$date = $item['pubdate'];
+				elseif ( isset($item['published']) )
+					$date = $item['published'];
+
+				if ( $date ) {
+					if ( $date_stamp = strtotime( $date ) )
+						$date = '<span class="rss-date">' . gmdate( get_option( 'date_format' ), $date_stamp ) . '</span>';
+					else
+						$date = '';
+				}
+			}
+
+			$author = '';
+			if ( $show_author ) {
+				if ( isset($item['dc']['creator']) )
+					$author = ' <cite>' . wp_specialchars( strip_tags( $item['dc']['creator'] ) ) . '</cite>';
+				elseif ( isset($item['author_name']) )
+					$author = ' <cite>' . wp_specialchars( strip_tags( $item['author_name'] ) ) . '</cite>';
+			}
+
+			echo "<li><a class='rsswidget' href='$link' title='$desc'>$title</a>{$date}{$summary}{$author}</li>";
 		}
 		echo '</ul>';
 	} else {
 		echo '<ul><li>' . __( 'An error has occurred; the feed is probably down. Try again later.' ) . '</li></ul>';
 	}
-
-	echo $after_widget;
 }
 
 function wp_widget_rss_control($widget_args) {
@@ -1082,7 +1147,7 @@
 		if ( isset($option['url']) )
 			$urls[$option['url']] = true;
 
-	if ( !$updated && !empty($_POST['sidebar']) ) {
+	if ( !$updated && 'POST' == $_SERVER['REQUEST_METHOD'] && !empty($_POST['sidebar']) ) {
 		$sidebar = (string) $_POST['sidebar'];
 
 		$sidebars_widgets = wp_get_sidebars_widgets();
@@ -1099,22 +1164,9 @@
 		}
 
 		foreach( (array) $_POST['widget-rss'] as $widget_number => $widget_rss ) {
-			$items = (int) $widget_rss['items'];
-			if ( $items < 1 )
-				$items = 10;
-			$url = sanitize_url(strip_tags(stripslashes($widget_rss['url'])));
-			$title = trim(strip_tags(stripslashes($widget_rss['title'])));
-
-			if ( !isset($urls[$url]) ) {
-				require_once(ABSPATH . WPINC . '/rss.php');
-				$rss = fetch_rss($url);
-				$error = false;
-				if ( !is_object($rss) ) {
-					$url = wp_specialchars(__('Error: could not find an RSS or ATOM feed at that URL.'), 1);
-					$error = sprintf(__('Error in RSS %1$d'), $widget_number );
-				}
-			}
-			$options[$widget_number] = compact( 'title', 'url', 'items', 'error' );
+			$widget_rss = stripslashes_deep( $widget_rss );
+			$url = sanitize_url(strip_tags($widget_rss['url']));
+			$options[$widget_number] = wp_widget_rss_process( $widget_rss, !isset($urls[$url]) );
 		}
 
 		update_option('widget_rss', $options);
@@ -1127,40 +1179,117 @@
 		$items = 10;
 		$error = false;
 		$number = '%i%';
+		$show_summary = 0;
+		$show_author = 0;
+		$show_date = 0;
 	} else {
-		$title = attribute_escape($options[$number]['title']);
-		$url = attribute_escape($options[$number]['url']);
-		$items = (int) $options[$number]['items'];
-		if ( $items < 1 )
-			$items = 10;
-		$error = $options[$number]['error'];
+		extract( $options[$number] );
 	}
 
+	wp_widget_rss_form( compact( 'number', 'title', 'url', 'items', 'error', 'show_summary', 'show_author', 'show_date' ) );
+}
+
+function wp_widget_rss_form( $args, $inputs = null ) {
+	$default_inputs = array( 'url' => true, 'title' => true, 'items' => true, 'show_summary' => true, 'show_author' => true, 'show_date' => true );
+	$inputs = wp_parse_args( $inputs, $default_inputs );
+	extract( $args );
+	$number = attribute_escape( $number );
+	$title  = attribute_escape( $title );
+	$url    = attribute_escape( $url );
+	$items  = (int) $items;
+	if ( $items < 1 || 20 < $items )
+		$items  = 10;
+	$show_summary   = (int) $show_summary;
+	$show_author    = (int) $show_author;
+	$show_date      = (int) $show_date;
+
+	if ( $inputs['url'] ) :
 ?>
-			<p>
-				<label for="rss-url-<?php echo $number; ?>"><?php _e('Enter the RSS feed URL here:'); ?>
-					<input class="widefat" id="rss-url-<?php echo $number; ?>" name="widget-rss[<?php echo $number; ?>][url]" type="text" value="<?php echo $url; ?>" />
-				</label>
-			</p>
-			<p>
-				<label for="rss-title-<?php echo $number; ?>"><?php _e('Give the feed a title (optional):'); ?>
-					<input class="widefat" id="rss-title-<?php echo $number; ?>" name="widget-rss[<?php echo $number; ?>][title]" type="text" value="<?php echo $title; ?>" />
-				</label>
-			</p>
-			<p>
-				<label for="rss-items-<?php echo $number; ?>"><?php _e('How many items would you like to display?'); ?>
-					<select id="rss-items-<?php echo $number; ?>" name="widget-rss[<?php echo $number; ?>][items]">
-						<?php
-							for ( $i = 1; $i <= 10; ++$i )
-								echo "<option value='$i' " . ( $items == $i ? "selected='selected'" : '' ) . ">$i</option>";
-						?>
-					</select>
-				</label>
-			</p>
-			<input type="hidden" id="rss-submit-<?php echo $number; ?>" name="rss-submit-<?php echo $number; ?>" value="1" />
+	<p>
+		<label for="rss-url-<?php echo $number; ?>"><?php _e('Enter the RSS feed URL here:'); ?>
+			<input class="widefat" id="rss-url-<?php echo $number; ?>" name="widget-rss[<?php echo $number; ?>][url]" type="text" value="<?php echo $url; ?>" />
+		</label>
+	</p>
+<?php endif; if ( $inputs['title'] ) : ?>
+	<p>
+		<label for="rss-title-<?php echo $number; ?>"><?php _e('Give the feed a title (optional):'); ?>
+			<input class="widefat" id="rss-title-<?php echo $number; ?>" name="widget-rss[<?php echo $number; ?>][title]" type="text" value="<?php echo $title; ?>" />
+		</label>
+	</p>
+<?php endif; if ( $inputs['items'] ) : ?>
+	<p>
+		<label for="rss-items-<?php echo $number; ?>"><?php _e('How many items would you like to display?'); ?>
+			<select id="rss-items-<?php echo $number; ?>" name="widget-rss[<?php echo $number; ?>][items]">
+				<?php
+					for ( $i = 1; $i <= 20; ++$i )
+						echo "<option value='$i' " . ( $items == $i ? "selected='selected'" : '' ) . ">$i</option>";
+				?>
+			</select>
+		</label>
+	</p>
+<?php endif; if ( $inputs['show_summary'] ) : ?>
+	<p>
+		<label for="rss-show-summary-<?php echo $number; ?>">
+			<input id="rss-show-summary-<?php echo $number; ?>" name="widget-rss[<?php echo $number; ?>][show_summary]" type="checkbox" value="1" <?php if ( $show_summary ) echo 'checked="checked"'; ?>/>
+			<?php _e('Display item content?'); ?>
+		</label>
+	</p>
+<?php endif; if ( $inputs['show_author'] ) : ?>
+	<p>
+		<label for="rss-show-author-<?php echo $number; ?>">
+			<input id="rss-show-author-<?php echo $number; ?>" name="widget-rss[<?php echo $number; ?>][show_author]" type="checkbox" value="1" <?php if ( $show_author ) echo 'checked="checked"'; ?>/>
+			<?php _e('Display item author if available?'); ?>
+		</label>
+	</p>
+<?php endif; if ( $inputs['show_date'] ) : ?>
+	<p>
+		<label for="rss-show-date-<?php echo $number; ?>">
+			<input id="rss-show-date-<?php echo $number; ?>" name="widget-rss[<?php echo $number; ?>][show_date]" type="checkbox" value="1" <?php if ( $show_date ) echo 'checked="checked"'; ?>/>
+			<?php _e('Display item date?'); ?>
+		</label>
+	</p>
+	<input type="hidden" id="rss-submit-<?php echo $number; ?>" name="rss-submit-<?php echo $number; ?>" value="1" />
 <?php
+	endif;
+	foreach ( array_keys($default_inputs) as $input ) :
+		if ( 'hidden' === $inputs[$input] ) :
+			$id = str_replace( '_', '-', $input );
+?>
+	<input type="hidden" id="rss-<?php echo $id; ?>-<?php echo $number; ?>" name="widget-rss[<?php echo $number; ?>][<?php echo $input; ?>]" value="<?php echo $$input; ?>" />
+<?php
+		endif;
+	endforeach;
 }
 
+// Expects unescaped data
+function wp_widget_rss_process( $widget_rss, $check_feed = true ) {
+	$items = (int) $widget_rss['items'];
+	if ( $items < 1 || 20 < $items )
+		$items = 10;
+	$url           = sanitize_url(strip_tags( $widget_rss['url'] ));
+	$title         = trim(strip_tags( $widget_rss['title'] ));
+	$show_summary  = (int) $widget_rss['show_summary'];
+	$show_author   = (int) $widget_rss['show_author'];
+	$show_date     = (int) $widget_rss['show_date'];
+
+	if ( $check_feed ) {
+		require_once(ABSPATH . WPINC . '/rss.php');
+		$rss = fetch_rss($url);
+		$error = false;
+		$link = '';
+		if ( !is_object($rss) ) {
+			$url = wp_specialchars(__('Error: could not find an RSS or ATOM feed at that URL.'), 1);
+			$error = sprintf(__('Error in RSS %1$d'), $widget_number );
+		} else {
+			$link = clean_url(strip_tags($rss->channel['link']));
+			while ( strstr($link, 'http') != $link )
+				$link = substr($link, 1);			
+		}
+	}
+
+	return compact( 'title', 'url', 'link', 'items', 'error', 'show_summary', 'show_author', 'show_date' );
+}
+
 function wp_widget_rss_register() {
 	if ( !$options = get_option('widget_rss') )
 		$options = array();
Index: wp-admin/wp-admin.css
===================================================================
--- wp-admin/wp-admin.css	(revision 6704)
+++ wp-admin/wp-admin.css	(working copy)
@@ -1017,7 +1017,7 @@
 	background: #464646 url('images/logo-ghost.png') no-repeat 20px 10px;
 	color: #999;
 	position: relative; 
- 	margin-top: -75px; 
+// 	margin-top: -75px; 
 }
 
 #footer a {
Index: wp-admin/includes/schema.php
===================================================================
--- wp-admin/includes/schema.php	(revision 6704)
+++ wp-admin/includes/schema.php	(working copy)
@@ -251,6 +251,7 @@
 	populate_roles_160();
 	populate_roles_210();
 	populate_roles_230();
+	populate_roles_250();
 }
 
 function populate_roles_160() {
@@ -390,4 +391,12 @@
 	}
 }
 
+function populate_roles_250() {
+	$role = get_role( 'administrator' );
+
+	if ( !empty( $role ) ) {
+		$role->add_cap( 'edit_dashboard' );
+	}
+}
+
 ?>
Index: wp-admin/includes/upgrade.php
===================================================================
--- wp-admin/includes/upgrade.php	(revision 6704)
+++ wp-admin/includes/upgrade.php	(working copy)
@@ -198,6 +198,9 @@
 	if ( $wp_current_db_version < 6124 )
 		upgrade_230_old_tables();
 
+	if ( $wp_current_db_version < 6689 )
+		upgrade_250();
+
 	maybe_disable_automattic_widgets();
 
 	$wp_rewrite->flush_rules();
@@ -716,6 +719,14 @@
 }
 
 
+function upgrade_250() {
+	global $wp_current_db_version;
+
+	if ( $wp_current_db_version < 6689 ) {
+		populate_roles_250();
+	}
+}
+
 // The functions we use to actually do stuff
 
 // General
Index: wp-admin/includes/dashboard.php
===================================================================
--- wp-admin/includes/dashboard.php	(revision 0)
+++ wp-admin/includes/dashboard.php	(revision 0)
@@ -0,0 +1,510 @@
+<?php
+
+// Registers dashboard widgets, handles POST data, sets up filters
+function wp_dashboard_setup() {
+	global $wpdb, $wp_dashboard_sidebars;
+	$update = false;
+	if ( !$widget_options = get_option( 'dashboard_widget_options' ) )
+		$widget_options = array();
+
+
+	/* Register WP Dashboard Dynamic Sidebar */
+	register_sidebar( array(
+		'name' => 'WordPress Dashboard',
+		'id' => 'wp_dashboard',
+		'before_widget' => "\t<div class='dashboard-widget-holder %2\$s' id='%1\$s'>\n\n\t\t<div class='dashboard-widget'>\n\n",
+		'after_widget' => "\t\t</div>\n\n\t</div>\n\n",
+		'before_title' => "\t\t\t<h3 class='dashboard-widget-title'>",
+		'after_title' => "</h3>\n\n"
+	) );
+
+
+	/* Register Widgets and Controls */
+
+	// Recent Comments Widget
+	if ( $mod_comments = $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->comments WHERE comment_approved = '0'") ) {
+		$notice = sprintf( __ngettext( '%d comment awaiting moderation', '%d comments awaiting moderation', $mod_comments ), $mod_comments );
+		$notice = "<a href='moderation.php'>$notice</a>";
+	} else {
+		$notice = '';
+	}
+	wp_register_sidebar_widget( 'dashboard_recent_comments', __( 'Recent Comments' ), 'wp_dashboard_recent_comments',
+		array( 'all_link' => 'edit-comments.php', 'notice' => $notice, 'width' => 'half' )
+	);
+
+
+	// Incoming Links Widget
+	if ( !isset( $widget_options['dashboard_incoming_links'] ) ) {
+		$update = true;
+		$widget_options['dashboard_incoming_links'] = array(
+			'link' => apply_filters( 'dashboard_incoming_links_link', 'http://blogsearch.google.com/blogsearch?hl=en&scoring=d&partner=wordpress&q=link:' . trailingslashit( get_option('home') ) ),
+			'url' => apply_filters( 'dashboard_incoming_links_feed', 'http://blogsearch.google.com/blogsearch_feeds?hl=en&scoring=d&ie=utf-8&num=10&output=rss&partner=wordpress&q=link:' . trailingslashit( get_option('home') ) ),
+			'items' => 5,
+			'show_date' => 0
+		);
+	}
+	wp_register_sidebar_widget( 'dashboard_incoming_links', __( 'Incoming Links' ), 'wp_dashboard_empty',
+		array( 'all_link' => $widget_options['dashboard_incoming_links']['link'], 'feed_link' => $widget_options['dashboard_incoming_links']['url'], 'width' => 'half' )
+	);
+	wp_register_widget_control( 'dashboard_incoming_links', __( 'Incoming Links' ), 'wp_dashboard_rss_control', array(),
+		array( 'widget_id' => 'dashboard_incoming_links', 'form_inputs' => array( 'title' => false, 'show_summary' => false, 'show_author' => false ) )
+	);
+
+
+	// WP Plugins Widget
+	wp_register_sidebar_widget( 'dashboard_plugins', __( 'Plugins' ), 'wp_dashboard_empty',
+		array( 'all_link' => 'http://wordpress.org/extend/plugins/', 'feed_link' => 'http://wordpress.org/extend/plugins/rss/', 'width' => 'half' )
+	);
+	wp_register_widget_control( 'dashboard_plugins', __( 'Plugins' ), 'wp_dashboard_empty', array(),
+		array( 'widget_id' => 'dashboard_plugins' )
+	);
+	
+
+	// Primary feed (Dev Blog) Widget
+	if ( !isset( $widget_options['dashboard_primary'] ) ) {
+		$update = true;
+		$widget_options['dashboard_primary'] = array(
+			'link' => apply_filters( 'dashboard_primary_link', 'http://wordpress.org/development/' ),
+			'url' => apply_filters( 'dashboard_primary_feed', 'http://wordpress.org/development/feed/' ),
+			'title' => apply_filters( 'dashboard_primary_title', __( 'WordPress Development Blog' ) ),
+			'items' => 2,
+			'show_summary' => 1,
+			'show_author' => 0,
+			'show_date' => 1
+		);
+	}
+	wp_register_sidebar_widget( 'dashboard_primary', $widget_options['dashboard_primary']['title'], 'wp_dashboard_empty',
+		array( 'all_link' => $widget_options['dashboard_primary']['link'], 'feed_link' => $widget_options['dashboard_primary']['url'], 'width' => 'half', 'class' => 'widget_rss' )
+	);
+	wp_register_widget_control( 'dashboard_primary', __( 'Primary Feed' ), 'wp_dashboard_rss_control', array(),
+		array( 'widget_id' => 'dashboard_primary' )
+	);
+
+
+	// Secondary Feed (Planet) Widget
+	if ( !isset( $widget_options['dashboard_secondary'] ) ) {
+		$update = true;
+		$widget_options['dashboard_secondary'] = array(
+			'link' => apply_filters( 'dashboard_secondary_link', 'http://planet.wordpress.org/' ),
+			'url' => apply_filters( 'dashboard_secondary_feed', 'http://planet.wordpress.org/feed/' ),
+			'title' => apply_filters( 'dashboard_secondary_title', __( 'Other WordPress News' ) ),
+			'items' => 15
+		);
+	}
+	wp_register_sidebar_widget( 'dashboard_secondary', $widget_options['dashboard_secondary']['title'], 'wp_dashboard_empty', 
+		array( 'all_link' => $widget_options['dashboard_secondary']['link'], 'feed_link' => $widget_options['dashboard_secondary']['url'], 'width' => 'full' )
+	);
+	wp_register_widget_control( 'dashboard_secondary', __( 'Secondary Feed' ), 'wp_dashboard_rss_control', array(),
+		array( 'widget_id' => 'dashboard_secondary', 'form_inputs' => array( 'show_summary' => false, 'show_author' => false, 'show_date' => false ) )
+	);
+
+
+	// Hook to register new widgets
+	do_action( 'wp_dashboard_setup' );
+
+	// Hard code the sidebar's widgets and order
+	$dashboard_widgets = array( 'dashboard_recent_comments', 'dashboard_incoming_links', 'dashboard_primary', 'dashboard_plugins', 'dashboard_secondary' );
+
+	// Filter widget order
+	$dashboard_widgets = apply_filters( 'wp_dashboard_widgets', $dashboard_widgets );
+
+	$wp_dashboard_sidebars = array( 'wp_dashboard' => $dashboard_widgets );
+
+	add_filter( 'dynamic_sidebar_params', 'wp_dashboard_dynamic_sidebar_params' );
+
+	if ( 'POST' == $_SERVER['REQUEST_METHOD'] && isset($_POST['widget_id']) ) {
+		ob_start(); // hack - but the same hack wp-admin/widgets.php uses
+		wp_dashbaord_trigger_widget_control( $_POST['widget_id'] );
+		ob_end_clean();
+		wp_redirect( remove_query_arg( 'edit' ) );
+		exit;
+	}
+
+	if ( $update )
+		update_option( 'dashboard_widget_options', $widget_options );
+}
+
+// Echoes out the dashboard
+function wp_dashboard() {
+	echo "<div id='dashboard-widgets'>\n\n";
+
+	// We're already filtering dynamic_sidebar_params obove
+	add_filter( 'option_sidebars_widgets', 'wp_dashboard_sidebars_widgets' ); // here there be hackery
+	dynamic_sidebar( 'wp_dashboard' );
+	remove_filter( 'option_sidebars_widgets', 'wp_dashboard_sidebars_widgets' );
+
+	echo "<br class='clear' />\n</div>\n\n\n";
+}
+
+// Makes sidebar_widgets option reflect the dashboard settings
+function wp_dashboard_sidebars_widgets() { // hackery
+	return $GLOBALS['wp_dashboard_sidebars'];
+}
+
+// Modifies sidbar params on the fly to set up ids, class names, titles for each widget (called once per widget)
+// Switches widget to edit mode if $_GET['edit']
+function wp_dashboard_dynamic_sidebar_params( $params ) {
+	global $wp_registered_widgets, $wp_registered_widget_controls;
+
+	extract( $params[0], EXTR_PREFIX_ALL, 'sidebar' );
+	extract( $wp_registered_widgets[$sidebar_widget_id], EXTR_PREFIX_ALL, 'widget' );
+
+	$the_classes = array();
+	if ( in_array($widget_width, array( 'third', 'fourth', 'full' ) ) )
+		$the_classes[] = $widget_width;
+
+	if ( 'double' == $widget_height )
+		$the_classes[] = 'double';
+
+	if ( $widget_class )
+		$the_classes[] = $widget_class;
+
+	// Add classes to the widget holder
+	if ( $the_classes )
+		$sidebar_before_widget = str_replace( "<div class='dashboard-widget-holder ", "<div class='dashboard-widget-holder " . join( ' ', $the_classes ) . ' ', $sidebar_before_widget );
+
+	$links = array();
+	if ( $widget_all_link )
+		$links[] = '<a href="' . clean_url( $widget_all_link ) . '">' . __( 'See&nbsp;All' ) . '</a>';
+
+	$content_class = 'dashboard-widget-content';
+	if ( current_user_can( 'edit_dashboard' ) && isset($wp_registered_widget_controls[$widget_id]) && is_callable($wp_registered_widget_controls[$widget_id]['callback']) ) {
+		// Switch this widget to edit mode
+		if ( isset($_GET['edit']) && $_GET['edit'] == $widget_id ) {
+			$content_class .= ' dashboard-widget-control';
+			$wp_registered_widgets[$widget_id]['callback'] = 'wp_dashboard_empty';
+			$sidebar_widget_name = $wp_registered_widget_controls[$widget_id]['name'];
+			$params[1] = $widget_id;
+			$sidebar_before_widget .= '<form action="' . remove_query_arg( 'edit' )  . '" method="post">';
+			$sidebar_after_widget   = "<div class='dashboard-widget-submit'><input type='hidden' name='sidebar' value='wp_dashboard' /><input type='hidden' name='widget_id' value='$widget_id' /><input type='submit' value='" . __( 'Save' ) . "' /></div></form>$sidebar_after_widget";
+			$links[] = '<a href="' . remove_query_arg( 'edit' ) . '">' . __( 'Cancel' ) . '</a>';
+		} else {
+			$links[] = '<a href="' . add_query_arg( 'edit', $widget_id ) . "#$widget_id" . '">' . __( 'Edit' ) . '</a>';
+		}
+	}
+
+	if ( $widget_feed_link )
+		$links[] = '<img class="rss-icon" src="' . get_option( 'siteurl' ) . '/' . WPINC . '/images/rss.png" alt="' . __( 'rss icon' ) . '" /> <a href="' . clean_url( $widget_feed_link ) . '">' . __( 'RSS' ) . '</a>';
+
+	$links = apply_filters( "wp_dashboard_widget_links_$widget_id", $links );
+
+	// Add links to widget's title bar
+	if ( $links ) {
+		$sidebar_before_title .= '<span>';
+		$sidebar_after_title   = '</span><small>' . join( '&nbsp;|&nbsp;', $links ) . "</small><br class='clear' />$sidebar_after_title";
+	}
+
+	// Could have put this in widget-content.  Doesn't really matter
+	if ( $widget_notice )
+		$sidebar_after_title .= "\t\t\t<div class='dashboard-widget-notice'>$widget_notice</div>\n\n";
+
+	if ( $widget_error )
+		$sidebar_after_title .= "\t\t\t<div class='dashboard-widget-error'>$widget_error</div>\n\n";
+
+	$sidebar_after_title .= "\t\t\t<div class='$content_class'>\n\n";
+
+	$sidebar_after_widget .= "\t\t\t</div>\n\n";
+
+	foreach( array_keys( $params[0] ) as $key )
+		$$key = ${'sidebar_' . $key};
+
+	$params[0] = compact( array_keys( $params[0] ) );
+
+	return $params;
+}
+
+
+/* Dashboard Widgets */
+
+function wp_dashboard_recent_comments( $sidebar_args ) {
+	global $comment;
+
+	extract( $sidebar_args, EXTR_SKIP );
+
+	echo $before_widget;
+
+	echo $before_title;
+	echo $widget_name;
+	echo $after_title;
+
+	$lambda = create_function( '', 'return 5;' );
+	add_filter( 'option_posts_per_rss', $lambda ); // hack - comments query doesn't accept per_page parameter
+	$comments_query = new WP_Query('feed=rss2&withcomments=1');
+	remove_filter( 'option_posts_per_rss', $lambda );
+
+	$is_first = true;
+
+	if ( $comments_query->have_comments() ) {
+		while ( $comments_query->have_comments() ) { $comments_query->the_comment();
+
+			$comment_post_url = get_permalink( $comment->comment_post_ID );
+			$comment_post_title = get_the_title( $comment->comment_post_ID );
+			$comment_post_link = "<a href='$comment_post_url'>$comment_post_title</a>";
+			$comment_link = '<a class="comment-link" href="' . get_comment_link() . '">#</a>';
+			$comment_meta = sprintf( __( 'From <strong>%s</strong> on %s %s' ), get_comment_author(), $comment_post_link, $comment_link );
+
+			if ( $is_first ) : $is_first = false;
+?>
+				<blockquote><?php comment_text(); ?></blockquote>
+				<p class='comment-meta'><?php echo $comment_meta; ?></p>
+
+				<ul id="dashboard-comments-list">
+<?php
+			else :
+?>
+
+					<li class='comment-meta'><?php echo $comment_meta; ?></li>
+<?php
+			endif;
+		}
+?>
+
+				</ul>
+
+<?php
+
+	}
+
+	echo $after_widget;
+}
+
+// Empty widget used for JS/AJAX created output.  Also used when widget is in edit mode.
+function wp_dashboard_empty( $sidebar_args, $widget_control_id = false ) {
+	extract( $sidebar_args, EXTR_SKIP );
+
+	echo $before_widget;
+
+	echo $before_title;
+	echo $widget_name;
+	echo $after_title;
+
+	if ( $widget_control_id ) // If in edit mode
+		wp_dashbaord_trigger_widget_control( $widget_control_id );
+
+	echo $after_widget;
+}
+
+/* Dashboard Widgets Controls. Ssee also wp_dashboard_empty() */
+
+// Calls widget_control callback
+function wp_dashbaord_trigger_widget_control( $widget_control_id = false ) {
+	global $wp_registered_widget_controls;
+	if ( is_scalar($widget_control_id) && $widget_control_id && isset($wp_registered_widget_controls[$widget_control_id]) && is_callable($wp_registered_widget_controls[$widget_control_id]['callback']) )
+		call_user_func_array( $wp_registered_widget_controls[$widget_control_id]['callback'], $wp_registered_widget_controls[$widget_control_id]['params'] );
+}
+
+// Sets up $args to be used as input to wp_widget_rss_form(), handles POST data from RSS-type widgets
+function wp_dashboard_rss_control( $args ) {
+	extract( $args );
+	if ( !$widget_id )
+		return false;
+
+	if ( !$widget_options = get_option( 'dashboard_widget_options' ) )
+		$widget_options = array();
+
+	if ( !isset($widget_options[$widget_id]) )
+		$widget_options[$widget_id] = array();
+
+	$number = 1; // Hack to use wp_widget_rss_form()
+	$widget_options[$widget_id]['number'] = $number;
+
+	if ( 'POST' == $_SERVER['REQUEST_METHOD'] && isset($_POST['widget-rss'][$number]) ) {
+		$_POST['widget-rss'][$number] = stripslashes_deep( $_POST['widget-rss'][$number] );
+		$widget_options[$widget_id] = wp_widget_rss_process( $_POST['widget-rss'][$number] );
+		// title is optional.  If black, fill it if possible
+		if ( !$widget_options[$widget_id]['title'] && isset($_POST['widget-rss'][$number]['title']) ) {
+			require_once(ABSPATH . WPINC . '/rss.php');
+			$rss = fetch_rss($widget_options[$widget_id]['url']);
+			$widget_options[$widget_id]['title'] = htmlentities(strip_tags($rss->channel['title']));
+		}
+		update_option( 'dashboard_widget_options', $widget_options );
+	}
+
+	wp_widget_rss_form( $widget_options[$widget_id], $form_inputs );
+}
+
+
+// Move this into wp-admin.css
+function temp_dashboard_css() {
+?>
+<style type="text/css">
+/* <![CDATA[ */
+
+div#dashboard-widgets {
+	position: relative;
+	left: 20px;
+	margin-left: -20px;
+}
+
+div.dashboard-widget-holder {
+	margin-top: 20px;
+	width: 50%;
+	float: left;
+}
+
+div.dashboard-widget-holder.third {
+	width: 33.3%;
+}
+
+div.dashboard-widget-holder.fourth {
+	width: 25%;
+}
+
+div.dashboard-widget-holder.full {
+	width: 100%;
+}
+
+div.dashboard-widget-holder.double div.dashboard-widget {
+	height: 52em;
+	padding-bottom: 28px /* lame */
+}
+
+div.dashboard-widget {
+	position: relative;
+	margin-right: 20px;
+	border: 1px solid #ccc;
+	padding: 2px;
+	height: 26em;
+	overflow: auto;
+}
+
+h3.dashboard-widget-title  {
+	background-color: #eaf3fa;
+	margin: 0;
+	position: relative;
+	padding: 0 10px;
+	font-size: 1.2em;
+	line-height: 2;
+}
+
+h3.dashboard-widget-title span {
+	background-color: #eaf3fa;
+	display: block;
+	text-align: left;
+	float: left;
+}
+
+h3.dashboard-widget-title small {
+	background-color: #eaf3fa;
+	display: block;
+	text-align: right;
+	float: right;
+	font-size: 75%;
+	line-height: 2.67; /* math: it works */
+	margin-top: 2px;
+}
+
+h3.dashboard-widget-title img.rss-icon {
+	vertical-align: middle;
+}
+
+div.dashboard-widget-notice {
+	background-color: #cfe1ef;
+	padding: 0 20px;
+	font-size: 1.2em;
+	line-height: 2;
+}
+
+div.dashboard-widget-error {
+	background-color: #c43;
+	padding: 0 20px;
+	font-size: 1.2em;
+	line-height: 2;
+}
+
+div.dashboard-widget-content {
+	margin: 10px 15px;
+}
+
+div.dashboard-widget-submit {
+	border-top: 1px solid #ccc;
+	margin: 10px 15px;
+	padding-top: 10px;
+}
+div.dashboard-widget-content ul, div.dashboard-widget-content ol, div.dashboard-widget-content dl {
+	text-indent: 0;
+	padding-left: 15px;
+}
+
+div.dashboard-widget-content blockquote {
+	margin: 0 0 -1em;
+}
+
+div.dashboard-widget-content .comment-meta {
+	font-size: 95%;
+}
+
+#dashboard_secondary div.dashboard-widget-content ul {
+	list-style: none;
+	padding: 0;
+	margin: 0;
+}
+
+#dashboard_secondary div.dashboard-widget-content ul li {
+	display: block;
+	width: 20%;
+	height: 6em;
+	padding-bottom: 20px;
+	margin: 0;
+	float: left;
+	font-size: 95%;
+}
+
+#dashboard_secondary div.dashboard-widget-content {
+	margin: 10px 5px;
+	padding: 0;
+}
+
+#dashboard_secondary div.dashboard-widget-content ul li .post {
+	display:block;
+	font-family:Georgia,"Times New Roman",Times,serif;
+	font-size:18px;
+	height:60px;
+	overflow:hidden;
+}
+
+#dashboard_secondary div.dashboard-widget-content ul li a {
+	background:#DDDDDD none repeat scroll 0%;
+	display:block;
+	height:100%;
+	overflow:hidden;
+	margin: 10px;
+	padding: .5em;
+}
+
+#dashboard_secondary div.dashboard-widget-content ul li a cite {
+	display: block;
+}
+
+#dashboard-widgets .widget_rss ul {
+	list-style: none;
+	padding: 0;
+	margin: 0;
+}
+
+#dashboard-widgets .widget_rss ul li {
+	clear: both;
+}
+
+#dashboard-widgets .widget_rss ul li span.rss-date {
+	float: left;
+	margin: .5em 0 1em;
+}
+
+#dashboard-widgets .widget_rss ul li a {
+	float: left;
+	margin: .5em .5em 1em 0;
+	font-weight: bold;
+}
+
+#dashboard-widgets .widget_rss ul li div {
+	clear: both;
+}
+/* ]]> */
+</style>
+<?php
+}
+
+add_action( 'admin_head', 'temp_dashboard_css' );
+
+?>
Index: wp-admin/index.php
===================================================================
--- wp-admin/index.php	(revision 6704)
+++ wp-admin/index.php	(working copy)
@@ -1,13 +1,19 @@
 <?php
+
 require_once('admin.php');
 
+require( './includes/dashboard.php' );
+
+wp_dashboard_setup();
+
 function index_js() {
 ?>
 <script type="text/javascript">
 	jQuery(function() {
-		jQuery('#incominglinks').load('index-extra.php?jax=incominglinks');
-		jQuery('#devnews').load('index-extra.php?jax=devnews');
-//		jQuery('#planetnews').load('index-extra.php?jax=planetnews');
+		jQuery('#dashboard_incoming_links div.dashboard-widget-content').not( '.dashboard-widget-control' ).load('index-extra.php?jax=incominglinks');
+		jQuery('#dashboard_primary div.dashboard-widget-content').not( '.dashboard-widget-control' ).load('index-extra.php?jax=devnews');
+		jQuery('#dashboard_secondary div.dashboard-widget-content').not( '.dashboard-widget-control' ).load('index-extra.php?jax=planetnews');
+		jQuery('#dashboard_plugins div.dashboard-widget-content').not( '.dashboard-widget-control' ).html( 'TODO' );
 	});
 </script>
 <?php
@@ -77,94 +83,12 @@
 $widgets_text = sprintf( __ngettext( '%d widget', '%d widgets', $num_widgets ), $num_widgets );
 ?>
 <p><?php printf( __( 'You are using %1$s theme with %2$s.' ), $ct->title, "<a href='widgets.php'>$widgets_text</a>" ); ?> <a href="themes.php" class="rbutton"><?php _e('Change Theme'); ?></a> You're using BetaPress TODO.</p>
-<p>
 <?php do_action( 'rightnow_end' ); ?>
 <?php do_action( 'activity_box_end' ); ?>
-</div>
+</div><!-- rightnow -->
 
-<div id="dashboard-widgets">
+<?php wp_dashboard(); ?>
 
-<div class="dashboard-widget">
-<div class="dashboard-widget-edit"><a href=""><?php _e('See All'); ?></a> | <a href=""><?php _e('Edit'); ?></a></div>
-<h3>Recent Comments</h3>
+</div><!-- wrap -->
 
-<?php
-$comments = $wpdb->get_results("SELECT comment_author, comment_author_url, comment_ID, comment_post_ID FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 5");
-$numcomments = $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->comments WHERE comment_approved = '0'");
-
-if ( $comments || $numcomments ) :
-?>
-
-<?php if ( $numcomments ) : ?>
-<p><strong><a href="moderation.php"><?php echo sprintf(__('Comments in moderation (%s) &raquo;'), number_format_i18n($numcomments) ); ?></a></strong></p>
-<?php endif; ?>
-
-<ul>
-<?php
-if ( $comments ) {
-foreach ($comments as $comment) {
-	echo '<li>' . sprintf(__('%1$s on %2$s'), get_comment_author_link(), '<a href="'. get_permalink($comment->comment_post_ID) . '#comment-' . $comment->comment_ID . '">' . get_the_title($comment->comment_post_ID) . '</a>');
-	edit_comment_link(__("Edit"), ' <small>(', ')</small>');
-	echo '</li>';
-}
-}
-?>
-</ul>
-<?php endif; ?>
-</div>
-
-
-<div class="dashboard-widget">
-<?php
-$more_link = apply_filters( 'dashboard_incoming_links_link', 'http://blogsearch.google.com/blogsearch?hl=en&scoring=d&partner=wordpress&q=link:' . trailingslashit( get_option('home') ) );
-?>
-<div class="dashboard-widget-edit"><a href="<?php echo htmlspecialchars( $more_link ); ?>"><?php _e('See All'); ?></a> | <a href=""><?php _e('Edit'); ?></a></div>
-<h3><?php _e('Incoming Links'); ?></h3>
-
-<div id="incominglinks"></div>
-</div>
-
-<div class="dashboard-widget">
-<?php
-$recentposts = $wpdb->get_results("SELECT ID, post_title FROM $wpdb->posts WHERE post_type = 'post' AND " . get_private_posts_cap_sql('post') . " AND post_date_gmt < '$today' ORDER BY post_date DESC LIMIT 5");
-?>
-<div class="dashboard-widget-edit"><a href="<?php echo htmlspecialchars( $more_link ); ?>"><?php _e('See All'); ?></a> | <a href=""><?php _e('Edit'); ?></a></div>
-<h3>Recent Posts</h3>
-
-<ul>
-<?php
-foreach ($recentposts as $post) {
-	if ($post->post_title == '')
-		$post->post_title = sprintf(__('Post #%s'), $post->ID);
-	echo "<li><a href='post.php?action=edit&amp;post=$post->ID'>";
-	the_title();
-	echo '</a></li>';
-}
-?>
-</ul>
-</div>
-
-<div class="dashboard-widget">
-<div class="dashboard-widget-edit"><a href="<?php echo htmlspecialchars( $more_link ); ?>"><?php _e('See All'); ?></a> | <a href=""><?php _e('Edit'); ?></a> | <a href=""><?php _e('RSS'); ?></a></div>
-<h3><?php echo apply_filters( 'dashboard_primary_title', __('Blog') ); ?></h3>
-
-<div id="devnews"></div>
-</div>
-
-<?php do_action( 'dashboard_widgets' ); ?>
-
-<p><a href=""><?php _e('Customize this page'); ?></a>.</p>
-
-</div>
-
-
-<div id="planetnews"></div>
-
-<div style="clear: both">&nbsp;
-<br clear="all" />
-</div>
-</div>
-
-<?php
-require('./admin-footer.php');
-?>
+<?php require('./admin-footer.php'); ?>
Index: wp-admin/index-extra.php
===================================================================
--- wp-admin/index-extra.php	(revision 6704)
+++ wp-admin/index-extra.php	(working copy)
@@ -4,23 +4,65 @@
 
 @header('Content-Type: ' . get_option('html_type') . '; charset=' . get_option('blog_charset'));
 
+$widgets = get_option( 'dashboard_widget_options' );
+
+
 switch ( $_GET['jax'] ) {
 
 case 'incominglinks' :
-
-$rss_feed = apply_filters( 'dashboard_incoming_links_feed', 'http://blogsearch.google.com/blogsearch_feeds?hl=en&scoring=d&ie=utf-8&num=10&output=rss&partner=wordpress&q=link:' . trailingslashit( get_option('home') ) );
-
-
-$rss = @fetch_rss( $rss_feed );
+@extract( @$widgets['dashboard_incoming_links'], EXTR_SKIP );
+$rss = @fetch_rss( $url );
 if ( isset($rss->items) && 1 < count($rss->items) ) { // Technorati returns a 1-item feed when it has no results
 ?>
 
 <ul>
 <?php
-$rss->items = array_slice($rss->items, 0, 10);
+$rss->items = array_slice($rss->items, 0, $items);
 foreach ($rss->items as $item ) {
+	$publisher = '';
+	$site_link = '';
+	$link = '';
+	$content = '';
+	$date = '';
+	$link = clean_url( strip_tags( $item['link'] ) );
+
+	if ( isset( $item['author_uri'] ) )
+		$site_link = clean_url( strip_tags( $item['author_uri'] ) );
+
+	if ( !$publisher = wp_specialchars( strip_tags( isset($item['dc']['publisher']) ? $item['dc']['publisher'] : $item['author_name'] ) ) )
+		$publisher = __( 'Somebody' );
+	if ( $site_link )
+		$publisher = "<a href='$site_link'>$publisher</a>";
+	else
+		$publisher = "<strong>$publisher</strong>";
+
+	if ( isset($item['description']) )
+		$content = $item['description'];
+	elseif ( isset($item['summary']) )
+		$content = $item['summary'];
+	elseif ( isset($item['atom_content']) )
+		$content = $item['atom_content'];
+	else
+		$content = __( 'something' );
+	$content = strip_tags( $content );
+	if ( 50 < strlen($content) )
+		$content = substr($content, 0, 50) . ' ...';
+	$content = wp_specialchars( $content );
+	if ( $link )
+		$text = _c( '%1$s linked here <a href="%2$s">saying</a>, "%3$s"|feed_display' );
+	else
+		$text = _c( '%1$s linked here saying, "%3$s"|feed_display' );
+
+	if ( $show_date ) {
+		if ( $show_author || $show_summary )
+			$text .= _c( ' on %4$s|feed_display' );
+		$date = wp_specialchars( strip_tags( isset($item['pubdate']) ? $item['pubdate'] : $item['published'] ) );
+		$date = strtotime( $date );
+		$date = gmdate( get_option( 'date_format' ), $date );
+	}
+
 ?>
-	<li><a href="<?php echo wp_filter_kses($item['link']); ?>"><?php echo wptexturize(wp_specialchars($item['title'])); ?></a></li>
+	<li><?php printf( _c( "$text|feed_display" ), $publisher, $link, $content, $date ); ?></li>
 <?php } ?>
 </ul>
 <?php
@@ -32,30 +74,17 @@
 break;
 
 case 'devnews' :
-$rss = @fetch_rss(apply_filters( 'dashboard_primary_feed', 'http://wordpress.org/development/feed/' ));
-if ( isset($rss->items) && 0 != count($rss->items) ) {
-
-$rss->items = array_slice($rss->items, 0, 2);
-foreach ($rss->items as $item ) {
-?>
-<h4><a href='<?php echo wp_filter_kses($item['link']); ?>'><?php echo wp_specialchars($item['title']); ?></a> <?php gmdate( 'Y/m/d', strtotime( $item['pubdate'] ) ); ?></h4>
-<p><?php echo $item['description']; ?></p>
-<?php
-	}
-}
-?>
-
-<?php
+wp_widget_rss_output( $widgets['dashboard_primary'] );
 break;
 
 case 'planetnews' :
-$rss = @fetch_rss(apply_filters( 'dashboard_secondary_feed', 'http://planet.wordpress.org/feed/' ));
+extract( $widgets['dashboard_secondary'], EXTR_SKIP );
+$rss = @fetch_rss( $url );
 if ( isset($rss->items) && 0 != count($rss->items) ) {
 ?>
-<h3><?php echo apply_filters( 'dashboard_secondary_title', __('Other WordPress News') ); ?></h3>
 <ul>
 <?php
-$rss->items = array_slice($rss->items, 0, 20);
+$rss->items = array_slice($rss->items, 0, $items);
 foreach ($rss->items as $item ) {
 $title = wp_specialchars($item['title']);
 $author = preg_replace( '|(.+?):.+|s', '$1', $item['title'] );
@@ -66,7 +95,6 @@
 	}
 ?>
 </ul>
-<p class="readmore"><a href="<?php echo apply_filters( 'dashboard_secondary_link', 'http://planet.wordpress.org/' ); ?>"><?php _e('Read more &raquo;'); ?></a></p>
 <?php
 }
 break;
