Update! Code at Git Hub works. I'll update this page once a few bugs are taken care of.
Wildflower is a CMS written in CakePHP. It's ao frickin simple to use as a base for my applications that I showed it to my boss on Monday ( I had figured out how to extend the admin interface for my own models which I had baked using ModelBaker. I used the difference viewer in Netbeans to merge the baked models in to WF 1.3b branch and the results were quite nice... SO.. We decided to see if we could get this multi-site extension to work. The provided branch and code samples on that page worked, but it was written against an old 'cake plugin' version of WildFlower. So I decided to fork from Klevo's 1.3b branch and try to adapt the existing "extension" to work with the new code. I would like for us to commit code back upstream if it we end up using it for other stuff.. I just need to get this initial multi-site code working with current sources for that to happen.
http://github.com/SteveHatherley/wildflower/tree/masterI have not figured out how to get Netbeans to cooperate with GitHub just yet so I don't have a way to commit my code up there.. but, I do want to share it and get some help making it work.. so I'll just attach a zip file with a SQL dump for now.
Ok so I am going to update the previous Multiple Install Extension install instructions and document the places I made changes, and to what, so yeah, credit to them and stuff.
Download Stevo's Wildflower Multi-Site Extension
Download Stevo's Wildflower Multi-Site Extension Full Schema Dump
*Remember to chmod +777 -r app/tmp/* and /app/webroot/uploads
Step 1 Wildflower Changes
The original Multiple Extension instructions tell us to enable Cakephp Theming, but this is not necessary in the 1.3b branch since both themes and layouts are already enabled.
We still need to check which site needs to be delivered by checking the HTTP_HOST. We will do this in a new method called checkInstall() [should be checkSite()]. This method will be called by beforeFilter() and is going to be used to check the current URL.
- In "function beforeFilter()", add the following method call After "$this->wildflowerCallback('before');":
$this->checkInstall();
(Around line 46) - Then at the end of the app_controller class, add this function:
/**
* Check Install
*
* Check to see which install is being used in the admin
* Assigns a global Install ID based on http host
*/
function checkInstall() {
// Load the install model
$this->loadModel('WildInstall');
// Predefine default settings.
$settings = array(
'id'=>1,
'name'=>'Default Template',
'code'=>'default',
'theme'=>'',
'layout'=>''
);
// Set the Installs Dropdown variables on the Wildflower Administration page
$wilduser = $this->Session->check('WildUser');
$skip_url = false;
if (!empty($wilduser) && $wilduser>0 && !empty($this->params['prefix']) && $this->params['prefix'] == 'wf') {
$install_id = $this->Session->read('WildInstall.id');
if (!empty($install_id) && $install_id > 0) {
$res = $this->WildInstall->findById($install_id);
if(!empty($res['WildInstall']) && is_array($res['WildInstall'])) {
$settings = am($settings, $res['WildInstall']);
$skip_url = true;
}
}
// Setting the installs pulldown menu on the admin page.
$installs_dropdown = $this->WildInstall->find('list');
$this->set('installs_dropdown', $installs_dropdown);
$this->set('install_dropdown', $settings);
}
// If you are not in the admin, try to find the Install based on the http_host
if (!$skip_url) {
$url = preg_replace("/\//",'', env('HTTP_HOST'));
$url = preg_replace("/^www./",'', $url);
$res = $this->WildInstall->find(array('WildInstall.http_host'=>"$url"));
if(!empty($res['WildInstall']) && is_array($res['WildInstall'])) {
$settings = am($settings, $res['WildInstall']);
}
}
// Set the theme based on the install settings
if (!empty($settings['theme']) && count($settings['theme'])) {
$this->theme = $settings['theme'];
}
// Set the layout based on the install settings
if (!empty($settings['layout']) && count($settings['layout'])) {
$this->layout = $settings['layout'];
}
// Write the install ID so it can be seen by the beforeFind
Configure::write('WildInstall.id', $settings['id']);
}
Here are the functions. I do not know if I have named them correctly because things are broken. There appears to be some sort of cakephp related naming crap going on. It throws an error about invalid column "wild_install_id".. but more on that later on when I talk about what I've done to debug.. here's the function:
/**
* Attaches an install_id to all the finds in the system
*
* @param array $passed the data passed to a model->find
*/
function beforeFind(&$passed) {
if (!empty($this->_schema['install_id'])) {
$iid = Configure::read("WildInstall.id");
$install_id_str = $this->alias . '.install_id';
if (!empty($passed['conditions']) && is_array($passed['conditions'])) {
$passed['conditions'][$install_id_str] = $iid;
} elseif(!empty($passed['conditions']) && strlen($passed['conditions'])>0) {
$passed['conditions'] .= ' AND '. $install_id_str . ' = ' . $iid . ' ';
} else {
$passed['conditions'] = array($install_id_str => $iid);
}
}
return true;
}
/**
* Attaches an install_id to all the saves in the system
*
*/
function beforeSave() {
if (!empty($this->_schema['install_id'])) {
$iid = Configure::read("WildInstall.id");
$this->data[$this->alias]['install_id'] = $iid;
}
return true;
}
Step 2 Wildflower "Installs" Controller & Model
You will need to create a controller and model for the "Installs" (Sites). I've done some re-naming in here from "Installs" to "WildInstalls" and so on (in both the controller and in the model)@file: /wildflower/controllers/wild_installs_controller.php
class WildInstallsController extends AppController {
public $components = array('RequestHandler');
public $helpers = array('Text', 'Time', 'Wildflower.List');
public $uses = array('Wildflower.WildInstall','Wildflower.WildPage','Wildflower.WildSetting');
public $paginate = array(
'order' => 'name ASC',
'limit' => 25
);
public $pageTitle = 'Installs';
function wf_index() {
$this->WildInstall->recursive=0;
$installs = $this->paginate('WildInstall');
$this->set(compact('WildInstalls'));
}
function wf_view($id) {
$this->WildInstall->recursive = -1;
$this->set('WildInstall', $this->WildInstall->findById($id));
}
function wf_edit($id = null) {
$this->data = $this->WildInstall->findById($id);
//pr($this->data);exit;
if (empty($this->data)) $this->cakeError('object_not_found');
$this->WildInstall->save($this->data);
}
function wf_update() {
$this->WildInstall->create($this->data);
if ($this->WildInstall->save()) {
return $this->redirect(array('action' => 'view', $this->WildInstall->id));
}
$this->render('wf_edit');
}
function wf_create() {
if ($this->WildInstall->save($this->data)) {
$id = $this->WildInstall->id;
$this->Session->write('WildInstall.id', $id); // Load the current Install
return $this->redirect('/wf/settings');
}
$this->WildInstall->recursive=-1;
$installs = $this->paginate('WildInstall');
$this->set(compact('WildInstalls'));
$this->render('wf_index');
}
function wf_change() {
// The WFAdmin changes the Install id for configuration.
if (!empty($this->data['WildInstall']['id']) && $this->data['WildInstall']['id']>0) {
$this->Session->write('WildInstall.id', $this->data['WildInstall']['id']);
}
$this->redirect('/wf');
}
function delete($id) {
$id = intval($id);
if ($this->RequestHandler->isAjax()) {
$this->WildInstall->del($id, true);
exit();
}
if (empty($this->data)) {
$this->data = $this->WildInstall->findById($id);
if (empty($this->data)) {
$this->indexRedirect();
}
} else {
$this->WildInstall->del($this->data[$this->modelClass]['id'], true);
$this->indexRedirect();
}
}
function beforeRender() {
parent::beforeRender();
$this->params['current']['body_class'] = 'contact-page';
}
}
and here is the Install's model. Note: The afterSave() function will create new settings and a new default page for the new install.
We need to make use of the beforeSave() override at the model level in order to make our HTTP_HOST filter we defined earlier to work..
class WildInstall extends AppModel {
// public $tablePrefix = 'wild_';
// public $useTable = 'installs';
public $validate = array();
public $actsAs = array('Containable');
public $hasMany = array(
'WildSetting' => array(
'className' => 'Wildflower.WildSetting'
),
);
function afterSave($created) {
$id = $this->id;
Configure::write("WildInstall.id", $id);
if($created) {
$this->recursive=1;
// Grab the WildSettings for InstallID 1
$res = $this->findById(1);
$new_settings = array();
if (!empty($res['WildSetting']) && count($res['WildSetting'])) {
foreach($res['WildSetting'] as $i => $setting) {
// Clean up fields...
unset($setting['id'], $setting['value']);
foreach($setting as $key=>$val) {
if (empty($this->WildSetting->_schema[$key])) {
unset($setting[$key]);
}
}
// Create the default settings
$this->WildSetting->create($setting);
$this->WildSetting->save();
}
}
// Create a default page based on the one found in DB.
$wildPage = ClassRegistry::init('WildPage');
$newpage = array('WildPage' => array(
'install_id'=>$id,
'slug'=>'home',
'url'=>'/',
'title'=>'Home Page',
'draft'=>1
));
$wildPage->create($newpage);
$a = $wildPage->save();
}
return true;
}
function beforeDelete() {
return false;
}
}
?>
Find
Function beforeSave(), and replace:
return true;
in
- wild_comment.php
- wild_page.php
- wild_post.php
- wild_user.php
- wild_assets.php
- Probably others too.. not sure yet
$res = parent::beforeSave();
if ($res === false) return false;
return true;
Step 3 Wildflower "Installs" views
You will need to create the View, Index, and Edit views for installs in /wildflower/views/wild_install/ . I'm having trouble copying the html into the form here so we'll just say use this stuff but remember to put it in the aforementioned folder.Step 4 Wildflower Database Modifications
Ok seriously, f'king back up your shit. The other tutorial mentioned renaming your tables and using a prefix and so on.. this is already there and table prefixes appear to be wild_ by default. Just make sure you keep a working sql dump of your data before you push it on anything important.So before we get in to the SQL, we need to solve a naming conflict. Originally the plugin used $useTable = "installs" in the model. We want to be using wild_installs (later to be renamed to wild_sites)..
DROP TABLE IF EXISTS `wild_installs`;
CREATE TABLE `installs` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(255) collate utf8_unicode_ci NOT NULL,
`code` varchar(255) collate utf8_unicode_ci NOT NULL,
`http_host` varchar(255) collate utf8_unicode_ci NOT NULL,
`layout` varchar(255) collate utf8_unicode_ci NOT NULL,
`theme` varchar(255) collate utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `http_host` (`http_host`),
KEY `name` (`name`),
KEY `code` (`code`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `wild_installs` VALUES (1, 'Default Layout', 'default', '', '', '');
ALTER TABLE `wild_categories` ADD `install_id` INT( 11 ) DEFAULT '1' NOT NULL AFTER `id` ;
ALTER TABLE `wild_categories` ADD INDEX ( `install_id` ) ;
ALTER TABLE `wild_comments` ADD `install_id` INT( 11 ) DEFAULT '1' NOT NULL AFTER `id` ;
ALTER TABLE `wild_comments` ADD INDEX ( `install_id` ) ;
ALTER TABLE `wild_messages` ADD `install_id` INT( 11 ) DEFAULT '1' NOT NULL AFTER `id` ;
ALTER TABLE `wild_messages` ADD INDEX ( `install_id` ) ;
ALTER TABLE `wild_pages` ADD `install_id` INT( 11 ) DEFAULT '1' NOT NULL AFTER `id` ;
ALTER TABLE `wild_pages` ADD INDEX ( `install_id` ) ;
ALTER TABLE `wild_posts` ADD `install_id` INT( 11 ) DEFAULT '1' NOT NULL AFTER `id` ;
ALTER TABLE `wild_posts` ADD INDEX ( `install_id` ) ;
ALTER TABLE `wild_revisions` ADD `install_id` INT( 11 ) DEFAULT '1' NOT NULL AFTER `id` ;
ALTER TABLE `wild_revisions` ADD INDEX ( `install_id` ) ;
ALTER TABLE `wild_settings` ADD `install_id` INT( 11 ) DEFAULT '1' NOT NULL AFTER `id` ;
ALTER TABLE `wild_settings` ADD INDEX ( `install_id` ) ;
ALTER TABLE `sitemaps` ADD `install_id` INT( 11 ) DEFAULT '1' NOT NULL AFTER `id` ;
ALTER TABLE `sitemaps` ADD INDEX ( `install_id` ) ;
ALTER TABLE `tags` ADD `install_id` INT( 11 ) DEFAULT '1' NOT NULL AFTER `id` ;
ALTER TABLE `tags` ADD INDEX ( `install_id` ) ;
ALTER TABLE `wild_assets` ADD `install_id` INT( 11 ) DEFAULT '1' NOT NULL AFTER `id` ;
ALTER TABLE `wild_assets` ADD INDEX ( `install_id` ) ;
-- Slugs should be unique per install, not per table.
ALTER TABLE `wild_pages` DROP INDEX `slug`;
ALTER TABLE `wild_pages` ADD UNIQUE `slug_install` ( `slug` , `install_id` );
ALTER TABLE `wild_posts` DROP INDEX `slug`;
ALTER TABLE `wild_posts` ADD UNIQUE `slug_install` ( `slug` , `install_id` );
ALTER TABLE `wild_categories` DROP INDEX `slug`;
ALTER TABLE `wild_categories` ADD UNIQUE `slug_install` ( `slug` , `install_id` );
ALTER TABLE `tags` DROP INDEX `name`;
ALTER TABLE `tags` ADD UNIQUE `name_install` ( `name` , `install_id` );
Step 6 Wildflower App Routing
App Routing is when you make ugly paramter strings turn in to pretty, search engine friendly urls like /blog/may/this-was-my-post. With WildFlower this means that you can have pretty url slugs for your articles and posts.Routes are declared in /wildflower/config/routes.php. Add 'installs' to $wfControllers as seen below.
$wfControllers = array('pages', 'posts', 'dashboards', 'users', 'categories',
'comments', 'assets', 'messages', 'uploads', 'settings', 'utilities',
'widgets','installs');
*More information about customization of views, themes, etc are available on the original blog. Will expand this section later once we have the app not crashing.

3 comments:
Great work Steve. really great stuff - I have just read your post on the wf forum and will be reading this later. Today I have made a dual sitemap for wf that generates .xml for google and also a traditional sitemap for IA purposes.
MU Wf is the reason argues the case for themes, as it then allows each site to have its own look and feel with also its own.
I hope to have a look at the issues your described, depending on time, responding to the thread and update it with what I findings
thx S
thanks
Monkee,
Thanks for doing this. After getting the initial multi-site setup working, we got caught up in other projects and haven't had a chance to get back into the later versions of Wildflower.
I agree that using "sites" would be better terminology than "installs". The "installs" terminology originally came from the concept of Wordpress having multiple install for different sites, which Wordpress-MU solves.
Again thanks for the work on this!
@Steve
There is a slight bug in logic for the system. wild_install.php line 18, where you find the wild_install with ID one. You can't really assume the database id list begins at one, I've had a problem where I've deleted the root site and the ID 1 no longer exists, so when the settings try to get created it doesn't work. It would probably be better to do $res = $this->find('all')
$res = $res[0];
Post a Comment