Integrate Wordpress into symfony
Fabrice Bernhard6 min read
What better topic to start this technical blog about symfony than to talk about my experience of integrating Wordpress into symfony !
I was looking for a nice blogging solution for symfony, and all I found was a very simple plugin and a lot of people encouraging me to build my own blog. Even though it is a nice exercise, my philosophy is to not reinvent the wheel. Wordpress is surely the best free blogging tool available, so I preferred to spend time integrating it into my symfony application than to create yet another sfVeryEasyBlogPlugin.
Integrating Wordpress into symfony can be done in three steps :
- integrating the blog into the application and its layout
- merging the authentification system
- integrating the blogging information into the symfony application
There is a wiki page handling the last two steps : http://trac.symfony-project.org/wiki/HowToIntegrateWordPressAndBbPressWithSymfony, written by Michael Nolan but I actually concentrated my efforts on the first step for the moment and that is what I will describe.
Integrating Wordpress into a symfony application and its layout
Install Wordpress
We need to store the wordpress files somewhere, I chose to create a new plugin sfWordpressPlugin and put the whole wordpress into the folder
plugins/sfWordpressPlugin/lib/vendor/wordpress
I then created a symbolic link in the web directory called blog pointing to the wordpress directory. That way I was able to run the Wordpress configuration and let it create its database
ln -s ../plugins/sfWordpressPlugin/lib/vendor/wordpress web/blog
Create a blog module
We then need a new module, which can be put in the new wordpress plugin and which I called sfWordpress. I enabled it in my frontend application and added the following routing :
blog: url: /blog/* param: { module: sfWordpress, action: index }
Create an action that executes Wordpress
It now becomes a little tricky. I want to execute Wordpress from inside symfony. The goal is to use output_buffering to send the output to the template. I experienced three difficulties :
- some actions in Wordpress output specific headers, such as feed actions, so their output should be sent directly to the browser and not go through the symfony template
- including the wordpress files inside of a function and using buffering to store the output seemed like an easy solution, unless Wordpress used a lot of global constants… which is unfortunately the case ! Wordpress has some very bad coding habits, they use a dozen of global variable, and some of them have such stupid names as “$name” which means anyone can override them by error (Me for example…)
- the __() function of Wordpress and symfony are conflicting…
I was able to overcome these difficulties, and here is how my action looks like :
/** * intégration de Wordpress * * @param sfWebRequest $request * @author fabriceb * @since Mar 4, 2009 fabriceb */ public function executeIndex(sfWebRequest $request) { // Don’t load symfony’s I18N $standard_helpers = sfConfig::get(‘sf_standard_helpers’); $standard_helpers = array_diff($standard_helpers, array(‘I18N’)); sfConfig::set(‘sf_standard_helpers’, $standard_helpers);
define(‘WP_USE_THEMES’, true); chdir( dirname(__FILE__) . DIRECTORY_SEPARATOR . ’..’ . DIRECTORY_SEPARATOR . ’..’ . DIRECTORY_SEPARATOR . ’..’ . DIRECTORY_SEPARATOR . ‘lib’ . DIRECTORY_SEPARATOR . ‘vendor’ . DIRECTORY_SEPARATOR . ‘wordpress’ ); global $wpdb; ob_start(); require_once( ‘wp-blog-header.php’ ); $this->blog = ob_get_contents(); if (function_exists(‘is_feed’) && is_feed()) { ob_end_flush(); throw new sfStopException(); } else { ob_end_clean(); } }
And I had to hack the wp-blog-header.php file to solve the problem of all the global variables :
require_once( dirname(__FILE__) . ‘/wp-load.php’ );
// @HACK FABRICE // All variables defined here are considered global by Wordpress $local_global_vars = get_defined_vars(); foreach($local_global_vars as $local_name => $local_value) { $GLOBALS[$local_name] = $local_value; } // Don’t create new global variables ourselves, and do not overwrite other global variables, for example $name… unset($local_name, $local_value, $local_global_vars); // @HACK FABRICE
wp();
// @HACK Fabrice global $posts; // @HACK Fabrice
require_once( ABSPATH . WPINC . ‘/template-loader.php’ );
My only small disappointment for the moment is that I did not solve the I18N conflict, I just avoided it. I will try to come back on this later to fnd a real solution… using namespaces for example ? :-)
Integrate Wordpress view in the symfony view
I created a new theme in my Wordpress, based on the default one, which goal was to output only the content of the blog without the layout. It is actually quite easy to do, you just go in each of the php files of the template and you remove all the references to the following functions :
- get_header()
- get_footer()
- get_sidebar()
That way, the output of Wordpress stored in the buffer is just the main content stripped out of the layout.
After that, the indexSuccess.php in the sfWordpress module is simple :
< ?php echo $blog ?>
However you still want to include Wordpress’s header or sidebar in your own layout. To do that I did the changes directly in my layout.php, as for example in the header :
< ?php if (defined(‘WP_USE_THEMES’) && WP_USE_THEMES): ?> < ?php get_header(); ?> < ?php else: ?> < ?php include_http_metas() ?> < ?php include_metas() ?> < ?php include_title() ?> < ?php endif; ?>
Or for the sidebar :
< ?php if (defined(‘WP_USE_THEMES’) && WP_USE_THEMES): ?> < ?php get_sidebar(); ?> < ?php else : ?> < ?php include_component(‘reference’, ‘quickList’) ?> < ?php endif; ?>
Secure the application
It was a big surprise to see that every dynamic Wordpress file is actually accessible from the web server. I do not feel at ease with this, and I plan on blocking direct access to any of the files. However for the moment I still see two files that are necessary and I have not yet wrapped inside a symfony action :
- wp-comments-post.php which is used to post the comments
- xmlrpc.php which is used for the pingbacks
So my philosophy for the moment is to trust Wordpress for the frontend files, and block access to all the other directories by including the following .htaccess in each of them :
AuthUserFile /etc/apache2/.htpasswd AuthName “Admin only” AuthType Basic require valid-user
Url rewriting
If you enable the url rewriting in Wordpress there is actually nothing to do, since the symfony routing already routes any /blog/* url to the Wordpress action. However you must be very careful about the .htaccess that Wordpress automatically generates and which will break everything !
Therefore I created an empty
plugins/sfWordpressPlugin/lib/vendor/wordpress/.htaccess
owned by root and removed any write access for the user www-data
Conclusion (for the moment)
My plan is to publish very soon the work done in a sfWordpressPlugin and work on the next two steps of integration :
- Merging authentication systems
This should be quite easy to do without a hack, since the whole authentication system of Wordpress is overridable by a plugin. I think Eric Kittell actually already did it, let us hope it is opensource.
- Exchange contents between symfony and Wordpress
There are two solutions here, create a schema file for the wordpress database or use the rss file as a web service content provider. Both are interesting.
Please feel free to comment on this work in progress…