fix: Update patient_tracker.php (#6595)
[openemr.git] / library / ajax / execute_background_services.php
bloba0385671e2a8331629219939ae53042e2f9ad553
1 <?php
3 /**
4 * Manage background operations that should be executed at intervals.
6 * This script may be executed by a suitable Ajax request, by a cron job, or both.
8 * When called from cron, optinal args are [site] [service] [force]
9 * @param site to specify a specific site, 'default' used if omitted
10 * @param service to specify a specific service, 'all' used if omitted
11 * @param force '1' to ignore specified wait interval, '0' to honor wait interval
13 * The same parameters can be accessed via Ajax using the $_POST variables
14 * 'site', 'background_service', and 'background_force', respectively.
16 * For both calling methods, this script guarantees that each active
17 * background service function: (1) will not be called again before it has completed,
18 * and (2) will not be called any more frequently than at the specified interval
19 * (unless the force execution flag is used). A service function that is already running
20 * will not be called a second time even if the force execution flag is used.
22 * Notes for the default background behavior:
23 * 1. If the Ajax method is used, services will only be checked while
24 * Ajax requests are being received, which is currently only when users are
25 * logged in.
26 * 2. All services are checked and called sequentially in the order specified
27 * by the sort_order field in the background_services table. Service calls that are "slow"
28 * should be given a higher sort_order value.
29 * 3. The actual interval between two calls to a given background service may be
30 * as long as the time to complete that service plus the interval between
31 * n+1 calls to this script where n is the number of other services preceding it
32 * in the array, even if the specified minimum interval is shorter, so plan
33 * accordingly. Example: with a 5 min cron interval, the 4th service on the list
34 * may not be started again for up to 20 minutes after it has completed if
35 * services 1, 2, and 3 take more than 15, 10, and 5 minutes to complete,
36 * respectively.
38 * Returns a count of due messages for current user.
40 * @package OpenEMR
41 * @link https://www.open-emr.org
42 * @author EMR Direct <https://www.emrdirect.com/>
43 * @author Brady Miller <brady.g.miller@gmail.com>
44 * @copyright Copyright (c) 2013 EMR Direct <https://www.emrdirect.com/>
45 * @copyright Copyright (c) 2018 Brady Miller <brady.g.miller@gmail.com>
46 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
49 use OpenEMR\Common\Csrf\CsrfUtils;
51 //ajax param should be set by calling ajax scripts
52 $isAjaxCall = isset($_POST['ajax']);
54 //if false ajax and this is a called from command line, this is a cron job and set up accordingly
55 if (!$isAjaxCall && (php_sapi_name() === 'cli')) {
56 $ignoreAuth = 1;
57 //process optional arguments when called from cron
58 $_GET['site'] = $argv[1] ?? 'default';
59 if (isset($argv[2]) && $argv[2] != 'all') {
60 $_GET['background_service'] = $argv[2];
63 if (isset($argv[3]) && $argv[3] == '1') {
64 $_GET['background_force'] = 1;
67 //an additional require file can be specified for each service in the background_services table
68 // Since from command line, set $sessionAllowWrite since need to set site_id session and no benefit to set to false
69 $sessionAllowWrite = true;
70 require_once(__DIR__ . "/../../interface/globals.php");
71 } else {
72 //an additional require file can be specified for each service in the background_services table
73 require_once(__DIR__ . "/../../interface/globals.php");
75 // not calling from cron job so ensure passes csrf check
76 if (!CsrfUtils::verifyCsrfToken($_POST["csrf_token_form"])) {
77 CsrfUtils::csrfNotVerified();
81 //Remove time limit so script doesn't time out
82 set_time_limit(0);
84 //Safety in case one of the background functions tries to output data
85 ignore_user_abort(1);
87 /**
88 * Execute background services
89 * This function reads a list of available services from the background_services table
90 * For each service that is not already running and is due for execution, the associated
91 * background function is run.
93 * Note: Each service must do its own logging, as appropriate, and should disable itself
94 * to prevent continued service calls if an error condition occurs which requires
95 * administrator intervention. Any service function return values and output are ignored.
98 function execute_background_service_calls()
101 * Note: The global $service_name below is set to the name of the service currently being
102 * processed before the actual service function call, and is unset after normal
103 * completion of the loop. If the script exits abnormally, the shutdown_function
104 * uses the value of $service_name to do any required clean up.
106 global $service_name;
108 $single_service = isset($_REQUEST['background_service']) ? $_REQUEST['background_service'] : '';
109 $force = (isset($_REQUEST['background_force']) && $_REQUEST['background_force']);
111 $sql = 'SELECT * FROM background_services WHERE ' . ($force ? '1' : 'execute_interval > 0');
112 if ($single_service != "") {
113 $services = sqlStatementNoLog($sql . ' AND name=?', array($single_service));
114 } else {
115 $services = sqlStatementNoLog($sql . ' ORDER BY sort_order');
118 while ($service = sqlFetchArray($services)) {
119 $service_name = $service['name'];
120 if (!$service['active'] || $service['running'] == 1) {
121 continue;
124 $interval = (int)$service['execute_interval'];
126 //leverage locking built-in to UPDATE to prevent race conditions
127 //will need to assess performance in high concurrency setting at some point
128 $sql = 'UPDATE background_services SET running = 1, next_run = NOW()+ INTERVAL ?'
129 . ' MINUTE WHERE running < 1 ' . ($force ? '' : 'AND NOW() > next_run ') . 'AND name = ?';
130 if (sqlStatementNoLog($sql, array($interval,$service_name)) === false) {
131 continue;
134 $acquiredLock = generic_sql_affected_rows();
135 if ($acquiredLock < 1) {
136 continue; //service is already running or not due yet
139 if ($service['require_once']) {
140 require_once($GLOBALS['fileroot'] . $service['require_once']);
143 if (!function_exists($service['function'])) {
144 continue;
147 //use try/catch in case service functions throw an unexpected Exception
148 try {
149 $service['function']();
150 } catch (Exception $e) {
151 //do nothing
154 $sql = 'UPDATE background_services SET running = 0 WHERE name = ?';
155 $res = sqlStatementNoLog($sql, array($service_name));
160 * Catch unexpected failures.
162 * if the global $service_name is still set, then a die() or exit() occurred during the execution
163 * of that service's function call, and we did not complete the foreach loop properly,
164 * so we need to reset the is_running flag for that service before quitting
167 function background_shutdown()
169 global $service_name;
170 if (isset($service_name)) {
171 $sql = 'UPDATE background_services SET running = 0 WHERE name = ?';
172 $res = sqlStatementNoLog($sql, array($service_name));
176 register_shutdown_function('background_shutdown');
177 execute_background_service_calls();
178 unset($service_name);