I want to add products via my script which is not connected with OpenCart.
For example: somedir/index.php. I try to do it in this way:
$productData = array(
'model' => 'ABC123',
'name'=>'aaa',
'description'=>'aaa',
'tag'=>'aaa',
...
);
require_once ('../../system/engine/model.php');
require_once ('../../admin/model/catalog/product.php');
$a= new ModelCatalogProduct();
$a->addProduct($productData);
But there are many functions that need to be triggered. How can this be achieved?
OpenCart uses a so called MVC-pattern. This patteren works within OpenCart in a very specialised and deeply coupled way, so you need the context of the routing system in case you would like to use controllers and models in your code.
Also, it really depends on which version you use, what semantics would be right, so that is hard to tell. Conceptually, you would do something like this:
- define a new contoller in the /admin/controller directory structure, i.e. /admin/controller/tool/product_import.php
Call that controller ControllerToolProductImport which extends Controller;
Create a public function index()
Have it load the model like $this->load->model("catalog/product");
Now the model function becomes available and you would use it like $this->model_catalog_product->addProduct($productData);
This public function index can be triggerd by https://hostname:port/admin/index.php?route=too/product_import&token=ABC (you'll see what the token should be, once logged in into the admin section). To trigger another function within that controller directly (it needs to be public), you could extend the route easily. So for public function doSomething() it would become https://hostname:port/admin/index.php?route=too/product_import/doSomething&token=ABC.
When triggering this function from within the admin section, you would use the OpenCart functions to do so. Depending on your version, this would go like this for 2.2.0.0:
$this->url->link('tool/product_import', 'token=' . $this->session->data['token'], true)
Related
Since my app is getting bigger i would like to separate admin prefix actions and views from normal actions and views. The new folder for admin is Controller/admin/UsersController.php.
I would like to change my cakephp controllers and views folder structure to match the prefix I'm using.
Example for admin prefix:
Controller:
app/Controller/UsersController.php (contain view(), index() ...)
app/Controller/admin/UsersController.php (contain admin_view(), admin_index() ...)
View:
app/View/Users/index.ctp (for index() in UsersController.php)
app/View/Users/admin/index.ctp (for admin_index() in admin/UsersController.php)
How can I implement this structure using Cakephp 2.6?
Unlike in 3.x where this is the default behavior for prefixes, this isn't supported in 2.x.
You could try hacking it in using a custom/extended dispatcher (in order to retrieve the desired controller) or even dispatcher filters in case you are adventurous, and in your app controller modify the view template path with respect to the prefix.
That should do it, however I would probably simply go for using plugins instead, this will separate things just fine without any additional fiddling.
If you just want to separate logic you could do something like this. It's untested an just thought to give you just the idea. I'll explain the concept after the code:
public function beforeFilter() {
if ($this->request->prefix === 'foo') {
$name = Inflector::classify($this->request->prefix);
$className = $name . 'ChildController';
App::uses($className, 'Controller/Foo');
$this->ChildController = new $className($this);
}
}
public function __call($method, $args) {
if ($this->request->prefix === 'foo' && method_exists($this->ChildController, $method)) {
call_user_func_array([$this->ChildController, $method], $args);
}
}
Depending on the prefix you can load other classes. How you load that class and how you instantiate it, what params you pass to it is up to you. In my example I'm passing the controller instance directly. I think you could actually init a complete controller here but be aware that components like the Session might cause a problem because they might have been already initiated by the "parent" controller.
When you now call a controller method that doesn't exist it will try to call the same method with the same arguments on the ChildController. Not really a great name for it, but maybe you can come up with something better.
You'll have to implement some logic to load the views from the right place in your classes as well but this shouldn't be hard, just check the controller class.
But actually I don't see your problem, I've worked on an app that hat over 560 tables and not putting the code into sub folders wasn't a problem, it did in fact use a similar solution.
I think you have to much code in your controllers, get more code into your models and the controller shouldn't be a problem.
Another solution might be to think about implementing a service layer in CakePHP which implements the actual business logic while a model is reduced to a data handler. The service would sit between a controller and the model. I've done this a few times as well now and if done right it works very well.
In my current implementation of the MVC design pattern (demonstrated using PHP and CodeIgniter):
Let's say I have a "page" located at "www.mysite.com/blue/page" that is ultimately generated by the following (greatly simplified) files:
/libraries
session_lib.php
/controllers
/red
/white
/blue
page.php
/models
/red
/white
/blue
funky_class.php
page_model.php
/views
/red
/white
/blue
page.php
And here's an easy to understand controller:
// FILE: /controllers/blue/page.php
// Get some paramaters from URL
$params = $this->input->get();
// Use a library to do some stuff with these params with a session
$this->load->library('session_lib');
$this->session_lib->store_in_session($params);
// Use a very page specific class to do some funky stuff with the params
$this->load->model('blue/funky_class');
$funkified_params = $this->funky_class->funkify($params);
// Pass params to a model
$this->load->model('blue/page_model');
$data['output_from_db'] = $this->page_model->get_some_output_from_db($funkified_params);
// Send to view
$this->load->view('blue/page', $data);
And now the question...
What is the best procedure for these "funky" little page specific classes that don't interact with the database? In this example I store the little class with the models, and in some cases might just add additional methods inside the model class to do the funky stuff. Is this good practice? Or should the library and funky class both go inside the model so the controller is even skinnier (however the model might be used in other places without the need for sessions and funkiness)?
I would use a helper.
When a helper is loaded, that function will be available in the global scope so you can call it anywhere.
They're great for small, stand-alone functions that do only one thing that is not necessarily related to code in a model or library
Helpers especially excel for me when converting things between one format or another (slugify, convert_timezone, change_query_string) or doing little tasks that you don't want to think about (force_ssl, is_image, uri_string)
funkify() seems like an appropriate helper function that may prevent a lot of repeated code.
Here's the codeigniter documentation page on them: http://ellislab.com/codeigniter/user-guide/general/helpers.html
If you're in a situation where the helper function is so specific it will be only used in one place, you can still use a helper. Name the helper after your controller, page_helper.php for example.
page_helper.php
function funkify($params) {
// do funky stuff
return $funky_params;
}
then in your controller:
class Page extends CI_Controller {
public function __construct() {
parent::__construct();
$this->load->helper('page');
}
public function page() {
// do stuff
$funky_params = funkify($params);
// do more stuff
$this->load->view('blue/page', $data);
}
I have no excuse for it, but sometimes if I am in a situation where I need a razor specific function that will only be used on one location (say, a controller) ever, I will put it right in the controller's file. You can paste a function outside of the class definition and it will act like a helper and be available globally (as long as that controller is loaded). You can also define functions inside of a view. Yucky, but possible. I don't like to do it often because it's unusual and not expected (by myself or other developers)
This might be a bit hard to comprehend so I apologize in advance if this is not clear enough.
I'm writing my own MVC framework and am once again stuck.
I am in the process of writing the controller classes for the framework. Basically this is how it works:
Instantiate class coreController which extends abstract class
coreController sets controller to be loaded by interpreting query string
query string values stored in variables
other variables assigned values
new controller is loaded
new controller checks if an action object needs to be instantiated.
new actioncontroller is loaded
action controller checks if it is the final object required.
action controller is returned as an object to be referenced during the rest of the script.
generic $controller->method() can be called and references final controller loaded.
Another overview:
coreController
pageController
pageControllerActionAdd
return as object to start
$controller->something(); //References pageControllerActionAdd
Esentially what I want to be able to do is be able enter a url like:
http://www.mywebsite.com/page/modify/
and have the script pull up the PageModifyController as a variable so I can execute it's methods.
If you can tell me a better method for what I am doing please go ahead. You don't have to write any code, just the idea would be great. It is just that the way I am currently doing is very confusing and hard to debug. I will end up with multiple nested objects and I don't like the concept of that.
I've been reading a lot of other source code and found that it too can be quite sophisticated.
I actually created a framework that works along the lines you are trying to implement. I think what you are missing is a RoutingHandler class. Routing is the physical manipulation of the URL, which tells your application which Controller to load, and which Action to run.
In my world I also have Modules, so the basic routing scheme is
Module -> Controller -> Action
These three items map to my URI scheme in that fashion. Variables can be appended also like so...
http://www.domain.com/module/controller/action/var1/val1/var2/val2
So, what happens after the URI is parsed, and control is passed over to the appropriate controller and action? Let's make some code up to demonstrate a simple example...
<?php
class indexController extends Controller {
protected function Initialize() {
$this->objHomeModel = new HomeModel;
$this->objHeader = new Header();
$this->objFooter = new Footer();
$this->objHeader
->SetPageId('home');
}
public function indexAction() {
$this->objHeader->SetPageTitle('This is my page title.');
}
// other actions and/or helper methods...
}
?>
In the Initialize method, I'm setting some controller-wide stuff, and grabbing an instance of my Model to use later. The real meat is in the indexAction method. This is where you would set up stuff to use in your View. For example...
public function randomAction() {
$this->_CONTROL->Append($intSomeVar, 42);
}
_CONTROL is an array of values that I manipulate and pass onto the View. The Controller class knows how to find the right template for the View because it is named after the Action (and in a sibling directory).
The Controller parent class takes the name of the action method and parses it like so...
indexAction -> index.tpl.php
You can also do some other fun stuff here, for example...
Application::SetNoRender();
...would tell the Controller not to render inside a template, but just complete the method. This is useful for those situations where you don't actually want to output anything.
Lastly, all of the controllers, models, and views live inside their own Module directory like so...
my_module
controllers
indexController.class.php
someotherController.class.php
:
:
models
HomeModel.class.php
:
:
templates
index.tpl.php
someother.tpl.php
:
:
I can have as many Modules as I need, which means I can separate functionality out by Module and/or Controller.
I could go on, but I'm writing this from memory, and there are some wrinkles here and there, but hopefully this gives you food for thought.
I have a mobile site that I added detection to for iPhones and other iOS devices. The iOS page needs a different layout and views than the regular pages (which are actually for older mobile devices). So, I have some code that does mobile detection, that part was easy. What I'd like to do is make it so that Zend automagically finds and uses the correct layout and view when an iOS device is detected, but that has turned out to be surprisingly hard...
I needed it to be up and running ASAP, so I did a quick and dirty hack that worked: in each action function, I have a simple If statement that detects if the iOS boolean flag has been set (which happens in the controller's init), and if so, overrides the layout and view explicitly. Existing code (in the actions):
if ($_SESSION['user']['iPhone']) {
$this->_helper->layout->setLayout('osriphone'); // 'osr' is the name of the app
$this->_helper->viewRenderer->setRender('iphone/index');
}
So this works, but it's kinda ugly and hacky and has to be put in each action, and each action's Renderer has to be set, etc. I got to reading about the Zend ContextSwitch, and that seemed like exactly the kind of thing I should use (I'm still kind of new to Zend), so I started messing around with it, but can't quite figure it out.
In the controller's init, I'm initializing the ContextSwitch, adding a context for 'iphone' and setting the suffix to 'iphone', and now what I'd like to do is have a single place where it detects if the user is an iOS device and sets the context to 'iphone', and that should make it automatically use the correct layout and view. New code (in the controller's init):
$this->_helper->contextSwitch()->initContext();
$contextSwitch = $this->_helper->getHelper('contextSwitch');
$contextSwitch->addContext('iphone', array('suffix' => 'iphone'));
$contextSwitch->setAutoDisableLayout(false);
if ($_SESSION['user']['iPhone']) {
//$this->_currentContext = 'iphone'; // Doesn't work.
//$contextSwitch->initContext('iphone'); // Doesn't work.
//$contextSwitch->setContext('iPhone'); // Not the function I'm looking for...
// What to put here, or am I barking up the wrong tree?
}
I did some reading on the contextSwitcher, and it seems like there is a lot of stuff on, e.g. setting it to be specific to each particular action (which I don't need; this needs to happen on every action in my app), and going through and modifying all the links to something like /osr/format/iphone to switch the context (which I also don't really need or want; it's already a mobile site, and I'd like the layout/view switch to be totally transparent to the user and handled only from the backend as it is with my quick and dirty hack). These seem like basically an equal amount of code to my quick and dirty hack. So... Anyone have some suggestions? I'm really hoping for just a single line like "$contextSwitch->setContext('iphone');" that I could use in an If statement in my controller's init, but the Zend documentation is awful, and I can't seem to find any examples of people doing something like this on Google or SO.
Ok I think I figured out how to put this into a plugin:
The Plugin:
//This is my own namespace for ZF 1.x library, use your own
class My_Controller_Plugin_Ios extends Zend_Controller_Plugin_Abstract {
public function preDispatch(Zend_Controller_Request_Abstract $request) {
parent::preDispatch($request);
if ($_SESSION['user']['iPhone']) {
$this->_helper->layout->setLayout('osriphone');
$this->_helper->viewRenderer->setRender('iphone/index');
}
}
}
register the plugin in your application.ini
resources.frontController.plugins.ios = "My_Controller_Plugin_Ios"
I think that's all there is to it. Although you may want to look into the userAgent plugin
ContextSwitch operates off the "format" property in the request object (by default). You need to set it somewhere in your app
$requestObject->setParam('format', 'iphone').
I'd set it in a bootstrap, or more appropriately, a controller plugin, but where it goes really depends on your app.
I don't use Zend ContextSwitch so I can't really help there, but you could use some inheritance in your controllers to set all layouts in just a couple of lines. Even though it might still be classed as a "hack" it is a way better hack
Now whenever you execute a action Zend first fires a number of other functions within the framework first, such as the routing, the preDispatch, Action helpers and so on. It also fires a number of things after the action such as PostDispatch. This can be used to your advantage.
First create a controller called something like "mainController" and let it extend Zend_Controller_action and in this controller create a function called predispatch()
Second. Extend your normal controllers to mainController. Since we now have a function called predispatch() Zend will automatically fire this on every controller, and if you do your iPhone/iOS check there it will automagically be performed on every action on every controller, as long as you don't overwrite the method in your controller (you can make this method final to prevent this). You can offcourse use a multitude of different non-Zend functions and/or helpers within the mainctroller to make the code as compact and reusable as possible Se example code below:
<?php
/**
*Maincontroller
*/
class MainController extends Zend_Controller_Action
{
/**
* Predispatch function is called everytime an action is called
*/
final public function preDispatch(){
//for security reasons, make sure that no one access mainController directly
$this->request = $this->getRequest();
if (strtolower($this->request->controller)=='main')
$this->_redirect('/index/index/');
//Check for iPhone
if ($_SESSION['user']['iPhone']) {
$this->_helper->layout->setLayout('osriphone'); // 'osr' is the name of the app
$this->_helper->viewRenderer->setRender('iphone/index');
}
}
}
<?php
/**
*Othercontroller
*/
class OtherController extends MainController
{
/**
* The correct layout for IndexAction is already set by the inherited preDispatch
*/
public function indexAction(){
/* YOUR CODE HERE */
}
}
For a good overview of the dispatch process check these links (same picture in both):
http://nethands.de/download/zenddispatch_en.pdf
http://img.docstoccdn.com/thumb/orig/22437345.png
I am trying to include a small code in each page of my site.
Is there any way to do this without modifying each controller?
For example - I want to read/unread message from Message model.
Can i do this using the app_controller? I have add following function in app_controller.php.
I need suggestion. Please help me.
function messageStatus() {
App::import('Model','Message');
$new_message = $this->Message->find(
'first',
array (
'conditions' => array (
'Message.status' => '1'
)
)
);
$this->set("new_message",$new_message);
}
Depending on when you want to execute your actions, you will have to override in the app_controller.php file one of the following functions (according to the documentation), :
beforeFilter()
afterFilter()
beforeRender()
Since all your other controllers will be inheriting the methods of this class, your actions will be executed every time (as specified in the docs) one of your controllers are executed.
If you want to have a controller that does not run the code in the app_controller simply override the method again locally.
As user559744 mentioned you can use AppController within your application to create attributes and methods that can be accessed by your controllers. AppController is the parent class of your controllers.
You should copy app_controller.php from /cake/libs/controller/ to YOURAPP/app_controller.php to avoid making changes to the core files.
http://book.cakephp.org/view/957/The-App-Controller