Projekt BISO 3 - Handbuch
[inhalt]

Configuration

Scheduler (Cron)

BISO uses crunz as the central task scheduler. Instead of setting up a separate cronjob for each recurring task, only a single cronjob is required. The scheduler independently checks which tasks are due and executes them.

The task files are located in the webroot/backend/scheduler/tasks/ directory. Individual tasks are conditionally activated depending on configuration (Config) or parameters (Admin > Parameters).

Cronjob Setup

On the customer system, a single crontab entry must be set up that runs every minute:

* * * * *   www-data   /usr/bin/php /var/www/biso/webroot/biso-cli scheduler run

Note: The path /var/www/biso/webroot/ must be adjusted to the respective installation.

The scheduler delegates to crunz, which reads the individual task files and only executes the tasks due at the respective time. Overlapping executions are prevented by preventOverlapping().

CLI Commands

Command Description
php biso-cli scheduler list Show all registered tasks with schedule
php biso-cli scheduler run Execute due tasks
php biso-cli scheduler run --force Execute all tasks immediately (independent of schedule)
php biso-cli scheduler run --task=N Execute a specific task by number

Create New Task

To add a new scheduler task, create a new PHP file in the directory webroot/backend/scheduler/tasks/. The filename must end with Tasks.php (e.g. MyNewTasks.php).

Basic structure of a task file:

<?php

use Crunz\Schedule;

require_once __DIR__ . '/../../../components/composer/kadenpartner/gaia/bootstrap.php';

$schedule = new Schedule();

// Conditional activation (optional):
if (Param::get('mein_feature_aktiv', false)) {
    $schedule->run(PHP_BINARY . ' ' . Config::get('rootdir') . '/biso-cli mein-command')
        ->daily()->at('08:00')
        ->description('Beschreibung des Tasks')
        ->preventOverlapping()
        ->appendOutputTo(Config::get('tmpdir') . '/mein-task.log');
}

return $schedule;

Scheduling Method Examples

Method Description
->everyMinute() Every minute
->hourly() Hourly
->hourlyAt('15') Hourly at minute 15
->daily() Daily at midnight
->daily()->at('08:00') Daily at 08:00
->weekly() Weekly
->weeklyOn(1, '13:30') Weekly on Monday at 13:30 (0=Sunday)
->monthly() Monthly
->cron('30 8 * * Mon,Fri') Arbitrary cron expression

Best Practices

Logging

Each task writes its output to its own log file in the data/tmp/ directory:

Mail (SMTP / Email Sending)

BISO sends all outgoing email via SMTP. Sending is centralized in MailManager (and its PHPMailer subclass BisoMailer), which is the single place where SMTP configuration takes effect. If smtp_server is left empty, BISO does not send mail (all callers return true without opening a connection).

The following configuration values are set in config.php.

SMTP Transport and Default Sender

The five smtp_* keys configure the SMTP connection and the default sender. The values are applied once when the PHPMailer instance is created.

// config.php

// SMTP server (hostname or IP). Empty = no mail sending.
Config::set('smtp_server', 'mail.example.com');

// SMTP authentication. Empty = open SMTP without login.
Config::set('smtp_user', '');
Config::set('smtp_pass', '');

// Additional PHPMailer options. Any PHPMailer property can be set here
// (commonly used: Port, SMTPAuth, SMTPSecure, SMTPOptions, SMTPDebug, ...).
Config::set('smtp_options', array(
    'Port' => 25,
    'SMTPAuth' => false,
    'SMTPSecure' => false,
    'SMTPOptions' => array(
        'ssl' => array(
            'verify_peer' => false,
            'verify_peer_name' => false,
            'allow_self_signed' => true,
        ),
    ),
));

// Default system sender address (From: for internally generated mails
// such as appointment invitations, cronjob notifications, CMBB-TG
// case-completion mails).
Config::set('smtp_sender', 'admin@biso.ch');
Key Required Meaning
smtp_server yes (to send mail) SMTP server (hostname or IP). Empty disables mail sending entirely.
smtp_user if authenticated SMTP username. Empty for open SMTP.
smtp_pass if authenticated SMTP password.
smtp_options no Array of PHPMailer properties (e.g. Port, SMTPAuth, SMTPSecure, SMTPOptions). Each key is assigned to the mailer instance.
smtp_sender recommended Default sender for mails triggered internally by the system (cronjobs, appointment invitations, canton delegates).

SMTP Envelope Override

Some authenticated SMTP servers reject mails whose SMTP envelope sender (MAIL FROM / PHPMailer property Sender) differs from the SMTP-authenticated user's mailbox. By default, BISO uses the actual user's email address as the envelope for every mail. The three keys below let you replace that envelope (and optionally the visible From: header) with a fixed system-owned address.

The override is applied centrally in BisoMailer::send() and therefore affects every mail call (the regular path through MailManager::sendEmail() and direct callers like the VCalendar entities).

// config.php

// Replace the SMTP envelope (MAIL FROM / Sender). null/empty = no override
// (BISO uses the original per-user envelope).
Config::set('smtp_envelope_sender', 'noreply@example.com');

// Also replace the visible "From:" header with the envelope address.
// The display name (FromName) of the original sender is preserved (the
// recipient sees e.g. "John Doe <noreply@example.com>").
// false = "From:" stays the original per-user address.
Config::set('smtp_force_envelope_sender_as_from', true);

// Add the original "From:" address as "Reply-To:" so replies still reach
// the original sender. Only effective when "From:" was actually overridden.
// false = no Reply-To is added (replies go to the envelope address).
Config::set('smtp_use_from_as_replyto', true);
Key Default Effect
smtp_envelope_sender null Fixed envelope address. null disables all three overrides.
smtp_force_envelope_sender_as_from false If true, also replace the visible From: header.
smtp_use_from_as_replyto false If true and From: was actually overridden, add the original From: address as Reply-To:.

Behaviour matrix:

smtp_envelope_sender smtp_force_envelope_sender_as_from smtp_use_from_as_replyto Result
null any any No override. From: and envelope = original sender.
set false false Envelope = fixed, From: = original sender.
set false true Envelope = fixed, From: = original sender (Reply-To redundant).
set true false Envelope and From: = fixed, display name preserved.
set true true and original ≠ envelope Envelope and From: = fixed, original From: as Reply-To:.
set true true and original = envelope Envelope and From: = fixed, no self-Reply-To (deduplication).

Logging

Mail sending writes to its own logger mailer (see webroot/config.orig.php). For targeted error diagnosis, configure a dedicated log file:

// config.php:
Config::set('gaia.logging', [
    'mailer' => [
        'type' => 'file',                          // 'file' | 'console' | 'none'
        'level' => 'DEBUG',                        // 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR'
        'console' => 'output',
        'logfile' => Config::get('tmpdir') . '/biso-mailer.log',
    ],
]);

See Also

SMS Configuration

BISO supports multiple SMS providers. The provider is selected via the sms_driver configuration option (see below). By default the historical mail-to-SMS gateway dispatch is active (sms_driver = 'email'), so existing installations continue to work without changes.

Note for existing installations: The new provider architecture is backward-compatible. If sms_driver is not set explicitly, the behaviour is identical to the previous setup (mail gateway via sms_gateway_mail_domain / sms_gateway_mail_sender). Switch to the Swisscom REST API, the ASP SMS REST API, or the dummy driver by setting the corresponding configuration values.

Supported SMS APIs

Driver Transport Configuration required Use case
email (default) SMTP mail to <mobile>@<sms_gateway_mail_domain> sms_gateway_mail_domain, optionally sms_gateway_mail_sender Existing federal SMS gateway solutions (e.g. smsc.admin.ch), customers with a mail-to-SMS bridge.
swisscom HTTPS POST to Swisscom Web-to-SMS REST API sms_swisscom_api_key (mandatory), optional sms_swisscom_api_url, sms_swisscom_sender, sms_swisscom_max_msg_parts, sms_swisscom_validity_minutes Cantons with a Swisscom contract requiring a pure REST integration.
aspsms HTTPS POST to ASP SMS JSON API (/SendSimpleTextSMS) sms_aspsms_userkey (mandatory), sms_aspsms_password (mandatory), optional sms_aspsms_api_url, sms_aspsms_sender Customers with an ASP SMS contract (aspsms.ch) using the ASP JSON endpoint.
dummy No dispatch, log entry only Test and DEV environments.

Driver Selection

In config.php:

Config::set('sms_driver', 'email');   // default, backward-compatible
// or
Config::set('sms_driver', 'swisscom');
// or
Config::set('sms_driver', 'aspsms');
// or
Config::set('sms_driver', 'dummy');   // for tests / staging

Configuration: Email SMTP gateway (driver: email)

Config::set('sms_gateway_mail_domain', 'smsc.admin.ch');
Config::set('sms_gateway_mail_sender', 'info@kunde.ch');
Config::set('sms_ausloeser_username', 'admin');
Config::set('sms_versand_iso_encoding', true);   // encode body to ISO-8859-1

Notes:

Configuration: Swisscom REST API (driver: swisscom)

Implementation per the Swisscom "Web to SMS" administrator manual (September 2025). SMS dispatch is performed via HTTPS POST to the Swisscom Web-to-SMS REST endpoint. Authentication uses an API key in the HTTP header HTTP_X_APIKEY.

The API key is required (mandatory). All other values have sensible defaults per the Swisscom specification.

Config::set('sms_driver', 'swisscom');

// Mandatory: API key from the SMMS portal
Config::set('sms_swisscom_api_key', '<api-key-from-smms-portal>');

// Optional with default values:
Config::set('sms_swisscom_api_url', 'https://web2sms.swisscom.com/v8/api/rest/sms');  // default
Config::set('sms_swisscom_sender', '<originator: 1-16 numeric or 1-11 alphanumeric>');
Config::set('sms_swisscom_max_msg_parts', 5);          // 1-9, default 5
Config::set('sms_swisscom_validity_minutes', 720);     // 1-10080, default 720 (12h)
Configuration value Type Default Meaning
sms_swisscom_api_key string Mandatory. API key from the SMMS portal. Sent in the HTTP header HTTP_X_APIKEY.
sms_swisscom_api_url string https://web2sms.swisscom.com/v8/api/rest/sms REST endpoint. Usually no need to change.
sms_swisscom_sender string Originator per the Swisscom contract. Sent in the JSON field smsorig.
sms_swisscom_max_msg_parts int 5 Maximum number of SMS splits (1-9). Text exceeding this is truncated.
sms_swisscom_validity_minutes int 720 SMS lifetime in the SMSC in minutes (1-10080). After expiry a non-delivery report is generated.

Fields submitted

For each SMS, BISO sends the following JSON body to Swisscom:

{
  "recipient": "+41790001122",
  "msg": "<SMS body, UTF-8, max. 1377 characters>",
  "smsorig": "<from sms_swisscom_sender or berater sender>",
  "maxMsgParts": 5,
  "validity": 720
}

BISO normalises recipient numbers from the internal 0041… format to the international E.164 format +41… required by the Swisscom API.

HTTP responses

Note: Delivery reports and SMS replies are sent asynchronously by Swisscom to a response channel (URL or e-mail) configured in the SMMS portal. BISO does not consume these in this version.

Configuration: ASP SMS JSON API (driver: aspsms)

Implementation per the ASPSMS JSON API. SMS dispatch is performed via HTTPS POST to the /SendSimpleTextSMS endpoint. Authentication and all SMS parameters are submitted as a JSON body (no query string).

Mandatory fields are Userkey and Password. The originator (Sender) is optional and falls back to BISO if not set.

Config::set('sms_driver', 'aspsms');

// Mandatory: API credentials from the ASP account
Config::set('sms_aspsms_userkey', '<userkey-from-aspsms-account>');
Config::set('sms_aspsms_password', '<password-from-aspsms-account>');

// Optional with default values:
Config::set('sms_aspsms_api_url', 'https://json.aspsms.com');  // base URL; default
Config::set('sms_aspsms_sender', '<originator: numeric or alphanumeric, max. 11 characters>');  // default: 'BISO'
Configuration value Type Default Meaning
sms_aspsms_userkey string Mandatory. API userkey from the ASP account. Sent as JSON field UserName.
sms_aspsms_password string Mandatory. API password from the ASP account. Sent as JSON field Password.
sms_aspsms_api_url string https://json.aspsms.com Base URL of the ASP JSON API. The suffix /SendSimpleTextSMS is appended to this URL. Usually no need to change (e.g. test tenants are possible).
sms_aspsms_sender string BISO Originator of the SMS (sender ID). Numeric or alphanumeric, max. 11 characters. Sent as JSON field Originator.

Fields submitted

For each SMS, BISO sends the following POST request to ASP:

POST https://json.aspsms.com/SendSimpleTextSMS
Content-Type: application/json; charset=utf-8

{
  "UserName": "...",
  "Password": "...",
  "Originator": "BISO",
  "Recipients": ["+41790001122"],
  "MessageText": "..."
}
JSON field Type Value range Meaning
UserName string API userkey (mandatory).
Password string API password (mandatory).
Originator string numeric or alphanumeric, max. 11 characters Sender ID. Default: BISO.
Recipients string[] E.164 format +41790001122 Recipient mobile numbers as a JSON array. BISO sends one request per number with a single array element and normalises numbers from Person::getMobileNumbers() to E.164 format +41….
MessageText string UTF-8 SMS body (UTF-8).

BISO normalises recipient numbers from the internal 0041… or 079… format to E.164 format +41790001122, as shown in the ASP JSON API examples.

HTTP responses

Success is determined solely by the HTTP status. The JSON response body ({"StatusCode": …, "StatusInfo": …}) is not evaluated.

Configuration: Dummy driver (driver: dummy)

Config::set('sms_driver', 'dummy');

No additional configuration is required. Dispatch is recorded in the sms logger channel only; no external HTTP or mail call is performed.

SMS Reminder

SMS reminders are a special case within SMS configuration: they are sent on a schedule, before an appointment, to the customer's mobile number.

Reminder templates

The following parameters (Admin > Parameters) are available in the admin panel:

Parameter Label Meaning
sms_versand With appointment SMS Appointment SMS module on/off
sms_notification_time Reminder distance in hours Defines the time distance before the appointment at which an SMS reminder is triggered
sms_versand_manuell Trigger manually SMS can be triggered manually via the appointment interface
sms_text_max_length SMS text max length Frontend textarea max length (e.g. 160 characters for one SMS). Disabled by default.
mit_sms_suppression Enable SMS suppression Activates suppression via meeting point and appointment type (default: false).
SMS texts

The SMS texts themselves are no longer parameters but letter templates of type SMS (Value list > Letter templates > New > SMS template). This allows several templates per use case, distinguished by filter criteria (language, age, chargeability, counselling type, case types, regional offices).

Every SMS template has an SMS type (column brief_typ, the same one letter templates use):

SMS type Used for
sms_termin Appointment reminder (cron job and manual dispatch on the BF appointment)
sms_workshop Reminder for group test / workshop appointments
sms_brief_info Info SMS when a letter is created (checkbox in the document dispatch)

On dispatch the template whose set filters all match is chosen. If several match, the most specific one wins (the most matching criteria); only one SMS is ever sent. If two templates match equally many criteria, the one with a matching language filter wins, so nobody receives a text in the wrong language. Best practice is to combine overlapping criteria in a single template (e.g. regional office and language), which makes the choice unambiguous. A template without any filter acts as the default. If no template matches, no SMS is sent and the error is recorded in the SMS log.

The language is not configured on the template: an SMS is always rendered in the recipient's language (the client's counselling language, falling back to the counsellor's, otherwise German) — the same one the language filter checks against. This affects weekday and month names (%A, %B) and the salutation placeholders. For a separate French text, create a second template with the language filter "client speaks French".

The available placeholders are the same Smarty scope as for email templates ({$kunde.*}, {$berater.*}, {$termin.*}, {$institution.*}, {$treffpunkt.*}, {$beratungsfall.*}, {$anrede.*}, {$mentor.*}, plus {$workshop_plan.*} for workshops). In the form, combo boxes insert the placeholders at the cursor position.

The former parameters sms_versand_termin_vorlage[_fr], sms_versand_workshop_vorlage[_fr] and sms_versand_einladungsbrief_vorlage[_fr] were migrated into such templates by a DB migration. The _fr variant became a template with the language filter "client speaks French", the German one the filter-less default template.

The old param rows are kept in the database as a backup for now, but are no longer read and no longer visible in the parameter form. Changing them has no effect — only the template matters.

Cron job for reminder dispatch

So that appointment SMS mails are sent to the SMS gateway, the CLI command biso-cli termin-sms --do-it must be executed at regular intervals.

Example Linux crontab:

0 *    * * *   www-data    /usr/bin/php /var/www/biso/webroot/biso-cli termin-sms --do-it

Alternatively the SmsReminderTasks crunz scheduler can be enabled (see webroot/backend/scheduler/tasks/SmsReminderTasks.php), which invokes the same CLI command hourly.

For test dispatch in DEV/staging, use sms_driver = 'dummy' (see above).

SMS suppression

(See above, parameter mit_sms_suppression.)

For appointments where no SMS dispatch is desired, suppression can be activated per meeting point or appointment type.

In addition two table fields control the actual behaviour:

Suppression applies both to the automatic appointment reminder and to the manual trigger from the appointment UI. In the brief info SMS flow (sendBriefInfo), an active suppression silently skips the dispatch.

Logging

SMS dispatch events are recorded in a dedicated sms logger channel. By default the logs land in data/tmp/biso-sms.log.

// config.php (see system_config.php for defaults):
Config::set('LOGGING', [
    'sms' => [
        'type'    => 'file',
        'level'   => 'DEBUG',
        'console' => 'output',
        'logfile' => Config::get('tmpdir') . '/biso-sms.log',
    ],
]);

Follow-up Emails

After case completion, cases can be supplied with a follow-up email. For this, the following prerequisites are necessary:

The follow-up survey is triggered via cron job, e.g. daily:

$ php webroot/biso-cli send-nachbefragung

Configuration

The following configurations are relevant for follow-up surveys:

Customer-specific configs

Logging

The follow-up CLI job logs its output to a separate logging target:

SendNachbefragung

The logs can therefore be directed to a dedicated logfile or to the console:

// config.php:
Config::set('LOGGING', [
    'SendNachbefragung' => [
        'type' => Logger::TYPE_CONSOLE,
        'level' => Logger::DEBUG,
    ]
]);

DB Log System

SQL

The log system requires a second database with the Log table.

CREATE DATABASE biso_log
    WITH
    OWNER = bisoadm
    ENCODING = 'UTF8'
    LC_COLLATE = 'de_CH.utf8'
    LC_CTYPE = 'de_CH.utf8'
    TABLESPACE = pg_default
    CONNECTION LIMIT = -1

CREATE TABLE IF NOT EXISTS log
(
    id serial NOT NULL,
    logtime timestamp,
    context text,
    benutzer text,
    action text,
    record_id int,
    record text ,
    CONSTRAINT log_pkey PRIMARY KEY (id)
)

config.php

In config.php, a second DB connection must be configured, and the log system can be activated separately for Store or Destroy actions:

Config::set('gaia.db',[
    'main' => [ /* .... */],
    'log' => [
        'driver' => 'pdo_pgsql',
        'dbname' => 'biso_log',
        'host' => 'db',
        'port' => 5432,
        'user' => 'xxxxxx',
        'password' => 'yyyyy',
        'schema' => 'biso'
    ],
])

Config::set('LOG_STORE', false);
Config::set('LOG_DESTROY', true);

Job Queue

See Background Job System for documentation of the job queue system.

OIDC / Single-Sign-On (user auto-provisioning)

OIDC login configuration (Apache mod_auth_openidc, oidc.auto_provision, oidc.sync_existing, oidc.userinfo_endpoint, oidc.roles_claim, log channel) is documented under OIDC Auto-Provisionierung. For the customer-specific Geneva configuration (GINA, oidc.ge.* ID mapping) see Kanton Genf: OIDC/GINA-Benutzerprovisionierung.