mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-23 19:47:06 +09:00
* PHPStan Level 8 - Phase 1 Conversion
Reduces the real (no-baseline) PHPStan level 8 error count from ~2,724 to 425
and regenerates phpstan-baseline.neon accordingly (8,941 -> 1,921 lines).
Core type-safety changes
- lib/database.php: narrow db_fetch_row()/db_fetch_row_prepared() to return
array (was bool|array) and db_fetch_assoc()/db_fetch_assoc_prepared() to
return array (was mixed); the _prepared workers coerce the connection-failure
path to an empty array so the declared type is honest. empty()/cacti_sizeof()
checks are unaffected.
- lib/database.php: narrow $db_conn guards from is_object() to instanceof PDO in
the transaction/quote/insert-id helpers so the PDO calls resolve.
- lib/mib_cache.php: the fluent MibCache methods mib()/object()/table()/row()
now return self instead of a bare object so chained calls type-check.
Type-safety casts and guards (mechanical, behaviour-preserving)
- Cast string|false/|null builtin results feeding string parameters
(json_decode, base64_encode, trim, htmle, preg_replace, explode, etc.).
- Cast int|string / int|false values feeding int id/pid parameters.
- Guard resource|false fopen()/popen() handles before fwrite/fclose/fread, and
wrap foreach over array|false/list|false results with `?: []`.
Genuine bugs found and fixed
- cli/add_tree.php / lib/api_tree.php: site tree nodes could not be created via
the CLI - the --site-id option was never parsed, $nodeTypes had no 'site'
entry (passing null to api_tree_item_save(int $type) -> TypeError), and
api_tree_item_save() never persisted site_id. All three fixed.
- tree.php: leaves_exist() is typed : int but returned db_fetch_assoc_prepared()
(an array of rows) for a SELECT COUNT(*); now uses db_fetch_cell_prepared()
and returns the scalar count.
- lib/mib_cache.php: $entries_per_object was a float (division / ceil) used as
an array key (PHP 8.1 implicit float-to-int key deprecation); now cast to int.
Config
- .phpstan.neon: add notIdentical.alwaysTrue and booleanAnd.alwaysFalse to the
existing always-true/false ignore family (same "PHPStan gets confused"
dynamic-constant cause already covered by identical.alwaysTrue,
notEqual.alwaysTrue, booleanAnd.leftAlwaysTrue, etc.).
- phpstan-baseline.neon: regenerated; the remaining 425 entries are the
untouched long tail for Phase 2.
* Remove redundant @phpstan-ignore comments
Follow-up cleanup for the level 8 Phase 1 work. Removes 14 inline
@phpstan-ignore comments that no longer suppress any reported error:
- cli/repair_database.php: `@phpstan-ignore booleanAnd.alwaysFalse` is now
covered by the global booleanAnd.alwaysFalse ignore added to .phpstan.neon.
- 13 others (host.php, include/global.php, include/session.php,
lib/api_automation.php x2, lib/functions.php x2, lib/html_utility.php x2,
lib/installer.php, lib/package.php, lib/reports.php, lib/rrd.php) no longer
match any reported error after the type-safety fixes (e.g. the
db_fetch_row_prepared() return-type narrowing removed the offset-assign
errors the api_automation ignores were suppressing).
Verified with reportUnmatchedIgnoredErrors enabled: 0 unmatched ignores
remain and the full level 8 run still reports 0 errors.
* ci: satisfy validator ratchet and refresh security sink baselines
- lib/api_tree.php: give the new tree-item site_id a real numeric
constraint ('^[-0-9]+$', matching html_reports.php) instead of an
empty regex, so the validator constraint ratchet passes without
bumping its baseline.
- tests/security/baselines: refresh the three sink/hotspot signatures
whose code text changed from the (string) casts added in this branch
(lib/poller.php setsid, cli/rrdresize.php json_encode, lib/import.php
simplexml_load_string).
* ci(js): allow DOMPurify 3.4.16 and update sync_js pin test
The dependabot bump (#8134) moved package.json/package-lock.json to
DOMPurify 3.4.16 on develop but left tests/js/sync_js.test.mjs pinned to
3.4.15, so the PR merge commit fails the 'DOMPurify is pinned' contract.
Bring package.json/package-lock.json to 3.4.16 (matching develop) and
update the pin test to expect 3.4.16.
* test: update sql.php null-handling assertion for the (string) cast
This branch changed scripts/sql.php from trim(\ ?? '') to
trim((string) \) (the phpstan-safe form that coerces both null and
false from shell_exec()). Update SqlScriptsTest's null-return assertion
to match the new cast instead of the removed '?? ''.
* fix: address Copilot review - ownership IDs, fopen guards, mib_cache bound
- chown()/chgrp(): pass integer UID/GID from fileowner()/filegroup()
(guarding the false lookup) instead of string-casting them, which made
them resolve account/group names like '1001' and fail. Covers
lib/api_device.php, lib/boost.php and poller_maintenance.php (x2).
- fopen() failure now short-circuits instead of reporting success:
lib/package.php returns false, lib/clog_webapi.php and utilities.php
render an error, and snmpagent_mibcachechild.php skips the cache write
and sentinel unlink so a concurrent writer's lock is preserved.
- lib/mib_cache.php: keep the float quotient as the loop bound and cast
only the array offset, so a non-even entries/columns split no longer
truncates the loop and drops a cached row.
* revert: keep db_fetch_* returning false on error; baseline Level 8 instead
Per review, restoring the intentional fail-safe: db_fetch_row[_prepared]()
return bool|array and db_fetch_assoc[_prepared]() return mixed again, so a
genuine SQL error (not just a missing connection) is signalled as false and
the === false checks in cdef_is_in_use(), boost_process_* and aggregate.php
work as before. Dropped the is_array() ? : [] coercion.
Level 8 stays green by regenerating phpstan-baseline.neon to capture the
re-introduced bool|array/mixed call-site errors (the ~1,850 the narrowing
had eliminated). Tightening those callers to drop them from the baseline is
Phase 2 work.
---------
Co-authored-by: TheWitness <copilot@users.noreply.github.com>
806 lines
22 KiB
PHP
806 lines
22 KiB
PHP
<?php
|
|
/*
|
|
+-------------------------------------------------------------------------+
|
|
| Copyright (C) 2004-2026 The Cacti Group |
|
|
| |
|
|
| This program is free software; you can redistribute it and/or |
|
|
| modify it under the terms of the GNU General Public License |
|
|
| as published by the Free Software Foundation; either version 2 |
|
|
| of the License, or (at your option) any later version. |
|
|
| |
|
|
| This program is distributed in the hope that it will be useful, |
|
|
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
| GNU General Public License for more details. |
|
|
+-------------------------------------------------------------------------+
|
|
| Cacti: The Complete RRDtool-based Graphing Solution |
|
|
+-------------------------------------------------------------------------+
|
|
| This code is designed, written, and maintained by the Cacti Group. See |
|
|
| about.php and/or the AUTHORS file for specific developer information. |
|
|
+-------------------------------------------------------------------------+
|
|
| http://www.cacti.net/ |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
/**
|
|
* Generates the form configuration array for the API scheduler.
|
|
*
|
|
* @return array The form configuration array.
|
|
*/
|
|
function api_scheduler_form() : array {
|
|
global $sched_types, $heartbeats;
|
|
|
|
return [
|
|
'spacer2' => [
|
|
'method' => 'spacer',
|
|
'friendly_name' => __('Schedule'),
|
|
'collapsible' => 'true'
|
|
],
|
|
'sched_type' => [
|
|
'method' => 'drop_array',
|
|
'friendly_name' => __('Schedule Type'),
|
|
'description' => __('Define the collection frequency.'),
|
|
'value' => '|arg1:sched_type|',
|
|
'array' => $sched_types,
|
|
'default' => SCHEDULE_MANUAL
|
|
],
|
|
'start_at' => [
|
|
'method' => 'textbox',
|
|
'friendly_name' => __('Starting Date/Time'),
|
|
'description' => __('What time will this Network discover item start?'),
|
|
'value' => '|arg1:start_at|',
|
|
'max_length' => '30',
|
|
'default' => date('Y-m-d H:i:s'),
|
|
'size' => 60
|
|
],
|
|
'recur_every' => [
|
|
'method' => 'drop_array',
|
|
'friendly_name' => __('Rerun Every'),
|
|
'description' => __('Rerun discovery for this Network Range every X.'),
|
|
'value' => '|arg1:recur_every|',
|
|
'default' => '1',
|
|
'array' => [
|
|
1 => '1',
|
|
2 => '2',
|
|
3 => '3',
|
|
4 => '4',
|
|
5 => '5',
|
|
6 => '6',
|
|
7 => '7'
|
|
],
|
|
],
|
|
'day_of_week' => [
|
|
'method' => 'drop_multi',
|
|
'friendly_name' => __('Days of Week'),
|
|
'description' => __('What Day(s) of the week will this Network Range be discovered.'),
|
|
'array' => [
|
|
1 => __('Sunday'),
|
|
2 => __('Monday'),
|
|
3 => __('Tuesday'),
|
|
4 => __('Wednesday'),
|
|
5 => __('Thursday'),
|
|
6 => __('Friday'),
|
|
7 => __('Saturday')
|
|
],
|
|
'value' => '|arg1:day_of_week|',
|
|
'class' => 'multiselect day_of_week'
|
|
],
|
|
'month' => [
|
|
'method' => 'drop_multi',
|
|
'friendly_name' => __('Months of Year'),
|
|
'description' => __('What Months(s) of the Year will this Network Range be discovered.'),
|
|
'array' => [
|
|
1 => __('January'),
|
|
2 => __('February'),
|
|
3 => __('March'),
|
|
4 => __('April'),
|
|
5 => __('May'),
|
|
6 => __('June'),
|
|
7 => __('July'),
|
|
8 => __('August'),
|
|
9 => __('September'),
|
|
10 => __('October'),
|
|
11 => __('November'),
|
|
12 => __('December')
|
|
],
|
|
'value' => '|arg1:month|',
|
|
'class' => 'multiselect month'
|
|
],
|
|
'day_of_month' => [
|
|
'method' => 'drop_multi',
|
|
'friendly_name' => __('Days of Month'),
|
|
'description' => __('What Day(s) of the Month will this Network Range be discovered.'),
|
|
'array' => [1 => '1', 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 => __('Last')],
|
|
'value' => '|arg1:day_of_month|',
|
|
'class' => 'multiselect days_of_month'
|
|
],
|
|
'monthly_week' => [
|
|
'method' => 'drop_multi',
|
|
'friendly_name' => __('Week(s) of Month'),
|
|
'description' => __('What Week(s) of the Month will this Network Range be discovered.'),
|
|
'array' => [
|
|
1 => __('First'),
|
|
2 => __('Second'),
|
|
3 => __('Third'),
|
|
'32' => __('Last')
|
|
],
|
|
'value' => '|arg1:monthly_week|',
|
|
'class' => 'multiselect monthly_week'
|
|
],
|
|
'monthly_day' => [
|
|
'method' => 'drop_multi',
|
|
'friendly_name' => __('Day(s) of Week'),
|
|
'description' => __('What Day(s) of the week will this Network Range be discovered.'),
|
|
'array' => [
|
|
1 => __('Sunday'),
|
|
2 => __('Monday'),
|
|
3 => __('Tuesday'),
|
|
4 => __('Wednesday'),
|
|
5 => __('Thursday'),
|
|
6 => __('Friday'),
|
|
7 => __('Saturday')
|
|
],
|
|
'value' => '|arg1:monthly_day|',
|
|
'class' => 'multiselect monthly_day'
|
|
],
|
|
'run_limit' => [
|
|
'method' => 'drop_array',
|
|
'friendly_name' => __('Run Limit'),
|
|
'description' => __('Define the maximum allowed runtime of this scheduled task.'),
|
|
'value' => '|arg1:run_limit|',
|
|
'array' => $heartbeats,
|
|
'default' => '20'
|
|
],
|
|
'next_start' => [
|
|
'method' => 'hidden',
|
|
'value' => '|arg1:next_start|'
|
|
],
|
|
'orig_sched_type' => [
|
|
'method' => 'hidden',
|
|
'value' => '|arg1:sched_type|'
|
|
],
|
|
'orig_start_at' => [
|
|
'method' => 'hidden',
|
|
'value' => '|arg1:start_at|'
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Outputs the JavaScript code for the scheduler functionality.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_scheduler_javascript() : void {
|
|
?>
|
|
<script type='text/javascript'>
|
|
$(function() {
|
|
$('#day_of_week').multiselect({
|
|
selectedList: 7,
|
|
noneSelectedText: '<?php print __('Select the days(s) of the week'); ?>',
|
|
header: false,
|
|
height: 54,
|
|
groupColumns: true,
|
|
groupColumnsWidth: 90,
|
|
menuWidth: 385
|
|
});
|
|
|
|
$('#month').multiselect({
|
|
selectedList: 7,
|
|
noneSelectedText: '<?php print __('Select the month(s) of the year'); ?>',
|
|
header: false,
|
|
height: 82,
|
|
groupColumns: true,
|
|
groupColumnsWidth: 90,
|
|
menuWidth: 380
|
|
});
|
|
|
|
$('#day_of_month').multiselect({
|
|
selectedList: 15,
|
|
noneSelectedText: '<?php print __('Select the day(s) of the month'); ?>',
|
|
header: false,
|
|
height: 162,
|
|
groupColumns: true,
|
|
groupColumnsWidth: 50,
|
|
menuWidth: 275
|
|
});
|
|
|
|
$('#monthly_week').multiselect({
|
|
selectedList: 4,
|
|
noneSelectedText: '<?php print __('Select the week(s) of the month'); ?>',
|
|
header: false,
|
|
height: 28,
|
|
groupColumns: true,
|
|
groupColumnsWidth: 70,
|
|
menuWidth: 300
|
|
});
|
|
|
|
$('#monthly_day').multiselect({
|
|
selectedList: 7,
|
|
noneSelectedText: '<?php print __('Select the day(s) of the week'); ?>',
|
|
header: false,
|
|
height: 54,
|
|
groupColumns: true,
|
|
groupColumnsWidth: 90,
|
|
menuWidth: 385
|
|
});
|
|
|
|
$('#start_at').datetimepicker({
|
|
minuteGrid: 10,
|
|
stepMinute: 5,
|
|
timeFormat: 'HH:mm',
|
|
dateFormat: 'yy-mm-dd',
|
|
minDateTime: new Date(<?php print date('Y') . ', ' . (date('m') - 1) . ', ' . date('d, H') . ', ' . date('i', intval(ceil(time() / 300)) * 300) . ', 0, 0'; ?>)
|
|
});
|
|
|
|
$('#sched_type').change(function() {
|
|
setSchedule();
|
|
});
|
|
|
|
setSchedule();
|
|
});
|
|
|
|
function setSchedule() {
|
|
var schedType = $('#sched_type').val();
|
|
|
|
toggleFields({
|
|
start_at: schedType > 1,
|
|
recur_every: (schedType > 1 && schedType < 4) || schedType == 6,
|
|
day_of_week: schedType == 3,
|
|
month: schedType > 3 && schedType != 6,
|
|
day_of_month: schedType == 4,
|
|
monthly_week: schedType == 5,
|
|
monthly_day: schedType == 5,
|
|
});
|
|
|
|
if (schedType == 2) { // Daily
|
|
$('#row_recur_every').find('div:first').each(function() {
|
|
var html = $(this).html();
|
|
|
|
if (html.indexOf('X Weeks') >= 0) {
|
|
html = html.replace('<?php print __('every X Weeks'); ?>', '<?php print __('every X Days'); ?>');
|
|
html = html.replace('<?php print __('Rerun Every X Weeks'); ?>', '<?php print __('Rerun Every X Days'); ?>');
|
|
} else if (html.indexOf('X Hours') >= 0) {
|
|
html = html.replace('<?php print __('Rerun Every X Hours'); ?>', '<?php print __('Rerun Every X Days'); ?>');
|
|
html = html.replace('<?php print __('every X Hours'); ?>', '<?php print __('every X Days'); ?>');
|
|
} else if (html.indexOf('X Days') < 0) {
|
|
html = html.replace('<?php print __('Rerun Every'); ?>', '<?php print __('Rerun Every X Days'); ?>');
|
|
html = html.replace('<?php print __('every X'); ?>', '<?php print __('every X Days'); ?>');
|
|
}
|
|
|
|
$(this).html(html);
|
|
});
|
|
} else if (schedType == 6) { // Hourly
|
|
$('#row_recur_every').find('div:first').each(function() {
|
|
var html = $(this).html();
|
|
|
|
if (html.indexOf('X Weeks') >= 0) {
|
|
html = html.replace('<?php print __('every X Weeks'); ?>', '<?php print __('every X Hours'); ?>');
|
|
html = html.replace('<?php print __('Rerun Every X Weeks'); ?>', '<?php print __('Rerun Every X Hours'); ?>');
|
|
} else if (html.indexOf('X Days') >= 0) {
|
|
html = html.replace('<?php print __('Rerun Every X Days'); ?>', '<?php print __('Rerun Every X Hours'); ?>');
|
|
html = html.replace('<?php print __('every X Days'); ?>', '<?php print __('every X Hours'); ?>');
|
|
} else if (html.indexOf('X Hours') < 0) {
|
|
html = html.replace('<?php print __('Rerun Every'); ?>', '<?php print __('Rerun Every X Hours'); ?>');
|
|
html = html.replace('<?php print __('every X'); ?>', '<?php print __('every X Hours'); ?>');
|
|
}
|
|
|
|
$(this).html(html);
|
|
});
|
|
} else if (schedType == 3) { //Weekly
|
|
$('#row_recur_every').find('div:first').each(function() {
|
|
var html = $(this).html();
|
|
|
|
if (html.indexOf('X Days') >= 0) {
|
|
html = html.replace('<?php print __('every X Days'); ?>', '<?php print __('every X Weeks'); ?>');
|
|
html = html.replace('<?php print __('Rerun Every X Days'); ?>', '<?php print __('Rerun Every X Weeks'); ?>');
|
|
} else if (html.indexOf('X Hours') >= 0) {
|
|
html = html.replace('<?php print __('Rerun Every X Hours'); ?>', '<?php print __('Rerun Every X Weeks'); ?>');
|
|
html = html.replace('<?php print __('every X Hours'); ?>', '<?php print __('every X Weeks'); ?>');
|
|
} else if (html.indexOf('X Weeks') < 0) {
|
|
html = html.replace('<?php print __('Rerun Every'); ?>', '<?php print __('Rerun Every X Weeks'); ?>');
|
|
html = html.replace('<?php print __('every X'); ?>', '<?php print __('every X Weeks'); ?>');
|
|
}
|
|
|
|
$(this).html(html);
|
|
});
|
|
}
|
|
}
|
|
</script>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Augments the save array with scheduler settings and validates the input data.
|
|
*
|
|
* @param array $save The array to be augmented with scheduler settings.
|
|
* @param array $post The array containing the input data to be validated and saved.
|
|
*
|
|
* @return array The augmented save array with validated scheduler settings.
|
|
*/
|
|
function api_scheduler_augment_save(array $save, array $post) : array {
|
|
// scheduler settings
|
|
$save['sched_type'] = form_input_validate($post['sched_type'], 'sched_type', '^[0-9]+$', false, 3);
|
|
$save['start_at'] = form_input_validate($post['start_at'], 'start_at', '', false, 3);
|
|
|
|
// accommodate a schedule start change
|
|
if (isset($post['orig_start_at']) && $post['orig_start_at'] != $post['start_at']) {
|
|
$save['next_start'] = '0000-00-00';
|
|
}
|
|
|
|
if (isset($post['orig_sched_type']) && $post['orig_sched_type'] != $post['sched_type']) {
|
|
$save['next_start'] = '0000-00-00';
|
|
}
|
|
|
|
$save['recur_every'] = form_input_validate($post['recur_every'], 'recur_every', '', true, 3);
|
|
$save['run_limit'] = form_input_validate($post['run_limit'], 'run_limit', '', true, 3);
|
|
|
|
// convert arrays to strings
|
|
$variables = ['day_of_week', 'month', 'day_of_month', 'monthly_week', 'monthly_day'];
|
|
$aposts = [];
|
|
|
|
foreach ($variables as $v) {
|
|
if (isset($post[$v])) {
|
|
$save[$v] = form_input_validate(implode(',', $post[$v]), $v, '', true, 3);
|
|
} else {
|
|
$save[$v] = '';
|
|
}
|
|
}
|
|
|
|
// check for bad rules
|
|
if ($save['sched_type'] == SCHEDULE_WEEKLY) {
|
|
if ($save['day_of_week'] == '') {
|
|
$save['enabled'] = '';
|
|
|
|
raise_message('sched_err', __esc('ERROR: You must specify the day of the week. Disabling Network %s!.', $save['name']), MESSAGE_LEVEL_ERROR);
|
|
}
|
|
} elseif ($save['sched_type'] == SCHEDULE_MONTHLY) {
|
|
if ($save['month'] == '' || $save['day_of_month'] == '') {
|
|
$save['enabled'] = '';
|
|
|
|
raise_message('sched_err', __esc('ERROR: You must specify both the Months and Days of Month. Disabling Network %s!', $save['name']), MESSAGE_LEVEL_ERROR);
|
|
}
|
|
} elseif ($save['sched_type'] == SCHEDULE_MONTHLY_ON_DAY) {
|
|
if ($save['month'] == '' || $save['monthly_day'] == '' || $save['monthly_week'] == '') {
|
|
$save['enabled'] = '';
|
|
|
|
raise_message('sched_err', __esc('ERROR: You must specify the Months, Weeks of Months, and Days of Week. Disabling Network %s!', $save['name']), MESSAGE_LEVEL_ERROR);
|
|
}
|
|
}
|
|
|
|
$now_time = time();
|
|
|
|
if (isset($save['next_start'])) {
|
|
$next_start = strtotime($save['next_start']);
|
|
} else {
|
|
$next_start = 0;
|
|
}
|
|
|
|
$start_at = strtotime($save['start_at']);
|
|
$poller_int = read_config_option('poller_interval');
|
|
|
|
/**
|
|
* The next_start is really when the report will be checked if it's time to
|
|
* start not the time the report will actually be run. So, the numbers can
|
|
* be a little loose up front.
|
|
*
|
|
* The schedulers check will actually adjust the actual next start
|
|
* when it performs the first check.
|
|
*/
|
|
|
|
if ($save['sched_type'] != 1) {
|
|
if ($next_start === '0000-00-00 00:00:00') {
|
|
$save['next_start'] = date('Y-m-d H:i:s', $start_at);
|
|
}
|
|
|
|
if ($start_at + $poller_int < $now_time) {
|
|
// adjust to todays date and check if it's in the past
|
|
$timestamp = strtotime('12:00am') + date('H', $start_at) * 3600 + date('i', $start_at) * 60 + date('s', $start_at);
|
|
|
|
if ($timestamp < $now_time + $poller_int) {
|
|
// if the time is in the past, adjust forward by one day
|
|
$timestamp += 86400;
|
|
}
|
|
|
|
$save['next_start'] = date('Y-m-d H:i:s', (int) $timestamp);
|
|
} else {
|
|
// the time is in the future, we are safe to store it
|
|
$save['next_start'] = date('Y-m-d H:i:s', $start_at);
|
|
}
|
|
}
|
|
|
|
return $save;
|
|
}
|
|
|
|
/**
|
|
* Determines if it is time to start a scheduled task based on the provided schedule.
|
|
*
|
|
* @param array $schedule The schedule details
|
|
* @param string $table The name of the table to update the next start time in. Default is 'automation_networks'.
|
|
*
|
|
* @return bool Returns true if it is time to start the scheduled task, false otherwise.
|
|
*/
|
|
function api_scheduler_is_time_to_start(array $schedule, string $table = 'automation_networks') : bool {
|
|
$now = time();
|
|
|
|
if (empty($schedule['next_start'])) {
|
|
$schedule['next_start'] = date('Y-m-d H:i:s', strtotime($schedule['start_at']) + 86400);
|
|
}
|
|
|
|
if (is_null($schedule['start_at'])) {
|
|
$schedule['start_at'] = date('Y-m-d H:i:s');
|
|
}
|
|
|
|
switch($schedule['sched_type']) {
|
|
case SCHEDULE_MANUAL:
|
|
return false;
|
|
case SCHEDULE_HOURLY:
|
|
case SCHEDULE_DAILY:
|
|
if ($schedule['sched_type'] == SCHEDULE_HOURLY) {
|
|
$recur = $schedule['recur_every'] * 3600; // days
|
|
} else {
|
|
$recur = $schedule['recur_every'] * 86400; // days
|
|
}
|
|
|
|
$start = strtotime($schedule['start_at']);
|
|
$next = strtotime($schedule['next_start']);
|
|
|
|
if ($schedule['next_start'] == '0000-00-00 00:00:00') {
|
|
$target = $start;
|
|
} else {
|
|
$target = $next;
|
|
}
|
|
|
|
if ($now > $target) {
|
|
while ($now > $target) {
|
|
$target += $recur;
|
|
}
|
|
|
|
db_execute_prepared("UPDATE $table
|
|
SET next_start = ?
|
|
WHERE id = ?",
|
|
[date('Y-m-d H:i', $target), $schedule['id']]);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
case SCHEDULE_WEEKLY:
|
|
$recur = $schedule['recur_every'] * 86400 * 7; // weeks
|
|
$start = strtotime($schedule['start_at']);
|
|
$next = strtotime($schedule['next_start']);
|
|
$days = explode(',', $schedule['day_of_week']);
|
|
$day = 86400;
|
|
$week = 86400 * 7;
|
|
|
|
if ($schedule['next_start'] == '0000-00-00 00:00:00') {
|
|
$target = $start;
|
|
} else {
|
|
$target = $next;
|
|
}
|
|
|
|
if ($now > $target) {
|
|
while (true) {
|
|
$target += $day;
|
|
$cur_day = date('w', $target) + 1;
|
|
|
|
$key = array_search($cur_day, $days, false);
|
|
|
|
if ($key !== false) {
|
|
if ($key == 0) {
|
|
$target += $recur - $week;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
db_execute_prepared("UPDATE $table
|
|
SET next_start = ?
|
|
WHERE id = ?",
|
|
[date('Y-m-d H:i', $target), $schedule['id']]);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
case SCHEDULE_MONTHLY:
|
|
case SCHEDULE_MONTHLY_ON_DAY:
|
|
$next = api_scheduler_calculate_next_start($schedule);
|
|
|
|
/* An unschedulable configuration returns false, and date() would
|
|
turn that into 1970, which the run check below then reads as
|
|
overdue on every poller cycle. Leave next_start alone. The test
|
|
is strict: a calculated timestamp of 0 is a different thing from
|
|
"nothing to schedule", and should not be silently equated. */
|
|
if ($next === false) {
|
|
return false;
|
|
}
|
|
|
|
db_execute_prepared("UPDATE $table
|
|
SET next_start = ?
|
|
WHERE id = ?",
|
|
[date('Y-m-d H:i', $next), $schedule['id']]);
|
|
|
|
if ($schedule['next_start'] == '0000-00-00 00:00:00') {
|
|
if ($now > strtotime($schedule['start_at'])) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} elseif ($now > strtotime($schedule['next_start'])) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Calculate the next start time for a given schedule.
|
|
*
|
|
* @param array $schedule The schedule configuration array.
|
|
*
|
|
* @return mixed - The timestamp of the next start time, or false if no valid next start time is found.
|
|
*/
|
|
function api_scheduler_calculate_next_start(array $schedule) : mixed {
|
|
$now = time();
|
|
|
|
/* Try this year, then next. Without the rollover every selected date is in
|
|
the past by December and the schedule resolves to false for the rest of
|
|
the year. */
|
|
$next = api_scheduler_calculate_next_start_for_year($schedule, (int) date('Y', $now), $now);
|
|
|
|
if ($next === false) {
|
|
$next = api_scheduler_calculate_next_start_for_year($schedule, (int) date('Y', $now) + 1, $now);
|
|
}
|
|
|
|
return $next;
|
|
}
|
|
|
|
/**
|
|
* Calculate the next start time for a given schedule within one calendar year.
|
|
*
|
|
* @param array $schedule The schedule configuration array.
|
|
* @param int $year The calendar year to resolve the schedule against.
|
|
* @param int $now The timestamp a candidate date has to be later than.
|
|
*
|
|
* @return mixed - The timestamp of the next start time, or false if no valid next start time is found.
|
|
*/
|
|
function api_scheduler_calculate_next_start_for_year(array $schedule, int $year, int $now) : mixed {
|
|
$dates = [];
|
|
|
|
// Some defaults
|
|
$smonth = 'January';
|
|
$sweek = 'first';
|
|
$sday = 'Sunday';
|
|
|
|
switch($schedule['sched_type']) {
|
|
case SCHEDULE_MANUAL:
|
|
break;
|
|
case SCHEDULE_MONTHLY:
|
|
$months = explode(',', $schedule['month']);
|
|
$days = explode(',', $schedule['day_of_month']);
|
|
|
|
foreach ($months as $month) {
|
|
foreach ($days as $day) {
|
|
switch($month) {
|
|
case '1':
|
|
$smonth = 'January';
|
|
|
|
break;
|
|
case '2':
|
|
$smonth = 'February';
|
|
|
|
break;
|
|
case '3':
|
|
$smonth = 'March';
|
|
|
|
break;
|
|
case '4':
|
|
$smonth = 'April';
|
|
|
|
break;
|
|
case '5':
|
|
$smonth = 'May';
|
|
|
|
break;
|
|
case '6':
|
|
$smonth = 'June';
|
|
|
|
break;
|
|
case '7':
|
|
$smonth = 'July';
|
|
|
|
break;
|
|
case '8':
|
|
$smonth = 'August';
|
|
|
|
break;
|
|
case '9':
|
|
$smonth = 'September';
|
|
|
|
break;
|
|
case '10':
|
|
$smonth = 'October';
|
|
|
|
break;
|
|
case '11':
|
|
$smonth = 'November';
|
|
|
|
break;
|
|
case '12':
|
|
$smonth = 'December';
|
|
|
|
break;
|
|
}
|
|
|
|
if ($day == '32') {
|
|
$dates[] = strtotime("last day of $smonth $year");
|
|
} else {
|
|
$dates[] = strtotime("$smonth $day $year");
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
case SCHEDULE_MONTHLY_ON_DAY:
|
|
$months = explode(',', $schedule['month']);
|
|
$weeks = explode(',', $schedule['monthly_week']);
|
|
$days = explode(',', $schedule['monthly_day']);
|
|
|
|
foreach ($months as $month) {
|
|
foreach ($weeks as $week) {
|
|
foreach ($days as $day) {
|
|
switch($month) {
|
|
case '1':
|
|
$smonth = 'January';
|
|
|
|
break;
|
|
case '2':
|
|
$smonth = 'February';
|
|
|
|
break;
|
|
case '3':
|
|
$smonth = 'March';
|
|
|
|
break;
|
|
case '4':
|
|
$smonth = 'April';
|
|
|
|
break;
|
|
case '5':
|
|
$smonth = 'May';
|
|
|
|
break;
|
|
case '6':
|
|
$smonth = 'June';
|
|
|
|
break;
|
|
case '7':
|
|
$smonth = 'July';
|
|
|
|
break;
|
|
case '8':
|
|
$smonth = 'August';
|
|
|
|
break;
|
|
case '9':
|
|
$smonth = 'September';
|
|
|
|
break;
|
|
case '10':
|
|
$smonth = 'October';
|
|
|
|
break;
|
|
case '11':
|
|
$smonth = 'November';
|
|
|
|
break;
|
|
case '12':
|
|
$smonth = 'December';
|
|
|
|
break;
|
|
default:
|
|
$smonth = 'January';
|
|
|
|
break;
|
|
}
|
|
|
|
switch($week) {
|
|
case '1':
|
|
$sweek = 'first';
|
|
|
|
break;
|
|
case '2':
|
|
$sweek = 'second';
|
|
|
|
break;
|
|
case '3':
|
|
$sweek = 'third';
|
|
|
|
break;
|
|
case '4':
|
|
$sweek = 'fourth';
|
|
|
|
break;
|
|
case '32':
|
|
$sweek = 'last';
|
|
|
|
break;
|
|
default:
|
|
$sweek = 'first';
|
|
|
|
break;
|
|
}
|
|
|
|
switch($day) {
|
|
case '1':
|
|
$sday = 'Sunday';
|
|
|
|
break;
|
|
case '2':
|
|
$sday = 'Monday';
|
|
|
|
break;
|
|
case '3':
|
|
$sday = 'Tuesday';
|
|
|
|
break;
|
|
case '4':
|
|
$sday = 'Wednesday';
|
|
|
|
break;
|
|
case '5':
|
|
$sday = 'Thursday';
|
|
|
|
break;
|
|
case '6':
|
|
$sday = 'Friday';
|
|
|
|
break;
|
|
case '7':
|
|
$sday = 'Saturday';
|
|
|
|
break;
|
|
default:
|
|
$sday = 'Sunday';
|
|
|
|
break;
|
|
}
|
|
|
|
$dates[] = strtotime("$sweek $sday of $smonth $year");
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if ($schedule['sched_type'] !== SCHEDULE_MANUAL) {
|
|
asort($dates);
|
|
|
|
$newdates = [];
|
|
|
|
foreach ($dates as $date) {
|
|
// an unparsable month/week/day combination yields false, not a date
|
|
if ($date === false) {
|
|
continue;
|
|
}
|
|
|
|
$ndate = date('Y-m-d', $date) . ' ' . date('H:i:s', strtotime($schedule['start_at']));
|
|
$ntime = strtotime($ndate);
|
|
|
|
cacti_log('Start At: ' . $schedule['start_at'] . ', Possible Next Start: ' . $ndate . ' with Timestamp: ' . $ntime, false, 'SCHEDULER', POLLER_VERBOSITY_DEBUG);
|
|
|
|
if ($ntime > $now) {
|
|
return $ntime;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|