<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" /><style type="text/css"><!--
#msg dl { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fc0 solid; padding: 6px; }
#msg ul, pre { overflow: auto; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<title>[13177] trunk: Option validation error reporting.</title>
</head>
<body>

<div id="msg">
<dl>
<dt>Revision</dt> <dd><a href="http://trac.wordpress.org/changeset/13177">13177</a></dd>
<dt>Author</dt> <dd>ryan</dd>
<dt>Date</dt> <dd>2010-02-17 17:50:42 +0000 (Wed, 17 Feb 2010)</dd>
</dl>

<h3>Log Message</h3>
<pre>Option validation error reporting. Props jeremyclarke. see <a href="http://trac.wordpress.org/ticket/11474">#11474</a></pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkwpadminincludestemplatephp">trunk/wp-admin/includes/template.php</a></li>
<li><a href="#trunkwpadminoptionsheadphp">trunk/wp-admin/options-head.php</a></li>
<li><a href="#trunkwpadminoptionsphp">trunk/wp-admin/options.php</a></li>
<li><a href="#trunkwpincludesformattingphp">trunk/wp-includes/formatting.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkwpadminincludestemplatephp"></a>
<div class="modfile"><h4>Modified: trunk/wp-admin/includes/template.php (13176 => 13177)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-admin/includes/template.php        2010-02-17 13:14:45 UTC (rev 13176)
+++ trunk/wp-admin/includes/template.php        2010-02-17 17:50:42 UTC (rev 13177)
</span><span class="lines">@@ -2981,12 +2981,22 @@
</span><span class="cx"> /**
</span><span class="cx">  * Add a new section to a settings page.
</span><span class="cx">  *
</span><ins>+ * Part of the Settings API. Use this to define new settings sections for an admin page.
+ * Show settings sections in your admin page callback function with do_settings_sections().
+ * Add settings fields to your section with add_settings_field()
+ *
+ * The $callback argument should be the name of a function that echos out any
+ * content you want to show at the top of the settings section before the actual
+ * fields. It can output nothing if you want.
+ *
</ins><span class="cx">  * @since 2.7.0
</span><span class="cx">  *
</span><del>- * @param string $id String for use in the 'id' attribute of tags.
- * @param string $title Title of the section.
- * @param string $callback Function that fills the section with the desired content. The function should echo its output.
- * @param string $page The type of settings page on which to show the section (general, reading, writing, ...).
</del><ins>+ * @global $wp_settings_sections Storage array of all settings sections added to admin pages
+
+ * @param string $id Slug-name to identify the section. Used in the 'id' attribute of tags.
+ * @param string $title Formatted title of the section. Shown as the heading for the section. 
+ * @param string $callback Function that echo's out content for the section heading.
+ * @param string $page The slug-name of the settings page on which to show the section (general, reading, writing, ...).
</ins><span class="cx">  */
</span><span class="cx"> function add_settings_section($id, $title, $callback, $page) {
</span><span class="cx">         global $wp_settings_sections;
</span><span class="lines">@@ -3002,15 +3012,25 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> /**
</span><del>- * Add a new field to a settings page.
</del><ins>+ * Add a new field to a section of a settings page
</ins><span class="cx">  *
</span><ins>+ * Part of the Settings API. Use this to define a settings field that will show
+ * as part of a settings section inside a settings page. The fields are shown using
+ * do_settings_fields() in do_settings-sections()
+ *
+ * The $callback argument should be the name of a function that echoes out the
+ * html input tags for this setting field. Use get_option() to retrive existing
+ * values to show. 
+ *
</ins><span class="cx">  * @since 2.7.0
</span><span class="cx">  *
</span><del>- * @param string $id String for use in the 'id' attribute of tags.
- * @param string $title Title of the field.
- * @param string $callback Function that fills the field with the desired content. The function should echo its output.
- * @param string $page The type of settings page on which to show the field (general, reading, writing, ...).
- * @param string $section The section of the settingss page in which to show the box (default, ...).
</del><ins>+ * @global $wp_settings_fields Storage array of settings fields and info about their pages/sections
+ *
+ * @param string $id Slug-name to identify the field. Used in the 'id' attribute of tags.
+ * @param string $title Formatted title of the field. Shown as the label for the field during output.
+ * @param string $callback Function that fills the field with the desired form inputs. The function should echo its output.
+ * @param string $page The slug-name of the settings page on which to show the section (general, reading, writing, ...).
+ * @param string $section The slug-name of the section of the settingss page in which to show the box (default, ...).
</ins><span class="cx">  * @param array $args Additional arguments
</span><span class="cx">  */
</span><span class="cx"> function add_settings_field($id, $title, $callback, $page, $section = 'default', $args = array()) {
</span><span class="lines">@@ -3027,11 +3047,17 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> /**
</span><del>- * {@internal Missing Short Description}}
</del><ins>+ * Prints out all settings sections added to a particular settings page
</ins><span class="cx">  *
</span><ins>+ * Part of the Settings API. Use this in a settings page callback function
+ * to output all the sections and fields that were added to that $page with
+ * add_settings_section() and add_settings_field()
+ *
+ * @global $wp_settings_sections Storage array of all settings sections added to admin pages
+ * @global $wp_settings_fields Storage array of settings fields and info about their pages/sections
</ins><span class="cx">  * @since unknown
</span><span class="cx">  *
</span><del>- * @param unknown_type $page
</del><ins>+ * @param string $page The slug name of the page whos settings sections you want to output
</ins><span class="cx">  */
</span><span class="cx"> function do_settings_sections($page) {
</span><span class="cx">         global $wp_settings_sections, $wp_settings_fields;
</span><span class="lines">@@ -3051,12 +3077,18 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> /**
</span><del>- * {@internal Missing Short Description}}
</del><ins>+ * Print out the settings fields for a particular settings section
</ins><span class="cx">  *
</span><ins>+ * Part of the Settings API. Use this in a settings page to output
+ * a specific section. Should normally be called by do_settings_sections()
+ * rather than directly.
+ *
+ * @global $wp_settings_fields Storage array of settings fields and their pages/sections
+ *
</ins><span class="cx">  * @since unknown
</span><span class="cx">  *
</span><del>- * @param unknown_type $page
- * @param unknown_type $section
</del><ins>+ * @param string $page Slug title of the admin page who's settings fields you want to show.
+ * @param section $section Slug title of the settings section who's fields you want to show.
</ins><span class="cx">  */
</span><span class="cx"> function do_settings_fields($page, $section) {
</span><span class="cx">         global $wp_settings_fields;
</span><span class="lines">@@ -3078,6 +3110,131 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> /**
</span><ins>+ * Register a settings error to be displayed to the user
+ *
+ * Part of the Settings API. Use this to show messages to users about settings validation
+ * problems, missing settings or anything else.
+ *
+ * Settings errors should be added inside the $sanitize_callback function defined in
+ * register_setting() for a given setting to give feedback about the submission.
+ *
+ * By default messages will show immediately after the submission that generated the error.
+ * Additional calls to settings_errors() can be used to show errors even when the settings
+ * page is first accessed.
+ *
+ * @global array $wp_settings_errors Storage array of errors registered during this pageload
+ * 
+ * @param string $setting Slug title of the setting to which this error applies
+ * @param string $id Slug-name to identify the error. Used as part of 'id' attribute in HTML output.
+ * @param string $message The formatted message text to display to the user (will be shown inside styled &lt;div&gt; and &lt;p&gt;)
+ * @param string $type The type of message it is, controls HTML class. Use 'error' or 'updated'.
+ */
+function add_settings_error( $setting, $id, $message, $type = 'error' ) {
+        global $wp_settings_errors;
+
+        if ( !isset($wp_settings_errors) )
+                $wp_settings_errors = array();
+
+        $new_error = array(
+                'setting' =&gt; $setting,
+                'title' =&gt; $title, // @todo $title not defined. Use $id instead?
+                'message' =&gt; $message,
+                'type' =&gt; $type
+        );
+        $wp_settings_errors[] = $new_error;
+}
+
+/**
+ * Fetch settings errors registered by add_settings_error()
+ *
+ * Checks the $wp_settings_errors array for any errors declared during the current
+ * pageload and returns them.
+ *
+ * If changes were just submitted ($_GET['updated']) and settings errors were saved 
+ * to the 'settings_errors' transient then those errors will be returned instead. This
+ * is used to pass errors back across pageloads.
+ *
+ * Use the $sanitize argument to manually re-sanitize the option before returning errors.
+ * This is useful if you have errors or notices you want to show even when the user
+ * hasn't submitted data (i.e. when they first load an options page, or in admin_notices action hook)
+ *
+ * @global array $wp_settings_errors Storage array of errors registered during this pageload
+ *
+ * @param string $setting Optional slug title of a specific setting who's errors you want.
+ * @param boolean $sanitize Whether to re-sanitize the setting value before returning errors.
+ * @return array Array of settings errors
+ */
+function get_settings_errors( $setting = '', $sanitize = FALSE ) {
+        global $wp_settings_errors;
+
+        // If $sanitize is true, manually re-run the sanitizisation for this option
+        // This allows the $sanitize_callback from register_setting() to run, adding
+        // any settings errors you want to show by default.
+        if ( $sanitize )
+                sanitize_option( $setting, get_option($setting));
+
+        // If settings were passed back from options.php then use them
+        // Ignore transients if $sanitize is true, we dont' want the old values anyway
+        if ( isset($_GET['updated']) &amp;&amp; $_GET['updated'] &amp;&amp; get_transient('settings_errors') ) {
+                $settings_errors = get_transient('settings_errors');
+                delete_transient('settings_errors');
+        // Otherwise check global in case validation has been run on this pageload
+        } elseif ( count( $wp_settings_errors ) ) {
+                $settings_errors = $wp_settings_errors;
+        } else {
+                return;
+        }
+
+        // Filter the results to those of a specific setting if one was set
+        if ( $setting ) {
+                foreach ( (array) $settings_errors as $key =&gt; $details )
+                        if ( $setting != $details['setting'] )
+                                unset( $settings_errors[$key] );
+        }
+        return $settings_errors;
+}
+
+/**
+ * Display settings errors registered by add_settings_error()
+ *
+ * Part of the Settings API. Outputs a &lt;div&gt; for each error retrieved by get_settings_errors().
+ * 
+ * This is called automatically after a settings page based on the Settings API is submitted.
+ * Errors should be added during the validation callback function for a setting defined in register_setting()
+ *
+ * The $sanitize option is passed into get_settings_errors() and will re-run the setting sanitization
+ * on its current value.
+ *
+ * The $hide_on_update option will cause errors to only show when the settings page is first loaded.
+ * if the user has already saved new values it will be hidden to avoid repeating messages already
+ * shown in the default error reporting after submission. This is useful to show general errors like missing
+ * settings when the user arrives at the settings page.
+ *
+ * @param string $setting Optional slug title of a specific setting who's errors you want.
+ * @param boolean $sanitize Whether to re-sanitize the setting value before returning errors.
+ * @param boolean $hide_on_update If set to true errors will not be shown if the settings page has already been submitted.
+ * @return &lt;type&gt;
+ */
+function settings_errors ( $setting = '', $sanitize = FALSE, $hide_on_update = FALSE ) {
+
+        if ($hide_on_update AND $_GET['updated']) return;
+        
+        $settings_errors = get_settings_errors( $setting, $sanitize );
+
+        if ( !is_array($settings_errors) ) return;
+
+        $output = '';
+        foreach ( $settings_errors as $key =&gt; $details ) {
+                $css_id = 'setting-error-' . $details['title'];
+                $css_class = $details['type'] . ' fade settings-error';
+                $output .= &quot;&lt;div id='$css_id' class='$css_class'&gt; \n&quot;;
+                $output .= &quot;&lt;p&gt;&lt;strong&gt;{$details['message']}&lt;/strong&gt;&lt;/p&gt;&quot;;
+                $output .= &quot;&lt;/div&gt; \n&quot;;
+        }
+        echo $output;
+}
+
+/**
</ins><span class="cx">  * {@internal Missing Short Description}}
</span><span class="cx">  *
</span><span class="cx">  * @since unknown
</span></span></pre></div>
<a id="trunkwpadminoptionsheadphp"></a>
<div class="modfile"><h4>Modified: trunk/wp-admin/options-head.php (13176 => 13177)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-admin/options-head.php        2010-02-17 13:14:45 UTC (rev 13176)
+++ trunk/wp-admin/options-head.php        2010-02-17 17:50:42 UTC (rev 13177)
</span><span class="lines">@@ -10,8 +10,7 @@
</span><span class="cx">  */
</span><span class="cx"> 
</span><span class="cx"> wp_reset_vars(array('action', 'standalone', 'option_group_id'));
</span><del>-?&gt;
</del><span class="cx"> 
</span><del>-&lt;?php if (isset($_GET['updated'])) : ?&gt;
-&lt;div id=&quot;message&quot; class=&quot;updated&quot;&gt;&lt;p&gt;&lt;strong&gt;&lt;?php _e('Settings saved.') ?&gt;&lt;/strong&gt;&lt;/p&gt;&lt;/div&gt;
-&lt;?php endif; ?&gt;
</del><span class="cx">\ No newline at end of file
</span><ins>+settings_errors();
+
+?&gt;
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="trunkwpadminoptionsphp"></a>
<div class="modfile"><h4>Modified: trunk/wp-admin/options.php (13176 => 13177)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-admin/options.php        2010-02-17 13:14:45 UTC (rev 13176)
+++ trunk/wp-admin/options.php        2010-02-17 17:50:42 UTC (rev 13177)
</span><span class="lines">@@ -2,11 +2,14 @@
</span><span class="cx"> /**
</span><span class="cx">  * Options Management Administration Panel.
</span><span class="cx">  *
</span><del>- * Just allows for displaying of options.
</del><ins>+ * If accessed directly in a browser this page shows a list of all saved options
+ * along with editable fields for their values. Serialized data is not supported
+ * and there is no way to remove options via this page. It is not linked to from
+ * anywhere else in the admin.
</ins><span class="cx">  *
</span><del>- * This isn't referenced or linked to, but will show all of the options and
- * allow editing. The issue is that serialized data is not supported to be
- * modified. Options can not be removed.
</del><ins>+ * This file is also the target of the forms in core and custom options pages
+ * that use the Settings API. In this case it saves the new option values
+ * and returns the user to their page of origin.
</ins><span class="cx">  *
</span><span class="cx">  * @package WordPress
</span><span class="cx">  * @subpackage Administration
</span><span class="lines">@@ -76,6 +79,9 @@
</span><span class="cx"> 
</span><span class="cx"> switch($action) {
</span><span class="cx"> 
</span><ins>+/**
+ * If $_GET['action'] == 'update' we are saving settings sent from a settings page
+ */
</ins><span class="cx"> case 'update':
</span><span class="cx">         if ( isset($_POST[ 'option_page' ]) ) {
</span><span class="cx">                 $option_page = $_POST[ 'option_page' ];
</span><span class="lines">@@ -123,7 +129,18 @@
</span><span class="cx">                 }
</span><span class="cx">         }
</span><span class="cx"> 
</span><del>-        $goback = add_query_arg( 'updated', 'true', wp_get_referer() );
</del><ins>+        /**
+         *  Handle settings errors and return to options page
+         */
+        // If no settings errors were registered add a general 'updated' message.
+        if ( !count( get_settings_errors() ) )
+                add_settings_error('general', 'settings_updated', __('Settings saved.'), 'updated');
+        set_transient('settings_errors', get_settings_errors(), 30);
+
+        /**
+         * Redirect back to the settings page that was submitted
+         */
+        $goback = add_query_arg( 'updated', 'true',  wp_get_referer() );
</ins><span class="cx">         wp_redirect( $goback );
</span><span class="cx">         break;
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkwpincludesformattingphp"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/formatting.php (13176 => 13177)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/formatting.php        2010-02-17 13:14:45 UTC (rev 13176)
+++ trunk/wp-includes/formatting.php        2010-02-17 17:50:42 UTC (rev 13177)
</span><span class="lines">@@ -2380,7 +2380,9 @@
</span><span class="cx"> 
</span><span class="cx">         switch ($option) {
</span><span class="cx">                 case 'admin_email':
</span><del>-                        $value = sanitize_email($value);
</del><ins>+                        if ( !$value = sanitize_email($value) &amp;&amp; function_exists('add_settings_error') )
+                                add_settings_error('admin_email', 'invalid_admin_email', __('The E-Mail Address submitted was not in the right format. Please enter a valid Email Address'));
+
</ins><span class="cx">                         break;
</span><span class="cx"> 
</span><span class="cx">                 case 'thumbnail_size_w':
</span></span></pre>
</div>
</div>

</body>
</html>