I'm creating my own script using the CodeIgniter MVC framework. Now, i want users to easily modify the site functionality and adding their own without modifying the code which i've already written.
How do i make my site pluginable ?
EDIT: The users would be the site admins. Not the end user. Basically just like drupal or joomla. Want the admin to be able to create/add plugins to extend site functionality.
There may be a better way that's specific to CodeIgniter, but this is what I would do:
First, create functions for various "hook points" in your code. Say, a function named PreArticle that you call in your code, before an article is displayed.
Allow the user to write code like this:
addHook_PreArticle('funcToCall');
function funcToCall( &$articleText ) {
$articleText = str_replace('Hello', 'World', $articleText);
}
addHook_PreArticle is a function you've defined, which would add the passed string to some internal list. Then when the PreArticle function is called, each of those functions are executed, passing in any appropriate parameters that you define.
Many CMS's Like Joomla and Blogs like Wordpress use variable function names:
$function="phpinfo";
$function();
You could load this into an array to create a list of functions that can be overridden.
That's a perfect case to use the Observer Pattern.
http://devzone.zend.com/article/5
Related
I am trying to give a shot to HMVC in Codeigniter. Here is my folder structure.
-ROOT
--APPLICATION
---MODULES
----Module_Email
-----Controllers
-----Models
-----Views
-----Assets
------JS
------CSS
------IMG
To render the Module i have to use
Module::run('Module_Email');
This method will output the rendered output, an example is given below
<script type="text/javascript" src="PATH/TO/EMAIL_MODULE/JS/JS_FILE.JS"></script>
<div data-module-name="Module_Email" class="Email_wrapper">
//RENDERED HTML CONTENT
</div>
Now here my problem start. Normally i would like to put all my resources to header. So when i call any module, its dependence need to be added in header instead of from where its get called.
I searched a lot but i couldn't find any good methods.
Please help.
Update
Currently i have a function on my header called get_assets() which will output predefined resources to header. But i cant say which modules is going to use in pages, so the system need to check which modules are used in this page, and if its used then its dependencies need to be added on header.
Seems like your main problem then is trying to figure out what modules were used.
Unfortunately as far as I can tell with the default Wiredesignz modular extension there is no way to access the module name unless you write some sort of hack to get at that data. The module being used is stored in the protected variable $module in the MX_Router class, however, there is no public method to allow you to get access to it. So your only choice would be to extend the class and create a public function.
Alternatively you could use a forked version of Wiredesignz implementation which I did which provides numerous other features including a public function to get at the $module variable. Using the forked version I wrote you could then use code such as this:
<?php $module_name = $this->router->fetch_module(); ?>
However, that will only record the last module you loaded, so you would still need to do work to store all the modules, and then have your function use this information to determine what assets to load. If I were doing something like you I would probably fork my version and then create an additional data structure to store every module that was loaded that you could then later get access to.
I don't think this is exactly what you were hoping for, but might be something to get you on the right track to finding a solution.
I added an array to the Module class to store the assets and two functions to store/retrieve the items. Here is the source (updated Modules.php)
# Register your assets
public static function register_asset( $asset )
{
if( in_array($asset,self::$assets) === FALSE )
{
self::$assets[] = $asset;
}
}
public static function assets()
{
return self::$assets;
}
and now you can register your assets like this inside your module
Modules::register_asset('myslider.js');
You can retrieve all your assets using
Modules:assets();
Which will return an array of assets that can be processed depending up on the situation.
Situation: only main page is accessible by default, all other pages needs a logged in user. When a module is loaded without user, a login template should be displayed, and no module. In other words, the $sf_content must be emptied in layout.php which is not 100% ok since there is logic in the layout. Is there elegant way for that? I dont think a helper is OK either....
Check out security filters, this is one standard way security is designed in symfony.
You even can implement your own SecurityFilter class with the functionality you want.
http://symfony.com/legacy/doc/reference/1_4/en/12-Filters#chapter_12_security
It is done by default for you by the sfBasicSecurityFilter filter. You just need a good configuration. Read this part of the Jobeet tutorial. You should use sfDoctrineGuardPlugin (or sfGuardPlugin if you using propell) for user authentication.
To complete my comments above: There are different ways to override the layout. You could use the methods:
setLayout($name)
//or using foward, which forwards current action to a new one (without browser redirection)
forward($module, $action);
inside your action class. In case you wand to modify the layout inside a filter, you can use something simular to this:
class yourFilter extends sfFilter {
public function execute($filterChain) {
if($yourConditionForOverrideTheDefaultLayout) {
//here the syntax to change the layout from the filer
$actionStack = $this->getContext()->getActionStack();
$actionStack->getFirstEntry()->getActionInstance()->setLayout('yourLayout');
}
$filterChain->execute();
}
}
To avoid unnecessary duplication in the layout file you can work with Fragments and Partials.
I've volunteered to create some db app, and I told those guys that it will be very easy, since I wanted to use CakePHP. Sadly after some time they told me they want it inside their already existing web, which is ancient highly customized PHPNuke.
So what I want is to generate just content of one <div> inside an already existing page with CakePHP. I looked up on the internet, but I didn't find what I was looking for. I'm rather a user of the framework, not developer, so I don't know much about the backend and how MVC frameworks are working inside (and this is my first try with CakePHP, since I'm Rails guy).
What I did so far is disabling mod_rewrite for Cake. Inside PHPNuke module I included Cake's index.php and rendering views with an empty layout. This somehow works, but the thing is how to form URLs. I got it working by now with
http://localhost/modules.php/posts?op=modload&name=xxxxx&file=index&do=xxxxx
but with this all links to CSS and images on PHPNuke site are broken.
Is there any way to use something like
http://localhost/modules.php?op=modload&name=xxxxx&file=index&do=xxxxx&CakePHP=/posts/bla/bla
or any other way that could do the job? I really don't want to change anything in existing PHPNuke app.
Thank you very much
Well, if you don't understand how CakePHP works you'll have trouble doing what you want, since it would mean putting hacks into the CakePHP core files to bypass the default routing. This basically means that you would be re-working the way CakePHP works, so you can forget about ever updating to a newer CakePHP version, and maintenance would be hell.
If you want to modify the system, but keep PHP-Nuke, I'd advise against jamming CakePHP in there, since that would open up too many problems to be able to predict beforehand.
I think your options are as follows:
Learn how PHP-Nuke works so you can modify it
Use regular php for the pages
Either of those are easier by orders of magnitude compared to what you wanted to do.
So to sum up solution I found, if someone will be looking for something similar. Problem solved by using two custom route classes ( http://manual.cakephp.neoboots.com/2.0/en/development/routing.html#custom-route-classes )
class CustomParserRoute extends CakeRoute {
function parse($url) {
if (parent::parse($url) != false) //if default parser has the match continue
{
// call to Router class to do the routing for new url string again,
// if &cakePHP= is in query string, use this, or use default
if ($_GET['cakePHP']) {
$params = Router::parse($_GET['cakePHP']);
} else {
$params = Router::parse("/my_controller");
}
return $params;
}
return false;
}
}
class CustomMatcherRoute extends CakeRoute {
// cusotm mathc function, that generates url string.
// If this route matches the url array, url string is generated
// with usual way and in the end added to url query used by PHPNuke
function match($url) {
$result_url = parent::match($url);
if($result_url!= false) {
$newurl = function_to_generate_custom_query()."&cakePHP=".$result_url;
return $newurl;
} else {
return $result_url;
}
}
}
And then simple configuration in routes php
App::import('Lib', 'CustomParserRoute');
App::import('Lib', 'CustomMatcherRoute');
// entry point to custom routing, if route starts with modules.php it matches
// the url and CustomParserRoute::parse class is called
// and route from query string is processed
Router::connect('/modules.php', array('controller' => 'my_controller'), array('routeClass' => 'CustomParserRoute'));
// actual routes used by cakephp app, usual routes that need to use
// CustomMatcherRoute classe, so when new url is generated, it is modified
// to be handled later by route defined above.
Router::connect('/my_controller/:action/*', array('controller' => 'my_controller'), array('routeClass' => 'CustomMatcherRoute'));
I'm using CodeIgniter, and will likely use their template library as I want to keep things extremely simple to use. The content for the template variables will come from the database, but I want the business admins to know what content areas are available. Basically the names of the parameters when they choose a specific template. For instance, Joomla uses an extra XML file that defines each area, whereas Wordpress uses comments within a page template to inform the system that the PHP file is a template. I like the Joomla approach because you don't have to parse the PHP file to find the areas, but I like the Wordpress approach because you don't have an extra XML file associated with every template. Are there other approaches that I'm missing?
I think the nicest way would be to add a small hack to the template parser class. The code looks quite readable and clean in system/libraries/Parser.php. You could insert a hook in that class that can be used to keep track of the variables. I don't know, if it works, but here's a snippet:
class CI_Parser {
var $varCallback;
function setVarCallback($callbackFunction) {
$this->varCallback = $callbackFunction;
}
...
function _parse_single(...) {
$callback = $this->varCallback;
$callback($key);
}
...
//Somewhere in your code
function storeVarName($variableName) {
// Persist the variable name wherever you want here
}
$this->parser->setVarCallback('storeVarName');
You could do this directly in the controller:
// in the controller
print_r($data);
$this->load->view("main", $data);
Or a little more rudimentary, but you could pass to the template a PHP array of variables (or an object):
// in the controller
$data = array();
$data["namespace"] = array(
"title" => "My website",
"posts" => array("hi", "something else")
);
$this->load->view("main", $data);
And then in the view, have a flag to print_r the namespace to show all the vars available, so that business admins know exactly what to use.
// in the view
if(isset($namespace["showAllVars"])) print_r($namespace);
One option would be to call token_get_all on the PHP file (only when your business admins are loading it up), and parse the output of that.
The best approach, in my opinion, is to keep the variable definitions in another place (such as a database table, or a separate file). This will help with testing (i.e., a programmer can't just remove a tag and it's gone) and making sure things are still working as you move on with the application development in time.
Another advantage is that your application logic will be independent from the templating engine.
On a side note, if you expect a lot of traffic, you may consider using smarty instead. We have done extensive testing with most of the templating engines around and smarty is the fastest.
I have a CakePHP application that in some moment will show a view with product media (pictures or videos) I want to know if, there is someway to include another view that threats the video or threats the pictures, depending on a flag. I want to use those "small views" to several other purposes, so It should be "like" a cake component, for reutilization.
What you guys suggest to use to be in Cake conventions (and not using a raw include('') command)
In the interest of having the information here in case someone stumbles upon this, it is important to note that the solution varies depending on the CakePHP version.
For CakePHP 1.1
$this->renderElement('display', array('flag' => 'value'));
in your view, and then in /app/views/elements/ you can make a file called display.thtml, where $flag will have the value of whatever you pass to it.
For CakePHP 1.2
$this->element('display', array('flag' => 'value'));
in your view, and then in /app/views/elements/ you can make a file called display.ctp, where $flag will have the value of whatever you pass to it.
In both versions the element will have access to all the data the view has access to + any values you pass to it. Furthermore, as someone pointed out, requestAction() is also an option, but it can take a heavy toll in performance if done without using cache, since it has to go through all the steps a normal action would.
In your controller (in this example the posts controller).
function something() {
return $this->Post->find('all');
}
In your elements directory (app/views/element) create a file called posts.ctp.
In posts.ctp:
$posts = $this->requestAction('posts/something');
foreach($posts as $post):
echo $post['Post']['title'];
endforeach;
Then in your view:
<?php echo $this->element('posts'); ?>
This is mostly taken from the CakePHP book here:
Creating Reusable Elements with requestAction
I do believe that using requestAction is quite expensive, so you will want to look into caching.
Simply use:
<?php include('/<other_view>.ctp'); ?>
in the .ctp your action ends up in.
For example, build an archived function
function archived() {
// do some stuff
// you can even hook the index() function
$myscope = array("archived = 1");
$this->index($myscope);
// coming back, so the archived view will be launched
$this->set("is_archived", true); // e.g. use this in your index.ctp for customization
}
Possibly adjust your index action:
function index($scope = array()) {
// ...
$this->set(items, $this->paginate($scope));
}
Your archive.ctp will be:
<?php include('/index.ctp'); ?>
Ideal reuse of code of controller actions and views.
For CakePHP 2.x
New for Cake 2.x is the abilty to extend a given view. So while elements are great for having little bits of reusable code, extending a view allows you to reuse whole views.
See the manual for more/better information
http://book.cakephp.org/2.0/en/views.html#extending-views
Elements work if you want them to have access to the same data that the calling view has access to.
If you want your embedded view to have access to its own set of data, you might want to use something like requestAction(). This allows you to embed a full-fledged view that would otherwise be stand-alone.
I want to use those "small views" to
several other purposes, so It should
be "like" a cake component, for
reutilization.
This is done with "Helpers", as described here. But I'm not sure that's really what you want. The "Elements" suggestion seems correct too. It heavily depends of what you're trying to accomplish. My two cents...
In CakePHP 3.x you can simple use:
$this->render('view')
This will render the view from the same directory as parent view.