<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>error Archives - Alexandros Georgiou</title>
	<atom:link href="https://www.alexgeorgiou.gr/tag/error/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.alexgeorgiou.gr/tag/error/</link>
	<description>Balancing brackets for a living</description>
	<lastBuildDate>Wed, 20 Dec 2023 10:32:48 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://www.alexgeorgiou.gr/wp-content/uploads/2021/07/cropped-alexgeorgiou-icon-32x32.png</url>
	<title>error Archives - Alexandros Georgiou</title>
	<link>https://www.alexgeorgiou.gr/tag/error/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>auto_increment flag repair on primary keys of a WordPress MySQL database</title>
		<link>https://www.alexgeorgiou.gr/repair-auto_increment-primary-key-wordpress-mysql/</link>
					<comments>https://www.alexgeorgiou.gr/repair-auto_increment-primary-key-wordpress-mysql/#comments</comments>
		
		<dc:creator><![CDATA[alexg]]></dc:creator>
		<pubDate>Sun, 18 Dec 2016 12:52:02 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[auto_increment]]></category>
		<category><![CDATA[backup]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[error]]></category>
		<category><![CDATA[export]]></category>
		<category><![CDATA[import]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[mysqldump]]></category>
		<category><![CDATA[primary key]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[wordpress]]></category>
		<guid isPermaLink="false">http://www.alexgeorgiou.gr/?p=173</guid>

					<description><![CDATA[<p>Here's some code to restore the auto_increment flag on the primary keys of a WordPress MySQL database after a faulty export/import cycle.</p>
<p>The post <a href="https://www.alexgeorgiou.gr/repair-auto_increment-primary-key-wordpress-mysql/">auto_increment flag repair on primary keys of a WordPress MySQL database</a> appeared first on <a href="https://www.alexgeorgiou.gr">Alexandros Georgiou</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p><em>Here&#8217;s some code that I used to restore the auto_increment flag on the primary keys of my WordPress MySQL database after a somewhat faulty export/import cycle.</em></p>



<h2 class="wp-block-heading">The auto_increment problem and its symptoms</h2>



<p>When you export the WordPress database and then import it again, either via <code>phpmyadmin</code> or via <code>mysqldump</code> and the <code>mysql</code> CLI, all sorts of things can (and often will) go wrong. This can be understandably very stressful, especially on a live system. Please take a deep breath and read on.</p>



<p>I did a dump of my (fortunately development) environment and then imported it again. At first glance, all was working perfectly.</p>



<p>Then, all of a sudden I noticed that the <strong>Administrator</strong> user had no rights to create posts or pages. The usual <strong>Publish</strong> button was replaced with <strong>Submit for review</strong>.</p>



<p>Naïvely my first thought was to install <a href="https://wordpress.org/plugins/user-role-editor/" target="_blank" rel="noopener noreferrer">a plugin to fix roles and capabilities</a>. Sure enough, the admin user had no right to create posts or pages. When I tried to give those rights, I saw the following in my logs:</p>



<pre class="wp-block-preformatted">PHP message: WordPress database error Duplicate entry '0' for key 'PRIMARY' for query INSERT INTO `wp_usermeta` (`user_id`, `meta_key`, `meta_value`) VALUES (1, 'wp_capabilities', 'a:1:{s:13:\"administrator\";b:1;}') made by require_once('wp-admin/admin.php'), do_action('users_page_users-user-role-editor'), User_Role_Editor-&gt;edit_roles, Ure_Lib-&gt;editor, Ure_Lib-&gt;process_user_request, Ure_Lib-&gt;permissions_object_update, Ure_Lib-&gt;update_user, WP_User-&gt;add_role, update_user_meta, update_metadata, add_metadata</pre>



<p>(I have debug logs enabled in my development environment. If you&#8217;re in a production environment you might not see this.)</p>



<p>Why would a primary key be set to <code>0</code> you ask? A quick glance at the structure of the <code>wp_usermeta</code> table via <code>phpmyadmin</code> reveals that the primary key column had no <code>auto_increment</code> flag.</p>



<p>SQL <code>INSERT</code> statements from various plugins were inserting rows in various tables with the primary key being undefined (and therefore set to a default of <code>0</code>). Since primary keys have a unique constraint, attempting to do a second insert to the same table fails, causing all sorts of havoc.</p>



<p><strong>For some reason the <code>auto_increment</code> flag had not been preserved when I re-imported the SQL dump.</strong> Everything else seemed to be in order though. I did not investigate why this happened but decided to simply fix this.</p>



<h2 class="wp-block-heading">Coding is the solution</h2>



<p>By now I could see the front-end but was not able to login to the admin interface any more. Any <code>INSERT</code> query to the database, including those that store session information upon login, were failing. As I had quite a lot of tables, I decided not to do this manually, but to write a generic script.</p>



<p>As a side-note, the script needs to connect with the <code>NO_ZERO_DATE</code> SQL mode. WordPress uses a lot of <code>DATETIME</code> fields with a default value of <code>0000-00-00 00:00:00</code> and this script will be <strong>very unhappy</strong> if this mode is not set.</p>



<h3 class="wp-block-heading">Pseudocode</h3>



<pre class="wp-block-preformatted">for all tables in database
&nbsp;&nbsp;&nbsp; get the primary key column's name and type
&nbsp;&nbsp;&nbsp; get the next available primary key value
&nbsp;&nbsp;&nbsp; change the row with zero primary key so it has the next available primary key
&nbsp;&nbsp;&nbsp; set the auto_increment flag to the primary key column</pre>



<p><strong>Note: The above assumes that all the primary keys are numeric. YMMV.</strong></p>



<h3 class="wp-block-heading">What to do:</h3>



<p>Next is a PHP listing of the above solution. Here&#8217;s what to do:</p>



<ol class="wp-block-list">
<li><strong>Check to see that your issue is actually one of missing auto_increment flags.</strong> This script will only repair this particular error.</li>



<li><strong>Check to see that all your primary keys are numeric.</strong> There shouldn&#8217;t be an issue if some aren&#8217;t but you might have to hack the code manually or go update the structure of those tables via <code>phpmyadmin</code>.</li>



<li><strong>Change the host, dbname, username and password in the code to those that match your system.</strong></li>



<li><strong>Backup your database</strong> (I guess you already have a backup and that&#8217;s what caused the issue, but still, you want to be able to go back if something goes wrong when running this.) You are solely responsible for any damages including data loss from running this script. Don&#8217;t blame me for corrupting your data please. Read and understand the code first!</li>
</ol>



<h3 class="wp-block-heading">The PHP script</h3>



<p>Save this in a file, take another deep breath, and run it via the PHP CLI. I hope it solves your MySQL woes. Good luck buddy.</p>



<pre class="wp-block-preformatted">&lt;?php

// change these settings
$servername = 'localhost';
$username = 'dbuser';
$password = 'password';
$db = 'database';

// connect
$conn = new mysqli($servername, $username, $password);
try {
&nbsp;&nbsp; &nbsp;$conn = new PDO("mysql:host=$servername;dbname=$db", $username, $password, array(PDO::MYSQL_ATTR_INIT_COMMAND =&gt; 'SET sql_mode="NO_ZERO_DATE"') );
&nbsp;&nbsp; &nbsp;$conn-&gt;setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
&nbsp;&nbsp; &nbsp;echo "Connected successfully";
} catch(PDOException $e) {
&nbsp;&nbsp; &nbsp;exit( "Connection failed: " . $e-&gt;getMessage() );
}

// get all table names
$stm = $conn-&gt;prepare('SHOW TABLES');
$stm-&gt;execute();
$table_names = array();
foreach ( $stm-&gt;fetchAll() as $row ) {
&nbsp;&nbsp; &nbsp;$table_names[] = $row[0];
}

// for all tables
foreach ( $table_names as $table_name ) {
&nbsp;&nbsp; &nbsp;echo "\nRepairing table $table_name...\n";

&nbsp;&nbsp; &nbsp;// get the primary key name
&nbsp;&nbsp; &nbsp;$stm = $conn-&gt;prepare( "show keys from $table_name where Key_name = 'PRIMARY'" );
&nbsp;&nbsp; &nbsp;$stm-&gt;execute();
&nbsp;&nbsp; &nbsp;$key_name = $stm-&gt;fetch()['Column_name'];

&nbsp;&nbsp; &nbsp;// get the primary key type
&nbsp;&nbsp; &nbsp;$stm = $conn-&gt;prepare( "show fields from $table_name where Field = '$key_name'" );
&nbsp;&nbsp; &nbsp;$stm-&gt;execute();
&nbsp;&nbsp; &nbsp;$key_type = $stm-&gt;fetch()['Type'];

&nbsp;&nbsp; &nbsp;// if there is a primary key
&nbsp;&nbsp; &nbsp;if ($key_name) {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;echo "Primary key is $key_name\n";

&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;try {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;// if auto_increment was missing there might be a row with key=0 . compute the next available primary key
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;$sql = "select (ifnull( max($key_name), 0)+1) as next_id from $table_name";
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;$stm = $conn-&gt;prepare( $sql );
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;echo "$sql\n";
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;$stm-&gt;execute();
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;$next_id = $stm-&gt;fetch()['next_id'];

&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;// give a sane primary key to a row that has key = 0 if it exists
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;$sql = "update $table_name set $key_name = $next_id where $key_name = 0";
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;echo "$sql\n";
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;$stm = $conn-&gt;prepare( $sql );
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;$stm-&gt;execute();

&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;// set auto_increment to the primary key
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;$sql = "alter table $table_name modify column $key_name $key_type auto_increment";
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;echo "$sql\n";
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;$stm = $conn-&gt;prepare( $sql );
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;$stm-&gt;execute();

&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;} catch (PDOException $e) {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;echo "\n\nQuery: $sql\nError:" . $e-&gt;getMessage() . "\n\n";
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;} else {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;echo "No primary key found for table $table_name.\n";
&nbsp;&nbsp; &nbsp;}
}
echo "\n\nFinished\n";
$conn = null;

</pre>



<p>If I have helped you get your site up and running, you can donate a few Satoshis at: <a href="bitcoin:bc1qjkgp8u2jwy2n9k20avjweuy7etsjfpfplvf99q">bc1qjkgp8u2jwy2n9k20avjweuy7etsjfpfplvf99q</a></p>
<p>The post <a href="https://www.alexgeorgiou.gr/repair-auto_increment-primary-key-wordpress-mysql/">auto_increment flag repair on primary keys of a WordPress MySQL database</a> appeared first on <a href="https://www.alexgeorgiou.gr">Alexandros Georgiou</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.alexgeorgiou.gr/repair-auto_increment-primary-key-wordpress-mysql/feed/</wfw:commentRss>
			<slash:comments>41</slash:comments>
		
		
			</item>
		<item>
		<title>🗨 Dismissible notices that persist when refreshing the WordPress admin screens</title>
		<link>https://www.alexgeorgiou.gr/persistently-dismissible-notices-wordpress/</link>
					<comments>https://www.alexgeorgiou.gr/persistently-dismissible-notices-wordpress/#comments</comments>
		
		<dc:creator><![CDATA[alexg]]></dc:creator>
		<pubDate>Mon, 28 Nov 2016 12:15:47 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[admin screens]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[dismiss]]></category>
		<category><![CDATA[dismissable]]></category>
		<category><![CDATA[dismissible]]></category>
		<category><![CDATA[error]]></category>
		<category><![CDATA[error handling]]></category>
		<category><![CDATA[error reporting]]></category>
		<category><![CDATA[notification]]></category>
		<category><![CDATA[popup]]></category>
		<category><![CDATA[wordpress]]></category>
		<guid isPermaLink="false">http://www.alexgeorgiou.gr/?p=154</guid>

					<description><![CDATA[<p>In this article we will go through some code that I like to use to make dismissible  notices where the dismissal persists between page refreshes in the WordPress administration screens.</p>
<p>The post <a href="https://www.alexgeorgiou.gr/persistently-dismissible-notices-wordpress/">🗨 Dismissible notices that persist when refreshing the WordPress admin screens</a> appeared first on <a href="https://www.alexgeorgiou.gr">Alexandros Georgiou</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><em>In this article we will go through some code that I like to use to make dismissible  notices where the dismissal persists between page refreshes in the WordPress administration screens.</em></p>
<p>&nbsp;</p>
<h2><img fetchpriority="high" decoding="async" class="aligncenter wp-image-163 size-full" title="Dismissible notices that persist when refreshing the WordPress admin screens" src="http://www.alexgeorgiou.gr/wp-content/uploads/2016/11/wp-notices-1.png" alt="Make dismissible admin notices where the dismissal persists between page refreshes in the WordPress administration screens." width="963" height="480" srcset="https://www.alexgeorgiou.gr/wp-content/uploads/2016/11/wp-notices-1.png 963w, https://www.alexgeorgiou.gr/wp-content/uploads/2016/11/wp-notices-1-300x150.png 300w, https://www.alexgeorgiou.gr/wp-content/uploads/2016/11/wp-notices-1-768x383.png 768w, https://www.alexgeorgiou.gr/wp-content/uploads/2016/11/wp-notices-1-800x400.png 800w" sizes="(max-width: 599px) calc(100vw - 50px), (max-width: 767px) calc(100vw - 70px), (max-width: 991px) 429px, (max-width: 1199px) 637px, 354px" /></h2>
<h2>Some things to note about dismissible notices</h2>
<p>Don&#8217;t you just hate how this functionality has <strong>still</strong> not made it into core? <a href="https://make.wordpress.org/core/2015/04/23/spinners-and-dismissible-admin-notices-in-4-2/" target="_blank" rel="noopener noreferrer">Legend has it</a> that dismissible notices were introduced back in WordPress 4.2, but that only means that when you click the little &#8220;x&#8221; at the top right, the notice box becomes hidden. The way to enable this is that you add a <code>.is-dismissible</code> class into your notice&#8217;s markup:</p>
<pre>&lt;div class="notice notice-warn is-dismissible"&gt;your message here&lt;/div&gt;</pre>
<p>And that hides the box. Until the next page refresh. This is not only annoying, it also goes <a href="https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/" target="_blank" rel="noopener noreferrer">against the WordPress.org guidelines</a>:</p>
<blockquote><p>Upgrade prompts, notices, and alerts should be limited in scope and used sparingly or only on the plugin’s setting page. Any site wide notices or embedded dashboard widgets <em>must</em> be dismissible. Error messages and alerts should include information on how to resolve the situation, and remove themselves when completed.</p></blockquote>
<p>So for those of us aspiring to be hosted on <strong>WordPress.org</strong> it&#8217;s a necessity, rather than a luxury feature, to be able to let the user dismiss notices persistently.</p>
<h2>Requirements analysis</h2>
<p>WordPress tradition has it that you <strong>store an option in the database</strong>. That way you know not to show the same notice again. But this is something that you have to do over and over again, so it&#8217;s exactly the kind of thing that you want to <strong>include in all your code as a library</strong>. You want something that can be <strong>easily called from wherever</strong>, so the tried and true singleton pattern will do. You need the ability to be able to <strong>assign a slug to your notice</strong>, so you know how to store it in the options table. But you also want to be able to <strong>show the occasional non-dismissible notice</strong>. Finally, let&#8217;s be nice and <strong>clean up after ourselves</strong>. The options will be deleted when our plugin is uninstalled.</p>
<h2>Let&#8217;s do this!</h2>
<h3>Writing code with class</h3>
<p>We need something that can be included from our plugin (or theme). Let&#8217;s make a singleton class that can hold arrays of different types of notices (success, info, warn, error):</p>
<pre>&lt;?php

// don't load directly
defined( 'ABSPATH' ) || die( '-1' );

if ( ! class_exists( 'MyPlugin_Admin_Notices' ) ) {

    class MyPlugin_Admin_Notices {

        private static $_instance;
        private $admin_notices;
        const TYPES = 'error,warning,info,success';

        private function __construct() {
            $this-&gt;admin_notices = new stdClass();
            foreach ( explode( ',', self::TYPES ) as $type ) {
                $this-&gt;admin_notices-&gt;{$type} = array();
            }
        }

        public static function get_instance() {
            if ( ! ( self::$_instance instanceof self ) ) {
                self::$_instance = new self();
            }
            return self::$_instance;
        }
    }
}

MyPlugin_Admin_Notices::get_instance();</pre>
<p>Nice. This is a useful bucket where we can throw in notices. They will be retrieved for rendering only later, when we will add code to the <code>admin_notices</code> action.</p>
<h3>An API for entering notices from our code</h3>
<p>For now, let&#8217;s add in some methods to our class for populating our notice arrays:</p>
<pre>public function error( $message, $dismiss_option = false ) {
    $this-&gt;notice( 'error', $message, $dismiss_option );
}

public function warning( $message, $dismiss_option = false ) {
    $this-&gt;notice( 'warning', $message, $dismiss_option );
}

public function success( $message, $dismiss_option = false ) {
    $this-&gt;notice( 'success', $message, $dismiss_option );
}

public function info( $message, $dismiss_option = false ) {
    $this-&gt;notice( 'info', $message, $dismiss_option );
}

private function notice( $type, $message, $dismiss_option ) {
    $notice = new stdClass();
    $notice-&gt;message = $message;
    $notice-&gt;dismiss_option = $dismiss_option;

    $this-&gt;admin_notices-&gt;{$type}[] = $notice;
}</pre>
<p><strong>Notice the visibility of the functions.</strong> The four public functions that comprise our API all defer to the private function that does the data collection. We&#8217;ll let the user enter a message string, and optionally give a slug with the <code>dismiss_option</code> parameter. If set, this will be part of the database option&#8217;s slug.</p>
<h3>Writing the markup</h3>
<p>We have a mechanism for adding notices into our memory, now let&#8217;s dump them to the admin area as markup. This will happen on the <code>admin_notices</code> action, so first add this to the constructor:</p>
<pre>add_action( 'admin_notices', array( &amp;$this, 'action_admin_notices' ) );</pre>
<p>and then let&#8217;s actually write the markup. I&#8217;ve chosen to show notices in decreasing levels of severity, hence the nested loops:</p>
<pre>public function action_admin_notices() {
    foreach ( explode( ',', self::TYPES ) as $type ) {
        foreach ( $this-&gt;admin_notices-&gt;{$type} as $admin_notice ) {

            $dismiss_url = add_query_arg( array(
                'myplugin_dismiss' =&gt; $admin_notice-&gt;dismiss_option
            ), admin_url() );

            if ( ! get_option( "myplugin_dismissed_{$admin_notice-&gt;dismiss_option}" ) ) {
                ?&gt;&lt;div
                    class="notice myplugin-notice notice-&lt;?php echo $type;

                    if ( $admin_notice-&gt;dismiss_option ) {
                        echo ' is-dismissible" data-dismiss-url="' . esc_url( $dismiss_url );
                    } ?&gt;"&gt;

                    &lt;h2&gt;&lt;?php echo "My Plugin $type"; ?&gt;&lt;/h2&gt;
                    &lt;p&gt;&lt;?php echo $admin_notice-&gt;message; ?&gt;&lt;/p&gt;

                &lt;/div&gt;&lt;?php
            }
        }
    }
}</pre>
<p><strong>Note how we show a notice only if the DB does not have a corresponding option</strong> named <code>myplugin_dismissed_$dismiss_option</code>. We&#8217;ll need a mechanism to set that DB option when the user clicks to dismiss the notice. We will do this from JavaScript, making a call with that option name as a GET parameter, so that we know to set the correct database option.</p>
<h3>Notifying the backend from the admin front</h3>
<p>When a notice is dismissible, we output a <code>data-dismiss-url</code> attribute in the HTML. We&#8217;ll use that from JavaScript to make a call to that URL:</p>
<pre>/**
 * Admin code for dismissing notifications.
 *
 */
(function( $ ) {
    'use strict';
    $( function() {
        $( '.myplugin-notice' ).on( 'click', '.notice-dismiss', function( event, el ) {
            var $notice = $(this).parent('.notice.is-dismissible');
            var dismiss_url = $notice.attr('data-dismiss-url');
            if ( dismiss_url ) {
                $.get( dismiss_url );
            }
        });
    } );
})( jQuery );</pre>
<p>Pretty standard stuff. When a dismissible notice from our plugin is clicked on its <code>.notice-dismiss</code>, get the <code>dismiss_url</code> and call it. I guess I could have used a fancy framework like <a href="http://vanilla-js.com/" target="_blank" rel="noopener noreferrer">VanillaJS</a> for this, but I chose plain old jQuery instead :-p</p>
<p>Don&#8217;t forget the usual enqueue script shenanigans:</p>
<pre>public function action_admin_enqueue_scripts() {
    wp_enqueue_script( 'jquery' );
    wp_enqueue_script(
        'myplugin-notify',
        plugins_url( 'assets/scripts/myplugin-notify.js', __FILE__ ),
        array( 'jquery' )
    );
}</pre>
<p>and add this to our constructor so the enqueuing will actually happen:</p>
<pre>add_action( 'admin_enqueue_scripts', array( &amp;$this, 'action_admin_enqueue_scripts' ) );</pre>
<p>Perfect. Our little piece of JavaScript code now lives in the WordPress admin screens.</p>
<h3>Setting the DB option</h3>
<p>Now we&#8217;ll just need to catch the request on the PHP side and set a database option. Let&#8217;s do this on the <code>admin_init</code> action:</p>
<pre>add_action( 'admin_init', array( &amp;$this, 'action_admin_init' ) );</pre>
<p>&#8230;and this is the code that will set an option in the database. Note how the name is constructed from the GET parameter in the request URL.</p>
<pre>public function action_admin_init() {
    $dismiss_option = filter_input( INPUT_GET, 'myplugin_dismiss', FILTER_SANITIZE_STRING );
    if ( is_string( $dismiss_option ) ) {
        update_option( "myplugin_dismissed_$dismiss_option", true );
        wp_die();
    }
}</pre>
<p>Once the option is set, we can just let WordPress die. No need to return the admin interface to the browser, this is just a background AJAX call.</p>
<h3>Cleaning up after ourselves</h3>
<p>Unfortunately, even in this day and age, many developers don&#8217;t think it&#8217;s important to clean up the trash they leave in the poor user&#8217;s database. This is even more infuriating when you consider that it rarely takes more than a couple of lines of code. Don&#8217;t be that guy. Create an <code>uninstall.php</code> file with the following content.</p>
<pre>&lt;?php
if ( defined( 'WP_UNINSTALL_PLUGIN' ) ) {
    global $wpdb;
    $wpdb-&gt;query( 'DELETE FROM wp_options WHERE option_name LIKE "myplugin_dismissed_%";' );
}</pre>
<p>We can exploit the fact that we know the common prefix of all the dismissal DB options, and thus we can delete them all in one go, circumventing the <code><a href="https://codex.wordpress.org/Function_Reference/delete_option" target="_blank" rel="noopener noreferrer">delete_option</a>()</code> function altogether:</p>
<h3>Expecting the unexpected</h3>
<p><strong>If you put together all of the above you already have a nice way to show persistent dismissible admin notices.</strong> But why stop there? We can hack away some more and make sure that any runtime errors in our code show up as notices too.</p>
<p>Imagine a clueless user sees your theme not working. Which do you prefer? To have to explain to them how to enable debugging and how to find and send you the logs from the wordpress unix directory on the host? Or do you simply ask them to copy the error on the screen and send it to you via email?</p>
<p>Observe this little hack that takes advantage of the <a href="http://php.net/manual/en/function.set-error-handler.php" target="_blank" rel="noopener noreferrer">PHP error reporting mechanism</a>:</p>
<pre>public static function error_handler( $errno, $errstr, $errfile, $errline, $errcontext ) {
    if ( ! ( error_reporting() &amp; $errno ) ) {
        // This error code is not included in error_reporting
        return;
    }

    $message = "errstr: $errstr, errfile: $errfile, errline: $errline, PHP: " . PHP_VERSION . " OS: " . PHP_OS;

    $self = self::get_instance();
    switch ($errno) {
        case E_USER_ERROR:
            $self-&gt;error( $message );
            break;

        case E_USER_WARNING:
            $self-&gt;warning( $message );
            break;

        case E_USER_NOTICE:
        default:
            $self-&gt;notice( $message );
            break;
    }

    // write to wp-content/debug.log if logging enabled
    error_log( $message );

    // Don't execute PHP internal error handler
    return true;
}</pre>
<p>Notice how this method is static, since we&#8217;d like to be able to call it from both dynamic and static contexts. We can now hook it up to PHP&#8217;s error reporting like so:</p>
<pre><a href="http://php.net/manual/en/function.set-error-handler.php" target="_blank" rel="noopener noreferrer">set_error_handler</a>( array( 'MyPlugin_Admin_Notices', 'error_handler' ) );</pre>
<p>and after your code ends, unhook it so as not to interfere with the WordPress core or other components:</p>
<pre><a href="http://php.net/manual/en/function.restore-error-handler.php" target="_blank" rel="noopener noreferrer">restore_error_handler</a>();</pre>
<p>This will pop your error handler from a stack and return to whatever error handling mechanism was there before. <strong>Make a habit of surrounding your function bodies with these two lines</strong>, and you will know that whatever happens, you&#8217;ll at least get a visible user-friendly <strong>and</strong> developer-friendly error message on the admin screens.</p>
<h2>Putting it all together</h2>
<h3>JS</h3>
<pre>/**
 * Admin code for dismissing notifications.
 *
 */
(function( $ ) {
    'use strict';
    $( function() {
        $( '.myplugin-notice' ).on( 'click', '.notice-dismiss', function( event, el ) {

            var $notice = $(this).parent('.notice.is-dismissible');
            var dismiss_url = $notice.attr('data-dismiss-url');
            if ( dismiss_url ) {
                $.get( dismiss_url );
            }
        });
    } );
})( jQuery );</pre>
<h3>PHP</h3>
<pre>&lt;?php
if ( defined( 'WP_UNINSTALL_PLUGIN' ) ) {
    global $wpdb;
    $wpdb-&gt;query( 'DELETE FROM wp_options WHERE option_name LIKE "/* @echo slugus */_dismissed_%";' );
}</pre>
<pre>&lt;?php
// don't load directly
defined( 'ABSPATH' ) || die( '-1' );

if ( ! class_exists( 'MyPlugin_Admin_Notices' ) ) {

    class MyPlugin_Admin_Notices {

        private static $_instance;
        private $admin_notices;
        const TYPES = 'error,warning,info,success';

        private function __construct() {
            $this-&gt;admin_notices = new stdClass();
            foreach ( explode( ',', self::TYPES ) as $type ) {
                $this-&gt;admin_notices-&gt;{$type} = array();
            }
            add_action( 'admin_init', array( &amp;$this, 'action_admin_init' ) );
            add_action( 'admin_notices', array( &amp;$this, 'action_admin_notices' ) );
            add_action( 'admin_enqueue_scripts', array( &amp;$this, 'action_admin_enqueue_scripts' ) );
        }

        public static function get_instance() {
            if ( ! ( self::$_instance instanceof self ) ) {
                self::$_instance = new self();
            }
            return self::$_instance;
        }

        public function action_admin_init() {
            $dismiss_option = filter_input( INPUT_GET, 'myplugin_dismiss', FILTER_SANITIZE_STRING );
            if ( is_string( $dismiss_option ) ) {
                update_option( "myplugin_dismissed_$dismiss_option", true );
                wp_die();
            }
        }

        public function action_admin_enqueue_scripts() {
            wp_enqueue_script( 'jquery' );
            wp_enqueue_script(
                'myplugin-notify',
                plugins_url( 'assets/scripts/myplugin-notify.js', __FILE__ ),
                array( 'jquery' )
            );
        }

        public function action_admin_notices() {
            foreach ( explode( ',', self::TYPES ) as $type ) {
                foreach ( $this-&gt;admin_notices-&gt;{$type} as $admin_notice ) {

                    $dismiss_url = add_query_arg( array(
                        'myplugin_dismiss' =&gt; $admin_notice-&gt;dismiss_option
                    ), admin_url() );

                    if ( ! get_option( "myplugin_dismissed_{$admin_notice-&gt;dismiss_option}" ) ) {
                        ?&gt;&lt;div
                            class="notice myplugin-notice notice-&lt;?php echo $type;

                            if ( $admin_notice-&gt;dismiss_option ) {
                                echo ' is-dismissible" data-dismiss-url="' . esc_url( $dismiss_url );
                            } ?&gt;"&gt;

                            &lt;h2&gt;&lt;?php echo "My Plugin $type"; ?&gt;&lt;/h2&gt;
                            &lt;p&gt;&lt;?php echo $admin_notice-&gt;message; ?&gt;&lt;/p&gt;

                        &lt;/div&gt;&lt;?php
                    }
                }
            }
        }

        public function error( $message, $dismiss_option = false ) {
            $this-&gt;notice( 'error', $message, $dismiss_option );
        }

        public function warning( $message, $dismiss_option = false ) {
            $this-&gt;notice( 'warning', $message, $dismiss_option );
        }

        public function success( $message, $dismiss_option = false ) {
            $this-&gt;notice( 'success', $message, $dismiss_option );
        }

        public function info( $message, $dismiss_option = false ) {
            $this-&gt;notice( 'info', $message, $dismiss_option );
        }

        private function notice( $type, $message, $dismiss_option ) {
            $notice = new stdClass();
            $notice-&gt;message = $message;
            $notice-&gt;dismiss_option = $dismiss_option;

            $this-&gt;admin_notices-&gt;{$type}[] = $notice;
        }

	public static function error_handler( $errno, $errstr, $errfile, $errline, $errcontext ) {
		if ( ! ( error_reporting() &amp; $errno ) ) {
			// This error code is not included in error_reporting
			return;
		}

		$message = "errstr: $errstr, errfile: $errfile, errline: $errline, PHP: " . PHP_VERSION . " OS: " . PHP_OS;

		$self = self::get_instance();

		switch ($errno) {
			case E_USER_ERROR:
				$self-&gt;error( $message );
				break;

			case E_USER_WARNING:
				$self-&gt;warning( $message );
				break;

			case E_USER_NOTICE:
			default:
				$self-&gt;notice( $message );
				break;
		}

		// write to wp-content/debug.log if logging enabled
		error_log( $message );

		// Don't execute PHP internal error handler
		return true;
	}
    }
}

MyPlugin_Admin_Notices::get_instance();</pre>
<h2>Thanks for reading</h2>
<p>Please comment on what you liked or didn&#8217;t like in my code. What would you have done differently? Can you spot any bugs?</p>
<p>Until next time. Dismissed!</p>
<p>The post <a href="https://www.alexgeorgiou.gr/persistently-dismissible-notices-wordpress/">🗨 Dismissible notices that persist when refreshing the WordPress admin screens</a> appeared first on <a href="https://www.alexgeorgiou.gr">Alexandros Georgiou</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.alexgeorgiou.gr/persistently-dismissible-notices-wordpress/feed/</wfw:commentRss>
			<slash:comments>8</slash:comments>
		
		
			</item>
	</channel>
</rss>
