Skip to content

Scheduled Tasks

Plugins koennen wiederkehrende Aufgaben registrieren, die der TaskRunner ueber die CLI abarbeitet — Sitemap-Generierung, Session-Cleanup, Expiry-Checks, Re-Indexing etc. Diese Seite zeigt die Registrierung in bootstrap.php und das Script-Pattern.

Registrierung via $this->scheduledTasks

Im install() des Plugins:

php
public function install()
{
    // ...
    $this->scheduledTasks = [
        '{
            "name":        "Session Cleanup",
            "script":      "session_cleanup.php",
            "interval":    300,
            "description": "Removes inactive sessions older than 2 hours"
        }',
        '{
            "name":        "Generate Sitemap",
            "script":      "generate_sitemap.php",
            "interval":    86400,
            "description": "Generates XML sitemap with hreflang from database"
        }'
    ];
}

Jeder Eintrag ist ein JSON-String mit vier Feldern:

FeldPflichtZweck
namejaUI-Label im Task Manager
scriptjaDateiname relativ zu {plugin}/scheduledTasks/
intervaljaIntervall in Sekunden (300 = 5 Min, 3600 = 1 h, 86400 = 24 h). Alias interval_seconds wird auch akzeptiert
descriptionneinText im Task Manager (Tooltip/Info)

Echtes Beispiel aus scheduledTasks-Plugin (_public/extensions/core/backend/scheduledTasks/bootstrap.php):

php
$this->scheduledTasks = [
    '{"name": "Session Cleanup",          "script": "session_cleanup.php",         "interval": 300,   "description": "Removes inactive sessions older than 2 hours"}',
    '{"name": "Generate Sitemap",         "script": "generate_sitemap.php",        "interval": 86400, "description": "Generates XML sitemap with hreflang from database"}',
    '{"name": "Publish Scheduled Pages",  "script": "publish_scheduled_pages.php", "interval": 300,   "description": "Publishes pages with scheduled publish times that have passed"}'
];

Beim installPlugin() ruft der Core installScheduledTasks($pluginID) auf, was pro Eintrag eine Zeile in scheduled_tasks anlegt — next_run wird initial auf NOW() gesetzt, der Task laeuft also beim naechsten Runner-Durchlauf sofort.

Script-Ordner

Die registrierten Scripts liegen im Plugin-Unterordner scheduledTasks/:

_public/extensions/core/backend/{plugin}/
├── bootstrap.php
└── scheduledTasks/
    ├── session_cleanup.php
    ├── generate_sitemap.php
    └── publish_scheduled_pages.php

Script-Pattern

Ein Scheduled-Task-Script ist eine normale PHP-Datei. Der TaskRunner inkludiert sie via require — das heisst: das Script laeuft im Kontext einer lebendigen DB-Connection, query(), fetch_assoc() etc. stehen bereit:

php
<?php
// _public/extensions/core/backend/{plugin}/scheduledTasks/cleanup_old_logs.php

// TaskRunner hat DB-Connection schon aufgebaut — keine zusaetzliche Init noetig

$cutoff = date('Y-m-d H:i:s', strtotime('-30 days'));
$cutoffEsc = real_escape_string($cutoff);

$q = query("DELETE FROM my_plugin_logs WHERE created_at < '$cutoffEsc'");

// Fuers Log in der task-Manager-UI sichtbar machen:
echo "Deleted " . mysqli_affected_rows($GLOBALS['connection']) . " old log entries\n";

Keine Ausgabe ans Web — das Script laeuft im CLI-Modus. Echo-Output landet im CLI-Log (bzw. im Task Manager, wenn der Runner die Ausgabe cached).

Tasks kurz halten

Der Cron-Aufruf scheduled-tasks --time-limit=540 hat ein Zeitlimit von 9 Minuten (Default des Cronjobs alle 10 Min). Laufen mehrere Tasks gleichzeitig, teilen sie sich das Budget. Fuer lange Arbeiten (Sitemap fuer 10k-Seiten, Bulk-Reindexing): in mehrere kurze Tasks aufteilen oder auf die Job-Queue (QueueWorker) auslagern.

CLI-Runner

Der TaskRunner wird ueber console/bin aufgerufen:

bash
# Alle faelligen Tasks ausfuehren, max. 540s Zeitlimit
php console/bin scheduled-tasks --time-limit=540

# Einzelnen Task sofort starten (unabhaengig von next_run)
php console/bin scheduled-tasks --run=42

# Alle Tasks auflisten
php console/bin list-tasks

Empfohlener Cron-Setup (crontab -e):

cron
# Alle 10 Minuten: scheduled tasks
*/10 * * * *  php /pfad/console/bin scheduled-tasks --time-limit=540

# Jede Minute: Job-Queue
* * * * *     php /pfad/console/bin process-queue --time-limit=55

# Alle 30 Sekunden: Webhooks
* * * * *     php /pfad/console/bin process-webhooks --time-limit=25
* * * * *     sleep 30 && php /pfad/console/bin process-webhooks --time-limit=25

Wie der TaskRunner Tasks ausfuehrt

Der TaskRunner::runDue()-Flow:

1. SELECT alle Tasks WHERE next_run <= NOW() AND active = 1 AND is_running = 0
2. Fuer jeden Task:
   - SET is_running = 1 (Stale-Check + Parallel-Schutz)
   - require script
   - SET last_run = NOW(), next_run = NOW() + interval_seconds, is_running = 0
3. Wenn Zeit vor Ende der Queue abgelaufen: Rest beim naechsten Run

is_running-Flag: Verhindert parallele Ausfuehrung desselben Tasks. Bei Crash bleibt das Flag haengen — dann im Task Manager manuell zuruecksetzen oder UPDATE scheduled_tasks SET is_running = 0 WHERE id = X.

Task Manager UI

Unter /admin/task-manager gibt es drei Tabs:

  • Status — APCu-Info, Last-Run pro Task, naechster Run-Zeitpunkt
  • Queue — Job-Queue (nicht Scheduled, sondern API-Call-Queue via QueueWorker)
  • Scheduled Tasks — Liste aller Tasks mit "Jetzt ausfuehren"-Button und Toggle fuer active

Die Sidebar zeigt eine Status-Lampe — gruen: Runner laeuft regelmaessig, rot: letzter Run liegt laenger zurueck als erwartet.

Job-Queue vs. Scheduled-Task

Fuer einmalige Jobs (nicht wiederkehrend) gibt es zusaetzlich die Job-Queue:

php
require_once $_SERVER['DOCUMENT_ROOT'] . '/_core/system/cron/QueueWorker.php';

QueueWorker::createJob('api/pages', 'GET', ['view' => 'test'], 10, 3);
// Endpoint, HTTP-Methode, Params, Priority (hoeher = wichtiger), maxAttempts

Der QueueWorker (via process-queue CLI) macht einen internen cURL-Call gegen die eigene Domain und speichert Response/Status in task_queue. Nuetzlich fuer Fire-and-Forget-Aufgaben aus API-Endpoints, die nicht blockieren sollen.

Details in CLAUDE.md-Sektion "Cron/Task-System" und im Skill php-backend-check.

Datenbank-Tabelle

sql
CREATE TABLE IF NOT EXISTS scheduled_tasks (
  id               INT AUTO_INCREMENT PRIMARY KEY,
  plugin_id        INT NOT NULL DEFAULT 0,           -- FK → plugins.id
  name             VARCHAR(255) NOT NULL,
  script           VARCHAR(255) NOT NULL,             -- relativ zu plugin/scheduledTasks/
  interval_seconds INT NOT NULL DEFAULT 3600,
  description      TEXT DEFAULT NULL,
  last_run         DATETIME DEFAULT NULL,
  next_run         DATETIME DEFAULT NULL,
  is_running       TINYINT(1) NOT NULL DEFAULT 0,
  active           TINYINT(1) NOT NULL DEFAULT 1,
  language_short   VARCHAR(5) NOT NULL DEFAULT '',    -- derzeit ungenutzt, bleibt leer
  base_id          INT NOT NULL DEFAULT 0,            -- derzeit ungenutzt, bleibt 0
  created_at       DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  KEY plugin_id (plugin_id),
  KEY active_next_run (active, next_run)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Schema-Migration: _migrations/core/005_create_task_system.sql. language_short und base_id existieren fuer konsistente Core-Tabellen-Konvention, werden vom TaskRunner aber nicht ausgewertet — Scheduled Tasks sind systemweit, nicht sprach-spezifisch.

Haeufige Fehler

Script nicht unter scheduledTasks/ abgelegt

Der TaskRunner sucht unter _public/extensions/core/backend/{plugin}/scheduledTasks/{script}. Liegt die Datei im falschen Ordner (z. B. script/ oder Root des Plugins), wirft der Runner einen File not found-Fehler und markiert den Task als failed.

interval vergessen → Default 3600s

Fehlt das interval-Feld im JSON, nimmt der Installer 3600 (1 Stunde) als Default. Das ist oft nicht was gewollt ist. Immer explizit setzen.

JSON-Syntax-Fehler → Task wird uebersprungen

installScheduledTasks() nutzt json_decode(); bei null-Return (Syntax-Fehler) wird der Eintrag stumm uebersprungen — keine Fehlermeldung, kein Log. Vor dem Commit das JSON mit einem Linter pruefen oder temporaer im Plugin error_log(print_r($task, true)) nach dem json_decode einbauen.

is_running bleibt nach Crash bei 1

Bei PHP-Fatal-Error oder Timeout wird is_running = 0 nicht zurueckgesetzt. Der Task wird beim naechsten Runner-Lauf als "bereits laufend" erkannt und uebersprungen. Manueller Fix: UPDATE scheduled_tasks SET is_running = 0 WHERE id = X.

update() aktualisiert Scheduled Tasks nicht

Aenderungen an $this->scheduledTasks in einer spaeteren Plugin-Version greifen nur bei frischer Installation. Fuer bestehende Installationen: Migration schreiben, die den scheduled_tasks-Eintrag updatet oder neu einfuegt.

Siehe auch

  • Plugin-Anatomie$this->scheduledTasks-Property, scheduledTasks/-Ordner
  • Migrations_migrations/core/005_create_task_system.sql
  • Webhook-Eventsprocess-webhooks-Worker mit demselben CLI-Dispatcher
  • Task Manager UI: /admin/task-manager