Opencart Admin Cron Jobs
Asked Answered
W

10

7

I know about CRON and how to create/manage it. But this issue was different.

I want to develop a module to delete any (unpaid) order that exceeds the time frame given. Ex: I want to delete any unpaid order that has not been paid for 2 days after the order was placed.

I want to use existed model in opencart (and not use a new one). Let's say the module URL would be: http://www.yourstore.com/admin/index.php?route=module/modulename/function And will be called from CRON, and then all any unpaid order will be disappeared.

But the main problem is: when CRON wants to access that URL, it needs a security token or it will never be executed.

My question is: how to execute that module from CRON without security token (in case just for that module)?

Please help me, if you have a better idea or a more clean way, I would say many thanks to you.

Wheatear answered 4/3, 2014 at 18:52 Comment(2)
Why do you want to set it in admin panel? Can't we set it in frontend? Also note that by default opencart doesn't allow to access admin pages without login.Two
@SankarV : it cant be set on frontend coz the model is in admin area. You said by "default", so is there any way to override it or make some mod?Wheatear
C
6

In 2.3.0.2 a very simple way I found was to add your controller function path into the ignored paths settings for login and permission restrictions. Then just add a url password or other check in that controller function to lock it down.

So first in admin/controller/startup/login.php add your controller function path to both $ignore arrays, eg 'common/cron/action'

And then in admin/controller/startup/permissions.php you want just the controller path, eg 'common/cron'

And then finally at start of your action() function do like:

if(!isset($_GET['pass']) || $_GET['pass'] != 'secretpassword')return;

Then i just added this to my cron:

php-cli -r 'echo file_get_contents("https://www.website.com/admin/index.php?route=common/cron/action&pass=secretpassword");'
Communist answered 11/11, 2018 at 1:2 Comment(3)
I still use OC 2.3 for several clients, and this approach is what I always did.Wheatear
nice method! In cron I use this instead curl --request GET 'https://...'Mustang
I also these days use wget -qO- "URL"Communist
D
12

Updated : For Opencart versions <= 1.5.6.4

For admin related cron jobs, Do like this.

  1. Copy the admin/index.php to admin/index_for_cron.php

  2. Now, in the admin/index_for_cron.php, search for these 2 lines and comment them out which are responsible for the login & the permissions.

    // Login
    // $controller->addPreAction(new Action('common/home/login'));
    
    // Permission
    // $controller->addPreAction(new Action('common/home/permission'));
    
  3. Now use this url for your cron job.

    http://www.yourstore.com/admin/index_for_cron.php?route=module/modulename/function

NOTE: it is highly recommended to changes the name of index_for_cron.php into an ugly, unpredictable name for the security reasons.

Hope this helps :)

Denishadenison answered 4/3, 2014 at 19:32 Comment(4)
No such line on opencart Ver 2.3Cummine
I would suggest to add an extra layer of security adding an exit if the index_for_cron.php is not being executed from the command line. This would work in the first line: (PHP_SAPI !== 'cli' || isset($_SERVER['HTTP_USER_AGENT'])) && die('cli only');Enrico
No longer a valid answer.Communist
@HaydenThring, Thanks. I added the supporting OC version. Didn;t check on the newer versions yet.Denishadenison
C
8

I've done something similar to IJas. Adjacent to admin and catalog, I've created a new folder called "cli".

This folder contains a php file for a specific function to be performed by cli (executing scripts via crontab on a set schedule, or manually in the command line), as well as a "bootstrap" of sorts for these types of scripts. The bootstrap is essentially a copy of the "index" found in catalog or admin, and includes some checks and removes the permission checking and some other unnecessary items. It calls whatever controller/action is set forth in the calling specific function script (in the example below, it calls the index method of the class defined in /admin/controller/common/cli_some_function.php).

Function-Specific Script:

<?php
$cli_action = 'common/cli_some_function';
require_once('cli_dispatch.php');
?>

CLI "Bootstrap"/Dispatcher:

<?php

// CLI must be called by cli php
if (php_sapi_name() != 'cli') {
    syslog(LOG_ERR, "cli $cli_action call attempted by non-cli.");
    http_response_code(400);
    exit;
}

// Ensure $cli_action is set
if (!isset($cli_action)) {
    echo 'ERROR: $cli_action must be set in calling script.';
    syslog(LOG_ERR, '$cli_action must be set in calling script');
    http_response_code(400);
    exit;
}

// Handle errors by writing to log
function cli_error_handler($log_level, $log_text, $error_file, $error_line) { 
    syslog(LOG_ERR, 'CLI Error: ' . $log_text . ' in ' . $error_file . ': ' . $error_line); 
    echo 'CLI Error: ' . $log_text . ' in ' . $error_file . ': ' . $error_line; 
}
set_error_handler('cli_error_handler');

// Configuration not present in CLI (vs web)
chdir(__DIR__.'/../admin');
set_include_path(get_include_path() . PATH_SEPARATOR . realpath(dirname(__FILE__)) . '../admin/');
$_SERVER['HTTP_HOST'] = '';

// Version
define('VERSION', '1.5.1');

// Configuration (note we're using the admin config)
require_once('../admin/config.php');

// Configuration check
if (!defined('DIR_APPLICATION')) {
    echo "ERROR: cli $cli_action call missing configuration.";
    $log->write("ERROR: cli $cli_action call missing configuration.");
    http_response_code(400);
    exit;
}

// Startup
require_once(DIR_SYSTEM . 'startup.php');

// Application Classes
require_once(DIR_SYSTEM . 'library/currency.php');
require_once(DIR_SYSTEM . 'library/user.php');
require_once(DIR_SYSTEM . 'library/weight.php');
require_once(DIR_SYSTEM . 'library/length.php');

// Registry
$registry = new Registry();

// Loader
$loader = new Loader($registry);
$registry->set('load', $loader);

// Config
$config = new Config();
$registry->set('config', $config);

// Database
$db = new DB(DB_DRIVER, DB_HOSTNAME, DB_USERNAME, DB_PASSWORD, DB_DATABASE);
$registry->set('db', $db);

// Settings
$query = $db->query("SELECT * FROM " . DB_PREFIX . "setting WHERE store_id = '0'");

foreach ($query->rows as $setting) {
    if (!$setting['serialized']) {
        $config->set($setting['key'], $setting['value']);
    } else {
        $config->set($setting['key'], unserialize($setting['value']));
    }
}

// Url
$url = new Url(HTTP_SERVER, HTTPS_SERVER);  
$registry->set('url', $url);

// Log 
$log = new Log($config->get('config_error_filename'));
$registry->set('log', $log);

function error_handler($errno, $errstr, $errfile, $errline) {
    global $log, $config;

    switch ($errno) {
        case E_NOTICE:
        case E_USER_NOTICE:
            $error = 'Notice';
            break;
        case E_WARNING:
        case E_USER_WARNING:
            $error = 'Warning';
            break;
        case E_ERROR:
        case E_USER_ERROR:
            $error = 'Fatal Error';
            break;
        default:
            $error = 'Unknown';
            break;
    }

    if ($config->get('config_error_display')) {
        echo "\n".'PHP ' . $error . ':  ' . $errstr . ' in ' . $errfile . ' on line ' . $errline."\n";
    }

    if ($config->get('config_error_log')) {
        $log->write('PHP ' . $error . ':  ' . $errstr . ' in ' . $errfile . ' on line ' . $errline);
    }

    return true;
}
set_error_handler('error_handler');
$request = new Request();
$registry->set('request', $request);
$response = new Response();
$response->addHeader('Content-Type: text/html; charset=utf-8');
$registry->set('response', $response); 
$cache = new Cache();
$registry->set('cache', $cache); 
$session = new Session();
$registry->set('session', $session); 
$languages = array();

$query = $db->query("SELECT * FROM " . DB_PREFIX . "language"); 
foreach ($query->rows as $result) {
    $languages[$result['code']] = $result;
}
$config->set('config_language_id', $languages[$config->get('config_admin_language')]['language_id']);
$language = new Language($languages[$config->get('config_admin_language')]['directory']);
$language->load($languages[$config->get('config_admin_language')]['filename']); 
$registry->set('language', $language);      

$document = new Document();
$registry->set('document', $document);      

$registry->set('currency', new Currency($registry));        
$registry->set('weight', new Weight($registry));
$registry->set('length', new Length($registry));
$registry->set('user', new User($registry));

$controller = new Front($registry);
$action = new Action($cli_action);
$controller->dispatch($action, new Action('error/not_found'));

// Output
$response->output();
?>

Using this scheme, I can ensure the script won't be called from the web, and I can have it fired off automatically from the server itself using a cron job (eg: 0 1 0 0 0 /path/to/php /path/to/opencart/cli/cli_some_function.php)

Note that the error_handler function is using some config options that aren't out-of-the-box. You can either set those up or put your own check there.

EDIT made some changes for the error handling

Chilly answered 7/7, 2014 at 15:54 Comment(9)
thank you for the solution. It works like a charm. I've been struggling though to pass command line arguments to the controller... I can read them of course in cli_dispatch.php but i have no idea how to pass them on to my cli_action controller. Would you have any suggestions? CheersNadenenader
you could try setting the superglobal $_GET or $_POST to the array of command line arguments. if you do this before the request object is created, you can access this as if it were a get or post variable ($this->request->get['somevar'])Chilly
you can also add things to registry directly from cli_dispatch: $registry->set('somevar', $someVal);Chilly
@MikeT Thanks for this solution. One question: I can't pass variable to my controller. For test I added $registry->set('somevar', 'testvalue'); before $controller = new Front($registry);. Then in the controller public function test () { $this->log->write('helllllllo '. $this->request->get['somevar']); } -> result in the log file: undefined variable somevar.Youthen
@Youthen It looks like in the controller you're trying to access your registry variable as if it were a request parameter (a url/query string/GET variable). Registry entries are accessed in the controller as member variables: $this->somevar should get you what you want. What is probably more appropriate would be using $config ( $config->set(...); $this->config->get(...); ) Or creating a new class that works like config if that isn't appropriate in your application.Chilly
@MikeT Thanks for the reply. In the meantime I found that $registry->set('variable_name', 'var_value'); also works. Then I can get var by $registry->get('variable_name') in the controller. :)Youthen
I see $log... is called on line 5 before being defined, which will throw an error while trying to log an error.Judgeship
yes absolutely @Judgeship I've copied over some edits I made to use syslog instead. ran into this awhile back for forgot to update here. also beware any code that uses $_SERVER variables that aren't set during CLI executionChilly
Good solution. I've been using your example for a while and it's very helpful.Judgeship
C
6

In 2.3.0.2 a very simple way I found was to add your controller function path into the ignored paths settings for login and permission restrictions. Then just add a url password or other check in that controller function to lock it down.

So first in admin/controller/startup/login.php add your controller function path to both $ignore arrays, eg 'common/cron/action'

And then in admin/controller/startup/permissions.php you want just the controller path, eg 'common/cron'

And then finally at start of your action() function do like:

if(!isset($_GET['pass']) || $_GET['pass'] != 'secretpassword')return;

Then i just added this to my cron:

php-cli -r 'echo file_get_contents("https://www.website.com/admin/index.php?route=common/cron/action&pass=secretpassword");'
Communist answered 11/11, 2018 at 1:2 Comment(3)
I still use OC 2.3 for several clients, and this approach is what I always did.Wheatear
nice method! In cron I use this instead curl --request GET 'https://...'Mustang
I also these days use wget -qO- "URL"Communist
M
3

As I had a similar requirement several times, I put my ideas into a lightweight commandline tool called OCOK.

Especially the Cli Task Command allows you to call Opencart controllers via the commandline and thus lets you call them as cron jobs. Simply create a controller like this and save it as admin/controller/task/example.php:

class ControllerTaskExample extends Controller {
    public function index() {
        if (isset($this->is_cli) && $this->is_cli === true) {
            // work done by the controller

            if (isset($this->request->get['param1'])) {
                echo "param1 is " . $this->request->get['param1'] . "\n";
            }

            if (isset($this->request->get['param2'])) {
                echo "param2 is " . $this->request->get['param2'] . "\n";
            }

        }
    }
} 

Via the commandline it can be called with parameters:

ocok run task/example param1=foo param2=bar

The above stated command would output:

param1 is foo
param2 is bar

Adding this to crontab is as easy as adding the following line to your cron file:

* * * * * (cd /path/to/opencart/folder; /path/to/ocok run task/example param1=foo param2=bar)

the respective paths need to be set correctly of course.

Installation available with composer. All further documentation can be found inside the docs: OCOK

Moralez answered 1/3, 2015 at 21:14 Comment(0)
S
3

I know this is a very old question, but I spent quite a long time trying to figure how to do the same in opencart version 2.x which works different. So I share here my solution.(based on Mike T approach)

1 - Create cli folder adjacent to admin and catalog.
2 - In this same folder create a file which you will run via cron or comandline, for example runcron.php

#!/usr/bin/php
<?php
require_once('cli_dispatch.php');

3 - In the same folder create the cli_dispatch.php file which is a copy of the index.php file in admin folder with some changes (Note in this is installation there is VQMOD activated, which may not be your case)

    <?php
    // CLI must be called by cli php
    if (php_sapi_name() != 'cli') {
        syslog(LOG_ERR, "cli $cli_action call attempted by non-cli.");
        http_response_code(400);
        exit;
    }

    // Ensure $cli_action is set
    if (!isset($cli_action)) {
        echo 'ERROR: $cli_action must be set in calling script.';
        syslog(LOG_ERR, '$cli_action must be set in calling script');
        http_response_code(400);
        exit;
    }

    // Handle errors by writing to log
    function cli_error_handler($log_level, $log_text, $error_file, $error_line) {
        syslog(LOG_ERR, 'CLI Error: ' . $log_text . ' in ' . $error_file . ': ' . $error_line);
        echo 'CLI Error: ' . $log_text . ' in ' . $error_file . ': ' . $error_line;
    }
    set_error_handler('cli_error_handler');



    // Configuration (note we're using the admin config)
    require_once __DIR__.('/../admin/config.php');


    // Configuration not present in CLI (vs web)
    chdir(__DIR__.'/../admin');
    set_include_path(get_include_path() . PATH_SEPARATOR . realpath(dirname(__FILE__)) . '../admin/');
    $_SERVER['HTTP_HOST'] = '';



    if (!defined('DIR_APPLICATION')) {
        echo "ERROR: cli $cli_action call missing configuration.";
        http_response_code(400);
        exit;
    }

    // Version
    define('VERSION', '2.3.0.3_rc');

    // Configuration
    if (is_file('config.php')) {
            require_once('config.php');
    }

    // Install
    if (!defined('DIR_APPLICATION')) {
            header('Location: ../install/index.php');
            exit;
    }

    //VirtualQMOD
    require_once('../vqmod/vqmod.php');
    VQMod::bootup();

    // VQMODDED Startup
    require_once(VQMod::modCheck(DIR_SYSTEM . 'startup.php'));

    start('cli');  

4 - Now create the file upload/system/config/cli.php which will be the one that opencart will use to read the configuration of your new cli bootrasp from file upload/system/framework.php

    <?php
    // Site
    $_['site_base']         = HTTP_SERVER;
    $_['site_ssl']          = HTTPS_SERVER;

    // Database
    $_['db_autostart']      = true;
    $_['db_type']           = DB_DRIVER; // mpdo, mssql, mysql, mysqli or postgre
    $_['db_hostname']       = DB_HOSTNAME;
    $_['db_username']       = DB_USERNAME;
    $_['db_password']       = DB_PASSWORD;
    $_['db_database']       = DB_DATABASE;
    $_['db_port']           = DB_PORT;

    // Session
    //$_['session_autostart'] = true;
    // Autoload Libraries
    $_['library_autoload'] = array(
            'openbay'
    );

    // Actions
    $_['action_pre_action'] = array(
            'startup/startup',
            'startup/error',
            'startup/event',
            'startup/sass',
    //      'startup/login',
    //      'startup/permission'
    );

    // Actions
    $_['action_default'] = 'sale/croninvoices';

    // Action Events
    $_['action_event'] = array(
        'view/*/before' => 'event/theme'
    );  

As you can see there i've commented all the Session and Actions lines related to permissions.
You will ave to edit the line

$_['action_default'] = 'sale/yourscript';  

changing 'sale/yourscript' with the path and filename of your controller.

In the example, runnunig the runcron.php file will execute the index funcion in

upload/admin/controller/sale/yourscript.php file    
Sacksen answered 31/1, 2017 at 0:34 Comment(0)
T
2

By default opencart doesn't allow to access admin pages without login. The login and token validations are checked in login() method in admin/controller/common/home.php.

it cant be set on frontend coz the model is in admin area. - You may create a new controller and model for frontend with the same functionality in admin panel and use it for cronjob.

Opencart has got usergroups which sets access rights for the users. So the admin pages will not get loaded for the users without permission. Hence you may need to modify the core files very much for setting cronjob in admin panel which may lead to severe security issues.

I suggest a frontend controller and model file for cronjob. For additional security you can pass a particular key parameter in url and write a condition to verify it.

Have a nice day !!

Two answered 4/3, 2014 at 19:16 Comment(2)
Thanks for suggestion.. I will try it first.. Thanks a lot.. U make my day brighter..Wheatear
no need to create separate models & controllers. There is an easy way.Denishadenison
M
2

In opencart 2.1.0.2. If you need db in cron job, but DONT need any opencart models. You can create file system/mycron/cron_task.php. And add such code to this file:

// CLI
include_once 'config.php';
include_once DIR_SYSTEM.'library/db/mysqli.php';
include_once DIR_SYSTEM.'helper/general.php';
mb_internal_encoding('UTF-8');
if (php_sapi_name() != 'cli') { error_log('NOT CLI CALL');print 'NOT CLI CALL';http_response_code(400);exit; }
$db = new DB\MySQLi(DB_HOSTNAME,DB_USERNAME,DB_PASSWORD,DB_DATABASE,DB_PORT);
// END CLI

// Your actual code, and you CAN use opencart DB!
foreach ($db->query("SELECT * FROM oc_product")->rows as $row) {
    //...
}

Now you can run this from your cron job:

12 7 * * * cd /path/to/opencart/root/folder && php system/mycron/cron_task.php
Musso answered 15/5, 2019 at 12:45 Comment(0)
B
0

"I want to develop a module to delete any (unpaid) order that exceed the time frame given. Ex : I want to delete any unpaid order that has not been paid for 2 days after the order was placed." "I want to use existed model in opencart (and not use a new one)."

So, as I'm sure you know, the problem is that you have to be logged in to the admin to access it's controllers and models, but a cron job will not be logged in when it runs.

You could see if the catalog model will do what you need, in which case no problem:

catalog/model/checkout/order.php

You could follow other answers here - i.e. find some way around logging in.

Or you could just write a stand-alone PHP script that runs a simple SQL query.

You're right that it's usually the correct thing to do to use the models of the system BUT OpenCart is so simple that it should be a pretty simple query (just a few lines) that does what you need so that is also an acceptable option in my opinion in this case.

Brash answered 16/5, 2019 at 9:38 Comment(0)
B
0
include_once($_SERVER['DOCUMENT_ROOT'].'/admin/model/module/ModelYourModel.php');
$mym = new ModelYourModel($this->registry);
$mym->yourFunction();

For version 2.1, possibly higher. Use the model from the admin folder in the catalog folder.

Boding answered 20/5, 2020 at 8:6 Comment(0)
G
0

In case anyone is looking for how to set a cron in opencart 3.0.3.8 here's how I did it:

I create a folder in the root path: my_opencart_installation/CLI/ and placed a file inside it: my_opencart_installation/CLI/cli.php

Here I initialised everything I need from the opencart framework:

<?php
define('VERSION', '3.0.3.8');

// CLI can not be run from browser
if (php_sapi_name() != 'cli') {
    exit('This software run only in CLI mode.');
}

// Configuration
if (is_file('config.php')) {
    require_once('config.php');
}

// Startup
require_once(DIR_SYSTEM . 'startup.php');

// Registry
$registry = new Registry();

// Event
$event = new Event($registry);
$registry->set('event', $event);

// Loader
$loader = new Loader($registry);
$registry->set('load', $loader);

// Config
$config = new Config();
$config->load('default');
$config->load('admin');
$registry->set('config', $config);

// Database 
$db = new DB(DB_DRIVER, DB_HOSTNAME, DB_USERNAME, DB_PASSWORD, DB_DATABASE);
$registry->set('db', $db);

// Settings
$query = $db->query("SELECT * FROM " . DB_PREFIX . "setting WHERE store_id = '0'");

foreach ($query->rows as $setting) {
    if (!$setting['serialized']) {
        $config->set($setting['key'], $setting['value']);
    } else {
        $config->set($setting['key'], json_decode($setting['value'], true));
    }
}

// Request
$request = new Request();
$registry->set('request', $request);

// Response
$response = new Response();
$response->addHeader('Content-Type: text/html; charset=utf-8');
$registry->set('response', $response);

// Session
$session = new Session($config->get('session_engine'), $registry);
$registry->set('session', $session);

// Language, Fetch the language ID for 'en-gb'
$query = $db->query("SELECT language_id FROM " . DB_PREFIX . "language WHERE code = 'en-gb'");

if ($query->num_rows) {
    $language_id = $query->row['language_id'];
} else {
    // Handle the error in case 'en-gb' is not found in the language table
    die('The specified language code was not found in the database.');
}
$language = new Language($config->get('config_language'));
$config->set('config_language_id', $language_id);
$registry->set('language', $language);

// Document$config->get('config_language')
$registry->set('document', new Document());

// Loader
$loader = new Loader($registry);
$registry->set('load', $loader);

// Cache
$cache = new Cache('file');
$registry->set('cache', $cache);

// Include your Controller
require_once(DIR_APPLICATION . 'controller/price_updater.php');

// Create a new instance of your controller
$updater = new ControllerPriceUpdater($registry);

// Call the index method
$updater->index();

I had to initialize other stuff because I was calling a controller that I had created with the purpose of updating all my products with new stock information and price information. Hope this helps someone, it was kind of a nightmare to me...

Grafton answered 1/8, 2023 at 16:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.