[ '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 { ?> $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; }