Category Archives: MultiSite

Dynamically control user capabilities on multisite with WordPress

Got an interesting question about dynamically altering capabilities depending on which site the user was on – the goal was to have a global site where everyone had some access, without having to manually add every user to the site in question.

This snippet below will give everyone trying to access blog id = 1 on the multisite the Contributor capabilities. You can easily tweak which site ID and which Role you want to give all users.

add_filter( 'user_has_cap', function($allcaps, $cap, $args) {

  //ID of the "global" site
  if(get_current_blog_id() === 1) {

    //Give everyone admin capabilities
    $admin_caps = get_role('contributor')->capabilities;
    $allcaps = array_merge($allcaps, $admin_caps);
  }

  return $allcaps;
}, 10, 3 );

Mark Jaquith: Next Generation WordPress Hosting Stack – Summary and highlights


Mark Jaquith gave an interesting talk on the future of the WordPress hosting stack at WordCamp Europe 2014 last week. Let’s see what the future holds!

WordPress is dynamic, and that’s both a strength and a weakness

WordPress has little cache built-in by default. There is only a non-persistent, in-memory Object Cache. Most data is fetched from database and processed on every request.

“Old-style” hosts can’t deliver the performance modern sites need

Old “cPanel-style” hosting isn’t equipped to handle high-performance WordPress. Specialized stacks do a much better job.

americans-don-t-have-time-for-slow-websites-infographic--ba3d727bea

People don’t like slow sites

Reducing your TTFB is crucial.

Guidelines for page generation speed:

  • 3+ seconds = emergency, loss of visitors imminent
  • 1-3 seconds = feels annoying to end users, reduces interaction
  • 500ms – 1000ms = fast, but user engagement still not perfect, users perceive the site as not responsive
  • <100ms – feels like there is no delay at all

WordPress Managed Hosting

Managed Hosting – a new breed of hosting providers tailored to WordPress.

  • SiteGround
  • Pressable
  • Pagely
  • WP Engine
  • GoDaddy
  • Kinsta
  • Pantheon
  • Dreamhost

In addition, you can purchase a VPS where you can customize the stack yourself. When choosing a VPS, use a reputable company, as many are fly-by-night and may disappear at any time.

Some reputable VPS providers:

  • Linode
  • DigitalOcean

stockvault-flying-particles158027

Basics of a high performance WordPress stack

nginx

nginx is a fast web server that uses little resources. It support SPDY, which improves loading sites with many assets (css, js, images) through pipelining. nginx also supports load-balancing and caching.

php-fpm or HHVM

nginx does not have php baked into it. this is good, since it reduces memory usage for static requests as compared to Apache with mod_php. Always use the latest PHP version – newer PHP versions have increased performance and built-on opcache.

An alternative is HHVM – a PHP implementation created by Facebook, which aims to improve performance. (up to 5x faster than regular PHP!) It’s fully compatible with WordPress core, but may not support all third party plugins.

Caching fundamentals

Caching is about doing as little as possible for as many users as possible without visibly affecting how dynamic the site feels to users.

Full page caching

Most cache plugins use full page caching, showing a pre-saved version to anonymous visitors.

Full page caching may be utilized at different levels of the stack:

  • Plugins (W3 Total Cache, et al)
  • Nginx
  • Varnish

Marks own cache settings using Nginx and php-fpm:
https://gist.github.com/markjaquith/04162825d159c8fb5534

Object Cache

Persistent caches include:

  • APCu
  • Memcache*
  • Redis (Marks favourite)*

  • *Network-based, works across multiple servers. *

Object caching is used extensively in core and is also available to plugins.

MySQL performance

MySQL is usually not the bottleneck – PHP performance is.

mysqltuner.pl – Gives you tips on improving MySQL performance!

HyperDB – replaces the built-in WordPress database layer. Can talk to multiple MySQL servers, has failover, sharding and much more! WordPress.org uses this.

But make sure you enable the MySQL query cache.

There are drop-in alternatives to MySQL like Percona and MariaDB, which can improve performance for some sites.

Making a stack

Mark presented a few stacks that build on the basic stack.

nginx-php

Nginx + PHP

Static files are served by Nginx and PHP requests go through to php-fpm or hhvm. The easiest stack.

sandwich

Nginx + Varnish + Nginx + PHP

Nicknamed the “nginx sandwich”. This variant uses Varnish for caching. nginx is used at the front because Varnish does not support SSL. It’s also possible to add HAProxy to the mix to load balance between multiple servers.

And finally, the big reveal:

The future hosting stack

wp-hosting-stack

The combo of choice is nginx + hhvm + mysql + redis.

This stack allows you to scale horizontally when required and has high performance.

Other useful stuff

Mark showed off a few useful functions that he had built.

  • TLC Transients – a library built by mark that improves on the Transient API. Support soft expiration, meaning we can do background loading of data in transients without affecting frontend performance, through function callbacks.
  • “Fragment cache” – Caching existing PHP code with minimal modification through “fragment cache”:
    https://gist.github.com/markjaquith/2653957

WordPress Object-oriented plugin skeleton

A quick OOP pattern / skeleton for creating WordPress plugins.

<?php
/*
Plugin Name: Your Plugin
Plugin URI: https://wordpress.org/plugins/your-plugin
Description: Your plugin skeleton
Version: 1.0
Author: you
Author URI: http://your-site.com
License: GPL2
*/

/* The class encapsulates the plugin and its functions */
class Your_Plugin
{
    /* Put any plugin variables here */
    var $plugin_variable;

    /* The constructor will be used to setup actions and filters */
    function __construct()
    {
        /**
         * Set a plugin variable
         */
        $this->plugin_variable = 'test';

        /**
         * Hook on plugins_loaded. This is the first hook available to regular plugins.
         * It happens when all plugins have been initialized and is the best place to put your logic.
         */
        add_action('plugins_loaded', array($this, 'plugin_loaded'));

        /* Optionally, add additional hooks here. */
    }

    /**
     * Put your main plugin logic here!
     */
    function plugin_loaded()
    {
        /* Here is how to call a function inside your class: */
        $sum = $this->add(1,2);
    }

    /**
     * A plugin function that adds
     * two numbers together and
     * returns them.
     *
     * @param $var1
     * @param $var2
     * @return mixed
     */
    function add($var1, $var2)
    {
        return $var1+$var2;
    }
}

/* This initiates your plugin */
$your_plugin = new Your_Plugin();

Interfacing with your plugin

Interfacing with your plugin is possible by either calling the global you created (last line), like this:

global $your_plugin;
$sum = $your_plugin->add(1,2);

If you hooked on plugins_loaded, this can be done pretty much anywhere – other plugins, themes, you name it!

Another popular pattern is creating a global function helper inside your plugin.

/**
 * Here is how to create a global function
 * for your plugin.
 */
function your_plugin_add($var1, $var2)
{
    global $your_plugin;
    return $your_plugin->add($var1,  $var2);
}

And here is how you would call the global function in that case:

/**
 * Here is how to call the external function:
 */
$sum = your_plugin_add(1,2);

Redirect WordPress users to their primary site when logging in on a multisite

Here’s a quick plugin from this thread. Create this as the file redirect-users.php in /wp-content/mu-plugins/

From here on your users will be redirected to their primary site upon logging in, and you will never see that nagging “You attempted to access the (main network site) dashboard, but you do not currently have privileges on this site.” error!

<?php
/*
Plugin Name: Redirect Users to Primary Site
Plugin URI:
Description: Never see "you do not currently have privileges on this site" when logging in on your multisite ever again!
Version: 2014.06.02
Author: khromov
Author URI: https://profiles.wordpress.org/khromov
License: GPL2
*/

/* http://premium.wpmudev.org/forums/topic/redirect-users-to-their-blogs-homepage */
add_filter('login_redirect', function($redirect_to, $request_redirect_to, $user)
{
    if (!is_wp_error($user) && $user->ID != 0)
    {
        $user_info = get_userdata($user->ID);
        if ($user_info->primary_blog)
        {
            $primary_url = get_blogaddress_by_id($user_info->primary_blog) . 'wp-admin/';
            if ($primary_url) {
                wp_redirect($primary_url);
                die();
            }
        }
    }
    return $redirect_to;
}, 100, 3);

The only shortcoming with this plugin is that it will redirect admins back to their primary site (blog id = 1) if they sign onto another site. To fix this we need a slightly more complicated solutions, which checks if the user has access to the blog they are logging in to and only if they do not have access do you redirect. If you prefer this behaviour you can use this version of the plugin:

<?php
/*
Plugin Name: Redirect Users to Primary Site
Plugin URI:
Description: Never see "you do not currently have privileges on this site" when logging in on your multisite ever again!
Version: 2014.06.02
Author: khromov
Author URI: https://profiles.wordpress.org/khromov
License: GPL2
*/

/* http://premium.wpmudev.org/forums/topic/redirect-users-to-their-blogs-homepage */
add_filter('login_redirect', function($redirect_to, $request_redirect_to, $user)
{
    global $blog_id;
    if (!is_wp_error($user) && $user->ID != 0)
    {
        $user_info = get_userdata($user->ID);
        if ($user_info->primary_blog)
        {
            $primary_url = get_blogaddress_by_id($user_info->primary_blog) . 'wp-admin/';
            $user_blogs = get_blogs_of_user($user->ID);

            //Loop and see if user has access
            $allowed = false;
            foreach($user_blogs as $user_blog)
            {
                if($user_blog->userblog_id == $blog_id)
                {
                    $allowed = true;
                    break;
                }
            }

            //Let users login to others blog IF we can get their primary blog URL and they are not allowed on this blog
            if ($primary_url && !$allowed)
            {
                wp_redirect($primary_url);
                die();
            }
        }
    }
    return $redirect_to;
}, 100, 3);