Yii 1.1: Integrating Wordpress and Yii: yet another approach

11 followers

It seems many people is trying to make Wordpress and Yii work together. I got stuck with the same problem, but now I think I have achieved doing it, after a lot of hard thinking and many work hours spent.

My starting point was reading imasia's and isekream's articles. The principle of both researches is calling the Yii application from inside Wordpress. The solution I propose goes on the opposite way: use Wordpress as page template for the Yii application, so the end-user won't even realize he left the former and entered the latter.

Let's proceed.

On Wordpress

My Wordpress installation is themed with a child theme of Twentyten (Wordpress 3.x default). For the purpose of this tutorial, let's assume that our WP installation is on http://yourserver.com/wp. When applying it, change the path accordingly.

First step is creating a file under your WP theme folder, and name it yii-template.php.

File: [wp-root]/wp-content/themes/[yourtheme]/yii-template.php

<?php
/**
 * Template Name: Yii Template
 *
 * Template for Yii-powered pages
 *
 * The "Template Name:" bit above allows this to be selectable
 * from a dropdown menu on the edit page screen.
 *
 * @package WordPress
 * @subpackage Twenty_Ten
 * @since Twenty Ten 1.0
 */
get_header();
?>
 
<div id="container">
  <div id="content" role="main">
 
    <?php
    /*
     * The placeholder below will be replaced by Yii-generated content later.
     */
    ?>
    <!-- ###yii-content-placeholder### -->
 
  </div><!-- #content -->
</div><!-- #container -->
 
<?php get_sidebar(); ?>
<?php get_footer(); ?>

We have just created a new template for Wordpress pages.

Second step is creating a Wordpress page that will use the brand-new template. Access your WP admin panel, create a page and choose "Yii Template" (should be listed) as the page template. Title it yiipage, and leave the page content empty. After saving, you should be able to see the page using the uri http://yourserver.com/wp/yiipage (assuming your permalinks are in path mode).

That's all for Wordpress.

On Yii

Create a Yii application (using yiic) under WP root, on a folder named yii. You should see the familiar Yii application frontpage by accessing http://yourserver.com/wp/yii.

We'll need a custom ClientScript class. Let's create it under [yii-root]/protected/components.

File: [yii-root]/protected/components/ClientScript.php

<?php
 
class ClientScript extends CClientScript {
 
  public function renderHead(&$output) {
    $html = '';
    foreach ($this->metaTags as $meta) $html.=CHtml::metaTag($meta['content'], null, null, $meta) . "\n";
    foreach ($this->linkTags as $link) $html.=CHtml::linkTag(null, null, null, null, $link) . "\n";
    foreach ($this->cssFiles as $url => $media) $html.=CHtml::cssFile($url, $media) . "\n";
    foreach ($this->css as $css) $html.=CHtml::css($css[0], $css[1]) . "\n";
    if ($this->enableJavaScript) {
      if (isset($this->scriptFiles[self::POS_HEAD])) {
        foreach ($this->scriptFiles[self::POS_HEAD] as $scriptFile) $html.=CHtml::scriptFile($scriptFile) . "\n";
      }
 
      if (isset($this->scripts[self::POS_HEAD])) $html.=CHtml::script(implode("\n", $this->scripts[self::POS_HEAD])) . "\n";
    }
 
    if ($html !== '') {
      $count = 0;
      /*
       * The line below ensures that everything registered by Yii at POS_HEAD goes just before
       * the head ending (</head>). This way, Yii styles and scripts will be rendered *BELOW* and
       * *AFTER* those of Wordpress.
       * 
       * The original line in parent reads:
       * $output = preg_replace('/(<title\b[^>]*>|<\\/head\s*>)/is', '<###head###>$1', $output, 1, $count);
       * 
       */
      $output = preg_replace('/(<\\/head\s*>)/is', '<###head###>$1', $output, 1, $count);
      if ($count) $output = str_replace('<###head###>', $html, $output);
      else $output=$html . $output;
    }
  }
 
}

We also need to override CController.beforeRender(). This can be done in the already existing file Controller.php, located under [yii-root]/protected/components.

File: [yii-root]/protected/components/Controller.php

<?php
/**
 * Controller is the customized base controller class.
 * All controller classes for this application should extend from this base class.
 */
class Controller extends CController
{
    /**
     * @var string the default layout for the controller view. Defaults to '//layouts/column1',
     * meaning using a single column layout. See 'protected/views/layouts/column1.php'.
     */
    public $layout='//layouts/column1';
    /**
     * @var array context menu items. This property will be assigned to {@link CMenu::items}.
     */
    public $menu=array();
    /**
     * @var array the breadcrumbs of the current page. The value of this property will
     * be assigned to {@link CBreadcrumbs::links}. Please refer to {@link CBreadcrumbs::links}
     * for more details on how to specify this property.
     */
    public $breadcrumbs=array();
 
  protected function beforeRender($view) {
 
    /* 
     * Let's prevent Yii from registering jQuery library. We'll stick with
     * the jQuery registered by Wordpress.
    */
    Yii::app()->clientScript->scriptMap=array(
      'jquery.js'=>false,
      'jquery.min.js'=>false,
    );
 
    /*
     * Wordpress works with jQuery in no-conflict mode, i. e., it doesn't define the $ alias for
     * the jQuery object. Nevertheless, many Yii scripts and plugins assume $ to point to jQuery,
     * so let's do the assignment ourselves.
     */
    if(! Yii::app()->clientScript->isScriptRegistered('jquery-alias', CClientScript::POS_HEAD)) {
      Yii::app()->clientScript->registerScript('jquery-alias', 'var $ = jQuery', CClientScript::POS_HEAD);
    }
 
    /*
     * To keep the visual coherence between normal Wordpress pages and Yii-powered pages,
     * we should use the CSS provided by the Wordpress theme. However, Yii use some 
     * specific CSS for forms, so it's a good idea to register the file containing it.
     */
    Yii::app()->clientScript->registerCssFile(Yii::app()->request->baseUrl . '/css/form.css');
 
    return parent::beforeRender($view);
  }
 
}

Now, the whole idea keystone. Let's change Yii main layout file, in order to fetch the Wordpress page we have previously prepared, replacing the placeholder within it by the content generated by the framework.

File: [yii-root]/protected/views/layouts/main.php

<?php
 
  /*
   * Here lies the core of the magic. Instead of using the content of the main layout
   * file provided by Yii, we fetch the proper Wordpress page (as a string) and replace the
   * placeholder within it with the actual content.
   */
 
  echo str_replace('<!-- ###yii-content-placeholder### -->', $content, file_get_contents('http://yourserver.com/wp/yiipage'));
 
  /* DO NOT FORGET TO ERASE THE ORIGINAL FILE CONTENT ;) */
 
?>

Finally, let's tell Yii to use our custom ClientScript class.

File: [yii-root]/protected/config/main.php

<?php
  (...)
  'components'=>array(
      /*
     * Let's use our very own ClientScript (application.components.ClientScript).
     */
    'clientScript' => array(
      'class' => 'ClientScript'
    ),
  (...)

That's all. You should be able to see your Wordpress-themed Yii application by accessing http://yourserver.com/wp/yii, and even logging on it through http://yourserver.com/wp/yii/index.php?r=site/login. Further tweaks include adjusting your Yii configuration to use url paths and hide the script name (so your urls will look like http://yourserver.com/wp/yii/controller/action). And yes, AJAX works! *__*

Back onto Wordpress, the final task is creating menu items linking to Yii powered pages.

Hope this be as useful for you as it was for me. Cheers!

Total 8 comments

#12445 report it
nhexia at 2013/03/21 01:26am
I have Error

file_get_contents() [function.file-get-contents]: URL file-access is disabled in the server configuration

#12194 report it
bluyell at 2013/03/05 11:10am
Using jQuery (in Wordpress)

When using this solution in a Wordpress environment (dont now if it works in joomla or similar) then we have an issue: some jQuery scripts doesnt works.

things like: "$('#anything').click(....);" doesnt works until we put jQuery instead of $, in other words, changing: $('#anything') by jQuery('#anything'), or preceding the script with: var $ = jQuery; leaving the remaining code unaltered.

to solve this: in your YII LAYOUT add the following line at bottom:

<script>jQuery(document).ready(function(){ $ = jQuery; });</script>

the new code will looks like this:

<?php
  /*
   * Here lies the core of the magic. Instead of using the content of the main layout
   * file provided by Yii, we fetch the proper Wordpress page (as a string) and replace the
   * placeholder within it with the actual content.
   */
  echo str_replace('<!-- ###yii-content-placeholder### -->', $content, 
          file_get_contents(Yii::app()->params['cms_layout']));
  /* DO NOT FORGET TO ERASE THE ORIGINAL FILE CONTENT ;) */
?>
<script>jQuery(document).ready(function(){ $ = jQuery; });</script>
#11970 report it
bluyell at 2013/02/16 05:32pm
nice work

it is a very nice wiki. my +1 for you. this is what im looking for, because the CMS option is always the best solution when talking about mature websites..

#7705 report it
Jonathan123 at 2012/04/10 09:46pm
Works pretty well

It's a bit of a hack, but it does the trick. I definitely recommend using the caching. Also you will probably want to do something like this:

echo str_replace(array('<!-- ###yii-content-placeholder### -->', 'yiipage'), array($content, CHtml::encode($this->pageTitle) ), $v);

Where "yiipage" is the title of the Wordpress Page you created.

#6839 report it
adbie at 2012/02/08 07:26pm
to avoid potential resource issue

As I mentioned previously, I'm using the technique mentioned in the above post to integrate with Wordpress.

To avoid the potential deadlock issue that redguy commented about, I use memcached to cache the content returned by file_get_contents:

$v=Yii::app()->cache->get('wpyiipageprod');
if($v===false)
{
    $v = file_get_contents(WPYIIPAGE);
    Yii::app()->cache->set('wpyiipageprod',$v,7200); //2 hr
}

Hope this helps someone.

#6814 report it
odongodong at 2012/02/07 02:39am
out of memory

hi, i got this if i write until final step:

Fatal error: Out of memory (allocated 2883584) (tried to allocate 8192 bytes) in C:\xampp\htdocs\wordpress\yii\protected\views\layouts\main.php on line 10

if i don't write final step : replace main.php File: [yii-root]/protected/views/layouts/main.php

it works

#5719 report it
adbie at 2011/11/06 03:21pm
i'm using this

Thanks for the excellent solution and clear instructions.

For those who are concerned about performance: If your wp header and sidebar don't change often, you can cache the result of the get_file_contents.

#4415 report it
redguy at 2011/07/06 04:13pm
potential server resources issue

Your concept is interesting but you should be aware that using inner HTTP call from PHP to the same server like this:

file_get_contents('http://yourserver.com/wp/yiipage')

may lead to reduced server performance and resources outage. Consider running PHP as FastCGI application with limited processess - your solution requires 2 PHP instances to serve single request, so server can process twice less request in single unit of time. There is also potential lock situation - when there are as many simultaneous request to Yii app as PHP pool size - every request will freeze waiting for WP template, but there will be no PHP processes left to serve such request until timeout (there should be timeout...). During this lock server won't serve any other dynamic content using same PHP pool.

I am not talking your solution is good or bad - just have in mind this side effect.

Leave a comment

Please to leave your comment.

Write new article