1<?php
2
3
4function scheduler_tasks(): array
5{
6 return array(
7 'backups' => array(
8 'label' => 'Automatic backups',
9 'default_interval_hours' => 24,
10 'run' => 'scheduler_run_backups',
11 ),
12 'purge_admin_log' => array(
13 'label' => 'Purge old admin log entries',
14 'default_interval_hours' => 24,
15 'run' => 'scheduler_run_purge_admin_log',
16 ),
17 'health_check' => array(
18 'label' => 'Health check',
19 'default_interval_hours' => 6,
20 'run' => 'scheduler_run_health_check',
21 ),
22 );
23}
24
25function scheduler_enabled(string $taskId): bool
26{
27 return setting('scheduler:' . $taskId . ':enabled', '0') === '1';
28}
29
30function scheduler_interval_hours(string $taskId, int $default): int
31{
32 return max(1, (int) setting('scheduler:' . $taskId . ':interval_hours', (string) $default));
33}
34
35function scheduler_last_run(string $taskId): int
36{
37 return (int) setting('scheduler:' . $taskId . ':last_run', '0');
38}
39
40function scheduler_last_result(string $taskId): string
41{
42 return (string) setting('scheduler:' . $taskId . ':last_result', '');
43}
44
45function scheduler_mark_run(string $taskId, string $resultSummary): void
46{
47 setting_set('scheduler:' . $taskId . ':last_run', (string) time());
48 setting_set('scheduler:' . $taskId . ':last_result', substr($resultSummary, 0, 255));
49}
50
51function scheduler_due(string $taskId, int $defaultIntervalHours): bool
52{
53 if (!scheduler_enabled($taskId)) {
54 return false;
55 }
56
57 $interval = scheduler_interval_hours($taskId, $defaultIntervalHours);
58
59 return (scheduler_last_run($taskId) + ($interval * 3600)) <= time();
60}
61
62function scheduler_tick(): void
63{
64 static $ran = false;
65 if ($ran || !function_exists('setting') || !function_exists('db')) {
66 return;
67 }
68 $ran = true;
69
70 foreach (scheduler_tasks() as $id => $task) {
71 if (!scheduler_due($id, (int) $task['default_interval_hours'])) {
72 continue;
73 }
74
75 scheduler_mark_run($id, 'running');
76
77 try {
78 $summary = (string) call_user_func($task['run']);
79 } catch (Throwable $e) {
80 $summary = 'error: ' . $e->getMessage();
81 error_log('[scheduler] task ' . $id . ' failed: ' . $e->getMessage());
82 }
83
84 scheduler_mark_run($id, $summary);
85
86 if (function_exists('acp_log')) {
87 acp_log('scheduler.' . $id, '', array('summary' => $summary));
88 }
89 }
90}
91
92function scheduler_run_backups(): string
93{
94 if (!function_exists('znote_backups_create')) {
95 return 'backups module unavailable';
96 }
97
98 list($ok, $result) = znote_backups_create();
99 if (!$ok) {
100 return 'backup failed: ' . $result;
101 }
102
103 $retention = max(1, (int) setting('config:backups.retention', '10'));
104 $removed = function_exists('znote_backups_prune') ? znote_backups_prune($retention) : 0;
105
106 return 'created ' . $result . ($removed > 0 ? ', pruned ' . $removed : '');
107}
108
109function scheduler_run_purge_admin_log(): string
110{
111 if (!function_exists('znote_table_exists') || !znote_table_exists('znote_admin_log')) {
112 return 'no admin log table';
113 }
114
115 $days = max(1, (int) setting('scheduler:purge_admin_log:keep_days', '90'));
116 $cutoff = time() - ($days * 86400);
117
118 db()->execute("DELETE FROM `znote_admin_log` WHERE `created` < ?;", array($cutoff));
119
120 return 'purged rows older than ' . $days . 'd';
121}
122
123function scheduler_run_health_check(): string
124{
125 if (!function_exists('znote_health_issues')) {
126 return 'health module unavailable';
127 }
128
129 $issues = znote_health_issues();
130 if ($issues) {
131 $details = array();
132 foreach ($issues as $issue) {
133 $details[] = (string) ($issue['label'] ?? '') . ': ' . (string) ($issue['detail'] ?? '');
134 }
135 error_log('[scheduler] health check found ' . count($issues) . ' issue(s): ' . implode('; ', $details));
136
137 return count($issues) . ' issue(s) found';
138 }
139
140 return 'ok';
141}
142
143if (function_exists('znote_hook_register')) {
144 znote_hook_register('page.footer', 'scheduler_tick');
145}
146