Bootstrapping a Startup: Zend and WordPress Auth Integration


Three weeks ago, Columbus hosted Startup Weekend, and one of the presenters (blah name here) effectively stated that the first step of a startup should be to get something out there to gauge interest. This is fair- fact is there’s no point in throwing development hours at a project if you don’t know whether it’ll gain traction. This also gives you an opportunity to gather metrics and information that you can take to potential investors.

The real challenge, however, is “Getting Something Out There” that you can build on. I don’t mean setting up a blog or a couple of static pages, I mean putting in place a scaffolding that you don’t have to replace in its entirety as your business concept matures. The optimal solution then seems to be to use an established software package that can be extended to suit your needs.

Easier said than done. Every CMS out there claims to be extendable, yet not all are created equal. Furthermore extensibility usually requires that you build within their own framework, which then locks you into a third party whom you don’t have control over.

This can be overcome by developing your business services independently, and create a loose integration between your application and your CMS. By doing this you’re suddenly free to proceed with your own plans without the added worry of being locked into a platform.

So why did I choose WordPress and Zend? Well, WordPress has an easily understood plugin structure as well as an active developer community, plus its plugins allow you to do pretty much anything your heart desires. Zend was chosen because it’s a professionally supported application framework with quite a few corporate backers, and it’s written in the same language as WordPress- PHP (Plus, it’s what I know- Added Bonus).

The rest of this post is going to get technical, so I hope those of you who aren’t as geeky as I don’t mind. In essence I describe how to accomplish a loose WordPress-to-Zend integration with all the benefits I described above.

Step 0: Assumptions

Before I begin, there are some assumptions I’m going to make.

  1. You know how a Zend MVC application is structured.
  2. Your Zend code base will be hosted on the same server as your WordPress install.
  3. You are running application stacks on the same primary domain. (blog.fancybrandname.com and www.fancybrandname.com works)
  4. Your Zend application will handle user authentication records. This is because you don’t necessarily want to be restricted to WordPress’ table structure.
  5. Your application uses the MVC Templating System that comes with Zend.
  6. The WordPress integration code will live in the plugins directory on the WordPress side and (with one notable exception) in the ~library/ directory on the Zend side.

Most importantly, please understand that this implementation is NOT for the faint of heart, and requires additional code on your part. I cannot easily make any assumptions about how user authentication is handled within your Zend application, nor what models or methods you have built to simplify object retrieval.

Step 1, Database: Create a linking table

The first fundamental requirement is that we need some way of linking the WordPress user table (usually named wp_users) to the user table in the zend application. Usually this is done via a linking table, that might look something like this:

CREATE TABLE `user_id_link_table` (
  `id_zend` bigint(20) unsigned NOT NULL,
  `id_wordpress` bigint(20) unsigned NOT NULL,
  KEY `id_zend ` (`id_zend `),
  KEY `id_wordpress ` (`id_wordpress `)
);

Step 2, Zend and WordPress: Configure the Cookie Domain

To make sure your authentication cookies are available across your two domains, you’ll need to make sure both WordPress and Zend are looking for the same cookie. The way to do this is to set a wildcard Cookie domain so it transfers from one domain to another. If you look through the various configuration files and session initialization methods, you might see entries like “www.yourdomain.com” or “blog.yourdomain.com” near a reference to a cookie domain. To make sure they transfer, replace all instances with “.yourdomain.com” (the period at the beginning is important). That should ensure that your cookies are portable.

Step 3, Zend: Setting up an integration controller

The first thing we need to do is make sure that your Zend Application is fully loaded. Since you might want to use view helpers in your page templates, this means that we actually have to set up a ‘dummy’ controller that is used for integration purposes only, but which doesn’t try to output anything. This is easily done:

File: ~controllers/IntegrationController.php

<?php
/**
 * The Integration Controller.
 */
class IntegrationController extends Zend_Controller_Action
{
    /**
     * Index action. Sets up the entire framework but disables output.
     */
    public function indexAction()
    {
        $this->_helper->viewRenderer->setNoRender();
    }
}

Step 4, Zend: Extending the AutoLoader

The fourth step is that we need to extend the Zend AutoLoader, because otherwise it will get confused when WordPress tries to load its plugins. Since we don’t really know which plugins will be loaded ( and thus we can’t explicitly include them in our $PATH ), we’re simply going to restrict the Zend Autoloader to restrict its activities to certain namespaces.

This file is called ~library/Wordpress/Loader.php

File: ~library/Wordpress/Loader.php

<?php
require_once('Zend/Loader.php');

/**
 * The WordPress loader class is a quick filter that ensures only specific application
 * libraries are passed through to load handling.
 */
class WordPress_Loader extends Zend_Loader
{
    /**
     * Override of the loadClass method.
     *
     * @param string $class
     * @param string|array $dirs
     */
    public static function loadClass($class, $dirs = null)
    {
        $parts = split("_", $class );

        switch ( $parts[0] )
        {
            case 'Zend':
            case 'Wordpress':
            case 'YourNamespace':
                parent::loadClass($class, $dirs);
        }
    }

    /**
     * This handler has to be here for Zend_Loader invocation purposes
     *
     * @param class $class
     * @return string|boolean
     */
    public static function autoload($class)
    {
        try {
            self::loadClass($class);
            return $class;
        } catch (Exception $e) {
            return false;
        }
    }
}
?>

Once we’ve extended our AutoLoader, we now have to properly invoke it in our Zend Bootstrapper.

NOTE: You may have to adjust the following code somewhat so it fits in with your application. My own application has a Bootstrap Class within which this is a private method.

File: ~bootstrap.php

/**
 * Application Bootstrapper.
 */
class Bootstrap
{
    private function setAutoLoad()
    {
        require_once ( 'Zend/Loader.php' );
        // Notice that I'm still calling Zend_Loader, but I'm passing the new Classname.
        Zend_Loader::registerAutoload('Wordpress_Loader');
    }
} 

Step 5, Zend: Creating a Session Model

This fifth step is necessary if you’re going to delegate all authentication tasks to the Zend application rather than to WordPress itself. By treating our user sessions like a CRUD-based application, we can abstract it entirely into a Model rather than keeping the code within a Controller. By doing this we can invoke the method from anywhere, including a WordPress plugin.

NOTE: I’m putting this session class into the WordPress namespace, while it really should go into a namespace appropriate to your application.

File: ~library/Wordpress/Model/Session.php

<?php
/**
 * The session model wraps basic session creation and destruction methods
 * into one single interface. 
 */
class WordPress_Model_Session extends WordPress_Model_Abstract
{
    /**
     * Creates a new session (aka logs in a user)
     *
     * @param string $user
     * @param string $password
     * @return 
     */
    public function create ( $user, $password )
    {   
        // Get our authentication adapter and check credentials
        $adapter    =   $this->_getAuthAdapter( $user, $password );
        $auth       =   Zend_Auth::getInstance();
        $result     =   $auth->authenticate($adapter);

        if ( !$result->isValid() )
        {
            return false;
        }
        else
        {
            // We're authenticated! Add your own logic here.


            return true;
        }
    }

    /**
     * Destroys a session (aka logs out a user)
     */
    public function destroy ( )
    {
        Zend_Session::forgetMe();
        Zend_Auth::getInstance()->clearIdentity();
    }

    /**
     * Constructs an instance of the auth adapter for this model.
     *
     * @return Zend_Auth_Adapter_DbTable
     */
    protected function _getAuthAdapter( $login, $password )
    {
        // Put your authentication plugin loader code here.

        return $adapter;
    }
}

Step 4, Zend: Add an integration method to your Zend Bootstrapper

The last thing we need to do is create a method in our bootstrapper that lets us load the entire application without Zend trying to parse the URL into which it was loaded. To do this, we create our own HTTP Request instance with a URL that invokes the Controller and Action we created in step 3, thus effectively ‘fooling’ the FrontController into thinking it was invoked from somewhere else.

File: ~/bootstrap.php

/**
 * Application Bootstrapper.
 */
class Bootstrap
{
    /**
     * Executes the application in a bootstrapped integration state.
     */
    public function integrate()
    {
        // Construct a 'Dummy' Url to use for our Request.
        $uri = sprintf ( 'http://%s/integration/index',$_SERVER['HTTP_HOST'] );
        // Create a new request object
        $request = new Zend_Controller_Request_Http( $uri );
        // Dispatch our FrontController
        $this->_frontController->dispatch( $request );
    }
}

At this point, we’re done with all the work we need to do on the Zend side of things. On to the WordPress Plugin!

Step 6, WordPress: Create a plugin

The sixth step is to make sure that your entire Zend Application is available to WordPress. This is a security risk- the instant someone discovers a vulnerability in WordPress they’ve got access to everything, so make sure you have a competent PHP developer who knows how to secure an application.

File: ~/wp-content/plugins/zend/zend.php

<?php
/*
Plugin Name: Zend Integration Plugin
Version: 1.0.0
Description: Allows WordPress to authenticate users against the a Zend Session Model
Author: Michael Krotscheck
Author URI: http://www.krotscheck.net/
*/

class ZendIntegrationPlugin
{
    /**
     * Constructor. Initializes this class.
     */
    function __construct()
    {

    }
}

// Load the plugin hooks, etc.
$zend_integration_plugin = new ZendIntegrationPlugin();

Step 7, WordPress: Load the Zend Framework

The next step is to load your Zend application. By creating a method that explicitly loads our Bootstrapper and then invoking the integration method we created in Step 3 above, we load all necessary classes and dependencies without them interfering with WordPress.

File: ~/wp-content/plugins/zend/zend.php

class ZendIntegrationPlugin
{
    function __construct()
    {
        // .... previous code ....
        $this->Wordpress_framework_init();
    }

    /**
     * Initialize the Zend framework for inclusion.
     */
    public function WordPress_framework_init()
    {
        // Import the Zend Bootstrapper. This path needs to be absolute.
        require_once ( '@APPLICATIONROOT@/bootstrap.php' );

        // Create a new Bootstrapper instance and invoke the integration method.
        $bootstrap = new Bootstrap ( );
        $bootstrap->integrate();
    }
}

Step 8, WordPress: Disable Unnecessary Functions

Since we’re delegating all session authentication tasks to your Zend application, we need to explicitly disable many of WordPress’ default functionality related to password and account management. We do this by using the application hooks created explicitly for that purpose.

File: ~/wp-content/plugins/zend/zend.php

<?php

class ZendIntegrationPlugin
{
    function __construct()
    {
        // ... previous code ...

        // Disable Lost Password, Retrieve Password, and Password Reset
        add_action('lost_password', array(&$this, 'disable_function'));
        add_action('retrieve_password', array(&$this, 'disable_function'));
        add_action('password_reset', array(&$this, 'disable_function'));

        // Remove Password Fields from the wordpress user profile.
        add_filter('show_password_fields', array(&$this, 'disable_password_fields'));
    }

    /**
     * Used to disable certain display elements, e.g. password
     * fields on profile screen.
     */ 
    function disable_password_fields($show_password_fields)
    {
        return false;
    }

    /*
     * Used to disable certain login functions, e.g. retrieving a
     * user's password.
     */
    function disable_function()
    {
        die('Disabled');
    }
}

Step 9, WordPress: Autoload the session

This is perhaps the most complex piece of the plugin, because we have to do three things: First, we have to detect whether the authentication states between your Zend application and WordPress match. If they don’t, we have to either create a wordpress session or destroy it. The tricky bit here is that WordPress requires use of its own user database, so we have to retrieve basic information from the Zend User tables and construct a WordPress user should it not already exit.

Unfortunately, I cannot say how your Zend application is built nor what models exist that would allow you to retrieve a user record. As such I’ve inserted pseudocode in the method below that describes what needs to happen rather than make guesses about your implementation.

File: ~/wp-content/plugins/zend/zend.php

<?php

class WordPressAuthenticationPlugin
{
    function __construct()
    {
        // ... previous code ...

        add_filter('plugins_loaded', array(&$this, 'auto_login'));
    }

    /**
     * This method runs after the plugins is loaded, and attempts to detect
     * an out-of-sync session.
     */
    public function auto_login()
    {
        $isZendAuthenticated = Zend_Auth::getInstance()->hasIdentity();
        $isWordpressAuthenticated = is_user_logged_in();

        // Check to see if we're logged out of Zend but still logged in to WordPress
        if ( !$isZendAuthenticated && $isWordpressAuthenticated )
        {
            // Log out of WordPress.
            wp_logout();
            wp_clearcookie();
            set_current_user(null);
        }

        // Check to see if we're logged in to Zend but not WordPress
        if ( $isZendAuthenticated && !$isWordpressAuthenticated )
        {
            // Retrieve the user record from Zend
            $yourUserObject = yourUserRetrievalMethod();
            $wordpress_user_id = yourWordpressIdRetrievalMethod($user);

            // Check for the wordpress user ID, create a new user if necessary.
            if ( $wordpress_user_id == NULL )
            {
                // Retrieve the user creation scripts.
                require_once(ABSPATH . WPINC . DIRECTORY_SEPARATOR . 'registration.php');

                // Create a user object, using the hashed password value (It matter doesn't what you use as long as it's unique)
                wp_create_user($yourUserObject->login, $yourUserObject->password, $yourUserObject->email);

                // Check for a successful insert
                $wordpress_user_id = username_exists($login);
                if ( !$user_id )
                {
                    die("Error creating user!");
                }
                else
                {
                    yourWordpressIdUpdateMethod($user, $user_id);
                }
            }

            // Now that we know we have an existing wordpress user record that matches our Zend Table,
            // try to create a wordpress session
            $wpUser = new WP_User($wordpress_user_id, $yourUserObject->login);
            $wpPassword = md5($yourUserObject->password);
            wp_login($yourUserObject->login, $wpPassword, true);
            wp_setcookie($yourUserObject->login, $wpPassword, true);
            wp_set_current_user($wpUser->ID, $yourUserObject->login);
            return;
        }
    }
}

Step 10, WordPress: Enable Login via WordPress

At this point we are maintaining the authenticated session across our applications, but we don’t yet have WordPress authenticating users against our Zend user table. As above, I can’t make assumptions about how your authentication is set up, but I can show you which application hooks to set up, leaving the rest of the logic for you to fill in.

File: ~/wp-content/plugins/zend/zend.php

<?php

class WordPressAuthenticationPlugin
{
    function __construct()
    {
        // ... previous code ...

        add_filter('check_password', array(&$this, 'check_password'), 10, 4);
    }

    /**
     * Override Check Password
     */
    public function check_password($check, $password, $hash, $user_id)
    {
        // Retrieve a user by the WordPress User ID. You can use any class in your Zend application.
        $user = findZendUserByWordpressId ( $user_id );

        // Check for a valid return data.
        if ( $user )
        {
            // Try to log in.
            $sessionModel = new WordPress_Model_Session();
            return $sessionModel->create( $user->login, $password );
        }

        return false;
    }
}

Step 11, WordPress: Enable Logout via WordPress

The very last vanity task to perform is to enable logging out via the WordPress buttons. Again we use an action handler, and in this case we explicitly call the Session destroy method we created in Step 5.

File: ~/wp-content/plugins/zend/zend.php

<?php

class WordPressAuthenticationPlugin
{
    function __construct()
    {
        // ... previous code ...

        add_action('wp_logout', array(&$this, 'logout'));
    }

    /**
     * Runs Logout routines against the Zend controller.
     */
    public function logout ()
    {
        $sessionModel = new WordPress_Model_Session();
        $sessionModel->destroy();
    }
}
 

And that’s it. With all these pieces in place your Zend application should now be integrated with your WordPress intall. Congratulations! Now your startup has a fully functional, plugin ready CMS without being tied to a commercial solution for the long term.

17 Comments

  • Friedel

    Hey Mike, I wish I could use it; would need a new brain, but: so much work on the evening before your Birthday!! that's why we are sending you more distractions..... mm +pb

  • Steve B

    Thanks for the tutorial! This is exactly the situation I'am in with a upcoming project. Unfortunately I'm going to be using CodeIgniter for my framework. Have you seen any tutorials out there with this sort of situation but using CI?

  • Not myself, no, but CI is actually a little more lightweight than Zend. Chances are you don't have to jump through the hoops necessary to get all the pieces loaded.

  • krif

    Steve, I am looking for exactly the same. Maybe you can contact me on the CodeIgniter Forums? My user name there is "krif". Michael: Thanks for the tutorial!! Cheers

  • Awesome tutorial, I was able to make it work in my application logic with the pseudo-code you provided. The hooks provided saved me a lot of time.

  • Bobby

    Could you also do this if you kept the blog on the primary domain? Instead of blog.fancybrandname.com, use www.fancybrandname.com/blog... I would like to make a custom shopping cart in zend that has an integrated wordpress blog and I am just trying to figure out the best way to do it.

  • I don't see a reason why not. The main sticking points would be making sure the cookies are transferable and that the zend FrontController doesn't intercept your calls to the blog. The former's a configuration setting I somewhat cover in step two, while the latter's a change in your .htaccess file or similar rewrite configuration. Though, I might point out that the amount of time you'll spend building a cart in Zend is probably worth more than buying and configuring a plugin.

  • Bobby

    I totally agree, building a shopping cart in Zend is not the best use of my time when it comes to monetary considerations. Its just that I like to learn by doing. I will be following the book zend framework 1.8 by Pope. Thanks for replying!!

  • dodo

    Hi, Its very good tutorial! Can you also explain how should be folder structure when you integrate ZEND+WORDPRESS, because zend project have his own structure.

  • DJIO

    It would be possible to share the code files of this tutorial? We had a bit of trouble with our code and wouls like to compare if we did everything right. Thanks a lot in advance Cheers from Brazil!

  • To be honest, this tutorial is a bit dated. With the recent release of Wordpress 3 I doubt it's still valid, and I really don't know whether I still have the files. If you want to drop me a line through the contact form so we can exchange email addresses, I might be able to look at your site directly and see what's going on.

  • Bill

    Yea would be nice to use something similar to integrate Magento with Wordpress or buddypress... ;)

  • lisa

    Hi, this tutorial is just what I needed. One problem, my english is not so good...what do you mean by : "The WordPress integration code will live in the plugins directory on the WordPress side and (with one notable exception) in the ~library/ directory on the Zend side." ? You mean that I need the plugin shown here: http://wordpress.org/extend/plugins/zend-framework/ in the directory of wordpress , which will be in the library/wordpress file? So, my whole code will be the initial Zend code, but , in the library directory i will add the whole Wordpress which will include the zend plugin? thank you for your time!

  • Have you ever considered doing it with Yii?

  • Crispijn

    Could you tell me a little bit more about modifying the cookies to .mydomain.com? I'm stuck!

  • I have a snippet for creating a single login for zend framework & wordpress. Zend and WordPress single login

  • Rohit

    Hi Rob, I am new in Wordpress. Could you please elaborate for Zend and WordPress single login.

Leave a Reply