CiviCRM cron via HTTP on Backdrop isn't documented and doesn't work
The problem is that running CiviCRM cron via HTTP is (a) undocumented, (b) doesn't work.
Documentation for CiviCRM cron is at https://docs.civicrm.org/sysadmin/en/latest/setup/jobs/#url-to-cronphp
There's no Backdrop instructions under "URL to cron.php". One might expect the URL to be the same as for Drupal 7, i.e.,
https://example.org/sites/all/modules/civicrm/bin/cron.php
but there are several problems with that.
First, sites/all/modules
is not where CiviCRM is likely to be stored in a Backdrop installation. More likely, it would be /modules/civicrm
or modules/contrib/civicrm
.
That would make the URL one of these:
https://example.org/modules/civicrm/bin/cron.php
https://example.org/modules/contrib/civicrm/bin/cron.php
But even if you use the appropriate URL, Backdrop won't let you load that file, instead directing you to a 404 page. This is because the Backdrop .htaccess file has a rewrite condition that grabs any path ending in cron.php
. See https://github.com/backdrop/backdrop-issues/issues/3903 for details and workarounds.
[UPDATE: Backdrop fixed the .htaccess issue in versions 1.21.5 and 1.22.0, released 2022-05-15.]
We can work around this without hacking .htaccess by making a copy of this file with a different name, e.g., backdrop_cron.php
. But at that point, we run into the next problem: the function civicrm_conf_init()
in file civicrm.config.php
doesn't necessarily return the right directory for the file civicrm.settings.php
, so the cron call fails.
Under Drupal 7, this file was commonly in sites/all/defaults
, right next to D7's settings.php
. So in Backdrop, where settings.php
resides at site root, it's a logical choice to put civicrm.settings.php
in the same directory, so also at site root. But civicrm_conf_init()
can't handle this location.
We can fix that, too, by editing civicrm.config.php
to add code to handle this option:
// check to see if civicrm is under modules/contrib or modules, and if so,
// see if settings file is at site root.
if (file_exists($currentDir . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'civicrm.settings.php')) {
return $currentDir . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..';
}
elseif (file_exists($currentDir . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'civicrm.settings.php')) {
return $currentDir . '..' . DIRECTORY_SEPARATOR . '..';
}
This returns the correct directory, and indeed, with this change, one can almost-successfully run CiviCRM cron from the URL:
http://example.org/modules/contrib/civicrm/bin/backdrop_cron.php?name=<user>&pass=<pass>&key=<site_key>
Except this generates a host of PHP warnings in watchdog like this:
Warning: Cannot modify header information - headers already sent by (output started at /mysite/core/includes/bootstrap.inc:745) in backdrop_send_headers() (line 1638 of /mysite/core/includes/bootstrap.inc).
Plus a bunch more notices and warnings (25 in total).
This happens during the call to CRM_Utils_System::authenticateScript(TRUE)
, which bootstraps the Backdrop site.
Aside from the warnings, the cron job proceeds fully, and the cron run is reported as being run on the CiviCRM status page.
A hacky workaround would be to suppress the warnings just for this run via error_reporting()
, but this isn't possible, because bootstrap.inc
does its own call to error_reporting()
. (And suppressing lots of warnings just to get rid of them is generally a bad thing to do.)
So at this point, running CiviCRM cron from HTTP isn't readily achieved.
A deeper look with a debugger narrows the problem to the CiviCRM function CRM_Utils_System::authenticate()
, which contains just this:
public static function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realPath = NULL) {
$config = CRM_Core_Config::singleton();
/* Before we do any loading, let's start the session and write to it.
* We typically call authenticate only when we need to bootstrap the CMS
* directly via Civi and hence bypass the normal CMS auth and bootstrap
* process typically done in CLI and cron scripts. See: CRM-12648
*
* Q: Can we move this to the userSystem class so that it can be tuned
* per-CMS? For example, when dealing with UnitTests UF, does it need to
* do this session write since the original issue was for Drupal.
*/
$session = CRM_Core_Session::singleton();
$session->set('civicrmInitSession', TRUE);
return $config->userSystem->authenticate($name, $password, $loadCMSBootstrap, $realPath);
}
The problem is that the call $session->set()
starts the session via CRM_Utils_System_Backdrop->setssionStart()
. But then the next call to $config->userSystem->authenticate()
(which resolves to CRM_UTILS_System_Backdrop->autheticate()
) ends up calling backdrop_bootstrap()
, which will attempt to do a bunch of ini_set()
s, resulting in the error since the session has already started.