I have hmvc setup and working fine,
I have a gallery module.
The gallery module breaks down into three controllers and it's structure is as follows:
/modules/gallery/
/modules/gallery/config/
/modules/gallery/helpers/
/modules/gallery/controllers/
/modules/gallery/controllers/gallery.php
/modules/gallery/controllers/galleries.php
/modules/gallery/controllers/images.php
/modules/gallery/models/
/modules/gallery/models/galleriesmodel.php
/modules/gallery/models/imagesmodel.php
/modules/gallery/views/dashboard.tpl
/modules/gallery/views/galleries/dashboard.tpl
/modules/gallery/views/images/dashboard.tpl
anyway, I have a function inside my images.php controller called list_items
So I want to map the url
http://example.com/gallery/images/list to
http://example.com/gallery/images/list_items
So I thought, sweet as,
I will just add a /modules/gallery/config/routes.php with the route inside it.
But it seems the routes are not being included.
The routes from /application/config/routes.php are included and if I put a die('loaded') in the module routes.php the it does kill the script,
But running
print_r($this->router) from one of the controllers does not show up any of the routes from the module routes.php.
What's going on here?
As far as I know,
HMVC only looks for requested controllers inside each module, with different hierarchy models, but route overrides using routes.php within modules are never read.
Look into MX_Router::locate it never looks for routes.php inside any module.
Also it does not override CI_Router::_set_routing which looks for routes.php in config folder.
You will have to override CI_Router::_set_routing and read config/router.php in every available module for your module route overrides to work.
Broncha shows the way, I show you the code. I use this SOLUTION:
You have to edit _set_routing() in system/core/Router.php or extend this method (better) within MY_Router class (third_party/Router.php).
Now it should always load module/config/routes.php... (it's around line 140)
// Load the routes.php file.
if (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/routes.php'))
{
include(APPPATH.'config/'.ENVIRONMENT.'/routes.php');
}
elseif (is_file(APPPATH.'config/routes.php'))
{
include(APPPATH.'config/routes.php');
}
// Include routes every modules
$modules_locations = config_item('modules_locations') ? config_item('modules_locations') : FALSE;
if(!$modules_locations)
{
$modules_locations = APPPATH . 'modules/';
if(is_dir($modules_locations))
{
$modules_locations = array($modules_locations => '../modules/');
}
else
{
show_error('Modules directory not found');
}
}
foreach ($modules_locations as $key => $value)
{
if ($handle = opendir($key))
{
while (false !== ($entry = readdir($handle)))
{
if ($entry != "." && $entry != "..")
{
if(is_dir($key.$entry))
{
$rfile = Modules::find('routes'.EXT, $entry, 'config/');
if($rfile[0])
{
include($rfile[0].$rfile[1]);
}
}
}
}
closedir($handle);
}
}
$this->routes = ( ! isset($route) OR ! is_array($route)) ? array() : $route;
unset($route);
I hope it will help...
Related
In codeigniter 3 application i have directory structure like this:
-Myproject
-application
-controllers
-home
Welcome.php //This is my controller inside home directory
How to set Welcome controller as default controller?
I use below code
$route['default_controller'] = 'home/Welcome';
This routing works for previous versions of codeigniter.
By default, you are not allowed to do that. To get around this, you need to hack your system Router.php:
codeigniter/system/core/Router.php
Edit a few lines of code so that it becomes like this:
line 1. if (!sscanf($this->default_controller, '%[^/]/%[^/]/%s', $directory, $class, $method) !== 2)
line 2. if ( ! file_exists(APPPATH.'controllers'. DIRECTORY_SEPARATOR . $directory. DIRECTORY_SEPARATOR .ucfirst($class).'.php'))
line 3. $this->set_directory($directory);
Once you've done, you can call the default controller under directory.
$route['default_controller'] = 'home/Welcome';
You need not to change anything from files inside CODEIGNITER system folder. Codeigniter allows developer to extend their feature. You can create a file named as MY_Router.php.
<?php
class MY_Router extends CI_Router {
protected function _set_default_controller() {
if (empty($this->default_controller)) {
show_error('Unable to determine what should be displayed. A default route has not been specified in the routing file.');
}
if (sscanf($this->default_controller, '%[^/]/%s', $class, $method) !== 2) {
$method = 'index';
}
if( is_dir(APPPATH.'controllers/'.$class) ) {
$this->set_directory($class);
$class = $method;
if (sscanf($method, '%[^/]/%s', $class, $method) !== 2) {
$method = 'index';
}
}
if ( ! file_exists(APPPATH.'controllers/'.$this->directory.ucfirst($class).'.php')) {
return;
}
$this->set_class($class);
$this->set_method($method);
$this->uri->rsegments = array(
1 => $class,
2 => $method
);
log_message('debug', 'No URI present. Default controller set.');
}
}
Note: Do not change the file name.
Try This in routes.php
$route['default_controller'] = 'welcome';
$route['404_override'] = '';
$route['translate_uri_dashes'] = FALSE;
I'm writing a template_loader for my CodeIgniter project. As usual, I need several security layers for my templates. One of them, which is the very first one, is checking if the files exists or not.
Now, My Problem is: I can't configure what address to give to the template loader. When I use simple php function called 'include()', it works, but with my template_loader function, it fails to work.
Here is my actual page (index.php):
<?php
/**
Add the page-suitable template from in folder.
**/
$this->template_loader->load_template('inc/temp_one_col.php');
// include('inc/temp_one_col.php');
?>
And here is my class and template_loader:
class Template_loader
{
function load_template ($arg)
{
$base_name = basename($arg);
$CI =& get_instance();
if(file_exists($arg) === true)
{
echo 'it is also good.';
if (pathinfo($base_name, PATHINFO_EXTENSION) == 'php' ||
pathinfo($base_name, PATHINFO_EXTENSION) == 'html'
)
{
$file_name_check = substr($base_name, 0, 4);
if($file_name_check === TEMP_FILE_INDICATOR)
{
include($arg);
}
else
{
redirect($CI->base_url . '/error/show_problem');
}
}
else
{
redirect($CI->base_url . '/error/show_problem');
}
}
else
{
redirect($CI->base_url . '/error/show_problem');
}
}
}
Out of interest, what are you passing to the function as the $arg parameter?
Sounds like you just need to use the correct path to the file, which should be the absolute path to the file in the filesystem.
To get the absolute path you could create a new global variable in your sites index.php to point to your views folder.
webroot/index.php:
if (realpath('../application/views') !== FALSE)
{
$views_path =realpath('../application/views').'/';
define('VIEWSPATH', $views_path);
}
Now you can use this as the base for your $arg parameter VIEWSPATH.$path_to_file
I've created a plugins system, and I've created everything in that system except, how can I inclusion plugins files to execute it.
I'm tried to create a method, Which is doing include plugins files to execute it.
-- Firstly -- :
The method that get all plugins files, and that begin with index word which indicates the main file of plugin (i.g. index-pluginName.php), and add the path and file name to an array.
public function getPluginFiles($plugin_folder) {
$dir = opendir($plugin_folder);
while ($files = readdir($dir)) {
if ($files == '.' || $files == '..')
continue;
if (is_dir($plugin_folder.'/'.$files))
$this->getPluginFiles($plugin_folder.'/'.$files);
if (preg_match('/^[index]+/i', $files)) {
$this->plugins_path[$plugin_folder.'/'.$files] = $files;
}
}
closedir($dir);
}
-- secondly -- :
The method that include all the main file of plugins to execute, and this method get the path and name of plugin file from the array that created earlier .
public function includePlugFiles() {
$this->getPluginFiles($this->plugin_folder);
foreach ($this->plugins_path as $dir=>$file) {
include_once (dirname($dir)."/".$file);
}
}
Also see an example of code that exists in plugin file:
function test() {
echo " This is first plugin <br/>";
}
$plugin->addHook('top', test); // parameters(top=position, test=callback)
Now, when I create an instance of the object to be this form .
$plugin = new plugin;
$plugin->includePlugFiles();
But after all this, shows error message
Fatal error: Call to a member function addHook() on a non-object in .... projects\plugins\index-test.php on line 7
This is the code of line 7:
$plugin->addHook('top', test); // parameters(top=position, test=callback)
I know the problem occur because, the object will not be created.
and the problem is can't create the object in every main plugin file.
It's probably not the cleanest solution, but instead of trying to reference the $plugin symbol (which is outside the scope of the plugin file), you could also do this:
$this->addHook('top', test);
Alternatively, you could explicitly create the reference inside the includePlugFiles() method:
public function includePlugFiles()
{
$plugin = $this;
$this->getPluginFiles($this->plugin_folder);
foreach ($this->plugins_path as $dir=>$file) {
include_once (dirname($dir)."/".$file);
}
}
Hi i wont to make something like that.
http:// example.com/ - Main Controller
http:// example.com/rules/ - Main Controller where content get from database, but if not exist
return 404 page. (It's ok, isn't problem.)
But if i have subfolder in application/controlles/rules/
I want to redirect it to Main Contorller at Rules folder.
This follow code can solve problem, but i don't know how it right realise.
At routes.php:
$route['default_controller'] = "main";
$route['404_override'] = '';
$dirtest = $route['(:any)'];
if (is_dir(APPPATH.'controllers/'.$dirtest)) {
$route['(:any)'] = $dirtest.'/$1';
} else {
$route['(:any)'] = 'main/index/$1';
}
Ok, what I have:
controllers/main.php
class Main extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('main_model');
}
public function index($method = null)
{
if (is_dir(APPPATH.'controllers/'.$method)) {
// Need re-rout to the application/controllers/$method/
} else {
if ($query = $this->main_model->get_content($method)) {
$data['content'] = $query[0]->text;
// it shows at views/main.php
} else {
show_404($method);
}
}
$data['main_content'] = 'main';
$this->load->view('includes/template', $data);
}
}
Updated Again (routes.php):
So, seem's like what i search (work example):
$route['default_controller'] = "main";
$route['404_override'] = '';
$subfolders = glob(APPPATH.'controllers/*', GLOB_ONLYDIR);
foreach ($subfolders as $folder) {
$folder = preg_replace('/application\/controllers\//', '', $folder);
$route[$folder] = $folder.'/main/index/';
$route[$folder.'/(:any)'] = $folder.'/main/$1';
}
$route['(:any)'] = 'main/index/$1';
But, in perfectly need some like this:
http:// example.com/1/2/3/4/5/6/...
Folder "controllers" has subfolder "1"?
YES: Folder "1" has subfolder "2"?
NO: Folder "1" has controller "2.php"?
NO: Controller "controllers/1/main.php" has function "2"?
YES: redirect to http:// example.com/1/2/ - where 3,4,5 - parameters..
It is realy nice, when you have structure like:
http://example.com/blog/ - recent blog's posts
http://example.com/blog/2007/ - recent from 2007 year blog's posts
http://example.com/blog/2007/06/ - same with month number
http://example.com/blog/2007/06/29/ - same with day number
http://example.com/blog/web-design/ - recent blog's post's about web design
http://example.com/blog/web-design/2007/ - blog' posts about web design from 2007 years.
http://example.com/blog/current-post-title/ - current post
Same interesting i find http://codeigniter.com/forums/viewthread/97024/#490613
I didn't thoroughly read your question, but this immediately caught my attention:
if (is_dir($path . '/' . $folder)) {
echo "$route['$folder/(:any)'] = '$folder/index/$1';"; //<---- why echo ???
}
Honestly I'm not sure why this didn't cause serious issues for you in addition to not working.
You don't want to echo the route here, that will just try to print the string to screen, it's not even interpreted as PHP this way. There are also some issues with quotes that need to be remedied so the variables can be read as variables, not strings. Try this instead:
if (is_dir($path . '/' . $folder)) {
$route[$folder.'/(:any)'] = $folder.'/index/$1';
}
Aside: I'd like to offer some additional resources that are not directly related to your problem, but should help you nonetheless with a solution:
Preferred way to remap calls to controllers: http://codeigniter.com/user_guide/general/controllers.html#remapping
Easier way to scan directories: http://php.net/manual/en/function.glob.php
It's hard to say why the registering of your routes fails. From your code I can see that you're not registering the routes (you just echo them), additionally I see that the usage of variables in strings are used inconsistently. Probably you mixed this a bit, the codeigniter documentation about routes is not precise on it either (in some minor points, their code examples are not really syntactically correct, but overall it's good).
I suggest you first move the logic to register your dynamic routes into a function of it's own. This will keep things a bit more modular and you can more easily change things and you don't pollute the global namespace with variables.
Based on this, I've refactored your code a bit. It does not mean that this works (not tested), however it might make things more clear when you read it:
function register_dynamic_routes($path, array &$route)
{
$nodes = scandir($path);
if (false === $nodes)
{
throw new InvalidArgumentException(sprintf('Path parameter invalid. scandir("$path") failed.', $path));
}
foreach ($nodes as $node)
{
if ($node === '.' or $node === '..')
continue
;
if (!is_dir("{$path}/{$node}")
continue
;
$routeDef = "{$folder}/(:any)";
$routeResolve = "{$folder}/index/\$1";
$route[$routeDef] = $routeResolve;
# FIXME debug output
echo "\$route['{$routeDef}'] = '{$routeResolve}';";
}
}
$path = APPPATH.'controllers/';
register_dynamic_routes($path, $route);
$route['(:any)'] = 'main/index/$1';
Next to this you probably might not want to shift everything onto the index action, but a dynamic action instead. Furthermore, you might want to have a base controller that is delegating everything into the sub-controllers instead of adding the routes per controller. But that's up to you. The example above is based on the directory approach you outlined in your question.
Edit: Additional information is available in the Controllers section next to the URI Routing section
All this seems kind of complicated.
Plus, if you have hundreds (or thousands or more?) of possible routes in a database you may not want to load all of them into the "$routes" array every time any page loads in your application.
Instead, why not just do this?
last line of routes.php:
$route['404_override'] = 'vanity';
Controller file: Vanity.php:
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Vanity extends MY_Controller {
/**
* Vanity Page controller.
*
*/
public function __construct() {
parent::__construct();
}
public function index()
{
$url = $_SERVER['PHP_SELF'];
$url = str_replace("/index.php/", "", $url);
// you could check here if $url is valid. If not, then load 404 via:
//
// show_404();
//
// or, if it is valid then load the appropriate view, redirect, or
// whatever else it is you needed to do!
echo "Hello from page " . $url;
exit;
}
}
?>
This situation arises from someone wanting to create their own "pages" in their web site without having to get into creating the corresponding actions.
So say they have a URL like mysite.com/index/books... they want to be able to create mysite.com/index/booksmore or mysite.com/index/pancakes but not have to create any actions in the index controller. They (a non-technical person who can do simple html) basically want to create a simple, static page without having to use an action.
Like there would be some generic action in the index controller that handles requests for a non-existent action. How do you do this or is it even possible?
edit: One problem with using __call is the lack of a view file. The lack of an action becomes moot but now you have to deal with the missing view file. The framework will throw an exception if it cannot find one (though if there were a way to get it to redirect to a 404 on a missing view file __call would be doable.)
Using the magic __call method works fine, all you have to do is check if the view file exists and throw the right exception (or do enything else) if not.
public function __call($methodName, $params)
{
// An action method is called
if ('Action' == substr($methodName, -6)) {
$action = substr($methodName, 0, -6);
// We want to render scripts in the index directory, right?
$script = 'index/' . $action . '.' . $this->viewSuffix;
// Script file does not exist, throw exception that will render /error/error.phtml in 404 context
if (false === $this->view->getScriptPath($script)) {
require_once 'Zend/Controller/Action/Exception.php';
throw new Zend_Controller_Action_Exception(
sprintf('Page "%s" does not exist.', $action), 404);
}
$this->renderScript($script);
}
// no action is called? Let the parent __call handle things.
else {
parent::__call($methodName, $params);
}
}
You have to play with the router
http://framework.zend.com/manual/en/zend.controller.router.html
I think you can specify a wildcard to catch every action on a specific module (the default one to reduce the url) and define an action that will take care of render the view according to the url (or even action called)
new Zend_Controller_Router_Route('index/*',
array('controller' => 'index', 'action' => 'custom', 'module'=>'index')
in you customAction function just retrieve the params and display the right block.
I haven't tried so you might have to hack the code a little bit
If you want to use gabriel1836's _call() method you should be able to disable the layout and view and then render whatever you want.
$this->_helper->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
I needed to have existing module/controller/actions working as normal in a Zend Framework app, but then have a catchall route that sent anything unknown to a PageController that could pick user specified urls out of a database table and display the page. I didn't want to have a controller name in front of the user specified urls. I wanted /my/custom/url not /page/my/custom/url to go via the PageController. So none of the above solutions worked for me.
I ended up extending Zend_Controller_Router_Route_Module: using almost all the default behaviour, and just tweaking the controller name a little so if the controller file exists, we route to it as normal. If it does not exist then the url must be a weird custom one, so it gets sent to the PageController with the whole url intact as a parameter.
class UDC_Controller_Router_Route_Catchall extends Zend_Controller_Router_Route_Module
{
private $_catchallController = 'page';
private $_catchallAction = 'index';
private $_paramName = 'name';
//-------------------------------------------------------------------------
/*! \brief takes most of the default behaviour from Zend_Controller_Router_Route_Module
with the following changes:
- if the path includes a valid module, then use it
- if the path includes a valid controller (file_exists) then use that
- otherwise use the catchall
*/
public function match($path, $partial = false)
{
$this->_setRequestKeys();
$values = array();
$params = array();
if (!$partial) {
$path = trim($path, self::URI_DELIMITER);
} else {
$matchedPath = $path;
}
if ($path != '') {
$path = explode(self::URI_DELIMITER, $path);
if ($this->_dispatcher && $this->_dispatcher->isValidModule($path[0])) {
$values[$this->_moduleKey] = array_shift($path);
$this->_moduleValid = true;
}
if (count($path) && !empty($path[0])) {
$module = $this->_moduleValid ? $values[$this->_moduleKey] : $this->_defaults[$this->_moduleKey];
$file = $this->_dispatcher->getControllerDirectory( $module ) . '/' . $this->_dispatcher->formatControllerName( $path[0] ) . '.php';
if (file_exists( $file ))
{
$values[$this->_controllerKey] = array_shift($path);
}
else
{
$values[$this->_controllerKey] = $this->_catchallController;
$values[$this->_actionKey] = $this->_catchallAction;
$params[$this->_paramName] = join( self::URI_DELIMITER, $path );
$path = array();
}
}
if (count($path) && !empty($path[0])) {
$values[$this->_actionKey] = array_shift($path);
}
if ($numSegs = count($path)) {
for ($i = 0; $i < $numSegs; $i = $i + 2) {
$key = urldecode($path[$i]);
$val = isset($path[$i + 1]) ? urldecode($path[$i + 1]) : null;
$params[$key] = (isset($params[$key]) ? (array_merge((array) $params[$key], array($val))): $val);
}
}
}
if ($partial) {
$this->setMatchedPath($matchedPath);
}
$this->_values = $values + $params;
return $this->_values + $this->_defaults;
}
}
So my MemberController will work fine as /member/login, /member/preferences etc, and other controllers can be added at will. The ErrorController is still needed: it catches invalid actions on existing controllers.
I implemented a catch-all by overriding the dispatch method and handling the exception that is thrown when the action is not found:
public function dispatch($action)
{
try {
parent::dispatch($action);
}
catch (Zend_Controller_Action_Exception $e) {
$uristub = $this->getRequest()->getActionName();
$this->getRequest()->setActionName('index');
$this->getRequest()->setParam('uristub', $uristub);
parent::dispatch('indexAction');
}
}
You could use the magic __call() function. For example:
public function __call($name, $arguments)
{
// Render Simple HTML View
}
stunti's suggestion was the way I went with this. My particular solution is as follows (this uses indexAction() of whichever controller you specify. In my case every action was using indexAction and pulling content from a database based on the url):
Get an instance of the router (everything is in your bootstrap file, btw):
$router = $frontController->getRouter();
Create the custom route:
$router->addRoute('controllername', new Zend_Controller_Router_Route('controllername/*', array('controller'=>'controllername')));
Pass the new route to the front controller:
$frontController->setRouter($router);
I did not go with gabriel's __call method (which does work for missing methods as long as you don't need a view file) because that still throws an error about the missing corresponding view file.
For future reference, building on gabriel1836 & ejunker's thoughts, I dug up an option that gets more to the point (and upholds the MVC paradigm). Besides, it makes more sense to read "use specialized view" than "don't use any view".
// 1. Catch & process overloaded actions.
public function __call($name, $arguments)
{
// 2. Provide an appropriate renderer.
$this->_helper->viewRenderer->setRender('overload');
// 3. Bonus: give your view script a clue about what "action" was requested.
$this->view->action = $this->getFrontController()->getRequest()->getActionName();
}
#Steve as above - your solution sounds ideal for me but I am unsure how you implmeented it in the bootstrap?