338 lines
9.2 KiB
PHP
338 lines
9.2 KiB
PHP
<?php
|
|
/**
|
|
* @package FOF
|
|
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
|
|
* @license GNU General Public License version 3, or later
|
|
*/
|
|
|
|
// Do not put the JEXEC or die check on this file
|
|
|
|
/**
|
|
* FOF-powered Joomla! CLI application implementation.
|
|
*
|
|
* Get all the power of Joomla in CLI without all the awkward decisions which make CLI scripts fail on many common,
|
|
* commercial hosting environments. We've been doing that in our software before Joomla got CLI support. We know of all
|
|
* the pitfalls and this little gem here will work around most of them (or at least fail gracefully).
|
|
*
|
|
* Your CLI script must begin with the following boilerplate code:
|
|
*
|
|
* // Boilerplate -- START
|
|
* define('_JEXEC', 1);
|
|
*
|
|
* foreach ([__DIR__, getcwd()] as $curdir)
|
|
* {
|
|
* if (file_exists($curdir . '/defines.php'))
|
|
* {
|
|
* define('JPATH_BASE', realpath($curdir . '/..'));
|
|
* require_once $curdir . '/defines.php';
|
|
*
|
|
* break;
|
|
* }
|
|
*
|
|
* if (file_exists($curdir . '/../includes/defines.php'))
|
|
* {
|
|
* define('JPATH_BASE', realpath($curdir . '/..'));
|
|
* require_once $curdir . '/../includes/defines.php';
|
|
*
|
|
* break;
|
|
* }
|
|
* }
|
|
*
|
|
* defined('JPATH_LIBRARIES') || die ('This script must be placed in or run from the cli folder of your site.');
|
|
*
|
|
* require_once JPATH_LIBRARIES . '/fof40/Cli/Application.php';
|
|
* // Boilerplate -- END
|
|
*
|
|
* Create a class which extends FOFCliApplication and implements doExecute, e.g.
|
|
*
|
|
* // Class definition -- START
|
|
* class YourClassName extends FOFCliApplication
|
|
* {
|
|
* protected function doExecute()
|
|
* {
|
|
* // Do something useful
|
|
* }
|
|
* }
|
|
* // Class definition -- END
|
|
*
|
|
* Finally, execute your script with:
|
|
*
|
|
* // Execute script -- START
|
|
* FOFCliApplication::getInstance('YourClassName')->execute();
|
|
* // Execute script -- END
|
|
*
|
|
* You can optionally define $minphp before the boilerplate code to enforce a different minimum PHP version.
|
|
*/
|
|
|
|
// Abort immediately when this file is executed from a web SAPI
|
|
if (array_key_exists('REQUEST_METHOD', $_SERVER))
|
|
{
|
|
die('This is a command line script. You are not allowed to access it over the web.');
|
|
}
|
|
|
|
// Work around some badly configured servers which print out notices
|
|
if (function_exists('error_reporting'))
|
|
{
|
|
$oldLevel = error_reporting(E_ERROR | E_NOTICE | E_DEPRECATED);
|
|
}
|
|
|
|
// Minimum PHP version check
|
|
if (!isset($minphp))
|
|
{
|
|
$minphp = '5.6.0';
|
|
}
|
|
|
|
if (version_compare(PHP_VERSION, $minphp, 'lt'))
|
|
{
|
|
require_once __DIR__ . '/wrong_php.php';
|
|
|
|
die;
|
|
}
|
|
|
|
// Required by scripts written for old Joomla! versions.
|
|
define('DS', DIRECTORY_SEPARATOR);
|
|
|
|
/**
|
|
* Timezone fix
|
|
*
|
|
* This piece of code was originally put here because some PHP 5.3 servers forgot to declare a default timezone.
|
|
* Unfortunately it's still required because some hosts STILL forget to provide a timezone in their php.ini files or,
|
|
* worse, use invalid timezone names.
|
|
*/
|
|
if (function_exists('date_default_timezone_get') && function_exists('date_default_timezone_set'))
|
|
{
|
|
$serverTimezone = @date_default_timezone_get();
|
|
|
|
// Do I have no timezone set?
|
|
if (empty($serverTimezone) || !is_string($serverTimezone))
|
|
{
|
|
$serverTimezone = 'UTC';
|
|
}
|
|
|
|
// Do I have an invalid timezone?
|
|
try
|
|
{
|
|
$testTimeZone = new DateTimeZone($serverTimezone);
|
|
}
|
|
catch (\Exception $e)
|
|
{
|
|
$serverTimezone = 'UTC';
|
|
}
|
|
|
|
// Set the default timezone to a correct thing
|
|
@date_default_timezone_set($serverTimezone);
|
|
}
|
|
|
|
// This is not necessary if you have used the boilerplate code.
|
|
if (!isset($curdir) && !defined('JPATH_ROOT'))
|
|
{
|
|
foreach ([__DIR__ . '/../../../cli', getcwd()] as $curdir)
|
|
{
|
|
if (file_exists($curdir . '/defines.php'))
|
|
{
|
|
define('JPATH_BASE', realpath($curdir . '/..'));
|
|
require_once $curdir . '/defines.php';
|
|
|
|
break;
|
|
}
|
|
|
|
if (file_exists($curdir . '/../includes/defines.php'))
|
|
{
|
|
define('JPATH_BASE', realpath($curdir . '/..'));
|
|
require_once $curdir . '/../includes/defines.php';
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
defined('JPATH_LIBRARIES') || die ('This script must be placed in or run from the cli folder of your site.');
|
|
}
|
|
|
|
// Restore the error reporting before importing Joomla core code
|
|
if (function_exists('error_reporting'))
|
|
{
|
|
error_reporting($oldLevel);
|
|
}
|
|
|
|
// Awkward Joomla version detection before we can actually load Joomla! itself
|
|
$joomlaMajorVersion = 3;
|
|
$joomlaMinorVersion = 0;
|
|
$jVersionFile = JPATH_LIBRARIES . '/src/Version.php';
|
|
|
|
if ($versionFileContents = @file_get_contents($jVersionFile))
|
|
{
|
|
preg_match("/MAJOR_VERSION\s*=\s*(\d*)\s*;/", $versionFileContents, $versionMatches);
|
|
$joomlaMajorVersion = (int) $versionMatches[1];
|
|
preg_match("/MINOR_VERSION\s*=\s*(\d*)\s*;/", $versionFileContents, $versionMatches);
|
|
$joomlaMinorVersion = (int) $versionMatches[1];
|
|
}
|
|
|
|
// Load the Trait files
|
|
include_once __DIR__ . '/Traits/CGIModeAware.php';
|
|
include_once __DIR__ . '/Traits/CustomOptionsAware.php';
|
|
include_once __DIR__ . '/Traits/JoomlaConfigAware.php';
|
|
include_once __DIR__ . '/Traits/MemStatsAware.php';
|
|
include_once __DIR__ . '/Traits/MessageAware.php';
|
|
include_once __DIR__ . '/Traits/TimeAgoAware.php';
|
|
|
|
// The actual implementation of the CliApplication depends on the Joomla version we're running under
|
|
switch ($joomlaMajorVersion)
|
|
{
|
|
case 3:
|
|
default:
|
|
require_once __DIR__ . '/Joomla3.php';
|
|
|
|
abstract class FOFApplicationCLI extends FOFCliApplicationJoomla3
|
|
{
|
|
}
|
|
|
|
;
|
|
|
|
break;
|
|
|
|
case 4:
|
|
require_once __DIR__ . '/Joomla4.php';
|
|
|
|
abstract class FOFApplicationCLI extends FOFCliApplicationJoomla4
|
|
{
|
|
}
|
|
|
|
;
|
|
|
|
break;
|
|
}
|
|
|
|
/**
|
|
* A default exception handler. Catches all unhandled exceptions, displays debug information about them and sets the
|
|
* error level to 254.
|
|
*
|
|
* @param Throwable $ex The Exception / Error being handled
|
|
*/
|
|
function FOFCliExceptionHandler($ex)
|
|
{
|
|
echo "\n\n";
|
|
echo "********** ERROR! **********\n\n";
|
|
echo $ex->getMessage();
|
|
echo "\n\nTechnical information:\n\n";
|
|
echo "Code: " . $ex->getCode() . "\n";
|
|
echo "File: " . $ex->getFile() . "\n";
|
|
echo "Line: " . $ex->getLine() . "\n";
|
|
echo "\nStack Trace:\n\n" . $ex->getTraceAsString();
|
|
echo "\n\n";
|
|
exit(254);
|
|
}
|
|
|
|
/**
|
|
* Timeout handler
|
|
*
|
|
* This function is registered as a shutdown script. If a catchable timeout occurs it will detect it and print a helpful
|
|
* error message instead of just dying cold. The error level is set to 253 in this case.
|
|
*
|
|
* @return void
|
|
*/
|
|
function FOFCliTimeoutHandler()
|
|
{
|
|
$connection_status = connection_status();
|
|
|
|
if ($connection_status == 0)
|
|
{
|
|
// Normal script termination, do not report an error.
|
|
return;
|
|
}
|
|
|
|
echo "\n\n";
|
|
echo "********** ERROR! **********\n\n";
|
|
|
|
if ($connection_status == 1)
|
|
{
|
|
echo <<< END
|
|
The process was aborted on user's request.
|
|
|
|
This usually means that you pressed CTRL-C to terminate the script (if you're
|
|
running it from a terminal / SSH session), or that your host's CRON daemon
|
|
aborted the execution of this script.
|
|
|
|
If you are running this script through a CRON job and saw this message, please
|
|
contact your host and request an increase in the timeout limit for CRON jobs.
|
|
Moreover you need to ask them to increase the max_execution_time in the
|
|
php.ini file or, even better, set it to 0.
|
|
END;
|
|
}
|
|
else
|
|
{
|
|
echo <<< END
|
|
This script has timed out. As a result, the process has FAILED to complete.
|
|
|
|
Your host applies a maximum execution time for CRON jobs which is too low for
|
|
this script to work properly. Please contact your host and request an increase
|
|
in the timeout limit for CRON jobs. Moreover you need to ask them to increase
|
|
the max_execution_time in the php.ini file or, even better, set it to 0.
|
|
END;
|
|
|
|
|
|
if (!function_exists('php_ini_loaded_file'))
|
|
{
|
|
echo "\n\n";
|
|
|
|
return;
|
|
}
|
|
|
|
$ini_location = php_ini_loaded_file();
|
|
|
|
echo <<<END
|
|
The php.ini file your host will need to modify is located at:
|
|
$ini_location
|
|
Info for the host: the location above is reported by PHP's php_ini_loaded_file() method.
|
|
|
|
END;
|
|
|
|
echo "\n\n";
|
|
exit(253);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Error handler. It tries to catch fatal errors and report them in a meaningful way. Obviously it only works for
|
|
* catchable fatal errors. It sets the error level to 252.
|
|
*
|
|
* IMPORTANT! Under PHP 7 the default exception handler will be called instead, including when there is a non-catchable
|
|
* fatal error.
|
|
*
|
|
* @param int $errno Error number
|
|
* @param string $errstr Error string, tells us what went wrong
|
|
* @param string $errfile Full path to file where the error occurred
|
|
* @param int $errline Line number where the error occurred
|
|
*
|
|
* @return void
|
|
*/
|
|
function FOFCliErrorHandler($errno, $errstr, $errfile, $errline)
|
|
{
|
|
switch ($errno)
|
|
{
|
|
case E_ERROR:
|
|
case E_USER_ERROR:
|
|
echo "\n\n";
|
|
echo "********** ERROR! **********\n\n";
|
|
echo "PHP Fatal Error: $errstr";
|
|
echo "\n\nTechnical information:\n\n";
|
|
echo "File: " . $errfile . "\n";
|
|
echo "Line: " . $errline . "\n";
|
|
echo "\nStack Trace:\n\n" . debug_backtrace();
|
|
echo "\n\n";
|
|
|
|
exit(252);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Custom default handlers for otherwise unhandled exceptions and PHP catchable errors.
|
|
*
|
|
* Moreover, we register a shutdown function to catch timeouts and SIGTERM signals, because some hosts *are* monsters.
|
|
*/
|
|
set_exception_handler('FOFCliExceptionHandler');
|
|
set_error_handler('FOFCliErrorHandler', E_ERROR | E_USER_ERROR);
|
|
register_shutdown_function('FOFCliTimeoutHandler'); |