<?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>wp-cli Archives - Alexandros Georgiou</title>
	<atom:link href="https://www.alexgeorgiou.gr/tag/wp-cli/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.alexgeorgiou.gr/tag/wp-cli/</link>
	<description>Balancing brackets for a living</description>
	<lastBuildDate>Mon, 22 Apr 2024 09:03:11 +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>wp-cli Archives - Alexandros Georgiou</title>
	<link>https://www.alexgeorgiou.gr/tag/wp-cli/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>🖧 The wacky world of network activated WordPress plugins in multisite</title>
		<link>https://www.alexgeorgiou.gr/network-activated-wordpress-plugins/</link>
					<comments>https://www.alexgeorgiou.gr/network-activated-wordpress-plugins/#comments</comments>
		
		<dc:creator><![CDATA[alexg]]></dc:creator>
		<pubDate>Mon, 19 Jun 2017 17:46:25 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[cron]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[documentation]]></category>
		<category><![CDATA[multisite]]></category>
		<category><![CDATA[multiuser]]></category>
		<category><![CDATA[network activation]]></category>
		<category><![CDATA[site-wide]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[wordpress]]></category>
		<category><![CDATA[wp-cli]]></category>
		<category><![CDATA[wpmu]]></category>
		<guid isPermaLink="false">http://www.alexgeorgiou.gr/?p=224</guid>

					<description><![CDATA[<p>Insider secrets to making your plugin work well on multisite installs, both when it is network activated and when it is not.</p>
<p>The post <a href="https://www.alexgeorgiou.gr/network-activated-wordpress-plugins/">🖧 The wacky world of network activated WordPress plugins in multisite</a> appeared first on <a href="https://www.alexgeorgiou.gr">Alexandros Georgiou</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><em>Multisite</em> is arguably one of the weirdest features in <em>WordPress</em>. Making your plugin work well on multisite installs, both when it is network activated and when it is not, is one of the most fun things that you will have to do as a WordPress plugin developer. And by <em>fun</em>, I mean <em>not fun</em>. Not fun at all.</p>
<p>Introduced in 3.0, the multisite feature feels a little awkward when developing code, partly because things you take for granted in single-site installs are simply not there, partly because other things are unnecessarily different, partly because of the shifting terminology that has evolved over time, and partly because of the significantly less documentation, compared to that available on single-site features. Moreover there are significantly less articles about it out there, since most people care about single-site installs.</p>
<p>But, it works! After all, last time I checked, <a href="http://wordpress.com" target="_blank" rel="noopener noreferrer">wordpress.com</a> was up and running! The fact that people were able to extend WordPress so drastically in a way that actually works, is impressive to say the least. I can only imagine how hard it must have been. The idea is that you can make your WordPress behave just like <code>wordpress.com</code>, where users can create their own site.</p>
<p>So yes, some things about <strong>network-activated plugins</strong> on multisite suck, but they don&#8217;t suck to the point of being unusable. You just need to learn a few tricks. Fortunately, you came across this article, so all will become clear. Or will they?</p>
<h2>Some basic terminology</h2>
<p>A <em>multisite</em> (aka <em>MS</em>) install is sometimes also called a <em>network</em>. A network can have a multitude of <em>sites</em>, and sites can have many <em>blogs</em>. Confusingly, <em>site</em> can also mean <em>blog</em> besides <em>network</em>. And <em>network</em> does not mean what we normally mean in IT, it means a collection of <em>sites</em> or <em>multi-sites</em>, depending on who you ask. And a <em>blog</em> of course is not necessarily an actual weblog, but can be any type of <em>site</em>.</p>
<p>To add to the fun, in the past the <em>multisite</em> feature was named <em>multiuser</em> (or WPMU, or MU for short). Presumably this was changed because <em>multiuser</em> is a stupid name for this feature: all WordPress installations can have multiple users. But you do need to be aware of all the terms out there, because you will come across them at some point.</p>
<p>To paraphrase an old saying,</p>
<blockquote><p>There are only two hard problems in computer science, cache invalidation and naming WordPress things.</p></blockquote>
<p>Users who create blogs, or sites, or whatever (sigh!) on your <em>multisite</em> are <em>administrators</em> of their respective sites, and you, the owner of the <em>network</em> are a <em>network admin</em>, also known as a <em>super admin</em>. The <em>network admin</em> can administer global settings for the entire <em>network</em> from the <em>network admin menu</em>.</p>
<h2>What does network activated actually mean?</h2>
<p>Themes and plugins can either be:</p>
<ul>
<li><strong>activated</strong> on individual blogs by <em>administrators</em>, if the administrators have the capability to do so, or</li>
<li><strong>network-activated</strong> once, site-wide, by the <em>network admin</em>, making them available to all blogs.</li>
</ul>
<h2>Custom DB tables</h2>
<p>Most tables in the DB are duplicated for each blog, where the table name prefix contains the blog ID, so that, for example, options for blogs 3 and 4 are safely stored in tables <code>wp_3_options</code> and <code>wp_4_options</code> respectively. This allows for blog administrators to activate and configure their plugins separately for their blogs. As long as they activate the plugins themselves, everything should work just fine for most plugins, out of the box.</p>
<p>If you are maintaining custom DB tables, these will need some extra coding work. There are two ways to do this: Either</p>
<ul>
<li>add a <code>blog_id</code> column to your rows, and then filter your SQL queries by <code>blog_id=<a href="https://codex.wordpress.org/Function_Reference/get_current_blog_id" target="_blank" rel="noopener noreferrer">get_current_blog_id()</a></code>, or</li>
<li>create separate tables for each blog when your plugin activates, by binding with the <a href="https://codex.wordpress.org/Function_Reference/register_activation_hook" target="_blank" rel="noopener noreferrer"><code>register_activation_hook()</code></a> function, and also create tables whenever a new blog is created after your plugin has been activated, by binding to <a href="https://codex.wordpress.org/Plugin_API/Action_Reference/wpmu_new_blog"><code>wpmu_new_blog</code></a>.</li>
</ul>
<p>You can read more about this here:</p>
<blockquote data-secret="cBkskEYduB" class="wp-embedded-content"><p><a href="http://sudarmuthu.com/blog/how-to-properly-create-tables-in-wordpress-multisite-plugins/">How To Properly Create Tables In WordPress Multisite Plugins</a></p></blockquote>
<p><iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  src="http://sudarmuthu.com/blog/how-to-properly-create-tables-in-wordpress-multisite-plugins/embed/#?secret=cBkskEYduB" data-secret="cBkskEYduB" width="600" height="338" title="&#8220;How To Properly Create Tables In WordPress Multisite Plugins&#8221; &#8212; Night Dreaming (by Sudar)" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe></p>
<h2>To network activate, or not to network activate?</h2>
<p>Depending on what your plugin actually does, you might want to have parts of it operate site-wide. This often makes sense, but know that in doing so you might be opening a big can of worms. The network admin area introduces its own set of actions and filters, its own menu structure, its own URL structure, a somewhat lacking settings API where you have to do some things manually, and you will have to think about how your <code>wp_cron</code> hooks work, new user capabilities, and different plugin activation code.</p>
<p>The function <a href="https://codex.wordpress.org/Function_Reference/is_multisite" target="_blank" rel="noopener noreferrer"><code>is_multisite()</code></a> tells you whether the WordPress install is multisite, but it tells you nothing about whether your plugin is activated for the network or whether it was activated at the blog level.</p>
<p>To find out whether your plugin is network activated or not, first import <a href="https://codex.wordpress.org/Function_Reference/is_plugin_active_for_network" target="_blank" rel="noopener noreferrer">the right function</a>:</p>
<pre>if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
    require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
}</pre>
<p>Then, you can do this:</p>
<pre><code>if ( is_plugin_active_for_network( 'myplugin/myplugin.php' ) ) {</code> // do stuff }</pre>
<p>assuming that your plugin&#8217;s slug is <code>myplugin</code>. The function will always return <code>false</code> on single-site installs.</p>
<h2>So many options&#8230;</h2>
<p>If some options must apply to the entire network, you will need to expose a menu of panels with those options to the network administrator.</p>
<h3>Know your DB options</h3>
<p>Those options can be thought of as &#8220;global&#8221; for a site (aka network), and they ought to be stored in the <code>wp_sitemeta</code> table. The table name is programmatically available as <code>$wpdb-&gt;sitemeta</code>. It is just like the <code>wp_options</code> table, but totally different: Columns <code>option_name</code> and <code>option_value</code> correspond to <code>meta_key</code> and <code>meta_value</code>. If you&#8217;re wondering about the <code>site_id</code> column, no, it&#8217;s not an identifier to individual blogs, it&#8217;s a unique identifier to a site, because, yes, you can have several sites on one network, each with several blogs.</p>
<h3>:s/_option/_site_option/</h3>
<p>These site meta options are accessed not by the usual functions <a href="https://developer.wordpress.org/reference/functions/get_option/" target="_blank" rel="noopener noreferrer"><code>get_option()</code></a> and friends, but by <a href="https://codex.wordpress.org/Function_Reference/get_site_option" target="_blank" rel="noopener noreferrer"><code>get_site_option()</code></a>, <a href="https://codex.wordpress.org/Function_Reference/update_site_option" target="_blank" rel="noopener noreferrer"><code>update_site_option()</code></a>, <a href="https://codex.wordpress.org/Function_Reference/add_site_option" target="_blank" rel="noopener noreferrer"><code>add_site_option()</code></a> and <a href="https://codex.wordpress.org/Function_Reference/delete_site_option" target="_blank" rel="noopener noreferrer"><code>delete_site_option()</code></a>.</p>
<h3>Option-related capabilities</h3>
<p>You might be interested in the <a href="https://codex.wordpress.org/Roles_and_Capabilities#manage_network_options" target="_blank" rel="noopener noreferrer"><code>manage_network_options</code></a> capability that is normally granted to Super Admins. In fact, have a look at <a href="https://codex.wordpress.org/Roles_and_Capabilities#manage_network" target="_blank" rel="noopener noreferrer">Roles and Capabilities</a> in the Codex to make sure you know about all of the <code>manage_network_*</code> capabilities, as well as the <code>*_sites</code> capabilities. Essential reading if you&#8217;re a network admin.</p>
<h3>Updating site options</h3>
<p>Know that you will have to do things differently when creating admin forms for network-wide options. In a nutshell:</p>
<ul>
<li>you need to hook to the <code>network_admin_menu</code> action rather than <code>admin_menu</code>.</li>
<li>your HTML forms need to have a different <code>action</code> attribute,</li>
<li>you need to bind a submit handler to:
<ul>
<li>check for the admin nonce,</li>
<li>actually save the settings to the DB, and</li>
<li>redirect back to the right admin panel.</li>
</ul>
</li>
<li>Use <a href="https://codex.wordpress.org/Function_Reference/network_admin_url"><code>network_admin_url()</code></a> wherever you&#8217;d normally use <a href="https://codex.wordpress.org/Function_Reference/admin_url"><code>admin_url()</code></a>.</li>
</ul>
<p>I will not go through all of this detail here, as it is explained very well in this rare article:</p>
<blockquote data-secret="Gc6F9iV1q6" class="wp-embedded-content"><p><a href="https://vedovini.net/2015/10/using-the-wordpress-settings-api-with-network-admin-pages/">Using the WordPress Settings API with Network Admin pages</a></p></blockquote>
<p><iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  src="https://vedovini.net/2015/10/using-the-wordpress-settings-api-with-network-admin-pages/embed/#?secret=Gc6F9iV1q6" data-secret="Gc6F9iV1q6" width="600" height="338" title="&#8220;Using the WordPress Settings API with Network Admin pages&#8221; &#8212; vedovini.net" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe></p>
<p>I find this article tells you all that you need to know about how to actually save site meta, but if you want to read more, you can <a href="https://codex.wordpress.org/Creating_Options_Pages#Pitfalls" target="_blank" rel="noopener noreferrer">have a look at the official documentation</a>, which currently simply links to <a href="https://wordpress.stackexchange.com/questions/64968/settings-api-in-multisite-missing-update-message/72503#72503" target="_blank" rel="noopener noreferrer">here</a> and <a href="https://web.archive.org/web/20111102180213/http://code.hyperspatial.com/1250/save-plugin-options-multisite-3-1" target="_blank" rel="noopener noreferrer">here</a>.</p>
<h3>Getting hooked to site-wide options yet? Not to worry, there&#8217;s more!</h3>
<p>Keep in mind that there exists a multitude of hooks that are specific to network administration. For example, to do various custom stuff to a site-wide option before saving it to the DB, do not bind to <a href="https://codex.wordpress.org/Plugin_API/Filter_Reference/pre_update_option_(option_name)" target="_blank" rel="noopener noreferrer"><code>pre_update_option_{$option_name}</code></a>, but to <a href="https://developer.wordpress.org/reference/hooks/pre_update_site_option_option/" target="_blank" rel="noopener noreferrer"><code>pre_update_site_{$option_name}</code></a>.</p>
<h3>Set option values via the terminal like a 1334 h@x0r</h3>
<p>If you&#8217;re like me, you probably have set up your build process to auto-inject options to your dev environment, using <code>wp-cli</code>. Instead of</p>
<pre> wp option update option_name option_value</pre>
<p>do something like this instead:</p>
<pre>wp network meta set 1 option_name option_value</pre>
<p>where <code>1</code> is the number of your multisite environment.</p>
<h3>How to nag the right admin</h3>
<p>To show notices to the <em>network admin</em>, do not bind to <a href="https://codex.wordpress.org/Plugin_API/Action_Reference/admin_notices" target="_blank" rel="noopener noreferrer"><code>admin_notices</code></a>, instead bind to <a href="https://codex.wordpress.org/Plugin_API/Action_Reference/network_admin_notices" target="_blank" rel="noopener noreferrer"><code>network_admin_notices</code></a>.</p>
<h3>The surprising secret argument of the activation hook that nobody knew about!!!</h3>
<p>Apologies, when I&#8217;m blogging I sometimes go into full SEO mode. Anyhow&#8230;</p>
<p>For your activation handler to work, you will first need to bind a function with <a href="https://codex.wordpress.org/Function_Reference/register_activation_hook" target="_blank" rel="noopener noreferrer"><code>register_activation_hook()</code></a>, as usual. It turns out that your activation handler can take a parameter that tells you whether the plugin is being network-activated or not.</p>
<p>Here&#8217;s a handy way I like to use to create options that can either be global on the multisite level, or bound to the current blog, depending on whether your plugin was network-activated or activated on a single blog:</p>
<pre>function activation_hook( $network_active ) {
    call_user_func(
        $network_active ? 'add_site_option' : 'add_option',
        'option_name',
        'option_value'
    );
}
register_activation_hook( __FILE__, 'activation_hook' );</pre>
<h3>Clean up after yourself!</h3>
<p>Remember to do the same in your <code>uninstall.php</code>. You do clean up after yourself, don&#8217;t you? Of course you do, you&#8217;re a developer, not a filthy pig.</p>
<p>Remember, you will not be able to use <a href="https://codex.wordpress.org/Function_Reference/is_plugin_active_for_network" target="_blank" rel="noopener noreferrer"><code>is_plugin_active_for_network()</code></a>, because by the time the uninstall code runs, the plugin should have already been deactivated. Since the plugin is being uninstalled completely, you can do something like the following, just to be on the safe side:</p>
<pre>// delete from site meta
delete_site_option( 'option_name' );

// also delete from options table for all blogs
global $wpdb;
foreach ( $wpdb-&gt;get_col( "SELECT blog_id FROM $wpdb-&gt;blogs" ) as $blog_id ) {
    switch_to_blog( $blog_id );
    delete_option( 'option_name' );
    restore_current_blog();
}</pre>
<p>You will also likely want to delete any custom tables you may have created. The trick here is to iterate over all the blogs and let WordPress choose the right DB prefix.</p>
<pre>foreach ( $wpdb-&gt;get_col( "SELECT blog_id FROM $wpdb-&gt;blogs" ) as $blog_id ) {
    switch_to_blog( $blog_id );
    $wpdb-&gt;query( "DROP TABLE IF EXISTS {$wpdb-&gt;prefix}mytable" );
    restore_current_blog();
}</pre>
<p>If you have chosen the separate tables way, you should also hook a <code>DROP TABLE</code> query to the <a href="https://developer.wordpress.org/reference/hooks/delete_blog/" target="_blank" rel="noopener noreferrer"><code>delete_blog</code></a> hook:</p>
<pre>function delete_blog_tables( $blog_id, $drop ) {
    if ( $drop ) {
        switch_to_blog( $blog_id );
        $wpdb-&gt;query( "DROP TABLE IF EXISTS {$wpdb-&gt;prefix}mytable" );
        restore_current_blog();
    }
}
add_action( 'delete_blog', 'delete_blog_tables' );</pre>
<p>If on the other hand you have one table for all blogs, and your rows have a <code>blog_id</code> column, do something more in the tune of:</p>
<pre>function delete_blog_tables( $blog_id, $drop ) {
    if ( $drop ) {
        $wpdb-&gt;query( "DELETE FROM {$wpdb-&gt;prefix}mytable WHERE blog_id = $blog_id" );
    }
}
add_action( 'delete_blog', 'delete_blog_tables' );</pre>
<p>There! All clean now!</p>
<h3>Cron</h3>
<p>You will want to use that handy little <em>foreach</em> loop in more than just the uninstall script. For instance, suppose you&#8217;re using <a href="https://developer.wordpress.org/plugins/cron/" target="_blank" rel="noopener noreferrer">cron</a>. If your plugin is network activated, cron will run once for the network, not once per blog. Assuming your cron handler needs to do stuff to each blog, you&#8217;d do something like:</p>
<pre>function cron_handler( ) {
    if ( is_plugin_active_for_network( 'myplugin/myplugin.php' ) ) {
        global $wpdb;
        foreach ( $wpdb-&gt;get_col( "SELECT blog_id FROM $wpdb-&gt;blogs" ) as $blog_id ) {
            switch_to_blog( $blog_id );
            do_stuff();
            restore_current_blog();
        }
    } else {
        do_stuff();
    }
}
add_action( 'cron_hook', 'cron_handler' );

if ( false === wp_next_scheduled( 'cron_hook' ) ) {
    wp_schedule_event( time(), 'every_now_and_then', 'cron_hook' );
}

function do_stuff() {
    // do your stuff here
}</pre>
<p>This way if your plugin runs network-wide, then your stuff is done on each one of the blogs on your multisite. But if the plugin is activated on the blog level, it does stuff only on the current blog.</p>
<p>Good stuff!</p>
<h2><del datetime="2017-08-13T18:22:13+00:00">Super secret plugin header: Network</del></h2>
<p><strong>Edit: This plugin header seems to not be respected any more; perhaps this is why it is not documented. It is part of WordPress history.</strong></p>
<p><del datetime="2017-08-13T18:22:13+00:00">Although you wouldn&#8217;t have guessed it from <a href="https://developer.wordpress.org/plugins/the-basics/header-requirements/" target="_blank" rel="noopener noreferrer">reading the relevant documentation</a>, the plugin header, i.e. that special block of comments that you put at the top of your main plugin file, can have something to say about network activation. (A tiny hint is given <a href="https://codex.wordpress.org/File_Header" target="_blank" rel="noopener noreferrer">in the Codex</a> that such a header exists, although the documentation does not currently bother explaining what it does. After all, if everything was documented, where would the fun be?)</del></p>
<p><del datetime="2017-08-13T18:22:13+00:00">If your plugin is intended to be <strong>only network activated</strong>, and not activated on the blog-level in multi-sites, then add this line to your plugin&#8217;s header:</del></p>
<pre>Network: True</pre>
<p><del datetime="2017-08-13T18:22:13+00:00">Now WordPress will not give the activation option to mere blog admins.</del></p>
<h2>Are you still with me?</h2>
<p>Awesome! There you have it! Now you know all that I currently know about WordPress multisite and network activated plugins. Which is arguably not a lot. If you know more and feel like sharing, you&#8217;re free to comment below.</p>
<p>Hopefully you now have some grasp of the types of things you should be looking out for when you take your plugin for a ride into the wacky world of network activation.</p>
<p>Take care.</p>
<p>Alex</p>
<p>The post <a href="https://www.alexgeorgiou.gr/network-activated-wordpress-plugins/">🖧 The wacky world of network activated WordPress plugins in multisite</a> appeared first on <a href="https://www.alexgeorgiou.gr">Alexandros Georgiou</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.alexgeorgiou.gr/network-activated-wordpress-plugins/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>🐗 Building WordPress plugins with an increasingly verbose Gruntfile</title>
		<link>https://www.alexgeorgiou.gr/increasingly-verbose-gruntfile/</link>
					<comments>https://www.alexgeorgiou.gr/increasingly-verbose-gruntfile/#comments</comments>
		
		<dc:creator><![CDATA[alexg]]></dc:creator>
		<pubDate>Fri, 24 Feb 2017 11:01:27 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[alias]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[grunt]]></category>
		<category><![CDATA[project management]]></category>
		<category><![CDATA[wordpress]]></category>
		<category><![CDATA[wp-cli]]></category>
		<guid isPermaLink="false">http://www.alexgeorgiou.gr/?p=192</guid>

					<description><![CDATA[<p>I am always aiming for a hierarchical Gruntfile.js structure that reminds me of increasingly verbose memes. And you should, too.</p>
<p>The post <a href="https://www.alexgeorgiou.gr/increasingly-verbose-gruntfile/">🐗 Building WordPress plugins with an increasingly verbose Gruntfile</a> appeared first on <a href="https://www.alexgeorgiou.gr">Alexandros Georgiou</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>When building WordPress plugins, I&#8217;m using <strong>gruntjs</strong> for all my project tooling needs. I am always aiming for a hierarchical Gruntfile.js structure that reminds me of <a href="http://knowyourmeme.com/memes/increasingly-verbose-memes" target="_blank" rel="noopener noreferrer">increasingly verbose memes</a>. And you should, too.</p>
<p>No matter which tool you&#8217;re using, building, testing, packaging and deploying a WordPress plugin should be one command. That way when you&#8217;re dev testing, you are actually testing the whole process, including installation, activation and deactivation of the plugin&#8217;s zip file. <a href="https://github.com/sindresorhus/grunt-shell" target="_blank" rel="noopener noreferrer">grunt-shell</a> and <a href="http://wp-cli.org/" target="_blank" rel="noopener noreferrer">wp-cli</a> are your friends. Use <a href="http://wp-cli.org/commands/option/" target="_blank" rel="noopener noreferrer"><strong>wp option</strong></a> liberally in your dev deployment code.</p>
<p>My Gruntfile.js describes all the steps needed between my code sitting in my working directory and the same code running in my WordPress dev environment.</p>
<p><a href="http://gruntjs.com/creating-tasks" target="_blank" rel="noopener noreferrer">Grunt alias tasks</a> let you define this process hierarchically. Use them! I aim to model the process so that at some level it has some resemblance to steps from the waterfall model: <strong>build</strong>, <strong>package</strong>, <strong>test</strong>, and <strong>deploy</strong> are verbs that I always aim to define in my script. Then, at a lower level I can define which grunt plugins actually accomplish each step.</p>
<p>When you have defined the steps in this way, then you can begin to create builds of varying quality or for various purposes. Testing takes too long to run every time in your development cycle? Create a quick build that excludes it. Want to have separate builds with and without documentation included? Define another package verb. Want to create a deliverable package but not deploy it? Create a task that omits the deploy verb.</p>
<p>The following is from an actual project I&#8217;m working on. Don&#8217;t worry about the details. You have your own workflow. All I&#8217;m saying is that you will benefit from structuring your script hierarchically.</p>
<p>Here&#8217;s my increasingly verbose meme. Make it viral!</p>
<p><img fetchpriority="high" decoding="async" class="aligncenter size-full wp-image-193" src="http://www.alexgeorgiou.gr/wp-content/uploads/2017/02/grunt-meme.jpeg" alt="Increasingly verbose Gruntfile" width="640" height="800" srcset="https://www.alexgeorgiou.gr/wp-content/uploads/2017/02/grunt-meme.jpeg 640w, https://www.alexgeorgiou.gr/wp-content/uploads/2017/02/grunt-meme-240x300.jpeg 240w" sizes="(max-width: 599px) calc(100vw - 50px), (max-width: 767px) calc(100vw - 70px), (max-width: 991px) 429px, (max-width: 1199px) 637px, 354px" /></p>
<p>The post <a href="https://www.alexgeorgiou.gr/increasingly-verbose-gruntfile/">🐗 Building WordPress plugins with an increasingly verbose Gruntfile</a> appeared first on <a href="https://www.alexgeorgiou.gr">Alexandros Georgiou</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.alexgeorgiou.gr/increasingly-verbose-gruntfile/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>💻 wp-cli executed by www-data user to avoid Linux permission issues</title>
		<link>https://www.alexgeorgiou.gr/wp-cli-www-data-user-permissions-linux/</link>
					<comments>https://www.alexgeorgiou.gr/wp-cli-www-data-user-permissions-linux/#comments</comments>
		
		<dc:creator><![CDATA[alexg]]></dc:creator>
		<pubDate>Wed, 26 Oct 2016 14:02:19 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[bash]]></category>
		<category><![CDATA[permissions]]></category>
		<category><![CDATA[wordpress]]></category>
		<category><![CDATA[wp-cli]]></category>
		<guid isPermaLink="false">http://www.alexgeorgiou.gr/?p=124</guid>

					<description><![CDATA[<p>I'm going to share with you a trick that I use to run wp-cli as the www-data user on my Linux development environment.</p>
<p>The post <a href="https://www.alexgeorgiou.gr/wp-cli-www-data-user-permissions-linux/">💻 wp-cli executed by www-data user to avoid Linux permission issues</a> appeared first on <a href="https://www.alexgeorgiou.gr">Alexandros Georgiou</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>In this article I&#8217;m going to share with you a trick that I use to run <em>wp-cli</em> as the <code>www-data</code> user on my Linux development environment.</p>
<h2>wp-cli and file owner issues</h2>
<p>I can&#8217;t imagine doing any serious WordPress development without <em>wp-cli</em>. It lets you automate the <em>WordPress</em> installation and configuration, lets you install and manage themes and plugins, post articles, and even post file-based media such as images. Whether you run it from <em>grunt</em>, from shell scripts, <a href="/wordpress-testing-with-docker/">from inside docker containers</a>, or just from the plain old shell, <em>wp-cli</em> rocks!</p>
<p>One issue I often face is that when I run <em>wp-cli</em> as my default Linux user, naturally all the file operations write files owned by the current user. Sometimes this is an issue, as my web files are normally owned by user <code>www-data</code> (in the <code>www-data</code> group).</p>
<p>In the past that meant that I had to do this a lot, in order to avoid various permission issues:</p>
<pre>sudo chown -R www-data:www-data /var/www/wordpress</pre>
<p>It quickly becomes tedious. There&#8217;s a better way.</p>
<h2>finding your wp-cli installation</h2>
<p>If you&#8217;ve followed the installation instructions, then you probably have <code>wp-cli.phar</code> installed as <code>wp</code> at <code>/usr/local/bin/wp</code>. In any case, you can do</p>
<pre>which wp</pre>
<p>or</p>
<pre>whereis wp</pre>
<p>to find its location.</p>
<h2>running as www-data</h2>
<p>Now on to put a <a href="https://en.wikipedia.org/wiki/Setuid" target="_blank" rel="noopener noreferrer">setuid</a> flag on that file and change its owner to that of your web server (usually <code>www-data</code>), right?</p>
<p>Not so fast! The astute Linux aficionado will notice that <em>wp-cli</em> is not a binary, even though it&#8217;s placed under a <code>bin</code> dir. Actually it&#8217;s a PHP CLI script:</p>
<pre>$ head -n 1 `which wp`
#!/usr/bin/env php
$</pre>
<p>Here&#8217;s what you do instead. First, run <em>visudo</em> with elevated privileges:</p>
<pre>sudo visudo</pre>
<p>This will let you edit your sudoers file. You want to allow your current user to run your script as <code>www-data</code>. So, add the following line: (here I&#8217;m logged in as alex, the local user I use for development)</p>
<pre>alex ALL=(www-data) NOPASSWD: /usr/local/bin/wp</pre>
<p>Save the file and exit. Now you can do stuff like this:</p>
<pre>mkdir /tmp/t
cd /tmp/t
sudo -u www-data wp core download
ls -l</pre>
<p>You should see a WordPress installation owned by the <code>www-data</code> user, without ever having to enter a password for <code>www-data</code> (by default there shouldn&#8217;t be any password for that user anyway).</p>
<p>Of course, having to type <code>sudo -u www-data wp</code> every time is also tedious. So add this to your <code>~/.bashrc</code>, or better yet to your <code>~/.bash_aliases</code> if you have one.</p>
<pre>alias wp="sudo -u www-data wp"</pre>
<p>That should do it, assuming <em>bash</em> is your shell. For other shells, YMMV.</p>
<h2>wp-cli cache permission issue</h2>
<p>But now you might notice some warnings of the form:</p>
<pre>Warning: copy(/home/alex/.wp-cli/cache/plugin/XXXXXXXX.zip): failed to open stream: Permission denied in phar:///usr/local/bin/wp/php/WP_CLI/FileCache.php on line 164</pre>
<p>This just means that the script, which now runs as the <code>www-data</code> user, cannot write to its cache directory, because that directory is owned by you. You can fix this with a simple:</p>
<pre>sudo chown -R www-data:www-data ~/.wp-cli/</pre>
<p>Now you can use commands such as <code>wp plugin install foo.zip</code> <code>--activate </code>and <code>wp plugin uninstall foo --deactivate</code> in your dev-testing lifecycle without ever worrying about your web server choking on permission issues.</p>
<p>The post <a href="https://www.alexgeorgiou.gr/wp-cli-www-data-user-permissions-linux/">💻 wp-cli executed by www-data user to avoid Linux permission issues</a> appeared first on <a href="https://www.alexgeorgiou.gr">Alexandros Georgiou</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.alexgeorgiou.gr/wp-cli-www-data-user-permissions-linux/feed/</wfw:commentRss>
			<slash:comments>12</slash:comments>
		
		
			</item>
	</channel>
</rss>
