The print dialog can also save the manual as PDF. Each chapter starts on a new page.

Manual for administrators

This manual is for users in the role of administrator who set up and look after camindo CMS: the structure of the contents, websites and languages, users and rights, media processing and the transfer of structures. Installation, updates and server operation (chapters 1 and 12) are usually the task of the system administrator.

If you set up camindo CMS for the first time, the best place to start is chapter 2 – The concept. It explains the ideas on which the system is built and ends with a guide to the first steps.

The daily work with contents – searching, editing, batch processing, export and import – is described in the manual for editors.

Contents

  1. Install camindo CMS – requirements, installer, web server, cron jobs, add-on components
  2. The concept – content areas instead of database tables, queries across areas, links, several websites, template sets, copying structures; guide to the first steps
  3. Instances and languages – websites, domains, start page, language versions
  4. Structure: folders and content areas – area types, blueprints, settings, copying and deleting areas
  5. Input fields – field types, display, input validation, multilingualism, relations, files, full text, meta fields, changing the type
  6. Managing templates – template areas, assignment, file system, list templates
  7. Users and rights – users, user groups, module, instance, language and area rights, owner
  8. Media – processors, converters, thumbnail fields, uploads
  9. Exporting and importing structures – duplicating structure trees, transferring development, passing on template sets
  10. Further modules – status, snippets (frontend and backend snippets), scheduler, tools, log files, audit log, components, trash, PDF, AI assistant
  11. Extensions – editors and input aids for fields
  12. Operation and maintenance – cron jobs, trash, backup, updates, command line, configuration, operating notices
  13. Community features – registration, sign-in, contributions of frontend users, protected files, data export and deletion
  14. Security, data protection and operation – for decision-makers – security overview, backup and recovery, security updates, reporting route (security.txt), GDPR, audit log, tasks of the operator
  15. Data protection – which personal data of users, website users and visitors is stored where and for how long, shortening IP addresses, deletion policies, export of personal data, checklist

1 Installing camindo CMS

This quick start guide summarizes what you need to get camindo CMS up and running. If you use camindo CMS in production, please read all chapters, in particular the chapter Operation and maintenance.

What you need

A server (or computer) with shell access (SSH or Terminal) and the following installed:

What Version How to check it
PHP 8.3 or newer, with the extensions mysqli, intl, gd, json, mbstring php -v and php -m
MySQL or MariaDB MySQL 8 / MariaDB 10.6 or newer mysql --version
Apache 2.4 with mod_rewrite and AllowOverride All apachectl -M \| grep rewrite
Composer 2.x composer --version
Git any version git --version
optional: ImageMagick, ffmpeg, mysqldump – which magick ffmpeg mysqldump
  • ImageMagick – Image versions can also be calculated with the GD library built into PHP. ImageMagick, however, supports many more formats (such as HEIC, TIFF, PSD) and settings.
  • ffmpeg is required to convert videos and audio files and to extract still images from videos.
  • mysqldump is required for database backups – before updates and through the Scheduler.

The steps at a glance

  1. Create a database
  2. Get the files – the installer starts automatically
  3. Answer the installer's questions
  4. Set up the web server and file permissions
  5. Set up cron jobs
  6. Sign in to the back end

Step 1 – Create a database

Have the access data for an empty database ready. At your hosting provider, you create it in the customer menu. On your own server, for example, like this:

mysql -u root -p
CREATE DATABASE camindo CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
CREATE USER 'camindo'@'localhost' IDENTIFIED BY 'A-SECURE-PASSWORD';
GRANT ALL PRIVILEGES ON camindo.* TO 'camindo'@'localhost';
FLUSH PRIVILEGES;
EXIT;

In MariaDB, the collation is called utf8mb4_uca1400_ai_ci (from version 10.10); in older versions, it is utf8mb4_unicode_ci. The installation adjusts the tables to the server itself; an index for searching list fields that only MySQL supports is left out on MariaDB.

camindo CMS needs the time zone tables of the database server. The official Docker images of MySQL and MariaDB include them, packages of Linux distributions often do not. If they are missing, the installation and php Core/cron/camindo.php status warn; load them once as the database administrator:

mariadb-tzinfo-to-sql /usr/share/zoneinfo | mariadb -u root -p mysql

On MySQL, the program is called mysql_tzinfo_to_sql and the client mysql.

Step 2 – Get the files

Change to the directory where the CMS is to be installed (here /var/www), and let Composer create the project:

cd /var/www
composer create-project camindo/project mysite

mysite is the name of the new project directory and can be chosen freely.

Composer downloads the skeleton (composer.json, public/, empty directories), the core into Core/, and the libraries into vendor/. The installer then starts automatically. If it does not start, or if you cancelled it:

cd /var/www/mysite
php Core/cron/camindo.php install

Step 3 – The installer

The installer first checks the requirements and marks each line with [ok], [--] (missing, but optional) or [!!] (missing – the installer aborts):

requirements
  [ok] PHP >= 8.3 (8.4.22)
  [ok] PHP extension mysqli
  [ok] PHP extension intl
  [ok] writable: var
  [--] magick: not found
  ...

Then it asks questions. The default value is shown in square brackets – press Enter to accept it.

Question Your input
Database host [localhost] usually Enter; at your hosting provider, the host is listed in the access data
Database port [3306] Enter
Database name [camindo] the name of your database
Database user [camindo] the name of your database user
Database password the password (not displayed). The installer then tests the connection right away; if it fails, it names the reason and asks for the database details again
Time zone [Europe/Berlin] your time zone
Locale [de_DE] country/language for date and number formats
Will the site be served over HTTPS? [y] Enter if the site runs on https://. n only for a local test without SSL – see Test locally without HTTPS
Admin name [admin] your sign-in name in the back end (lowercase letters, digits, _, -)
Admin password according to the password policy (by default at least 12 characters with uppercase and lowercase letters and a special character), enter it twice
Admin e-mail your e-mail address (may be left empty, but it is required for the sign-in code)
Backend language (ar_SA|bn_BD|de_DE|en_US|…) [en_US] de_DE for a German back end. The available options are ar_SA, bn_BD, de_DE, en_US, es_ES, fr_FR, hi_IN, ja_JP, ko_KR, pt_BR, ur_PK, zh_CN; the language can be changed later in the personal settings

After that, everything runs automatically: key file, configuration, directories, database tables, your user account (superuser), caches.
At the end, you see:

finish
  var/install.lock written

The installer runs only once. A repeated call reports “already installed” and does nothing.

Three files in the project directory are now important:

File Content If it is lost
.camindo.php configuration (database, paths, e-mail) recreate it with install --step=config
.camindo.key key for encrypted fields and passwords cannot be replaced – without it, encrypted content is lost. Please keep a copy in a safe place!
.sqlpwd password for mysqldump recreate it with install --step=sqlpwd

All three files receive the permissions 0600; only their owner can read them. That fits when the command line and the web server run as the same user – which is the case with most hosting offers that have a PHP user of their own per customer. If you run the installer as root, however (for example in a Docker container or with sudo), it hands the files to the web server user: the one named with --web-user=NAME (or in the environment variable CAMINDO_WEB_USER), otherwise the owner of the project directory. If neither is known, the files belong to root and the installer warns – catch up on the handover with php Core/cron/camindo.php install --step=key --web-user=NAME.

As an alternative, the passwords and keys from these files can also be stored in environment variables, if your server supports this; for more details, see Access data from environment variables.

Step 4 – Set up the web server

The website is not located directly in the project directory, but in its subdirectory public/. This is intentional: everything the browser must not see (configuration, keys, code) is one level higher.

Apache virtual host (for example /etc/apache2/sites-available/mysite.conf):

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/mysite/public
    <Directory /var/www/mysite/public>
        AllowOverride All
        Require all granted
    </Directory>
    ErrorLog  ${APACHE_LOG_DIR}/mysite-error.log
    CustomLog ${APACHE_LOG_DIR}/mysite-access.log combined
</VirtualHost>
sudo a2enmod rewrite
sudo a2ensite mysite
sudo systemctl reload apache2

At your hosting provider, set the target directory of the domain in the customer menu (often called “Document Root” or “Webroot”) to mysite/public. Set up HTTPS as usual (Let's Encrypt, the certificate of your host) – camindo CMS does not require anything special for this.

As a precaution, the project directory itself contains an .htaccess that blocks access: if the domain accidentally points to the project directory instead of public/, Apache answers every request with 403. The website in public/ is not affected, because its own .htaccess lifts the block. Do not delete the file.

Test locally without HTTPS

camindo CMS is set to HTTPS by default: the sign-in cookies for the back end and for frontend users are only sent over encrypted connections (cookie_secure). The browser discards these cookies over a plain http:// address – sign-in then fails, and the sign-in dialog points this out.

If you only want to try out camindo CMS on your computer and do not want to set up a certificate:

  1. Answer the installer question Will the site be served over HTTPS? with n (or call the installer with --https=n). The installer then writes the following to .camindo.php:

    'admin' => [
        'cookie_secure' => false,
    ],
    'user' => [
        'cookie_secure' => false,
    ],

    If you have already run the installer with y, enter these two values yourself in .camindo.php.

  2. Before the installation is reachable – on a server, in the company network, for colleagues – set up HTTPS and set both values to true (or delete the two lines, then the default true applies). Without this setting, session cookies travel over the network unencrypted and can be read.

Chrome and Firefox treat http://localhost (also http://mysite.localhost) like a secure connection. If you test locally under these addresses, you will usually manage with cookie_secure = true as well – Safari and other addresses (http://mysite.test, IP addresses on the network) do not.

File permissions: The web server (usually user www-data) must be allowed to write to var/ and public/media/, and nowhere else:

sudo chown -R www-data:www-data /var/www/mysite/var /var/www/mysite/public/media

If you ran the installer as a different user, the cache files under var/cache/ also belong to that user – the command above corrects this.

Step 5 – Cron jobs

Open the crontab of the web server user:

sudo crontab -u www-data -e

and add one line:

* * * * *   /usr/bin/php /var/www/mysite/Core/cron/cron.php  >/dev/null 2>&1

cron.php is the only cron job. Every minute, it checks what needs to be done: some tasks run every minute (applying template changes, converting images and videos), all other tasks are handled by the Scheduler. The installation sets up the maintenance tasks there – distributed between 2 and 4 a.m.: database backup, emptying the trash, applying deletion policies and retention periods, cleaning up logs and the frontend cache, updating the full text, checking the audit log, checking for updates; temporary files every hour. You can adjust times and periods in the Scheduler (see Operation). Without the cron job, no maintenance takes place.

Step 6 – Sign in

  • Back end: https://example.com/backend/ – with the name and password from step 3 (locally without SSL: http://…/backend/, see Test locally without HTTPS).
  • Frontend: https://example.com/ – initially reports that no content has been set up yet.

The chapter The concept describes what comes next in the back end, with a guide for the first steps.


Install additional components

Additional modules, media processors and extensions are Composer packages. There are always two steps:

  • Composer stores the files,
  • camindo CMS sets up the database:
cd /var/www/mysite
composer require camindo/module-pdf
php Core/cron/camindo.php component install Pdf --activate

php Core/cron/camindo.php component list shows what is installed and active – or the module “Components” in the back end.

If something goes wrong

Symptom Cause and remedy
Browser shows “camindo is not installed yet” The installer has not completed → php Core/cron/camindo.php install
requirements not met A PHP extension or a write permission is missing → the [!!] line names it
database connection failed Check host, name, user and password → at a hosting provider, the host is often not localhost
Back end loads without design (CSS/JS missing) public/backend/assets is missing or outdated → php Core/cron/camindo.php upgrade copies the files again
Error 500 in the frontend .htaccess is missing or AllowOverride is not All → is mod_rewrite active?
“503 – Maintenance” after an update → run php Core/cron/camindo.php upgrade (brings the database up to date)
Sign-in fails without an error message,
the sign-in dialog shows a note about HTTPS
You access the back end via http://, but cookie_secure is true → see Test locally without HTTPS
Sign-in code does not arrive → set up an e-mail mailbox in .camindo.php (see Operation)
Error 500 in the frontend and back end,
on the command line:
CONFIGURATION ERROR:
.camindo.key exists but is not readable
The user under which the web server or cron job runs is not allowed to read the key file – usually because the installer or a key change ran as root → php Core/cron/camindo.php status names the affected files; as root, install --step=key --web-user=NAME hands them to the web server user (otherwise chown; if the web server can only read the file through the group on your system, chmod 640). In this case camindo CMS deliberately does not fall back to the environment variable CAMINDO_KEY. The same applies to .camindo.key.retired
Error 500 in the frontend and back end,
on the command line:
CONFIGURATION ERROR:
paths.key_file -
file not found
The key file .camindo.key is missing. Restore it from your data backup – do not generate a new one: install --step=key would write a new key, and encrypted content and passwords would then be unusable. If the key comes from the environment variable CAMINDO_KEY, check that it is set for the web server and the cron job
Something else → php Core/cron/camindo.php status shows the state of the system; var/log/ contains the details

Individual steps of the installer can be repeated without touching the database: php Core/cron/camindo.php install --step=dirs (likewise key, config, sqlpwd, runtime).

Updates are described in the chapter Operation and maintenance.

2 The concept

This chapter explains the ideas on which camindo CMS is built. Once you know them, you set up a website faster and more cleanly – and you use possibilities that other systems do not offer.

Content areas instead of database tables

In many content management systems and web applications, every new kind of content – products, events, branches, job advertisements – requires its own database table, its own input masks, and its own program code. Every change to the structure is a job for a developer.

camindo CMS takes a different approach. In the back end, you create content areas and give them input fields – as many as you like, in any combination, extensible at any time, even while the website is in operation. The database does not change in the process, and not a single line of code is written.

Each content area automatically receives everything it needs to maintain its content:

  • an input mask with input validation, required fields, and help texts,
  • a search form with wildcards, comparison operators, and full-text search,
  • a sortable record list,
  • batch processing, CSV, DOCX and XML export and import,
  • multilingualism per field, working copies, edit locks and the trash,
  • access rights per user group,
  • and access from the templates with filters, sorting, and paging.

The fields are not mere text snippets: each field has a type – text, integer, currency amount, date with time zone, file, relation, and many more. The type determines how an input is validated, stored, sorted, and searched. Internally, each type is stored in its own indexed table; filtering and sorting therefore remain fast even with large amounts of data.

Example – an event calendar:

Content area Input fields
Events Title, start, end, description, image, admission (currency amount), location (relation), categories (relation)
Locations Name, address, directions, map (file)
Categories Label, color

Three content areas, half an hour of setup – and the editors can get to work.

One field name, one meaning: filtering across areas

Field names apply system-wide in camindo CMS: a name is always bound to the same type. If you create a field datum of the type “Date + time” in the areas “News”, “Events” and “Blog”, then datum is the same attribute everywhere.

This opens up a special possibility: templates can query, filter, and sort several content areas together, as if they were one table. A timeline of news items, events, and blog posts is a single command:

(CMS:SELECT zeitleiste_eintrag
  filter[area]=meldungen,veranstaltungen,blog
  filter[datum] >= 2026-01-01
  sort=datum
  order=desc
  limit=20
)

In the same way, a search for filter[schlagwort]=… finds the matching records in all areas that have a field schlagwort. In addition, each record has a main field (usually a title or name), through which lists and relations can name it across areas – without knowing what the field is called in the individual area.

Recommendation: Give fields with the same meaning the same name in all areas (titel, datum, text, bild, slug), and fields with different meanings different names. This naming convention pays off as soon as templates combine content.

Reserved name prefixes

The following prefixes are reserved for a field type. Using them can help avoid type conflicts, but makes it harder to change the type of a field later (the field must then be renamed first):

Prefix Type
bit_ bit
chr_ chars
cur_ currency
dat_ datetime
dec_ decimal
enc_ encrypted
fil_ file
flt_ float
ftx_ fulltext
rid_ id
ids_ ids
int_ integer
jsn_ json
pwd_ password
rel_ pointer, relation, link
txt_ text
tim_ time
tsl_ timestampl

Linking instead of maintaining several copies

Anything that appears in several places is created once as its own record and linked: the author with their photo, the location with its address. A single change is sufficient – all pages to which the record is linked are updated.

camindo CMS offers several kinds of links for this:

Kind Example
Relation, bidirectional (n:m) article ↔ tags: the article knows its tags, the tag knows its articles
Relation, bidirectional (n:1) article → main category: exactly one category per article
Pointer, unidirectional (n:m or n:1) “Related articles”, also from several areas
Hard-linked content (1:n) text-image blocks of a page: they belong exclusively to their record and are copied, moved, and deleted with it
Embedded content like hard-linked content, but edited directly in the input mask of the record – a flexible page construction kit

Templates can query records via their links and sort by fields of linked records – for example, articles by the name of their author.

Hierarchies

If a content area receives the meta field “Parent record”, its records become a tree: pages with subpages, categories with subcategories. Templates can query entire subtrees or individual levels – the basis for navigation and breadcrumb trails. Independently of this, folders arrange the content areas themselves clearly in the back end.

Multiple websites and languages

One installation can operate any number of websites, each as its own instance, with domains, languages, and a start page. Each content area belongs to an instance.

Instances can receive read access to other instances, and can thus use shared content on different websites, while other content remains strictly bound to one domain – useful, for example, for projects with several subdomains or branches of a company.

Each instance has a main language and any number of additional languages. Only what has to be translated is multilingual: you decide on multilingualism per field. If a translation is missing, the main language steps in – field by field. This way, the website is complete in every language while the translation is still in progress.

Templates: encapsulated functionality

Templates output the content. They consist of the design (HTML, CSS, JavaScript) and of CMS commands, which insert content and perform tasks: query and filter records, generate paging, create image versions, evaluate forms and save records from them, register and sign in visitors, manage shopping carts, or generate PDF documents.
When saving, camindo CMS translates each template into PHP. At runtime, there is no interpretation overhead. CMS commands and PHP can be combined optimally.

A template can include other templates. This creates self-contained building blocks with a clear task: an article list with paging, a form that saves submissions as records, a member area with registration, sign-in, and password reset.

Template sets

A template set is such a building block on a larger scale: the templates of a function together with the content areas they need – for example, a news module with the areas “News” and “Categories” and the templates for overview, detail page, archive, and RSS feed.

A template set is imported and is immediately ready for use. You can customize everything.

Template sets are generated via XML export. The option “Optimize templates for import into a foreign database” ensures that links between templates, areas, and records are adjusted when importing into another installation (see chapter 9).

Copying and passing on structures

Because content areas are settings and not programming, they can be copied and transferred:

  • Copy area – a copy of a content area with all input fields and template assignments, but without records.
  • Blueprints – prepared templates for new areas (for example “Articles”, “Members”, “Comments”) create the area and its fields in one step.
  • XML export and import – entire structure trees with folders, content areas, input fields, templates, and, if desired, all records and files. With it, you transfer a development from the test environment to the live website, duplicate a proven website structure for a new instance, pass on template sets, or back up individual areas.

Details in chapter 9.

Security in everyday editorial work

  • Working copies: Editing always happens on a copy. The website sees changes only after saving, complete and all at once.
  • Edit locks prevent two people from changing the same record at the same time.
  • Trash: Deleted items can be restored (usually up to 7 days after deletion). Later, changes can be reconstructed via the audit log.
  • Rights: Users see only the modules, websites, areas and languages that are released for their user group. Write and delete rights can be controlled down to individual records.
  • Encrypted fields: Password fields cannot be viewed by anyone; encrypted content can only be viewed in the back end or, if released, in protected areas of the website.
  • Sign-in: Two-factor sign-in and the prevention of password guessing protect both the back end and the website.

Guide: the first steps

After the installation, set up a website in this order:

  1. Check the languages and create new ones if needed – module “Languages”.
  2. Create an instance: name, main domain, main language, and additional languages – module “Instances”.
  3. Create content areas, preferably grouped in folders, and define their input fields – module “Structure”.
  4. Create a template area and write the templates or import a template set; assign their default template to the content areas – see chapter 6.
  5. Set the start page in the instance (start record and start template).
  6. Set up media converters for image versions and preview images and add thumbnail fields to the upload fields – chapter 8.
  7. Create user groups and users for the editorial team – chapter 7.
  8. Secure the operation: cron jobs, database backup, e-mail dispatch.

3 Instances and languages

Instances

An instance is a website: a frontend with its own domains, languages, and content. One camindo installation can operate any number of instances – for example different branches of a head office, or several websites of a group. Content and templates belong to an instance and are thereby bound to its domains. In addition, shared areas can be defined, with content that is accessible to several instances and may appear under all of their domains.

No multi-tenancy

The separation of content has limits: camindo CMS does not offer true multi-tenancy. A secure separation of tenants (for example competing company websites) is only achieved with separate camindo installations, each with its own database and its own public/ and var/ directories.

The default instance

After installation, there is a default instance in the module “Instances”. It catches all requests whose domain is not assigned to any other instance. Its main language after installation is English (en_US). It can be changed, but not deleted. If you manage only one website with camindo CMS, you can use this instance for it. If you manage several websites, create a separate instance for each website.

Because it catches all remaining domains, the default instance always stands last in all lists and selections of the back end – regardless of its position.

Settings of an instance

Setting Meaning
Label name of the instance in the back end
Position after... The order in the back end is changed by selecting the preceding instance. The default instance always stands at the end and cannot be selected as a predecessor
Frontend languages
Primary language language that steps in when a translation is missing; en_US is preselected for a new instance
Additional languages additional language versions; the order is kept in the back end and can also be used in the frontend
Frontend domains
Main domain the actual address of the website, without protocol, e.g. www.example.com
Frontend protocol https or http
Canonical requests permanently (301) redirects every request that does not come via the main domain and the valid address of a record to that domain – including the start page: with several languages it leads to the start page of the language (/en_US/, without a language code in the address according to the browser language), with one language to /. Recommended for search engines
Additional domains requests via these domains also end up in this instance, e.g. example.com without www. With the switch “RegEx”, an entry counts as a regular expression, for example to catch all subdomains: *\.camindo\.de
Access
Readable other instances content of the selected instances can be retrieved in the frontend of this instance – for example a shared product catalog for several websites
Start page
Start record record whose content is available on the start page
Start template template with which the start page is output
Script policy (CSP)
Script policy “Default” (setting security.frontend_csp, otherwise off), “Off”, “Report only” or “Enforce”. Details see below
Additional script sources addresses of external scripts that may run, one per line, e.g. https://www.googletagmanager.com or *.example.com
Allow scripts loaded by allowed scripts scripts that an allowed script loads (tag manager, widgets) also run
Restrict form targets / Additional form targets forms may only send to the own website and the stated addresses (e.g. the payment provider)

The start page can only be selected after the first save – before that, the instance has no content.

Media files of an instance are only delivered via its own domains and the domains of the instances it may read. This prevents images of one website from being retrieved via the address of another.

Script policy

The script policy (Content Security Policy) is a barrier in the browser: only scripts from the own domain and from the entered external sources run. If script code gets into a page via content – a comment, an imported text – the browser does not execute it.

Proceed in two steps:

  1. “Report only”: for testing – all scripts run, but the browser reports what it would block. The reports are in the module “Log files” under csp. Call up the most important pages (start page, product, shopping cart, checkout, customer account) and check the log.
  2. “Enforce”: for productive operation, once all reports have been resolved. If the log reports external scripts that you need (payment provider, maps, statistics), enter their address as a script source. Every script in the template needs the nonce of the page (<script(CMS:NONCE)>); scripts directly in the HTML (onclick="…") must be rebuilt by the template programmer. The template editor and the widget “Template messages” on the start page point out scripts without a nonce.

New instances are set to “Default”: they take over the setting security.frontend_csp of the configuration; without it, “off” applies. The configuration is only this default – an instance for which you have chosen “Off”, “Report only” or “Enforce” keeps its mode. For a staging environment, security.frontend_csp = 'report' is suitable if the instances there are set to “Default”.

Addresses in the frontend

The address of a record consists of the domain, for multilingual websites the language code, and the slug or the number of the record (record_id):

https://www.example.com/company-party-2026
https://www.example.com/en/summer-party-2026
https://www.example.com/1758

If the language code is missing, camindo CMS selects the language according to the browser's language setting (Accept-Language). If this is not set up as a frontend language, the main language of the instance is displayed. The address consisting of the language code only (without slug or record_id) leads to the start page (for example https://www.example.com/en). If there is no record at an address, the website responds with status 404. Records only have a slug if the field slug is set up in the content area; see chapter 5. Each content area can provide an additional slug prefix. It then stands in front of the slug of its records (/en/events/summer-party-2026) and can be different for each language. More on this in chapter 4.

Copying and deleting an instance

“Copy” opens an input mask in which the details of the instance to be copied are already filled in – without domains, start page, and content. Content areas, templates, and records are not copied. You can subsequently transfer these via XML export and import if desired.

Caution: When an instance is deleted, all of its content areas, input fields, records, and templates are permanently deleted with it. Instances have no trash. The confirmation prompt states how many elements are affected.

Languages

In the module “Languages”, you create the languages that are available to the instances. After installation, these languages are already created:

  • ‫العربية‬ (ar_SA)
  • বাংলা (bn_BD)
  • 简体中文 (zh_CN)
  • Deutsch (de_DE)
  • English (en_US)
  • Español (es_ES)
  • Français (fr_FR)
  • हिन्दी (hi_IN)
  • 日本語 (ja_JP)
  • 한국어 (ko_KR)
  • Português (pt_BR)
  • ‫اردو‬ (ur_PK)

Additional languages can be installed later – see below.

Setting Meaning
Language or locale code code of the language version, appears in the address, e.g. de, de_DE or de_AT. A variant such as de_AT also matches browsers that only request de
Backend language language of the snippets (text modules) of this language version, e.g. de_DE – it also applies to messages of the CMS commands in the frontend. de_DE and de_AT as frontend languages thus share the same text modules (see Backend snippets)
Label / Label (native language) name of the language, once in the language of the back end (“German”), once in its own language (“Deutsch”) – for language switchers
Time zone time zone in which date and time information of this language version appears on the website. Empty: the time zone of the configuration
Position after... order

Changing the languages of an instance

  • Adding a new language: Multilingual content of the instance receives the new language version. Until it is translated, the main language steps in.
  • Removing a language from an instance: All content of the instance in this language is permanently deleted: texts, numbers, dates, full texts, privacy templates, and the files of multilingual fields. Relations and the content of the other languages remain intact. When saving, the mask first appears again with a warning, which names the languages being removed – only a second click on “Save” removes them. If you change the main language at the same time, files that are not translated show the file of the new main language afterwards.
  • Deleting a language: The content of the language is deleted in all instances, just as when removing it from an instance. Before deleting, camindo CMS names the instances that use the language. If it is the main language anywhere, it cannot be deleted – first choose a different main language there.

How fields become multilingual is described in chapter 5.

4 Structure: folders and content areas

In the module “Structure”, you determine which content camindo CMS manages: you set up folders and content areas and define their input fields. The concept behind this is described in chapter 2.

The area list

The module shows all folders and areas in their tree structure, grouped by instance when there are several instances. “New” creates a folder or area, “Search” filters the list.

Dependent areas appear grayed out – for example the text-image blocks of a page. These areas are not directly accessible in the module “Content”. Their records are edited directly in the input mask of the record to which they belong (see chapter 5).

Area types

When creating an area, you first choose what it is to contain:

Type Symbol Content
RECORD content area for records of all kinds. You add the input fields yourself
PROFILE profiles of registered frontend users – see chapter 13
UGC contributions that signed-in frontend users create on the website – see chapter 13
TEMPLATE template area for page layouts (HTML), PHP code and CMS commands – see chapter 6
FOLDER folder that groups several areas

For RECORD, PROFILE, and UGC areas, the selection additionally offers blueprints: ready-made templates that create the area with suitable input fields.

Blueprint Type Fields
Article RECORD Title, date, teaser, text, and image
Members PROFILE Name, e-mail (sign-in name), and password
Comments UGC Name and comment text

The fields of a blueprint are a starting point – you can change and extend them as you like afterwards.

Own blueprints

You store your own blueprints as XML files in the directory var/area_blueprints/ of the installation; the selection when creating an area shows them together with the supplied ones. For this, you need access to the file system of the server.

To create a blueprint:

  1. Set up an area with the desired settings and input fields.
  2. Export only this area with the option “Export structure”, without content (see chapter 9).
  3. Unpack the ZIP file and save the XML file as var/area_blueprints/{name}.xml, e.g. var/area_blueprints/appointments.xml.

Rules:

  • The file name without .xml is the name of the blueprint: letters, digits, underscore, and hyphen. If a file has the same name as a supplied blueprint (article, member, comment) and the same type, it replaces that blueprint.
  • The type under which the blueprint appears (RECORD, PROFILE, or UGC) is determined by the area in the file. Other types do not appear.
  • The label of the area appears as the heading in the selection and its note below it. Both may be a text or a snippet reference (#label_articles) – a reference appears in the language of the user. The labels of the fields are taken over in the language of the user when creating.
  • Only the first area in the file is evaluated; records in it are ignored. Label and note of the new area remain empty and are entered in the mask; name, icon, and sort direction come from the file.
  • Fields of the type Relation, Pointer or Hard-linked content whose target area is the area itself – for example “Related articles” in an article area – point to the new area in the new area. Targets in other areas (for example a pointer to an images area) cannot be resolved: the field is created without this target area; add it afterwards in the settings of the field.

Back up var/area_blueprints/ together with the other project data (see chapter 12).

Settings of a content area

Setting Meaning
Name internal name for the templates, e.g. artikel – lowercase letters, digits, and underscore, starting with a letter. Templates address areas by this name, not by their number; this way, an area can be recreated or moved without changing templates
Icon symbol in the back end, selectable via a search
Label name of the area in the back end
Instance website to which the area belongs. Its records belong to the same instance
Parent folder, Preceding element place in the area tree
Note appears above the record list – keep it as short as possible. A snippet reference (#custom.key) appears in the language of the user, as with the label
Main field field that represents the record in lists and relations (usually title or name) – selectable only after the fields have been created
Default sort order, sort direction order of the records in the record list and default for templates
Multilingual input “All languages on one page” or “One tab per language” – how the input mask displays multilingual fields. A new area starts with “One tab per language”
Records per page number of records the record list shows on one page; you page between the pages. 0 shows all records on one page. New areas start with the value of admin.skiplist_default (see chapter 12); an empty field uses this value as well
Slug prefix optional first part of the address of all records of the area, per language (see below)
Deletion policy rule according to which records and audit logs of this area are handled; cf. chapter 15. The policy applies to every deletion – in the back end, via the API and with CMS:USER_DELETE in the frontend. The standard policy for the area type is preselected (“Delete record”, “Delete profile”, “Delete user-generated content”)
Dependent area the records belong exclusively to one record each of another area (see Hard-linked and embedded content). The area then does not appear in the area list of the module “Content”
Default template template with which each record of the area is output in the frontend, as long as no other is selected in the record
Selectable templates templates that editors may choose alternatively in the record (meta field “Design template”)
URL override templates templates that may be called via the address of a record instead of the assigned one – for example the print version of a content item or the output in another format (/news,xml_rss)
Overview template template that is called when the address consists only of the slug prefix (/en/events) – an overview of the area without a record, for example the list of all events. Without an overview template, this address results in “Page not found”. With an overview template and slug prefix, the area receives the button “Preview” (see Overview templates)
List template (back end) only for the back end: formats an additional last column of the record list for each record; for example a directly displayed RGB color value
Trigger templates templates that are called on events of the records and execute PHP code – when saving, deleting, restoring, and for the actions of the deletion policy (see Handbook for template programmers). You can assign several; they run one after another in the displayed order, which you change with the arrow buttons
Access for user groups appears only when creating a new area: which user groups may use the new area; can be changed later in the tab “Access”

PROFILE and UGC areas have additional settings for sign-in on the website (see chapter 13).

Slug prefix

A content area can prefix the addresses of its records. This allows identical slugs to occur in different areas of an instance:

Area Slug prefix Slug Address
Colors colors blue /colors/blue
Colors colors other/red /colors/other/red
Places – bayern/schwabing /bayern/schwabing
Places – red /red
  • The prefix is a single part of the address – without a slash. The slugs of the records, on the other hand, may contain slashes.
  • It is entered per language of the instance (all languages among each other); an empty language takes over the prefix of the main language.
  • Folders and template areas have no prefix. It is not taken over when an area is copied.
  • The address consisting of the prefix alone (/en/events) shows the overview template of the area, if one is assigned.

The address as a whole must be unique – within the instance and within the instances that are linked with it via “Readable other instances”:

  • Two areas cannot have the same prefix.
  • A record of an area without a prefix must not begin with a prefix: the slug colors/yellow in the area Places is rejected when saving (“'colors/' is a reserved slug prefix of content area …”).
  • camindo CMS rejects setting, changing, or removing a prefix if this would give two records the same address or if existing addresses would begin with the new prefix – the message names the affected record.

When an address is requested, camindo CMS first checks whether its first part is the prefix of an area and searches the remainder only there; otherwise it searches the entire path in the areas without a prefix. Templates generate links with CMS:URL – the prefix is added automatically.

The tabs of an area

The detail view of an area has the following tabs:

  • “Settings” – the settings above, with “Edit”, “Copy” and “Delete”;
  • “Input fields” – the fields of the area (see chapter 5);
  • “Access” – which user groups may read, write, and delete the area (see chapter 7);
  • “Records” or “Templates” – the content of the area.

Copying an area

“Copy” creates a new area with all settings – for content areas including all input fields, default and sort field, and template assignments. Records and templates are not copied, and the slug prefix is not copied either. Labels that refer to snippets of the instance remain references – when copying into another instance, the snippets come into its set. In the input mask, you assign label, name, instance, and place in the tree of the copy. After saving, camindo CMS shows the detail view of the copy.

If a relation field of the original points to the area itself (for example “Related articles”), it points to the copy in the copy. Relations to other areas remain in place; bidirectional relations receive a new counter-field there.

To copy entire folders with areas, templates, and content, use XML export and import.

Deleting areas and folders

A content area is deleted together with its records, a template area together with its templates; the confirmation prompt states the number. When deleting a folder, you choose whether the contained areas are deleted as well or moved up one level.

Deleted areas go to the trash like records and are only permanently removed after the retention period (see chapter 12). Until then, a superuser can restore them – with all subareas, records and input fields that were deleted together with them.

The relation fields of other areas that point to an area go to the trash with it – otherwise they would point nowhere. Restoring the area brings them back as well (see deleting an input field).

The records of a deleted area go with it into the trash and are permanently deleted after a few days. The deletion policy of the area takes effect here together with the blocks for the trigger “Area was deleted” (see chapter 15), with the periods calculated from the deletion – with the standard policies, the history in the audit log is deleted after 1 year. If it is a profile area, the contributions of the users are deleted immediately according to their own policies (trigger “UGC: Profile was deleted”) – this does not undo a restoration of the area.

Instances

If you operate several instances, each subtree belongs to one instance: an area can only stand in a folder of the same instance. If an area changes its instance, its records, templates, and media files move with it.

5 Input fields

You maintain the input fields of a content area in the module “Structure”. Select an area and switch to the tab “Input fields”. The list shows each field with its name, label, type, and three switches for the visibility. The visibility can be switched directly in the list:

Switch The field appears …
Input in the input mask
Search in the search form
List as a column of the record list

The order of the fields in the list is also their order in the mask. “New” creates a field. Clicking a field opens its detail view with “Edit”, “Copy”, “Delete” and “Change type”.

Field types

When creating, you choose what the field is to store:

Group Type Stores
Text Short text up to 255 characters; optimal for sorting and searching (titles, names, identifiers)
Long text running text up to 16 million characters
Encrypted text text up to 16 million characters, stored encrypted; search only by exact match (see below)
Text with multiple selection several values from a fixed list (check boxes)
Full text collects the text of other fields for the full-text search (see below)
Password stored encrypted irreversibly (hashed)
Date / time Date + time, time-zone dependent point in time with conversion between time zones (appointments, publications)
Date + time date and time without conversion (birthdays, reference dates)
Time time without conversion (opening hours)
Number Integer whole numbers
Bit two states: 0 or 1 (switch)
Currency amount amount with exactly 2 decimal places, exact without rounding losses
Floating-point number high precision, minimal rounding differences possible
Exact decimal number up to 10 digits before and 10 after the decimal point, exact
Relation Relation (bidirectional, n:1) exactly one record of another area, e.g. the main category
Relation (bidirectional, n:m) several records of another area, e.g. tags
Pointer (unidirectional, n:1) one-sided pointer to exactly one record
Pointer (unidirectional, n:m) one-sided pointer to several records, e.g. “Related articles”
Hard-linked content (1:n) exclusively assigned content, e.g. text-image blocks
Embedded content (1:n) as above, edited in the mask of the record
File File upload upload field for files
File version (thumbnail) automatically generated version of an uploaded file – generated preview image, reduced video … (see chapter 8)
Meta information Publication date, expiry date, design template, status, review required, parent record, position meta fields, see below

Choose the type with care – it determines how inputs are validated, sorted, and searched. Numbers in a text field sort alphabetically (10 before 9), and a date in a text field cannot be filtered by periods.

The following list of input options varies depending on the field type:

Name, label and help texts

Setting Meaning
Name internal name, required for the templates, e.g. titel. Begins with a lowercase letter, followed by lowercase letters, digits, and _ (see naming rules)
Display type see below
Position after... selection of the preceding field (order)
Label caption in the input, search form, and list
Note appears below the label
Tooltip appears when hovering over the label
Prefix, suffix appear to the left or right of the input field, e.g. “€” or “cm”
Width, height size of the input field
Visibility setting whether the field appears in the input mask, search form, and record list.
Main field the field represents the record in lists and relations (one per area)
Multilingual see below

Button “Snippets” : In the input fields “Label”, “Note”, and “Tooltip”, you can enter the name of a snippet instead of a text, prefixed with #, for example #label_file. The text then appears in the respective language of the back-end user. A click on the button allows a search across all registered snippets.

For common field and area names, camindo CMS already includes the translations in all back-end languages – a custom snippet is not necessary for these. Examples:

#label_customers, #label_profiles, #label_comments, #label_products, #label_articles, #label_shopitems, #label_categories, #label_tags, #label_pages, #label_news, #label_events, #label_contacts, #label_text_image_blocks, #label_documents, #label_downloads, #label_files, #label_images, #label_media, #label_videos, #label_title, #label_subtitle, #label_teaser, #label_text, #label_description, #label_author, #label_date, #label_price, #label_document, #label_file, #label_image, #label_link, #label_icon, #label_url

For example, #label_customers as the label of the area “Customers”: the counter-field of a relation takes over the label of the area and then also appears translated.

Field content

Setting Meaning
Default value value that is pre-filled in the input mask for new records (default value). For date fields, date functions are also allowed, see below
Processing the value entered in the record is processed according to the ticked rules (“Trim whitespace” removes spaces at the beginning and end; “Strip HTML tags” leaves only plain text)

Date functions

For date and time fields, the default value may be a date function. It is evaluated when the record is saved:

Default value yields
now, today now, or today at 0:00
now+2h, today+7d, today-1d with an offset in seconds (s), minutes (m), hours (h), or days (d)
UTC_TIMESTAMP(), UTC_DATE(), UTC_TIME() current point in time, day, or time in world time
NOW(), CURRENT_TIMESTAMP(), CURDATE(), CURTIME() the same in the time zone of the database

Display type

The display type determines how the field appears in the mask:

Display type for
Single-line / multi-line text input text, number, date
E-mail, phone, password input short text or password
Dropdown (single selection), radio buttons relations, text, number
Dropdown (multiple selection), check boxes relations, texts (JSON array)
Switch (on/off) bit (0 or 1)
Lookup field relations – once the target area has many records
Embedded content that belongs exclusively to the record
Upload field files
File version preview images, reduced videos

For selection lists (dropdown, radio buttons, check boxes), enter the options in the field “Default value” – one option per line. The first option is the default value. If you do not want to enter a default value, insert a line break at the beginning (an empty line).

Input validation

Setting Effect
Required field the field must not remain empty
Not zero number fields must not be 0
Minimum / maximum for texts the length, for numbers and dates the value, for relations and multiple selection the number. The comparison operator determines whether the limit itself is allowed (>=) or not (>)
RegEx your own check pattern as a regular expression, e.g. [A-Z]{2}-[0-9]{4} for article numbers such as “AB-1234”
Value unique in the content area no other record of the area may have the same value
Value unique in the database no other record of the installation may have the same value in this field
Store content encrypted see below

Multilingual fields

A multilingual field has its own content for each language of the instance. If a translation is missing, the content of the main language applies (fallback). Single-language fields have the same value in every language. Fields that can be multilingual: text (including “Encrypted text” and “Text with multiple selection”), number, bit, date, time, and file upload. Thumbnail fields take over the setting of their source: with a multilingual source, a separate thumbnail is created for each language; in languages without their own file, the thumbnail of the main language applies. A full text has no “Multilingual” switch, but is always created per language – from the content of its source fields in this language (for fields that have not been translated, from the fallback of the main language). Relation, password, and meta field are always single-language.

Relations

Setting Meaning
Related content area target area of a bidirectional relation
Related content areas target areas of a unidirectional pointer or of hard-linked content – also several
Counter-field For bidirectional relations, camindo CMS automatically creates the counter-field in the target area: if an article is linked with a tag, the tag knows the article
Manual sorting the editorial team determines the order of the linked records (for example the images of a gallery)
Detailed list in the lookup field, the preview of the linked records shows all fields that are also visible in the record list of the area instead of only the main field, but here one below the other

Target areas must belong to the same instance or to an instance that may read this one. If you deselect a target area in which relations still exist, camindo CMS asks whether these relations should be deleted.

A relation “n:1” is not merely a restriction to one record: templates can sort and filter very quickly by fields of the linked record. Choose this relation type where only one target makes sense in terms of content, for example a category for the article.

Hard-linked and embedded content

Hard-linked content forms a page construction kit: a page consists of blocks – text block, image block, quote, gallery –, each block type is its own content area with its own fields.

  1. For each block type, create a content area and enable “Dependent area” there. Only dependent areas can be selected as a target.
  2. Give the page area a field “Embedded content” (or “Hard-linked content” with a search window) and select the block areas as the target.

Each block belongs to exactly one page. It is saved, copied, moved, and deleted together with it. With the type “Embedded”, the editorial team edits all blocks directly in the input mask of the page: blocks can be added, re-sorted, and removed.

Files

An upload field accepts a file. Under “Allowed file types”, you restrict the permitted formats – as a MIME type (image/jpeg, image/*) or extension (.pdf), one entry per line. Executable files such as .php are always forbidden, and camindo CMS checks whether the content of a file matches its extension.

Under “Authentication”, the content of a file can be protected from access by unauthorized website visitors (see chapter 13).

Thumbnail fields automatically generate further versions from the file of an upload field: reduced images, preview images, web-compatible videos, still images from videos. You select the source (the upload field or another thumbnail) and one or more converters. The converters are processed in the set order; the first converter that matches the file type creates the version. Details in chapter 8.

Full text

A full-text field collects the content of several text fields in its own search index (in addition to fulltext, which is automatically available in every content area). Under “Source fields”, you select the fields; for upload fields, the file name counts (slant-front.jpg becomes “slant front slantfront jpg”: binding and underscore words are stored separately and together in the index). Encrypted fields and thumbnails are not available for selection. Below the fields of the area follow – each with the name of the target area and of the relation field as a heading – the fields of the target areas of its fields of the type:

  • “Hard-linked content”: this way, the search finds a product, for example, via the titles of its images. If a linked content item changes, the full text of its record is updated immediately – the content belongs to the record after all.
  • “Relation” (bidirectional): this way, the search finds a product, for example, via the names of its tags. However, if a linked record changes – for example the name of a tag – this only reaches the full text of the product the next time the product is saved. For such areas, therefore, run the task “Rebuild full text” regularly, for example at night.

Fields behind a “Pointer” are not available for selection: a pointer is a loose, one-sided reference to external content, often in several areas – whose texts do not describe the record itself. The full-text field is updated automatically with every save and has no input of its own; it appears only in the search form. The content is created per language of the website: in the English version, the search finds the English texts. HTML markup from formatted texts is not included. The search supports the syntax +must -mustnot (see editors handbook).

The standard full text

Each record area automatically has a full text across all fields of the type “Short text”, “Long text” and JSON as well as the file names of the upload fields – without you creating an input field for it. In addition, the same fields of the target areas of its fields of the type “Hard-linked content” (one level deep) are included. New fields are added automatically, deleted fields are removed; encrypted fields and passwords are never included. It therefore does not appear in the field list of the area, but in every search form as the search field “Full text” – also in the cross-area search. Internally, it is called fulltext.

What the full text of a record contains is shown in its detail view in the tab “Metadata” under “Show full text” – together with the self-created full-text fields, for each language. Full-text fields do not appear in the tab “Content”.

Rebuilding the full text

When saving a record, its full text is updated automatically. After certain changes to the structure, however, the full text of all existing records no longer matches the source fields:

  • You create a full-text field or change its source fields,
  • a source field is deleted, receives another type, or changes between single-language and multilingual,
  • the target areas of a field of the type “Hard-linked content” change or the field is deleted,
  • an instance receives a language with different sorting rules, or the code of a language changes (this affects all areas of the instance).

camindo CMS does not rebuild the full text immediately in these cases – for large areas, this would hold up ongoing operation, and with larger restructurings, it would be pointless after every step. Instead, the detail view and the field list of the area show the note “The full text of this area has been outdated since …”. Until it is updated, the search there may still find old content.

  • “Update now” in the note starts the rebuild as a background process. While it runs, “Background process ‘Update full text’ started” appears there; a second rebuild of the same area does not start.
  • Or leave it to the Scheduler: The task “Rebuild full text” with the option “Outdated areas only” – set up during installation, every night – processes all marked areas. Without the option, it rebuilds the selected areas (without a selection: all) in any case. It skips an area that another process is currently rebuilding.

After a complete rebuild, the note disappears.

Exception: If you convert a source field to “Encrypted text”, camindo CMS rebuilds the full text immediately – otherwise the plain text would remain in the search index until then.

Content changes to records behind a “Relation” do not mark the full text as outdated. To pick them up, run the Scheduler task “Rebuild full text” without “Outdated areas only”.

Encrypted fields

With the type “Encrypted text”, the content is stored encrypted in the database – suitable for personal data such as phone numbers or e-mail addresses. Even someone who has access to the database or a database backup cannot read it without the key file .camindo.key.

The price: encrypted fields can only be searched by their exact value, not with wildcards, and cannot be sorted meaningfully. In the CSV export, they appear in plain text.

You choose the type when creating the field or convert an existing text field later (see Changing the type).

Meta fields

camindo CMS stores some information for each record, independently of its area. So that the editorial team can edit it, you add it to the mask as a meta field:

Meta field Meaning
Publication date the record appears on the website on a schedule
Expiry date the record disappears from the lists of the website at the specified time
Design template selection of one of the selectable templates
Status processing state; only “Enabled” appears on the website
Review required marker for content that still needs to be reviewed
Parent record turns the area into a tree (pages with subpages)
Position order of the records (“Place after...”)

Without the meta field “Status”, new records are released immediately; without publication and expiry date, they are visible without limit.

Special field names

Name Effect
slug A field “Short text” with this name provides the web address of the record (/summer-party-2026 instead of /1758). If it remains empty, camindo CMS fills it from the main field when saving. A slug, once set, does not change with the title – links already shared remain valid. It may contain slashes (products/shoes) and can be multilingual. The address including the slug prefix of the area must be unique – in areas with different prefixes, the same slug may occur

Changing the type

The type and multilingualism of an existing field cannot be changed in the editing mask, but only via “Change type” in the detail view. camindo CMS converts all existing content in the process:

  • between text and number types, for example “Short text” ↔ “Integer” or “Currency amount”,
  • between “n:m” and “n:1” for relations and pointers,
  • between single-language and multilingual.

If a conversion can lose content (a text that is not a number; when switching to “n:1”, all relations except the first; when switching to single-language, all translations), the mask warns beforehand. If the field name is also used in other areas – also by a field in the trash –, the field must be renamed first; the type is bound to the name.

When converting to “Encrypted text”, the field is removed from all full-text fields and the standard full text; camindo CMS rebuilds these for all records of the area (also for deleted ones in the trash), so that no plain text remains in the search index. If the field is the main field of the area, lists and relations then show “ID …” instead of the content.

If records of the area are currently open for editing, the confirmation names their number and the editors. The conversion does not respect the edit lock. The cached working copy of the open records is converted as well – including newly uploaded files that have not been saved yet. When a file field is changed to single-language, only the file of the main language remains. However, the open input mask still knows the old field type: unsaved input in it may then no longer be saveable (for example after the change to single-language); in that case, the only option is to cancel the editing and reopen the record. Therefore, ask the editors to save or cancel beforehand.

Deleting an input field

“Delete” in the detail view moves the field to the trash. Its contents are kept until the permanent deletion, but the field itself disappears everywhere at once: from the input and search mask, from the record list, from export and output. Templates no longer find anything via the field and no longer sort by it – as if the area did not have the field.

The following go to the trash together with the field:

  • for a bidirectional relation, the counter field in the target area,
  • for an upload field, its file versions (thumbnails).

If the field is set in the area as “Main field”, “Default sort order” or for the login (name, password), this setting is cleared; restoring sets it back, unless another field has been entered in the meantime. The files of an upload field stay in place but can no longer be retrieved directly. Full texts that had the field as a source are marked as outdated.

As long as the field is in the trash, its name is taken in the area: a new field with the same name can only be created once the old one has been deleted permanently or restored. A field parent_id keeps the tree structure of the area until the permanent deletion.

Superusers restore deleted fields in the trash – with contents, counter field and file versions. After the retention period, the scheduler task “Empty trash” deletes the field and its contents permanently. If a whole area is deleted, its input fields go to the trash with it (see chapter 4).

Naming rules

  • Field names begin with a lowercase letter a-z and consist thereafter of lowercase letters, digits, and the underscore _. Names that begin with a digit or an underscore are not allowed (camindo CMS itself uses names beginning with _, such as _default). Minimum length for names: 2 characters.
  • The name fulltext is reserved for the standard full text.
  • A field name is bound system-wide to one type: once titel has been created as “Short text”, titel cannot be a number in any other area. This is the basis for the cross-area query.

Reserved name prefixes

To avoid type conflicts, the name prefixes listed below are reserved. They are particularly suitable for template sets: when content areas are exported to foreign databases, the name prefixes ensure that the names in the foreign database are not already assigned to other types.

Field type Prefix
bit bit_
chars chr_
currency cur_
datetime dat_
decimal dec_
encrypted enc_
file fil_
float flt_
fulltext ftx_
id rid_
ids ids_
integer int_
json jsn_
password pwd_
pointer, relation, link rel_
text txt_
time tim_
timestampl tsl_

Extensions

An extension adds additional behavior to an input field – a text editor, a calendar, a symbol selection. You select it in the field under “Extension”; only extensions that match the field and display type are offered. The available extensions are described in chapter 11.

6 Managing templates

How to write templates is explained in the handbook for template programmers. This chapter describes how templates are organized and assigned to the content.

Template areas

Templates are kept in template areas (area type TEMPLATE, created in the module “Structure”). Like content areas, they belong to an instance and can be grouped in folders. It has proven useful to have one template area per function or template set – for example “Layout” for the page frame, header and footer, “News” for the templates of the news area.

In the module “Templates”, you open a template area and edit its templates there.

Setting Meaning
Name (internal) name under which templates and addresses refer to the template. It only has to be unique within the template area; if it exists more than once, it is referred to as areaname.name
Label caption that appears in the record when a template is selected (optional)
Standalone the template may be called without a record, via the address /0,name – for example for a search page, a sitemap, or a contact form
Usage “Scheduled (scheduler)”: the template only runs as a task of the Scheduler and can never be called via an address (not even as a standalone template). Setting in the tab “Access”
Code HTML with CMS commands and PHP

The detail view shows the code with color highlighting. Errors in the code – an unknown command, a wrong parameter, a missing bracket – do not prevent saving, but are displayed with the line number above the code. References to other templates are clickable. If the referenced template does not exist yet, camindo CMS offers to create it right away; a list template (parameter resulttemplate of CMS:SELECT and CMS:SELECT_LANGVERSION) is created with the loop (CMS:RESULT_START) … (CMS:RESULT_END) as its code.

Insert CMS command: To the right above the code field of the input mask is a selection of all CMS commands (alphabetical, including the commands of add-on modules). When you choose one, a mask with all parameters of the command opens: one input field per parameter for the value, in front of it fields for the keys where needed (filter[price]) and – for filter – a selection of the comparison operator (=, ==, !=, <, >= …). Parameters with keys may occur several times; the small “+” below them adds another row. The question mark next to the parameter name shows its description; mandatory parameters are marked with *. “Insert” places the command at the cursor position in the code – parameters without a value are left out, from three parameters on each stands on its own line. It is only stored when you save the template.

A template can be moved to another template area – with the “Move” button of the template view or the context menu of the template list. All template areas you may write to are offered – with several instances grouped by instance: the instance name as a non-selectable entry, its template areas below it. The assignments are retained, the file moves to the directory of the target area. If the name already exists in the target area, the template is not moved. If the name then occurs in several areas, references from other templates have to name the area as well (area.name).

Assigning templates to content areas

Which template a record receives in the frontend is decided by camindo CMS in this order:

  1. a URL override template in the address (/birthday-party,print) – only if it is released for the content area,
  2. the template selected in the record (meta field “Design template”) – only from the selectable templates,
  3. the default template of the content area.

You maintain the assignment at two equivalent places: in the settings of the area or on the tab “Access” of the template (“Add content area”, then for each area “Default”, “Selectable” and “URL override”). The list “Add content area” offers only the content areas of the template's instance and of the instances whose content this instance may read; each instance appears there like a folder, with its areas indented below it.

A template that is requested via the address without being released for it is not delivered. In this way, no internal templates can be called from outside.

Templates in the file system

Each template is also stored as a file on the server:

var/templates/{instance_id}/{area_name}/{template_name}.cms.php

This makes it possible to work with a familiar code editor, a version control system (Git), or a deployment tool. camindo CMS synchronizes the files with the back end every minute (and immediately via the button “Refresh” of the template lists):

In the file system Effect in the back end
new file new template is created
file changed the template content is read in again
file renamed the old template is deleted, a new one is created
file deleted template is deleted – including its assignments
new directory new template area is created

Conversely, the back end writes every change back to the file.
It is better to rename templates in the back end: in the file system, renaming means “delete and create anew”, and the assignments are lost.

Overview templates

If a content area uses a slug prefix (for example events), each record has an address such as /en/events/company-party. The address consisting of the prefix alone – /en/events – shows the overview template of the area: typically the list of its records. It runs without a record and is selected in the settings of the area under “Template assignment”. Because the prefix is translated for each language, the overview also has its address in each language (/en/events). Without an overview template, this address results in “Page not found”.

Instead of a record, the overview template provides the details of the area – the ID (area_id), the name, the label, the instance, and the slug prefix in the requested language. This way, the template queries the records of its area without having to enter the area explicitly (see the handbook for template programmers).

If you have assigned an overview template and a slug prefix to an area, the button “Preview” appears in the settings of the area and above its record list. It shows the overview as it appears in the frontend – with several languages as a selection with one entry per language, each under the slug prefix of that language.

List templates for the back end

A content area can receive a list template. It is not used in the frontend, but in the back end: it designs an additional column of the record list for each record and the detailed preview in lookup fields – for example, a color field instead of the entered RGB color value, or an entire text-image block in its final layout.

Template sets

How you pass on templates together with their content areas as a template set, and use them in other installations, is described in chapter 9.

7 Users and rights

Anyone who works in the back end is a user – regardless of their role: as an editor, translator, administrator, or programmer. You manage users and their user groups in the module “Users”. Each user belongs to exactly one user group; it determines what they may see and do. Users of the group Superuser have full rights for everything.

The start page of the module “Users” lists all users (whom you may see with your group rights). Superusers are marked with a hexagonal symbol . Click the button “New” or select an entry from the list. The input/edit mask for this user appears.

Input fields

Item Meaning
User name sign-in name: begins with a lowercase letter, followed by lowercase letters, digits, and _. New names must not begin with admin_ – reserved for anonymized accounts
Password stored only as a check value (hash) and cannot be read; leave empty = unchanged. It must meet the password policy – the requirements appear while you type. When creating, you assign a initial password. Only a superuser can change the password of another existing user
Previous password,
your password
only for superusers when changing an existing account: anyone who changes their own password enters the previous one; anyone who changes the password of another user confirms this with their own password – an open session is therefore not enough to take over an account. When creating, this field is omitted. Everyone else changes their password in the personal settings
User group user group with its rights
Status “Enabled” or “Disabled” – a disabled user cannot sign in
Main (last) name,
Displayed name,
Sort value
names for lists and messages
E-mail,
mobile,
landline number
contact data, stored encrypted. The e-mail address is required for the sign-in code of the two-factor sign-in

Each user maintains their personal settings (language, date format, time zone, widgets, password, devices …) themselves; a superuser can also change them for others.

Deleting users: Deleted users are retained as “deleted”, so that records and the audit log continue to show who created and edited them. When deleting, camindo CMS removes everything with which the account could still sign in: its sessions (it is signed out immediately), the saved devices, passkeys and recovery codes, as well as the list of recently edited records and templates. The entries in the audit log remain. Your own account cannot be deleted. To remove the name and contact data of a deleted user, use Anonymize (see module “Privacy”, tab “Deleted users”).

Must change password: A password that another user has set must be changed at the first sign-in – for example the initial password when creating the account, or one reset by a superuser. The configuration setting admin.password_retention_days (0 = unlimited) likewise forces a password change after the set number of days. A new password must then differ from the previous one. The detail view shows under “Password” when it was last changed and whether a change is pending.

User groups

Only superusers see the tab “User groups”. The list shows all existing user groups. After installation, there is only one group, “Superuser”.

Creating a user group: For daily work, a separate group with restricted rights should be created, for example the group “Translators”, “Editors” or “Interns”. To do so, click the button “New”.

Deleting a user group: If users are still assigned to the user group, choose a replacement group when deleting, to which these users are assigned. Deleted users of the group lose their group assignment in the process – without a prompt; if the group has only deleted users, no selection appears. The group “Superuser” is not available for selection.

Interfaces

Under “Interfaces”, you determine through which channels the users of the user group may sign in:

Interface Purpose
Back end the familiar work surface in the browser
Command line (CLI) programs on the server, for example php Core/cron/camindo.php api … (see Operation) – without a password, because anyone who can operate the command line of the server has full access in any case
PHP API programs that sign in with name and password

The same rights of the user group apply on every interface; the audit log shows the user together with the channel (for example cli). A new user group has only the back end. If you withdraw an interface from a user group, ongoing sessions there end at the next access. For programs, it is recommended to use a separate user in a separate user group that has only the necessary interfaces and rights.

The rights of a user group have four levels:

1. Modules and additional rights

Which modules the user group may open. Some modules have additional rights for individual parts, indented below them – without an additional right, the part remains blocked:

Module Additional rights
Users “Read user details: own user group” · “Read user details: other user groups” · “Create users: own user group (with initial password)” · “Change and lock users: own user group (without password)”
Privacy “Create and change deletion policies” (only for accessible instances) · “Export personal data” (profiles from readable areas, users from visible user groups) · “Maintain templates of the personal data report” (accessible instances) · “Anonymize deleted users” (accessible user groups) – see chapter 15
Content “CSV import”, “Media import (ZIP file)”, “CSV export”, “DOCX import (translation)”, “DOCX export (translation)”, “Batch processing”
Tools one right each for the tabs “Server”, “Database”, “Data”, “Developer” and “Trash of a user” (“Permanently empty the trash of a website user”)

Reserved exclusively for superusers are: the management of user groups, the area rights, the XML export and import (backup and structure transfer), and the module “Log files”.

Without an entry in the user group, every user has access to internal messages, personal settings, and the trash. In the trash, a user may restore only what they would be allowed to delete in that area.

2. Instances

Which instances (websites) the user group sees. If you assign an instance to a user group, it initially receives all areas of this instance with all rights (except “Set owner”). You can restrict this afterwards in the tab “Area permissions”.

3. Languages

Two check boxes per language: read and write. Anyone who may write may also read – a check mark for “write” also sets “read”. A click on the column heading “read” or “write” ticks the whole column – or unticks it, if all check marks are already set there. Without check marks, the user group has no access to the language.

The setting applies to all instances and to the content in the module “Content”: only readable languages appear in the input mask, only writable ones can be changed there. In this way, a translator can read all languages but edit only French.

4. Set status

One check box per status: the ticked statuses may be assigned to a record by users of the group. Reading is permitted for all statuses – only setting is restricted. In this way, you map a flat workflow, for example: the editorial team may create posts and set them to “For review”, but may not release them – that is done by the editor-in-chief. Forwarding happens via internal messages. There are two ways to set access rights here: (here) in the user group, you select all statuses for a group; in the module “Status”, you select all user groups for a status.

Status in the record input mask:

  • The input field “Status” shows only statuses that the user may set.
  • A record whose current status cannot be set by the user cannot be edited by them: the button “Edit” is grayed out, and the entry is missing from the context menu of the list. Reading, copying, and – depending on the area right – deleting remain possible.

5. Area rights

After a user group has been created, its info page shows the tab “Area permissions”. Here it is listed what the users of this group may do with the records of different areas. Click the button “Edit” to make adjustments.

Right Meaning
Read the area is visible for the user group – without this right, it does not exist for them
Own user group: write / delete edit or delete records whose owner belongs to the own user group
Other user group: write / delete the same for records whose owner belongs to another user group
Advanced (adjust owner) change the owner of a record (selection at the end of the input mask)

Template areas know only read and write. Folders have no rights of their own.

You maintain the area rights at two equivalent places:

  • (here) in the module “Users”, tab “Area permissions”: all areas for one user group,
  • in the module “Structure”, tab “Access”: all user groups for one area.

The owner principle

Each record belongs to a user, its owner – initially the one who created it. Via the user group of the owner, the area rights distinguish between own and foreign content. Examples:

  • Two editorial teams, one area: the user groups “Editorial team North” and “Editorial team South” may read all news items, but only write and delete those of their own user group.
  • Release by the editor-in-chief: the user group “Authors” may write their own articles but not delete them; the “Editor-in-chief” may write and delete all articles.
  • Handover: when an employee leaves the editorial team, a user with the right “Set owner” changes the owner of their records.

Records that frontend users create receive the owner that the template or the configuration specifies (see chapter 13).

What the rights do

The interface shows only what is released for the user group: modules, areas, buttons, menu entries, languages. Regardless of this, camindo CMS checks every action also when saving and deleting – a right therefore cannot be bypassed by a manipulated request. Background processes (import, export, batch processing) run with the rights of the user who started them.

Relation fields whose target areas the user group may not read do not appear in the mask; their relations remain untouched when saving. Likewise, multilingual content that the user group may edit only in selected languages: the other content remains intact when saving.

Sign-in protection

  • Two-factor sign-in: After the password, a code is sent to the user's e-mail address. Switch it on in the configuration (see chapter 12).
  • Authenticator app (TOTP): Each user can set up an authenticator app in their personal settings; then the sign-in asks for its code instead of the e-mail code. Ten recovery codes are also provided (instructions in the handbook for editors).
    With security.admin_2fa = 'totp_required', it is mandatory for superusers – anyone who does not yet have it must set it up at the next sign-in in the sign-in dialog. With 'email', it is switched off (see chapter 12).
  • Passkeys: With a fixed back-end domain, each user can create passkeys (fingerprint, face, device PIN, security key) – the most secure form of the second factor (handbook for editors). Resetting as described below also removes them.
  • Reset the second factor: If a user has lost their phone or passkeys and their recovery codes, a superuser opens the user's detail view, tab “Two-factor sign-in”, and clicks “Reset second factor” (removes the app and passkeys). The user then signs in again with the e-mail code (or sets up the app again if it is mandatory) and receives an e-mail about it. The reset is recorded in the audit log with the name of the superuser. Make sure beforehand – for example by phone – that the request really comes from the account holder.
  • Switch back to the e-mail code: Anyone who no longer wants to use the authenticator app switches back in their settings with “Switch to code by email” (confirmed with a code from the app). If the e-mail code is not switched on for everyone, each user may switch it on for themselves.
    (If the app is mandatory for all users by configuration, it cannot be switched back to the e-mail code.)
  • Notice for a new device: If a user signs in from a browser that camindo CMS does not yet know, an e-mail is sent to their address – including the device, IP address, and time (see chapter 12).
  • Devices and sessions: Each user sees their sign-in devices with the running sessions in their personal settings, can remove individual devices (their sessions end immediately), and end all other sessions. A superuser can, in the detail view of another user, tab “Devices and sessions”, end all of their devices and sessions with “Remove all devices” – for example after a lost laptop. Both are recorded in the audit log. “Sign out” ends only the session in one's own browser.
  • Activities: A superuser sees what a user has changed in their detail view, tab “Activity” – the recently edited records and all their entries in the audit log.
  • A new password ends other sessions: When a password is changed (by the user themselves or by the superuser), all other sessions of the account end.
  • Protection against password guessing: After several failed attempts (default: 3 in 10 minutes), sign-in is blocked for the user name. Blocks are recorded in the security log.
  • Automatic sign-out after 30 minutes without activity (configurable).
  • Rights and sessions are signed directly in the database to prevent manipulation: anyone who has only access to the database cannot use it to acquire rights.

8 Media: uploads, converters and thumbnails

camindo CMS accepts files via upload fields and automatically creates from them the versions that the website needs: reduced images in several sizes, preview images, web-compatible videos, still images from videos, compressed audio files. Three terms are important here:

Term Meaning
Processor a program module that can convert files – for example “GD library” or “FFmpeg (video)”
Converter a configured processor: which files it converts how – for example “Image 800 px wide, JPEG, quality 80”
Thumbnail field a field that generates a version from the file of another field with one or more converters

Processors

The following processors are delivered with the core:

Processor Task Requirement
GD library scale, crop, convert images – basic functions without additional software PHP extension gd
ImageMagick images in very many formats (also HEIC, TIFF, PSD), expert mode with your own parameters, also in the background ImageMagick on the server
FFmpeg (video) convert, scale, compress videos ffmpeg
FFmpeg (audio) convert audio files, align loudness, excerpts ffmpeg
FFmpeg (preview image) still image from a video ffmpeg

Further processors can be installed later as components. Whether ImageMagick and ffmpeg are found is shown by the module “Tools” on the tab “Server”.

Setting up converters

You create converters in the module “Media converters”. The installation already sets up the converter “Internal Image Preview (GD)”: it converts every image format the GD library reads into JPEG images of at most 200 px width and 300 px height.

Setting Meaning
Processor which processor does the work
Label name of the converter
Included media types which files the converter applies to: MIME type (image/jpeg), group (image/*) or extension (.pdf), one entry per line
Excluded media types exceptions, for example image/gif, so that animated GIFs remain unchanged
File extension format of the generated file, e.g. .jpg or .mp4; empty = keep the format
Conversion options depending on the processor: width and height, fit or crop, only reduce or only enlarge the size, quality, background color, codec, data rate, excerpt, remove metadata, conversion in the background …

Size with ImageMagick and the GD library: In addition to “Target width” and “Target height”, four settings determine how the image is brought to this size:

Setting Choice Effect
Fit to format Fit the whole image into the format the whole image fits inside, one side may be shorter
Fill the format completely and crop fills the format; overhang is cropped at the center
Fill the format completely, let it overhang (^) fills the format; one side may be longer (nothing is cropped)
Force the exact format, ignore the aspect ratio (!) width and height exact – the image is distorted in the process
Resize Always / Shrink only / Enlarge only “Shrink only”: smaller images keep their size – a thumbnail 440 px wide is not scaled up from a 300 px wide image. “Enlarge only”: larger images keep their size
Unit Pixels / Percent of the original (%) with percent, target width and height are percentage values, e.g. 50 for half the size (only with fit or distort)
Maximum number of pixels number scales proportionally until width × height equals this value, e.g. 1000000 for one megapixel; applies after target width and height and likewise follows “Resize” – with “Shrink only”, only larger images are reduced, with “Always”, smaller ones are enlarged as well

Fill, crop, and distort require both dimensions; if one is missing, fit applies. Cropping with “Shrink only” crops smaller images only to the target format at the center, as far as the image extends – there is no border in the background color.

Differences with the GD library: Instead of “Fit to format”, there is the setting “Crop” with only two choices: “Fit entirely into new dimensions” and “Fill new dimensions completely” (crops at the center). “Resize”, “Unit” and “Maximum number of pixels” work as with ImageMagick; percent applies only without cropping. One percentage value applies to both sides; two different percentage values scale width and height separately.

Further settings:

Setting Choice Effect
Quality percent quality of compressed formats such as JPEG; 100 % = best quality, but the largest file
Number of colors number or TrueColor The formats GIF and PNG allow a reduced color palette (at most 256 colors). The number can be entered here, e.g. 2, 8 or 128. TrueColor: do not reduce the color palette
Transparency keep alpha channel switched on: transparent areas remain transparent; switched off: they are filled with the background color
Background color hexadecimal value, e.g. #ffffff replaces the transparency if the alpha channel is not kept

The detail view of a converter shows on the tab “Used by …” which thumbnail fields use it. If a converter in use is deleted, these fields no longer generate versions – the confirmation warns about this.

PDF and SVG with ImageMagick: camindo CMS allows ImageMagick to work only with its own security policy. SVG and PDF are blocked in it until you release them in .camindo.php:

'security' => [
    'magick_allow' => ['svg'],          // or ['svg', 'pdf']
],

ImageMagick reads PDF via Ghostscript, which has repeatedly been a gateway for attacks via manipulated files – release it only if you need it. Whether a format really works is shown in the detail view of the converter under “Security policy”: the policy of the server can also block it. PDF is blocked by many providers; camindo CMS cannot change this. More on this: Operation – securing ImageMagick.

Thumbnail fields

A thumbnail field (field type “File version (thumbnail)”) has a source – an upload field or another thumbnail field – and a sorted list of converters. For each new file, camindo CMS goes through the converters in turn; the first one whose media types match creates the version. So a field “preview” can use an image converter for images and a still-image converter for videos.

Thumbnail fields can be chained. Example for a video:

video                  upload field
 ├── video_web         FFmpeg (video): MP4/H.264, 1920 px, in the background
 └── video_standbild   FFmpeg (preview image): still image at 10 % of the runtime
      └── video_teaser ImageMagick: 400 × 225 px cropped, JPEG

The versions are created when the record is saved, once – not with every interim save. Images are converted immediately; videos and other complex conversions run as a background process (for this, the cron job must be running). Until then, the back end shows a placeholder image.

The placeholder image remains in the view even after the background process has finished – the view is only refreshed by clicking the button “Refresh”.

The processor “GD library” does not work in the background. If a version is to be created from a file that is itself only generated in the background (for example from the still image of a video), use ImageMagick.

Regenerating thumbnails

If you change the settings of a converter or add a new thumbnail field to an area, this initially applies only to new uploads. For the existing content, “Tools” → “Data” → “Regenerate thumbnails” recreates the versions of all records of an area – in the background for many records.

Upload fields

Setting Meaning
Allowed file types MIME types or extensions, one entry per line; empty = all permitted
Multilingual one file per language, for example a brochure in each language
Signed-in frontend users only deliver the file only to signed-in frontend users (see chapter 13)

For security reasons, executable files (.php, .phtml, .cgi, .sh, .htaccess and others) are always forbidden, and camindo CMS checks whether the content of a file matches its extension – another format disguised as .jpg is rejected.

Large files: the upload directory

The size of an upload via the browser is limited by the PHP configuration of the server (upload_max_filesize, post_max_size). Larger files – such as videos – are transferred to the directory var/upload/ via FTP or SCP. In the input mask and during import, editors select them via “Select file on the server”, the button to the right of each upload field.

Storage and delivery

Files are stored under public/media/ in the structure {instance}/{field}/{record}.{extension}. The addresses that the templates generate also carry the original file name, for example /media/1/12/1977,beach-north.jpg – good for search engines and for visitors who save a file.

  • Domain lock: The files of an instance are delivered only via its domains (and the domains of the instances that may read them). This prevents files from being linked or displayed unintentionally via a foreign website. It is not an access control (see note below).
  • Trash: If a record is deleted, its files go with it into the trash and return when it is restored.
  • Protected fields: Files from fields with “Signed-in frontend users only” are blocked in the media directory and are delivered only after a check.

Important: the domain lock is not access protection. Which domain a request addresses is decided by the caller: they can set the host name freely (header Host). Anyone who knows the address of a file can therefore retrieve it even if they pose as another, authorized domain of the same installation. As a result, files of another instance can also be read. Complete protection can only be achieved with separate camindo installations; cf. the section No multi-tenancy.

Confidential files are protected exclusively by the field option “Only for signed-in frontend users”. The directory of the field is then completely blocked on the server (Require all denied). Delivery takes place only via file.php, and only after a valid session of a frontend user has been checked (see chapter 13).

9 Exporting and importing structures

With the XML export you write whole structure trees into one file: folders, content areas with all input fields and settings, template areas with their templates and, if you wish, all records including their files. The XML import rebuilds this structure in the same or in another camindo installation.

Typical uses:

Task Procedure
Duplicating a website structure for a new instance Export the structure and templates, import them into the new instance, rename the areas
Transferring development to the live website Export the areas, fields and templates developed on the test system and import them on the live system – existing areas are updated
Passing on template sets Provide a function with its content areas and templates as one package (see below)
Backing up areas Save the structure and contents before a major rebuild

Exporting and importing whole structures is reserved for superusers. The export and import of the records of one area is described in the manual for editors.

Exporting

Open the module “Contents” and choose “Export” in the menu on the start page. The form shows the tree of all folders and areas of all types with check boxes, grouped by instances: each instance appears like a folder with its name, below it its folders and areas. A folder or an instance selects the elements it contains; individual elements can be deselected again.

Option Effect
Export structure Folders, content areas and input fields with all settings (including the slug prefix and the deletion policy), template assignments (including the trigger templates in their order), the converters of the thumbnail fields and the snippets of the instance that the labels refer to (#custom.…)
Export contents The records of the checked content areas (all fields, all languages) and the templates of the checked template areas – together with the snippets of the instance that these templates use (custom.…)
Export upload files The files of the records are included in the zip file
Optimize templates for import into a foreign database Marks references in the templates so that they are adjusted when imported into another installation (see below)
Run as a background process The download link comes as an internal message

The result is a zip file with the XML file and, if applicable, the files. The download link works once; files that are not collected are deleted after 24 hours.

The file contains encrypted contents in encrypted form and passwords only as a check value. Both depend on the key file .camindo.key: in an installation with a different key, encrypted contents cannot be read, and imported passwords (for example those of members) no longer match – the members then have to request a new password.

Importing

In the module “Contents”, choose “Import” in the menu on the start page, upload the zip or XML file (or select it from the upload directory). The form then shows:

Setting Meaning
Instance Instance for newly created areas. Within the same installation, the instance from the file can also apply
Target level for new folders and areas Folder under which newly created areas end up. Areas whose folder is also in the file end up under that folder. Only the folders of the selected instance are offered (with “from the import file”, all folders)
Converters of the thumbnail fields For each converter in the file: use an existing one, create a new one or ignore it (see below)
For each area in the file Name (editable), mode and matching criterion – separately for structure, records and templates

The areas of the file are listed indented according to their hierarchy. A selection in the column header sets the mode for a whole column.

If labels of areas and fields refer to snippets of the instance (module “Snippets”, #custom.31.title), the import takes these snippets into the set of the target instance and rewrites the references to them (#custom.32.title). Existing snippets of the target instance remain unchanged; the log names the snippets that were taken over and the keys that were kept.

The same applies to the snippets that imported templates output – for example (CMS:SNIPPET custom.weiterlesen). They end up in the set of the instance of the template area; the templates themselves remain unchanged (the short form custom.… always means the set of the instance in which the page runs). Composed keys such as "custom.status_{$cms['status']}" bring all snippets that start with this prefix.

The slug prefix of an area is assigned to the languages of the target instance by their language codes. If it is not permitted there – for example because another area already uses the same prefix – it is dropped with a note in the log.

Mode and matching

For each area, choose a mode separately for “Structure”, “Records” and “Templates” – INSERT, UPDATE, UPDATE_OR_INSERT, REPLACE, REPLACE_OR_INSERT or IGNORE (for the meaning, see the manual for editors) – and the criterion by which an existing element is recognized:

Element Matched by
Area (structure) Name or area number
Records Slug or record number
Templates Name or template number

If the file comes from the same installation, UPDATE_OR_INSERT is preselected: existing elements are updated, missing ones are created. If it comes from a different installation, INSERT is preselected, and numbers never count as a criterion – there they belong to completely different elements. Matching then only takes place by names and slugs; new records receive new numbers. An area that does not yet exist in the target knows only INSERT and IGNORE.

With REPLACE, input fields that the file does not name are removed from the area.

What happens during the import

  1. Areas are created or updated in the order of the file, parents before children.
  2. The converters of the thumbnail fields are assigned: within the same installation by their number, otherwise by their settings. If there is no matching converter, one is created – provided its processor is installed. A thumbnail field without a converter is skipped.
  3. Input fields, then templates (they are translated again in the process), then the settings of the areas: default and sort field, default template, template assignments, trigger templates, sign-in fields. The import takes over the deletion policy only from the same installation. If the file comes from another installation, a newly created area receives the default policy of its type (“Delete record”, “Delete profile” or “Delete user-generated content”), and an existing area keeps its own.
  4. The records in two passes: first the contents, then the links – using the actual numbers of the target database. Links between imported records are thus preserved, even if all numbers change.
  5. Thumbnails and full texts are created again.

As with the import of a single area, an analysis beforehand shows what will happen, and a log afterwards shows what has happened – per area, field, template, converter and, if requested, per record.

The creation date of the records is retained. The import takes over the creator only from the same installation; otherwise the importing user applies.

Duplicating a structure

Example: you run the website of an association and want to use the same structure for a second association.

  1. Create the new instance (module “Instances”).
  2. Export the folder of the first website with “Export structure” and “Export content” – this way the templates come along. To exclude records that should not be taken over, set their areas to IGNORE for the records when importing.
  3. Import the file: instance = the new instance, mode INSERT. Area names only need to be unique within an instance and the instances that it may read or that read it. If the two websites are independent of each other, the areas keep their names, and the templates work unchanged. Otherwise, enter new names in the import form, for example association2_dates instead of dates.
  4. In the new instance, enter the start record and the start template.

Passing on template sets

A template set consists of the templates of one function and the content areas that it needs (see chapter 2). Templates refer in their CMS commands to other templates, to areas and sometimes to individual records:

(CMS:INCLUDE header)
(CMS:SELECT news_list filter[area]=announcements src_id=1758 …)

In another installation – or after renaming during the import – these names and numbers no longer match. The export option “Optimize templates for import into a foreign database” marks such references in the export file. The import replaces them with the names and numbers of the imported elements:

  • An area that is renamed during the import is also named that way in the templates.
  • A reference to a record points to the imported record; if it was not imported, to the record with the same address (slug prefix and slug) in the instance of the template.
  • What cannot be matched keeps the value from the file; the log counts these cases.

To provide a template set:

  1. Create the content areas and the template area of the set in a folder of their own. Follow the naming conventions.
  2. If necessary, create a few sample records – after the import, they show immediately how the set works.
  3. Export the folder with structure, contents, files and the option “Optimize templates for import into a foreign database”.
  4. In the target installation: import, check the default templates, adjust the design (HTML, CSS) to the website.

References that a template computes only at runtime (for example from PHP variables) cannot be recognized by the import. Therefore always test an imported template set.

10 Further modules

In addition to the modules for contents, structure, templates, instances, languages, users and media (chapters 3 to 8), camindo CMS brings further modules. You determine which of them a user group sees in the user group – every user has the trash and messages.

Status

In the module “Status” you define the processing states that records can have. Each status has a label, an icon, a color and a position in the selection list. The following are prepared:

Status Meaning
Enabled appears on the website
Disabled does not appear on the website
Unconfirmed for registrations of frontend users, until the confirmation link has been clicked

On the website, by default only records with the status “Enabled” appear. The default for blocked records is the status “Disabled”. All further statuses correspond to this – they only serve to map editorial workflows, for example “Draft”, “For review”, “Ready to enable”, “To be revised” …

CMS:SELECT only finds enabled records. Only for internal views is it possible to pass the parameter filter[state] to the command, in order to list records with a particular or any status.

In the input form of a status, you define under “May be set by” which user groups may give records this status (multiple selection; superusers may always set all statuses). A new status can initially be set by all user groups. The same setting can be found in the form of each user group – see users and rights.

When you delete a status, you choose a replacement status; all records with the previous status are switched over. The prepared statuses “Enabled” and “Disabled” cannot be deleted.

Snippets

camindo CMS knows two snippet systems – two ways to maintain fixed texts separately from the templates. They differ in where the texts are stored and in which languages they exist:

Frontend snippets Backend snippets
Languages the language versions of the instance (for example de_DE, sv_SE) the backend languages (for example de_DE, en_US)
Maintained by editors, like any content – also via frontend editing developers (language files) or users (module “Snippets”)
Where they are stored records of a content area with a multilingual text field language files lang/{language}.ini and own snippets in the module “Snippets”
How they are included in the template (CMS:TEXT id=slug) or (CMS:TEXT id=record_id) #prefix.key in the fields “Label”, “Note” and “Tooltip”

For texts that appear on the website, the frontend snippets are the intended way: they follow exactly the languages of the website.

Frontend snippets

A frontend snippet is an ordinary record – for example in a content area “Text snippets” with a field slug and a multilingual text field text. The template fetches the text via the slug or the record_id:

<p class="hint">(CMS:TEXT id=shipping-note)</p>
<p class="contact">(CMS:TEXT id=3109) (CMS:FED id=3109)</p>

Without field, CMS:TEXT reads the field text of the given record, in the language of the page called; if a text is not translated, the text of the primary language appears. With CMS:FED id=…, each text block receives its own pencil symbol for frontend editing. The editorial team maintains these texts like all content – with the status, publication date and rights of the content area. The area does not need a default template.

Backend snippets

Backend snippets are the texts of the user interface. They come from three sources and are addressed by a prefix:

Source Prefix Example
Own snippets of the instance (module “Snippets”, see below) custom.{instance_id} #custom.12.label_price
Core: Core/lang/{language}.ini none #button_save
Modules: lang/ and backend/lang/ in the directory of the module name of the module in lowercase letters #area.field_area_slug_prefix

In the input fields “Label”, “Note” and “Tooltip” (content areas and their fields), instead of a single-language text the reference to a multilingual snippet is inserted – form: #prefix.key (see chapter 5). When displaying, the backend inserts the text of the backend language of the signed-in user (internally via Cms::textOrSnippet()); the extension Snippets offers a search there.

Backend snippets are also available in the frontend: the core uses them, for example, for the messages of the CMS:USER_* commands or for the ellipsis of CMS:TEXT limit=…. Because backend snippets exist only in the backend languages, each language version is assigned a backend language (setting “Backend language” in the module “Languages”): de_DE and de_AT both read the snippets of de_DE. A language version for which there is no backend translation shows the one in en_US. Templates can include backend snippets using $this->cms->snippet('custom.key').

Installing additional languages

Further backend languages come as translation packages in the form of .ini files. The language is at the beginning of the file name: sv_SE.ini, sv_SE_core.ini, sv_SE_ext_password.ini. These are stored in var/translation/translated/. A package takes effect after the snippet cache has been rebuilt (module “Tools”, tab “Developer”, or on the command line with php Core/cron/camindo.php refresh snippets).

Developing your own language pack

To translate the backend of camindo CMS itself into a further language, copy the translation templates from the directory var/translation/blueprint/.

en_US_core.ini becomes {language}_core.ini, en_US_en_US.ini becomes en_US_{language}.ini. {language}_core.ini contains all translations, en_US_{language}.ini registers the new language in the English language version and thus for all other languages.

Example for an en_US_no_NO.ini:

[backend]
backend_language_no_NO = "Norsk"

Example for a no_NO_core.ini:

[frontend]
core.yes = "Ja"

[backend]
core.button_save = "Lagre"
ext.datepicker.option_mode = "Modus"

Anything that a package does not translate appears in English.

Snippets module

In the module “Snippets” you maintain your own backend snippets – labels such as “Read more”, “Search” or a note in the footer – without access to the server or templates, in all backend languages.

Each instance has its own snippet set. The module lists the instances to which your user group has access, with the state of the translations: which languages are complete, which are partially translated (with the number) and which have no texts yet. A click opens the set as a table: one row per key, one column per displayed language. You select which languages appear as columns via “Languages” above the table (tick the boxes, click “Apply”); without a selection, you only see English. The selection applies to all sets and remains until you sign out.

A click on a row opens the form with all backend languages one below the other. A right-click opens the context menu: “Edit” like the click; “Copy” opens a new form with the texts of the snippet – you only enter the key again; “Delete” shows the snippet with all texts and removes it in all languages after confirmation. “Add snippet” creates a new one. Missing translations receive the English text. Keys consist of lowercase letters, digits and underscores and cannot be changed after creation – templates and label fields refer to the snippet via them.

Use Notation
in the templates of your own instance custom.{key}
in templates of other instances custom.{instance_id}.{key}
in label fields of the backend (Label, Note, Tooltip) #custom.{instance_id}.{key} – the extension “Snippets” offers a search there

Which snippet language applies in the frontend is determined by the setting “Backend language” of the respective language version.

The sets are stored under var/custom/snippets/{instance_id}/lang/{language}.ini; if the instance is deleted, its set is deleted as well. Sets in the earlier PHP format ({language}.php, even older de.php) are converted into .ini files by the rebuild of the snippet cache – in the module “Tools” (tab “Developer”, “Snippets”), with php Core/cron/camindo.php refresh snippets and at every upgrade. If a language already has an .ini file, both are merged; for the same key, the .ini file applies. The old file remains as {language}.php.bak and can be deleted once the snippets are correct again.

Scheduler

The scheduler runs tasks on a time schedule. It requires the cron job cron.php, which runs every minute (see Cron jobs). The maintenance tasks – backup, trash, temporary files, deadlines, logs, checks – are set up by the installation (see operation). You can adjust the time of execution.

Task Effect
URL call calls an address (optionally with user name and password) and saves the response under var/custom/html/… or discards it – for example to collect an export from another application or to warm up a cache. File names with a PHP extension are not allowed
PHP call runs a PHP script from var/custom/php/ on the command line – for project-specific automation. The output is saved under var/custom/html/… or discarded, as with the URL call
Database backup writes a complete backup of the database to var/dbbackup/. backup-1.sql is always the newest; older ones move up (backup-2.sql …), beyond the set number they are deleted. It runs at most once a day. Every new backup is checked: if it is smaller than 10 KB or the final line -- Dump completed is missing, it is discarded and the run counts as failed. The detail view of the task marks unusable files
Clean up logs deletes log files under var/log/ that are older than the set period (default 90 days). Security logs (security, csp) have their own, usually longer period (default 365 days). In addition, failed sign-in attempts with name and IP address are deleted from the database (default after 30 days). Recommended: every night. The audit log has its own periods
Clean up frontend cache deletes expired files of the output cache under var/cache/include/, which templates create with CMS:INCLUDE … caching=…, and files for deleted or since changed templates. Files with unlimited period remain until their template is changed. No settings. Recommended: hourly, for example at minute 15, all others *
Rebuild full text rebuilds the full text of the selected areas (without a selection: all areas) from the source fields – after a change to the source fields of a full-text field. The records themselves remain unchanged, the audit log receives no entries. If the maximum execution time is not sufficient, the run is aborted and counts as failed; then increase it
Empty trash permanently deletes records, input fields and content areas that have been in the trash for longer than admin.trash_retention_days (default 7 days). No settings
Delete temporary files cleans up var/tmp/ (admin.tmp_retention_hours) and deletes expired export files (admin.export_retention_hours). No settings. Recommended: hourly
Apply deletion policies carries out the due jobs of the deletion policies. No settings
Apply retention periods shortens IP addresses, deletes expired entries of the audit log, old internal messages and registrations that were never confirmed – according to the periods of the configuration; plus the periods of installed add-on modules, such as deleted chats of the AI assistant. No settings
Check audit log checks the files of the audit log for manipulation; findings appear on the start page. No settings
Check for updates looks for newer versions and known security vulnerabilities (see updates). No settings
Summarize messages by email sends users who have chosen it in their personal settings a summary of their new internal messages (see messages). New are the messages since the previous run – run it once a day. No settings
Template runs a frontend template with the use “Scheduled”, in one language of its website – for example for emails about the expiry of a blocking period, reminders or a feed. The template learns which period has passed since the last successful run; if a run fails, the next one covers that period. The output is saved or discarded as with the URL call. If the template reports an error (for example an email without a valid recipient), the run counts as failed

The time control follows the cron notation with six values: minute, hour, day, month, year, weekday (0 = Sunday). For each value: * for every value, a number, a range 1-5, a step */10 or a list 1,15,30. The task runs when all six values match. Example for daily at 3:30 a.m.: minute 30, hour 3, all others *. The times apply in the time zone of the system.

Outputs and scripts separated: Fetched or generated contents end up only under var/custom/html/, executable scripts lie only under var/custom/php/. In this way, a response that a URL call fetches can never be executed as a PHP script. Path specifications must not lead out of the respective directory (no .., no links to the outside). When you update, move existing scripts to var/custom/php/.

Run as: Each task runs under one user and with that user’s rights; in the audit log this user appears with the origin “scheduler”. New tasks run under the user scheduler, which the installation creates (user group superuser, without sign-in to the backend). To choose from are users whose user group may use the interface “Command line (CLI)”. If the user is deleted or blocked, or loses this right through its user group, the task fails with a notice until you assign another user to it. A PHP script works via the API under this user (see the developer documentation).

Further settings: active/inactive, maximum execution time, and whether every run is written to the system log (errors always are). The list shows the next and last run with its result; “Run now” starts a task immediately as a background process.

Tools

The module “Tools” has five tabs; each has its own additional right.

Tab Tools
“Server” versions of camindo CMS, operating system, web server, database and PHP; check and renewal of the .htaccess file; “Clear record cache” – for all websites, one website or one area; MySQL configuration; availability of ImageMagick and ffmpeg; PHP info
“Database” “Empty trash” – permanent deletion of records, input fields and areas that have been in the trash for more than n days (0 = everything); “Discard working copies” – remove leftover working copies older than n hours and release the originals again
“Data” “Regenerate thumbnails” for all records of an area (see chapter 8); “Normalize tree” – checks and repairs the order in areas with a tree structure, for example if it has become mixed up through direct database changes; the existing order is retained
“Trash of a user” immediately delete permanently the profile and contributions of a website user that are in the trash – for example at the user’s request (see chapter 15)
“Developer” renew caches (snippets, backend templates, instances); translate template files again and compare them with var/templates (including the parser messages – on the command line php Core/cron/camindo.php cache templates, automatically at upgrade); renew the access rules of the media directory; show session data

camindo CMS renews the caches itself after changes. The tools on the tab “Developer” are only needed after interventions in the file system or for troubleshooting.

Template messages (widget)

The module “Templates” brings a widget for the start page (personal settings, “Dashboard widgets”): it lists every template with its parser messages – errors in red, notes in yellow – and opens it with a click. This is helpful above all when templates are edited in the file system (var/templates) and nobody sees the editor. The notice always says that every script in a template needs <script(CMS:NONCE)> so that it runs under the script policy of the website (chapter 3). It is visible to user groups with access to the module “Templates”; only template areas that the user group may read are shown.

Log files

The module “Log files” (superusers only) makes the logs under var/log/ searchable in the backend. You can filter by log, day, type and search text. The result list shows the newest entries first, 100 per page; “Older entries” and “Newer entries” page on, the filters are kept.

The entries are sorted by types, in the order of their urgency:

Type Examples
Blocked sign-ins too many failed attempts at signing in
Security signs of manipulation
Unsent emails errors when sending email
PHP errors aborted programs
Database errors faulty database queries
Background processes errors in import, export, batch processing, thumbnails
PHP warnings, other notes

The widget “Log files” summarizes the most important messages of the last few days on the start page.

Audit log

The audit log records who changed what and when – with the state before and after. The following are logged:

  • every creation, change, deletion (into the trash), restoration and permanent deletion of records, areas, fields, templates, users, user groups, instances, languages, statuses, converters, scheduler tasks and components, regardless of whether it happens via the input form, the frontend (contributions of website users), an import, batch processing or the scheduler task “Empty trash”
  • failed sign-ins to the backend; successful sign-ins and sign-outs only with audit.log_logins = true (default: not recorded)
  • every export of personal data
  • every change made by a deletion policy (action “Privacy”)

When changes are made, all fields are compared, including meta fields such as status, publication date or owner; for multilingual fields, each language individually. Only what has actually changed is saved; saving without a change creates no entry. Passwords never appear in plain text, only as “changed”. Encrypted fields remain encrypted in the log and are decrypted only when displayed. Private messages between users are not logged, nor is the full text: it is derived from the text fields, and the scheduler task “Rebuild full text” leaves no entries.

Tab “Changes”: In the detail view of each record, the tab “Changes” shows the last ten entries of its history, newest change first; the complete history is shown by the module “Audit log”. The tab does not exist in selection windows (relation fields). For each field, before and after are displayed side by side. Texts are compared paragraph by paragraph: unchanged paragraphs are gray, removed ones red, new ones green; in changed paragraphs the individual words are marked. Everyone who may read the area can see the tab; the IP address only superusers.

The same tab “Changes” is available in the detail views of areas, input fields, templates, statuses, instances, languages, converters, scheduler tasks, components and user groups – in each case for everyone who may open the detail view. For users, only superusers see it; it shows what was changed on the account (including sign-ins), while the tab “Activity” beside it shows what the user did themselves. For templates, the source text is compared; the colored view that results from it does not appear in the log.

Module “Audit log” (superusers only): searches the entire log by period, user, action, object type, ID, designation, name or IP address and by changed fields (for example status_id), page by page like the other lists. A click on an entry opens the comparison; “Full history” shows all entries for this object.

Research via the API and the AI assistant: The same entries can be searched and read via the programming interface – so the AI assistant can also answer questions such as “Who deleted article X yesterday?” or “How has this record changed since it was created?”, even if the record is in the trash or has been permanently deleted. Superusers see the entire log; everyone else – as in the tab “Changes” – only the entries for records in areas they may read, and no IP addresses.

Tab “Activity”: In the detail view of a user (module “Users”), the tab “Activity” shows only superusers what this user has left in the audit log: at the top the recently edited records – for each record the last action with the time and the number of its entries, a click on it opens the history – below, all actions page by page, including sign-ins and changes to areas or fields. “Open in audit log” leads into the module, pre-filtered for this user.

Retention: Settings in .camindo.php (see configuration):

Key Default Meaning
audit.retention_days_changes 0 Entries for existing objects, in days. 0 = unlimited: every record remains traceable back to its creation
audit.log_logins false Record successful sign-ins and sign-outs to the backend. They say nothing about changes, but they do say something about working hours – therefore off
audit.retention_days_logins 90 Sign-ins and sign-outs as well as failed sign-ins, in days. 0 = as audit.retention_days_changes
audit.retention_days_deleted 365 History of permanently deleted objects, counted from the permanent deletion (after the trash). 0 = unlimited. Does not apply to records: their history is overwritten or deleted by the deletion policy of the area
audit.enabled true switch the audit log on or off
audit.page_bytes 1048576 size in bytes from which the file of an object under var/audit/ begins a new page

The scheduler task “Apply retention periods” removes expired entries (default: every night).

Privacy

The module “Privacy” has these tabs:

Tab Content Additional right
“Deletion policies” define what happens when a record is deleted – put into the trash, permanently delete immediately or with a delay, overwrite individual fields, overwrite or delete the history in the audit log, run your own rules in the trigger template – also for sub-records, the contributions of a deleted profile and the records of a deleted area. Every content area has one policy “Create and change deletion policies” – for the instances to which the user group has access
“Templates” templates of the personal data report for each instance, type (website user or user) and language – see chapter 15 “Maintain templates of the personal data report” – for the instances to which the user group has access
“Data export” export of personal data of a website user or a user as a zip file “Export personal data” – for profiles of the readable areas and users of the visible user groups
“Deleted users” list of deleted users with user group and deletion date – only from the user groups whose users you may see in the module “Users”. A click leads to the info page of the user in the module “Users”; if the user has not yet been anonymized, the button “Anonymize” appears there. See chapter 15 tab: access to the module “Users”. Anonymize: “Anonymize deleted users” – for users of the visible user groups

Everyone with access to the module may read the policies. Only a superuser changes the three standard policies and the policies for all instances. In detail: chapter 15.

Components

The module “Components” shows all modules, media processors and extensions with origin (core or add-on), version and state:

State Meaning
Core part of the core, always active
discovered – not installed the files are present, but the component is not yet set up
installed, inactive set up, but switched off
active available in the backend and frontend
update pending newer files are present; “Update” brings the database up to date
orphaned – files missing the files were removed without uninstalling the component

The actions “Install”, “Activate”, “Deactivate”, “Update” and “Uninstall” explain in their form what they do. When you uninstall, tables and data are retained unless you explicitly choose “Delete tables and data”; if you wish, the database is backed up beforehand. “Compare” compares the existing files with the database.

The files of a component are always fetched and removed by Composer on the command line (composer require … / composer remove …, see installation); the module sets them up in the database.

Trash

Every user has the module “Trash”, regardless of their user group; it appears as a button on the start page and as a widget that shows the number of entries. How editors work with it is described in the manual for editors.

Each entry stands for one deletion operation: if a record is deleted together with its sub-records, only the topmost appears; hard-linked content, the records and input fields of a deleted area, the counter field of a relation and the file versions of an upload field do not appear individually at all. “Restore” always brings back everything that was deleted in the same operation – recognized by the identical deletion time. What was already deleted in an operation of its own before remains in the trash.

Who Sees
every user “Records deleted by me” – from areas to which they have access, and not from deleted areas
superusers additionally “Deleted areas” (with instance, scope and who deleted them), “Deleted input fields” (with area, and the counter field and file versions deleted along) and, via “All users”, the deleted records of all users

Restoring requires the right to delete in the area (see area rights); if it is missing, the entry shows “no permission”. How long entries stay in the trash is determined by the retention period. Large operations (more records than admin.mass_delete_threshold) run as a background process, as with deletion.

Messages

The internal messages between users are reached via the envelope symbol in the sidebar. They are described in the manual for editors. Messages are deleted automatically privacy.message_days days (default 60) after sending; a superuser deletes all messages of a user in that user’s settings (see chapter 15).

Each user can have a daily summary by email sent in their personal settings: unread messages per sender and the new ones with date, sender and subject – without message texts, without contents from records and without a link into the backend. It is sent by the scheduler task “Summarize messages by email” (default daily at 7:00), and only if new messages have arrived since the previous run. Prerequisites are the email dispatch and an email address in the user’s account. Bear in mind that the sender and the subject thereby leave the house.

Add-on modules

Add-on modules are separate packages that you fetch via Composer and install as a component (see installing add-on components). Each brings its own manual: after installation it appears in the overview of the manuals under “Manuals of add-on components” – with setup, settings and all details.

Add-on PDF

The add-on PDF (camindo/module-pdf) creates PDF documents directly from templates – invoices, tickets, data sheets, certificates – on request on a PDF template such as letterhead, with texts, HTML, images, lines and tables, in RGB or CMYK.

composer require camindo/module-pdf
php Core/cron/camindo.php component install Pdf --activate

Add-on AI assistant

The add-on AI assistant (camindo/module-ai) brings a language model into the backend: users write in the side panel what they need, and the assistant searches, reads, creates and changes – with the rights of the user and only after confirmation. How editors work with it is described in the manual for editors.

composer require camindo/module-ai
php Core/cron/camindo.php component install Ai --activate

Who may use it is set in the module “AI assistant” in the user group. Provider and access key are in the configuration (ai.*).

11 Extensions

An extension extends an input field in the backend with additional behavior – a text editor, a calendar, a symbol selection. It is assigned in the field definition (module “Structure” → content area → edit input field → section “Extension”). The settings of the extension are below it, in the section “Settings”. A field has at most one extension, and only those that match the field and display type are offered.

Each core extension has a page of its own (links in the table). They follow as subpages of this chapter – “Next” leads through them first and then to chapter 12.

Core extensions

They are installed with the core and are always active.

Extension Field types / display What it does
Calendar (“Calendar”) Date/time, time / single line the date and time selection of the browser next to the field; preselected for new date fields (also those created by the AI assistant) – “by field type”, minute step 1
Datepicker Date/time / single line calendar in the language and date format of the user
IconPicker (“Icon picker”) all / single line selection of a symbol with preview, browsable categories and search by name and keywords; default for the icon of an area
Markdown (visual) (“MarkdownVisual”) Short/long text / multiline Markdown editor that edits in the formatted view; source text via the button <>
HTML (visual) (“HTMLVisual”) Short/long text / multiline HTML editor that edits in the formatted view; source text via the button <>. Like Markdown (visual), but stores HTML
Password (“Password policy”) Password / password requirements as a speech bubble, strength as a bar; the server checks the same rules. Default for the passwords of the users
Snippets (“Snippet picker”) all / single line selection of a text block (snippet) via a search window; writes a reference #prefix.key into the field, which appears in the respective backend language. Also used in the backend for “Label” fields

Additional extensions

Additional extensions are separate packages. They are fetched via Composer and then installed as a component (see installing additional components):

composer require camindo/extension-{name}
php Core/cron/camindo.php component install {Name} --activate

Each additional component brings its own manual. After installation, you find it in the overview of the manuals under “Manuals of add-on components”.

Custom stylesheets for editors

Editor extensions offer a stylesheet in their settings, with which you can match the display in the editor to the website. You store the files yourself under var/extensions/{name}/ (name in lowercase letters, so var/extensions/markdownvisual/ or var/extensions/htmlvisual/) – all .css files there appear in the selection.

So that one stylesheet can design several fields differently, each editor carries a CSS class that you can set for each field (default: c9-{name}-{fieldname}, for example c9-htmlvisual-teaser). Examples are given in the description of the respective extension.

Extension Calendar

Core extension for date and time fields (field types “Date + time”, “Date + time, timezone-dependent” and “Time”, single-line display). Next to the text field it adds the date or time picker of the browser; its appearance and language are therefore determined by the browser, not by camindo CMS.

Always available, no installation needed. A new date or time field receives it automatically (“by field type”, minute step 1) – even when the AI assistant creates the field. If you do not want an extension, choose “none” when creating the field.

Settings per field

Setting Default Meaning
Picker by field type “by field type”: time fields get a time picker, all others date and time. Can be fixed to: “date and time”, “date only”, “time only”
Minute step 1 grid of the time picker in minutes (1–60), e.g. 15 for quarter hours

Behavior

  • A selection writes the value into the text field in international notation: 2030-09-26 14:30, with “date only” 2030-09-26, with “time only” 14:30. The backend understands this form in every locale and, after saving, shows the value in the user's format.
  • A value in international notation in the text field also appears in the picker. The picker does not take over values in a national format (09/26/2030) – the text field itself accepts them, though.
  • The text field always remains freely editable.

If the calendar should appear in the language and date format of the user, Datepicker is the alternative.

Extension Datepicker

Core extension for date fields (field types “Date + time” and “Date + time, timezone-dependent”, single-line display – not for “Time”). Next to the text field it adds a calendar in the language and date format of the user and – depending on the setting – a time field. It is based on the library vanillajs-datepicker.

Always available, no installation needed. Unlike Calendar, it is not preselected automatically.

Settings per field

Setting Default Meaning
Picker by field type “by field type” and “date and time”: calendar and time field; “date only”: only the calendar
“Today” and “Clear” buttons on “Today” selects today's date, “Clear” empties the field
Show week numbers off column with the calendar weeks on the left of the calendar

Operation

  • A click into the calendar field opens the calendar; a click on a day takes it over and closes the calendar.
  • and go back or forward one month; a click on month and year in the header switches to the month, year and decade overview.
  • The time field takes hours and minutes.
  • Month names, weekdays and the first day of the week follow the locale of the user (personal settings, “Date and number format”); if a translation is missing, the calendar appears in English.

What is written into the field

The value appears in the text field the way the backend displays date values – in the user's medium date format, e.g. 26.09.2030, 14:30 (de_DE) or Sep 26, 2030, 2:30 PM (en_US). The time is only appended if it is not 00:00. Conversely, when opening, the calendar takes over a value in the display format or in international notation (2030-09-26 14:30) from the text field. The text field remains freely editable; changes there appear immediately in the calendar and time field.

Extension IconPicker (Icon picker)

Core extension for single-line fields of all field types that store the name of a Material Symbol (e.g. menu_book). Default for the icon of a content area (module “Structure” → area → Icon). Always available, no installation needed, no settings.

Operation

To the right of the field, a preview shows the entered icon; if it stays empty, the name is unknown or the field is empty. The button opens the picker:

  • Without a search term, the window shows the icons of one category (Action, Alert, Audio & video, Communication, Content, Device, Editor, File, Hardware, Home, Image, Maps, Navigation, Notification, Places, Search, Social, Toggle); page through the categories.
  • The search field searches all icons by name and tags (English, from the search index of Google Fonts, e.g. book, calendar, person). If you enter several terms, all of them must match. The Enter key takes the search term into the field: an exact icon name, otherwise the only hit, otherwise the text itself (spaces become _).
  • A click on an icon writes its name into the field and closes the window. “No icon” empties the field.
  • , the Esc key or a click outside close the window without any change.

You can also type the name directly; the preview follows as you type. The names and all icons are listed at fonts.google.com/icons (style “Material Symbols Outlined”).

Extension Markdown (visual)

Core extension – Markdown editor for multiline text fields (field types “Short text” and “Long text”): editors work in the formatted view as in a word processor; the Markdown source only appears via the button <> and can be edited there as well. The field still contains Markdown.

Always available, no installation needed.

What the editor can do

Bold, italic, underline, inline code, paragraph format (paragraph, heading 1–6), bulleted and numbered lists with sub-items, quote, code block, link, horizontal line, line break, special characters (currencies, ®, ™, dashes, non-breaking and narrow spaces, soft hyphen …), align left, center, right, text direction for selected sections, and tables with a header row. It does not support images.

When pasting, the editor only takes over what it can display: formatted text from Word, Google Docs or web pages is converted to Markdown, pasted Markdown is reduced to the supported formats. Images, colors, fonts and strikethrough are dropped; the text remains.

How editors work with it is described in the Manual for editors (Editing records → Input fields → Text).

Settings per field

Setting Meaning
Toolbar all buttons, basic (bold, italic, paragraph format, lists, link), custom
Custom buttons only with custom: names separated by comma or space, \| as separator – bold italic underline code heading ul ol outdent indent quote codeblock link hr br special align_left align_center align_right dir table undo redo
Minimum height / Maximum height in pixels; without a maximum, the editor grows with the content
Stylesheet a .css file from var/extensions/markdownvisual/
CSS class class on the editor container; empty = c9-markdownvisual-{fieldname}

The button <> (source) is always there. Keyboard shortcuts (such as Ctrl+B) and pasting work regardless of which buttons you select.

Markdown in the field

The editor writes common Markdown and four extensions:

Markdown Meaning
++Text++ underlined
::: center … ::: alignment of the enclosed paragraphs (left, center, right)
<br> line break in a table cell
U+2067 … U+2069 or U+2066 … U+2069 section right to left or left to right (Unicode isolates, invisible characters; also written as &#x2067; … &#x2069;)

Invisible special characters (non-breaking space, soft hyphen …) are stored as characters in the text.

Text direction: The field writes in the direction of its language (Arabic, Hebrew, Persian, Urdu … from right to left), single-language fields in the direction of the backend. The button dir shows the direction at the cursor and switches a selected section to the opposite direction – in Markdown enclosed by the Unicode characters RLI (U+2067, right to left) or LRI (U+2066, left to right) and PDI (U+2069, end). Browsers evaluate these characters themselves; the output in the template needs nothing special. In the editor, such a section has a dashed outline. A hard line break is two spaces at the end of the line.

In the template, output the field with (CMS:TEXT fieldname markdown=true) – the parameter knows the extensions (see Manual for template programmers, CMS:TEXT). Other Markdown converters show ++ and ::: as text.

Stylesheet

The editor sits directly in the backend page. Therefore always scope rules with the CSS class of the field – otherwise they affect the whole backend. The editing area is .c9-mdv-content:

.c9-markdownvisual-text .c9-mdv-content h1 { color: #036; }
.c9-markdownvisual-text .c9-mdv-content { font-family: Georgia, serif; }

Extension HTML (visual)

Core extension – HTML editor for multiline text fields (field types “Short text” and “Long text”). It is a copy of Markdown (visual) and is operated the same way: editors work in the formatted view as in a word processor; the HTML source only appears via the button <> and can be edited there as well. The field contains HTML instead of Markdown.

Always available, no installation needed.

What the editor can do

The same as Markdown (visual): bold, italic, underline, inline code, paragraph format (paragraph, heading 1–6), bulleted and numbered lists with sub-items, quote, code block, link, horizontal line, line break, special characters, align left, center, right, text direction for selected sections, and tables with a header row. Images, classes and styles can be specified in the source (see below).

When pasting, the editor only takes over what it can display: formatted text from Word, Google Docs or web pages is trimmed to the supported formats, pasted plain text that looks like Markdown is converted. Images, colors, fonts, classes and strikethrough are dropped; the text remains.

The source (<>) allows more: HTML written or changed there additionally keeps own attributes and images (see “Classes, styles and images” below). Everything else is trimmed in the same way when switching back to the formatted view and when saving. Existing HTML in the field remains unchanged until the text is changed in the editor – then the same rules apply.

How editors work with it is described in the Manual for editors (Editing records → Input fields → Text).

Settings per field

Setting Meaning
Toolbar all buttons, basic (bold, italic, paragraph format, lists, link), custom
Custom buttons only with custom: names separated by comma or space, \| as separator – bold italic underline code heading ul ol outdent indent quote codeblock link hr br special align_left align_center align_right dir table undo redo
Minimum height / Maximum height in pixels; without a maximum, the editor grows with the content
Stylesheet a .css file from var/extensions/htmlvisual/
CSS class class on the editor container; empty = c9-htmlvisual-{fieldname}

The button <> (source) is always there. Keyboard shortcuts (such as Ctrl+B) and pasting work regardless of which buttons you select.

HTML in the field

The editor writes only these elements – classes, styles and images only if they were specified in the source:

HTML Meaning
<p>, <h1> … <h6> paragraph, heading
<strong>, <em>, <u>, <code> bold, italic, underlined, inline code
<a href="…"> link (only http(s):, mailto:, tel:, ftp: and relative addresses)
<br> line break
<ul>, <ol start="…">, <li> lists, nested for sub-items
<blockquote> quote
<pre><code class="language-…"> code block
<hr> horizontal line
<table> with <thead>/<tbody>, <th>, <td> table, first row is the header row
style="text-align: left\|center\|right;" alignment of paragraph, heading, list, quote, table; on cells as column alignment
<bdi dir="rtl"> / <bdi dir="ltr"> section right to left or left to right
<span>, <img> only from the source, see below

Classes, styles and images

When specified in the source, these are kept:

What Where
class, style, id, title, data-…, aria-…, lang, role on <p>, <h1> … <h6>, <ul>, <ol>, <blockquote>, <pre>, <hr>, <table> and on <span>, <strong>, <em>, <u>, <code>, <a> (<b>, <i>, <ins> become <strong>, <em>, <u>)
target, rel, hreflang, download, type, referrerpolicy additionally on <a>
<img> with src, alt, title, width, height, loading, srcset, sizes, class, style, id as well as data-…, aria-…, lang, role in the text, also in links and table cells; src only http(s):, relative addresses and data:image/…
<p class="lead">Introduction with <span class="marker">highlight</span>.</p>
<p><a class="button" href="/contact">Contact</a></p>
<p><a href="https://example.com" target="_blank" rel="noopener">Partner</a></p>
<p><a href="#" data-mail="…" data-mail-text>name (at) example (dot) com</a></p>
<p><img src="/media/1/12/picture.jpg" alt="Showroom" width="600" loading="lazy"></p>

In the formatted view, classes and styles take effect immediately (together with the field's stylesheet); images appear in place and can be deleted or moved like a character. Attributes such as data-…, aria-…, target or id have no effect in the backend; they only appear in the saved HTML – for scripts of your website, for example. Attributes can only be changed in the source. Alignment via the buttons adds text-align to style; a text-align in the source becomes the alignment of the block.

Not kept: attributes on list items and table cells, <div> (becomes a paragraph, classes are dropped), other attributes (events such as onclick) and other elements. Pasted HTML from the clipboard still loses classes, styles and images – copying within the editor, on the other hand, keeps them.

Invisible special characters are stored as entities in the HTML: &nbsp;, &#8239; (narrow non-breaking space), &ensp;, &thinsp;, &shy;, &#8209; (non-breaking hyphen). The HTML is indented line by line so that the source remains readable.

Text direction: as with Markdown (visual) – the field writes in the direction of its language, single-language fields in the direction of the backend. A selected section in the opposite direction becomes <bdi dir="…">; in the editor it has a dashed outline.

In the template, output the field without conversion:

<div class="text">(CMS:TEXT fieldname entitydecode=false lbrconvert=false)</div>

entitydecode=false leaves escaped text (&lt;) escaped, lbrconvert=false prevents additional <br> at the line breaks of the source.

Stylesheet

The editor sits directly in the backend page. Therefore always scope rules with the CSS class of the field – otherwise they affect the whole backend. The editing area is .c9-htv-content:

.c9-htmlvisual-text .c9-htv-content h1 { color: #036; }
.c9-htmlvisual-text .c9-htv-content { font-family: Georgia, serif; }

Extension Password (Password policy)

Core extension for fields of the type “Password”. Below the field, a bar shows the strength of the entered password (red = weak, orange = medium, green = strong); while you type, a speech bubble with the requirements appears, and fulfilled ones are checked off. As long as a requirement is missing, the bar stays red.

Always available, no installation needed. The passwords of the users use it automatically.

Check on the server

The display is only an aid. What matters is that camindo CMS checks the same rules when saving – and it does so for a field exactly when the field has this extension. A password that is too weak is rejected with a message naming the missing requirements (“at least 2 special characters, …”).

Settings per field

Each rule can be set differently on the field. Empty fields use the default of the installation (security.password_policy, see Operation) – it is shown in gray in the field.

Setting Default Meaning
Minimum length 12 number of characters
Special characters (minimum) 1 anything that is neither a letter nor a digit – including spaces, hyphens, periods
Uppercase letters (minimum) 1 including umlauts and letters of other scripts
Lowercase letters (minimum) 1
Digits (minimum) 0
Uppercase letter at the start does not count yes “Summer2026!” does not meet the rule just because of the capital S

0 means: no requirement.

In the frontend

The registration and the customer account of a website use the same script. The template programmer copies it into the template and marks the field – see CMS:USER_PASSWORD_RULES.

Extension Snippets (Snippet picker)

Core extension for single-line fields of all field types. Instead of a fixed text, it puts a reference to a snippet into the field – #prefix.key – which appears everywhere in the backend in the language of the respective user. Default for the label fields (“Label”) of the modules, such as the name of an area or field. Always available, no installation needed, no settings.

Operation

  • If the value starts with #, a preview to the right of the field shows the resolved text in your own backend language – or “Snippet not found”.
  • The button opens the search window. If the field already contains a reference, it is preset as the search term.
  • The search covers the id and text of all snippets, case-insensitively; if you enter several terms, all of them must match. At most 50 hits are shown – if there are more, narrow down the search.
  • A click on a hit writes # + id into the field. “Text only (no snippet)” removes the #; the rest remains as normal text.
  • , the Esc key or a click outside close the window.

Labels on the hits: backend only – the snippet exists only in the backend texts; ICU – a text with placeholders (plural, numbers), shown in the preview with empty values.

Which snippets appear

All snippets in the user's backend language: those of the core (id without prefix), the modules, extensions and processors, as well as the custom snippets of a website from the module “Snippets” (custom.{instance_id}.…, see chapter 10) – the latter only for websites the user group has access to.

For your own labels, first create the snippet in the module “Snippets” (missing translations receive the English text) and then select it here. A reference to a snippet that does not exist appears in the backend as its id.

12 Operation and maintenance

Cron jobs

camindo CMS needs one cron job of the web server user (for the setup see installation, step 5):

Cron job Interval Tasks
Core/cron/cron.php every minute background conversion of videos and images, comparison of the template files and the due tasks of the scheduler – among them all maintenance tasks

If cron.php does not run, scheduled tasks stand still, template files that were changed in the file system do not reach the backend, and videos are not converted. Superusers see this on the start page: beneath the module tiles a notice then appears (see operating notices).

Scheduler

The scheduler performs recurring and demanding work in the background, without holding up the editors. It hangs on the cron job cron.php, which runs every minute: each minute this looks for the due tasks and starts each as its own background process – one long task does not hold up the others. The tasks are set up in the module “Scheduler”. Which types there are and how the time control is written is described in chapter 10.

Maintenance tasks: The installation creates these tasks (user scheduler, with log). Like any other task, their execution can be scheduled, switched off or deleted. If one of the maintenance tasks is missing or switched off, an operating notice appears.

Task Default What it does
Database backup daily at 2:00 backup to var/dbbackup/, 7 rotating. Recommendation: additionally store it outside the server (see data backup)
Delete temporary files hourly at :10 clean up var/tmp/ and expired export files (including personal data reports)
Empty trash daily at 2:20 permanently delete entries that have been in the trash longer than admin.trash_retention_days
Apply deletion policies daily at 2:30 due jobs of the deletion policies
Apply retention periods daily at 2:45 shorten IP addresses, delete expired entries of the audit log, old internal messages and registrations that were never confirmed (chapter 15), plus the periods of installed add-on modules – such as deleted chats of the AI assistant
Clean up logs daily at 3:00 delete old log files and sign-in attempts (see logs)
Clean up frontend cache daily at 3:10 expired files of the output cache. If templates use caching with short periods, this task should be set up hourly.
Rebuild full text daily at 3:20, only outdated areas rebuilds the areas whose full text is marked as no longer current after a structural change. If full-text fields use sources behind a “link”, an additional task makes sense for these areas. Without this option, weekly or manual suffices (see rebuild full text)
Check audit log daily at 3:40 check the files of the audit log for manipulation. The start page shows the findings
Check for updates daily at 3:55 newer versions and known security vulnerabilities (see updates)
Summarize messages by email daily at 7:00 email to the users who switched on the summary of their new internal messages (see messages)

Missing maintenance tasks – for example after an accidental deletion – are created again by the command php Core/cron/camindo.php scheduler defaults; existing ones remain unchanged. scheduler list shows all tasks, scheduler run trash (or an ID) runs one immediately.

Scheduling or running immediately: A task runs at the times of its time control, as long as it is active. “Run now” in its detail view starts it immediately as a background process – even an inactive one. So you can set up a task that is needed only occasionally (for example “Rebuild full text”) once, leave it inactive and start it by hand when needed.

Suspending temporarily: “Deactivate” in the context menu of the list or in the detail view suspends a task without touching its settings – the list then shows it as “temporarily suspended”, and no next run is scheduled. “Reactivate” lets it run again according to its time control.

Reports: A task of the type “URL call” or “PHP call” can be marked as a selectable report; at least one user group belongs to it. Users of these groups find the report in their personal settings under “Subscribe to report”. After every successful run, the subscribers receive the output as an internal message in HTML format (active content such as scripts is removed; for a complete HTML document only the content of <body>; outputs over 2 MB only as a note). The detail view of the task shows the number of subscribers.

Result and errors: The list and detail view show the next and last run with result and message. Errors always end up in the log var/log/{date}_scheduler.log, successful runs only with the setting “Write system log file”. Failed tasks are reported to superusers on the start page (see operating notices).

Maximum execution time: Each task has an upper limit in minutes. If it is exceeded, the run is aborted and counts as failed. A task that is marked as “running” for longer than this limit plus five minutes counts as aborted and starts again at the next appointment.

Rights: Each task runs under one user and with that user’s rights – new ones under the user scheduler, which the installation creates. More in chapter 10.

Trash

Deleted records, input fields and areas are first only marked as deleted; the files of deleted records move to var/deleted/, those of a deleted input field stay locked in place. Whether a record ends up in the trash at all is determined by the deletion policy of its area. Permanent deletion takes place after the retention period (admin.trash_retention_days, default 7 days) through the scheduler task “Empty trash” (every night) – or immediately via “Tools” → “Database” → “Empty trash”. Deleted users are always retained.

Within the period, restoring takes place – including sub-records, hard-linked content and files:

  • Records every user restores from their own trash themselves.
  • Areas are restored by a superuser from the trash. Their sub-areas and all records and input fields that were deleted together with them come back with the area.
  • Input fields are restored by a superuser from the trash – with their contents (see deleting an input field).

It also works on the command line, without the backend:

php Core/cron/mass_delete.php --table=record --id=1758 --mode=restore --withchilds=1 --admin=1

--id is the number of the record, --admin the number of a user with the necessary rights (for example a superuser) under whose name the restoration runs. --withchilds=1 restores the sub-records that were deleted in the same operation. For a deleted area including its records, use --table=area.

Working copies

If an editor cancels the editing without clicking “Cancel” – for example because they close the browser –, a working copy remains. It locks the record only until it has been unchanged for 90 minutes (admin.edit_lock_sec); after that, someone else can open the record again. “Tools” → “Database” → “Discard working copies” removes leftover working copies (default: older than 24 hours).

Data backup

A complete backup consists of:

What Where
Database via the scheduler (task “Database backup”) to var/dbbackup/, or with your own tools
Configuration .camindo.php
Key file .camindo.key (or the content of the environment variable CAMINDO_KEY, see access data from environment variables) – without it, encrypted contents and passwords are useless. Keep it in a safe place, separate from the database backups
Media files public/media/
Project data var/templates/ (template sources),
var/custom/ (snippets, scripts and outputs of the scheduler),
var/extensions/ (stylesheets),
var/modules/ (for example PDF templates and fonts),
var/area_blueprints/ (own blueprints),
var/deleted/ (trash),
var/translation/translated if an own backend translation has been created.
Composer state composer.json and composer.lock – so that exactly the same versions can be installed again
Change logs var/audit/ – the contents of the audit log; after importing a database backup, audit restore reconciles them with the database

Before every update and before changes to components, camindo CMS automatically creates a database backup in var/dbbackup/. Manually: php Core/cron/camindo.php backup; old backups are cleared with php Core/cron/camindo.php backup prune --keep=5.

Individual areas can additionally be backed up and selectively restored via XML export.

Updates

cd /var/www/mysite
php Core/cron/camindo.php maintenance on
composer update
php Core/cron/camindo.php upgrade
php Core/cron/camindo.php maintenance off

Composer exchanges the files of the core and add-on components; upgrade backs up the database, brings it to the new state and rebuilds all caches.

Maintenance mode: While maintenance on applies, the website answers with a maintenance page (HTTP 503), background processes pause, and the backend is accessible only to superusers. You can provide your own maintenance page as var/custom/maintenance.html. If files and database do not match after a composer update, camindo CMS switches itself into maintenance mode until upgrade has run.

Back to the previous state: Maintenance mode on, restore the database backup from var/dbbackup/, restore the previous composer.lock and run composer install, then upgrade and maintenance mode off.

Update check

Every night, the scheduler task “Check for updates” checks with Composer whether there are newer versions of camindo CMS and the installed packages (composer outdated, direct packages and their dependencies) and whether security vulnerabilities are known for installed packages (composer audit). The result is shown in the main menu under “Info” – only for superusers, with a table of the packages and the button “Check now”.

Only versions that composer update actually installs within the version constraints are reported. A new major version of a package does not appear – it requires a change to composer.json. An operating notice appears on the start page only for a new version of camindo CMS and for known security vulnerabilities; newer versions of other packages are listed under “Info” only.

The check requires network access to Packagist or the package sources and the program composer on the server (paths.composer, empty = check off). The web server and the cron job start Composer without a shell – an alias from .bashrc does not apply there. Enter the absolute path or the whole command line instead, for example for a composer.phar with its own PHP version:

'composer' => '/usr/bin/php8.4 -d memory_limit=-1 /home/www/bin/composer/composer.phar',

Put words containing spaces in double quotes. JavaScript libraries that camindo CMS brings along as files (jQuery, Bootstrap, editors) come with the update of camindo CMS.

The command line

All administrative commands run through one program:

php Core/cron/camindo.php <command> [arguments] [--options]

Commands:

status

State of the system: versions, components, pending actions, maintenance mode

install

First installation; repeat individual steps with --step=…

upgrade

Bring the database up to date after an update

component list
component install NAME [--activate]
component activate NAME
component deactivate NAME
component update NAME
component uninstall NAME [--purge]

Manage add-on components

maintenance on [--reason=…]
maintenance off
maintenance status

Maintenance mode

backup
backup prune --keep=N

Database backup

cache all
cache docs

Rebuild the caches for snippets and backend templates and the search index of the manuals (docs: only the index); all also empties the output cache of the templates and rewrites the buffer of the websites and group rights

cache include

Empty the output cache of the templates (CMS:INCLUDE … caching=…) – for example so that a content change becomes visible immediately

cache records [--instance=ID] [--area=ID,…]
refresh records [--instance=ID] [--area=ID,…]

Clear the record cache – all websites, one website (--instance) or single areas (--area, several separated by commas). Needed for example after changes made directly in the database. The entries are rebuilt on the next access, open working copies are not affected. Not part of cache all. The same in the backend under “Tools” → “Server” → “Clear record cache”

refresh

Compare components with the database

refresh snippets
refresh html

Rebuild the snippet cache or the cache of the backend templates; snippets first converts snippet sets in the earlier PHP format into .ini files (see chapter 10)

admin password NAME

Set the password of a user – for example if the only superuser has forgotten theirs

key status
key rotate [--invalidate-passwords]
key retire KID [--force]

Change the key file .camindo.key, see changing the key

key rehash

Recalculate search values of encrypted fields – once, for data that was stored case-insensitively before the search

audit verify [TYPE ID]

Check the audit log: checksum chain of the files under var/audit/ and comparison with the database

audit reseal TYPE ID [--reason=…] [--dry-run]

Re-chain a broken checksum chain after checking, see security and compliance

audit anonymize-ip [--dry-run]

Once after the update: shorten IP addresses in the files under var/audit/ that are still stored in full, see chapter 15

audit restore [--dry-run]

After restoring a database backup: remove entries under var/audit/ that are newer than the database (copy under var/audit-restore-…/)

audit delete TYPE ID [--dry-run]
audit delete --area=ID [--force] [--dry-run]

Delete the entire history of an object (for example audit delete record 4237, audit delete admin 34) – in the database and under var/audit/; for records also their jobs of the deletion policies. With --area=ID, the history of all records of an area, including those already deleted; without --force, the command only names the number. --dry-run shows what would be deleted. Only for exceptional cases – normally the retention periods and the deletion policies delete

audit strip-names

Once after the update: remove the names of the acting persons from older entries – afterwards they only appear as an ID (repeatable)

api login --admin=NAME [--lifetime=SEC]
api call METHOD '{…}'
api whoami
api methods
api describe METHOD
api logout

The API on the command line: login creates a session for the user and outputs its identifier, which the following commands need as --session=… or in the environment variable CAMINDO_API_SESSION. The user group of the user needs the interface “Command line (CLI)”

cache instances

Rewrite the buffer for websites, languages, areas and group rights (also part of cache all)

htaccess
htaccess renew

Check or renew the .htaccess – like the “Renew” button under “Tools” → “Server”. Without an argument the command reports whether the file matches the directory structure (exit code 1 if not); renew rewrites the section managed by camindo CMS, lines outside it are kept. upgrade renews the file anyway

template commands --lang=en_US
template commands --all --out=DIRECTORY

Output the CMS commands (core and active modules) with their parameters and descriptions as JSON – for editor support such as the extension for Visual Studio Code. Without --all in one language to standard output, with --all one file commands.{language}.json per backend language in the directory

Changing the key

The file .camindo.key encrypts confidential fields (email addresses, telephone numbers, encrypted input fields, messages, secrets of the authenticator app), salts the password check values and signs accounts, user groups and rights: for each account and each right camindo CMS stores a checksum that can only be calculated with the key. Whoever changes only the database thereby produces an invalid checksum – the account or right counts as manipulated and is ignored. After a key change, all checksums must therefore be recalculated (“re-sign”).

To introduce a new key, run

php Core/cron/camindo.php key rotate

The command backs up the database, switches on maintenance mode, re-encrypts everything with the new key, re-signs (including the checksum chain of the audit log, after the old one has been checked) and signs out all users. It can be run at most once every 24 hours – a second call so soon afterwards would be a mistake; key status names the earliest possible time. The old key remains as a retired key in .camindo.key.retired: passwords cannot be converted, because camindo CMS does not know them – they remain valid and are switched automatically to the new key at the next sign-in. Recovery codes and known devices remain valid as well.

File permissions of the key: The new .camindo.key and .camindo.key.retired receive the same permissions as the previous .camindo.key – even if you have deliberately set permissions other than 0600, for example because the web server can read the file on your system only through the group. If there is no .camindo.key, because the key comes from the environment variable CAMINDO_KEY, the setting permissions.key_file in .camindo.php applies (default 0600). key status warns if the permissions of the file deviate from it. If you run the change as root, both files also keep the owner of the previous .camindo.key – otherwise the web server could no longer read them afterwards.

key status shows the current key, retired keys with the deadline security.key_password_grace_days (default 90 days) and how many passwords still depend on the old key. Once the deadline has passed, remove the old key with key retire KID --force: whoever has not signed in by then needs a new password (user: admin password NAME, frontend user: “Forgot password”), and recovery codes from before the change no longer apply.

Passwords publicly exposed? Then

php Core/cron/camindo.php key rotate --invalidate-passwords

All passwords become invalid at once, all sessions, known devices and recovery codes are deleted, and the old key is removed immediately. Afterwards set the passwords of the users with php Core/cron/camindo.php admin password NAME; frontend users set theirs via “Forgot password”. If there is reason to suspect that the key itself has fallen into the wrong hands, also reset the authenticator apps of the users.

Back up .camindo.key again after every change – separately from the database backups.

Key in the environment variable CAMINDO_KEY? key rotate nevertheless writes the new key into the file .camindo.key – it takes precedence over the variable, so the system works with the new key immediately. Afterwards enter the content of .camindo.key into the variable; only then may you remove the file. Until then, an operating notice appears on the start page. The file .camindo.key.retired with the retired keys remains in any case.

Configuration

The configuration is in .camindo.php in the project directory. The installer writes the most important details; everything else has defaults that you can override there, see the following table. The complete, commented list is in Core/src/Config.php.

The file contains access data: permissions 0600, never in a version control system!

Key Default Meaning
system.locale – locale of the installation, for example de_DE
system.timezone – time zone of the system and the cron jobs
system.development_version false development mode – never on a live system
system.debugsql false log all SQL commands with runtime – only together with system.development_version, purely a developer feature
system.link_length 120 maximum length of automatically generated slugs
system.html_allowed_tags [] optional whitelist of permitted HTML tags in contents, for example ['p','br','strong','a','h1','img']; other tags are removed when saving and displaying. Script, iframe, object, embed, base, meta and event handlers are always removed by camindo CMS
admin.2_factor true two-factor sign-in by email code (users with an authenticator app always get its prompt)
admin.2_factor_expiration 600 validity of the sign-in code in seconds
admin.max_inactive_sec 1800 automatic sign-out after inactivity (seconds)
admin.edit_lock_sec 5400 expiry time of edit locks (seconds): as long as it remains unchanged, a working copy no longer locks the record. Longer than the automatic sign-out, so that an open form can still be saved after a break and renewed sign-in
admin.bruteforce_max,
admin.bruteforce_delay
3 / 600 permitted failed attempts per sign-in name and blocking period in seconds (0 = protection off). On a device on which the user has already signed in, their attempts count separately – a blocked name does not lock them out there
admin.bruteforce_ip true when there are too many failed attempts, note the visitor’s address in the log security
admin.throttle.login_ip [20, 600] at most this many failed sign-ins per visitor address in the time window (seconds), across all sign-in names – after that, sign-in from this address is blocked (0 = off). Behind a proxy, set security.trusted_proxies
admin.backend_domain '' fixed address of the backend, for example 'cms-k7x2.example.com' (see fixed backend domain). Empty: backend under /backend/ of every domain
admin.cookie_secure,
admin.cookie_samesite
true / 'Lax' send the session cookie only over HTTPS. false only for a local test without SSL (quick start); SameSite attribute of the cookie
admin.password_retention_days 0 validity of a password in days; afterwards the user must set a new, different one at sign-in (0 = unlimited). A password set by another user must always be changed at the first sign-in
admin.allowed_origins [] further addresses from which forms may be sent to the backend, for example ['https://cms.example.com'] – only needed if a proxy rewrites the host name (see security headers)
admin.skiplist_default 20 records per page in lists; default for the setting “Records per page” of a content area
admin.list_max_datasets 200 maximum entries in selection lists
admin.max_include_depth 20 maximum nesting depth of templates (CMS:INCLUDE, CMS:SELECT)
admin.trash_retention_days 7 retention period in the trash
admin.mass_delete_threshold 200 from this number on, mass deletion and batch processing necessarily run in the background (cannot be deselected in the backend; can be bypassed via the API with background: no); 0 = threshold off
admin.import_background_threshold 200 from this number of records on, an import runs in the background
admin.export_background_threshold 1000 from this number of records on, an export runs in the background
admin.export_retention_hours 24 retention time of export files that have not been collected
admin.tmp_retention_hours 24 retention of temporary files in var/tmp
admin.include_cache true output cache of the templates (CMS:INCLUDE … caching=…); false switches it off, all templates run again on every call
admin.include_cache_lock_wait 30 a page call waits at most this many seconds while another creates the same cache file; after that it creates the output itself
api.max_inactive_sec,
api.max_lifetime
3600 / 86400 sessions of the command line and the PHP API: automatic sign-out after inactivity and maximum runtime in seconds (also the upper limit of a runtime specified at sign-in)
api.max_limit 200 at most this many records per page of an API list
api.base64_max_bytes 20971520 maximum size of a file transferred as Base64 in bytes (0 = no limit)
security.hsts_max_age 0 HSTS: browsers only allow the domain via HTTPS, for this number of seconds (0 = off), see security headers
security.hsts_include_subdomains false HSTS also for all subdomains
security.trusted_proxies [] proxies or load balancers in front of the website (IP addresses or ranges such as 10.0.0.0/8). Only from them does camindo CMS take the visitor’s address from X-Forwarded-For – important for blocks and throttling per visitor
security.frontend_csp 'off' script policy of the websites without their own setting: 'off', 'report', 'enforce' – see chapter 3
security.form_challenge '' class of an external CAPTCHA for protected forms (developers)
security.magick_allow [] formats that ImageMagick may additionally process: 'svg', 'pdf' – see securing ImageMagick
security.password_policy 12 characters, 1 special character, 1 uppercase (not at the beginning), 1 lowercase letter password policy, see passwords and sign-in devices
security.password_hash Argon2id, otherwise bcrypt method and effort for stored passwords
security.device_notify,
security.device_days,
security.device_cookie
true / 180 / 'c9dev' note when signing in from a new device; how long an unused device stays known (at the same time the runtime of the cookie); name of the device cookie
security.login_notifier '' own class for the notes (for example SMS instead of email)
security.admin_2fa 'totp' authenticator app (TOTP) for users: 'totp' selectable, 'totp_required' mandatory for superusers, 'email' switched off – see two-factor sign-in
security.totp_issuer '' name under which the account appears in the app (empty: camindo); together with the sign-in name@domain
security.webauthn true passkeys and security keys as second factor in the backend – only with a fixed backend domain (admin.backend_domain). false switches off creating and signing in with a passkey
security.key_password_grace_days 90 deadline after a key change that key status names for passwords on the old key
security.backend_csp 'enforce' script protection of the backend: 'enforce' allows only scripts from camindo CMS. 'report' lets everything run and logs violations (module “Log files”, log csp), should an add-on no longer work. 'off' switches it off
audit.enabled true audit log: who changed what and when, see audit log
audit.retention_days_changes 0 retention of the entries for existing objects in days (0 = unlimited)
audit.log_logins false record successful sign-ins and sign-outs to the backend in the audit log (failed ones always), see chapter 15
audit.retention_days_logins 90 retention of sign-ins/sign-outs and failed attempts in days (0 = as audit.retention_days_changes)
audit.retention_days_deleted 365 retention of the history of permanently deleted objects in days, counted from the deletion (0 = unlimited) – not for records, whose history is governed by the deletion policy
audit.page_bytes 1048576 contents of the audit log are under var/audit/, one file per object; from this size in bytes a new page begins
privacy.ip_anonymize_days 7 after this many days, IP addresses in the audit log, in sign-in attempts and in the log files are shortened (0 = at the next run of the task “Apply retention periods”), see chapter 15
privacy.message_days 60 internal messages are deleted this many days after sending – for sender and recipient (0 = never)
privacy.ip_v4_octets 1 how many digits at the end of an IPv4 address are zeroed: 1 (203.0.113.0) or 2 (203.0.0.0). IPv6 always loses the last 80 bits
ai.* – add-on AI assistant: provider, access key, model, see AI assistant
email.* – email dispatch, see below
user.* – frontend community, see chapter 13
paths.php,
paths.mysqldump,
paths.magick,
paths.ffmpeg,
paths.ffprobe,
paths.composer
– paths to external programs; leave empty what does not exist (composer: update check)
paths.document_root,
paths.media,
paths.backend
public/,
'/media',
'/backend'
directory to which the web server points; address paths of the media files and the backend relative to it
paths.key_file,
paths.sqlpwd_file
.camindo.key,
.sqlpwd
key file and access file for mysqldump (absolute paths, default in the project directory)
paths.var_dir,
paths.tmp_dir,
paths.upload_dir
var/,
var/tmp/,
var/upload/
writable main directory, temporary files and directory for large files transferred via FTP (selection via the file icon next to upload fields)
permissions.dir,
permissions.file
0775 / 0664 Unix permissions for directories and files that camindo CMS creates
permissions.key_file 0600 permissions of .camindo.key and .camindo.key.retired during a key change, if there is no .camindo.key yet (key from CAMINDO_KEY) – otherwise the change keeps the permissions of the previous file
db.* – access data of the database

Access data from environment variables

Passwords and the key need not be in files in the project directory. If your host or your server environment lets you set environment variables (Docker, PHP-FPM, web server configuration, secret management of the platform), camindo CMS can take these values from there. The rest of the configuration – database host, mailbox, provider – remains in .camindo.php.

Variable replaces Precedence
CAMINDO_DB_PASS db.pass variable before .camindo.php
CAMINDO_SMTP_PASS email.smtp.pass variable before .camindo.php
CAMINDO_AI_API_KEY ai.api_key (add-on AI assistant) variable before .camindo.php
CAMINDO_KEY the key file .camindo.key (exactly its content) file before variable – so that after a key change the new key applies until you have updated the variable. If the file exists but is not readable, camindo CMS aborts instead of taking the variable

An empty variable counts as not set. camindo CMS does not read an .env file – the variables must reach the environment of PHP from outside, both for the web server and for the cron jobs and the command line.

If the database password comes from CAMINDO_DB_PASS, the backup does not need the file .sqlpwd: it writes a separate option file for each run and deletes it afterwards. The installer also takes over CAMINDO_DB_PASS, then leaves the password out of .camindo.php and does not create a .sqlpwd; if CAMINDO_KEY is set, it does not create a .camindo.key.

php Core/cron/camindo.php status shows which values come from the environment (“from environment”). After reading them in, camindo CMS removes the variables from the environment of PHP: programs that it calls (ImageMagick, ffmpeg, mysqldump) do not see them, nor does the PHP info in the module “Tools” – except under “Apache Environment”, if the web server sets them with SetEnv. Therefore prefer the environment of PHP-FPM or of the container.

Email dispatch

camindo CMS sends emails, among other things, for the sign-in code of the two-factor sign-in.

Key Meaning
email.noreply sender address of the system emails – prerequisite for the two-factor sign-in
email.reply contact address that is named in system emails (see below)
email.smtp.host, user, pass, enc, port SMTP mailbox for reliable sending (enc: starttls or smtps). Empty: PHP function mail()

With an SMTP mailbox of the sender domain, system emails land in spam much less often. Emails that could not be sent appear in the module “Log files”.

The contact address (email.reply) is not used as the sender (From) – otherwise automatic replies (autoresponders) would fill your mailbox. It appears only in the text of the email.

Fixed backend domain

Without a further setting, you reach the backend under /backend/ of every domain of your installation – and thus so does everyone who knows your website. Better is a fixed backend domain, preferably a subdomain that nobody guesses:

'admin' => [
    'backend_domain' => 'edit-website.example.com',
],

Prerequisites: the subdomain points via DNS to the same server and, in the web server, to the same directory public/ as the websites, and it has its own HTTPS certificate (Let’s Encrypt is sufficient).

After this, the following applies:

  • Under all other domains, /backend/ answers with “not found” – without a note of the correct address.
  • The preview opens under the domain of the website. The link in it applies only as long as you are signed in to the backend, and at most eight hours.
  • Frontend editing transfers the saving of personal settings to all websites.
  • The address of your backend does not appear on any page of the website.

A fixed backend domain is also a prerequisite for signing in with passkeys.

Security headers

camindo CMS sends with every page headers that the browser implements as protection:

  • No foreign frame: The backend cannot be displayed in any <iframe>, the website only within its own domain. This prevents a foreign page from slipping clicks into the backend, sign-in or shopping cart (clickjacking).
  • Only own scripts in the backend: The backend executes only JavaScript from camindo CMS itself. If a script reaches content despite all filters, it does not run. If an add-on no longer works afterwards, temporarily set security.backend_csp to 'report' and pass the entries of the log csp on to the manufacturer.
  • No reinterpretation of file types (nosniff) and economical passing on of the address to external sites (Referrer-Policy).
  • Origin check: camindo CMS accepts forms and actions in the backend that change something only if the browser sends them from the address of the backend (header Origin or Referer) – in addition to the security token of each session. If the backend runs behind a proxy that rewrites the host name, enter the public address in admin.allowed_origins.

You need not set this up. If a page is to be embedded by partners, the template programmer changes this in the template (CMS:HEADER).

HSTS instructs the browser to call the domain only via HTTPS. Only switch it on when the page runs permanently and completely over https:// – the browser remembers the rule for the set time, even if you switch it off again. Best set HSTS in the web server or with your host; if that is not possible, in .camindo.php:

'security' => [
    'hsts_max_age' => 300,        // five minutes for testing, then 31536000 (one year)
],

hsts_include_subdomains only if really every subdomain can do HTTPS.

Passwords and sign-in devices

Stored passwords are kept with Argon2id – a deliberately slow and memory-intensive method that makes trying out stolen data expensive. If the PHP of the server does not offer Argon2id, camindo CMS uses bcrypt, which every PHP supports. The default is the OWASP minimum (19 MB of memory, two passes, around 30 milliseconds per sign-in). Older passwords are switched automatically at the next sign-in; nobody has to set a new password.

'security' => [
    'password_hash' => ['algo' => 'auto', 'memory_cost' => 19456, 'time_cost' => 2, 'cost' => 11],
],

algo: auto, argon2id or bcrypt; memory_cost in KiB and time_cost for Argon2id, cost for bcrypt.

The password policy always applies to the passwords of the users and, for password fields in the frontend, when the field carries the extension Password policy. Each rule can be switched off with 0:

'security' => [
    'password_policy' => [
        'min_length'      => 12,    // minimum length
        'min_special'     => 1,     // special characters: everything except letters and digits
        'min_upper'       => 1,     // uppercase letters …
        'upper_not_first' => true,  // … an uppercase letter at the beginning does not count
        'min_lower'       => 1,     // lowercase letters
        'min_digit'       => 0,     // digits
    ],
],

Length protects more than special characters: a long password made of several words is stronger than a short one with punctuation. If you relax the rules, you should rather lower min_special than min_length.

Sign-in devices: camindo CMS recognizes the browser with which someone signs in. If an account signs in from a new device, the holder receives a note – users by email to their stored address (requires the email dispatch), frontend users via the templates of the website (see chapter 13). The first sign-in of an account triggers no note. A device that has not been used for device_days days counts as new again afterwards; likewise a browser whose cookies have been deleted. The two-factor sign-in remains in force even on known devices.

Two-factor sign-in

After the password, camindo CMS requires a second factor – one of three for each user:

Factor When Strength
Passkey (WebAuthn) the user has created one – requires a fixed backend domain highest: bound to the address of the backend, cannot be intercepted, no input on forged pages possible; plus recovery codes
Authenticator app (TOTP according to RFC 6238) the user has set it up in their personal settings and security.admin_2fa is not 'email' high: the code is generated on the phone; plus ten recovery codes that can each be used once
Code by email otherwise, if admin.2_factor is on as secure as the mailbox

security.admin_2fa controls the app:

'security' => [
    'admin_2fa' => 'totp_required',  // 'email' | 'totp' (default) | 'totp_required'
],

With 'totp_required', all superusers must use the app. Whoever does not yet have it sets it up at the next sign-in directly in the sign-in dialog; a superuser can then no longer switch it off. For the other user groups, the app remains voluntary. With 'email', the app cannot be set up, and existing ones are suspended – the email code applies again.

Passkeys are open to every user as soon as admin.backend_domain is entered, independently of security.admin_2fa. A passkey also fulfills the requirement of 'totp_required'. If a user has passkeys and the app, the sign-in dialog takes both. Passkeys can be switched off with 'security' => ['webauthn' => false] – accounts that had only passkeys then sign in with a recovery code. The binding to the domain means: if the backend domain changes, all passkeys must be created again.

A wrong code counts like a wrong password (admin.bruteforce_max), and a correct password does not reset the counter – only the complete sign-in does. In this way the six-digit code cannot be tried out. Every app code applies only once.

When a user sets up the app, switches it off or signs in with a recovery code, they receive an email (or a message via the login_notifier), and the process appears in the audit log. Lost phones are reset by a superuser (chapter 7).

Instead of an email, an SMS or another message can be sent: a developer writes a small PHP class for this that fulfills the interface Camindo\Core\LoginNotifierInterface, and you enter it:

'security' => [
    'login_notifier' => 'App\SmsNotifier',
],

The class receives the events “sign-in from a new device” (backend and frontend) and “password changed” (frontend) with account, device, IP address and time. If it cannot send, this is recorded in the log security.

Securing ImageMagick

Image files are a popular attack route: a manipulated file exploits a vulnerability in ImageMagick or in a program that calls ImageMagick. camindo CMS protects itself on three levels, without you having to set up anything:

  • Own security policy: Before each call, a policy applies that camindo CMS creates in var/config/magick/policy.xml. It blocks script and special formats, foreign helper programs (Ghostscript, curl, LibreOffice …) and PostScript, and limits memory, runtime and image size. It supplements the policy of your server, it cannot release anything that the server blocks. This also works on shared hosting.
  • Format from the content: ImageMagick reads each file only with the format that camindo CMS recognized when uploading. If the extension and content do not match, the upload is rejected.
  • Time limits: Each conversion is aborted after two minutes at most.

You release SVG and PDF with security.magick_allow (see media). The detail view of a converter shows the installed ImageMagick version, whether it is outdated, and whether SVG and PDF actually work.

If you operate the server yourself, you can further isolate ImageMagick:

  • Keep ImageMagick up to date (from version 7.1.1). Older versions work, but contain known vulnerabilities.

  • Do not install Ghostscript if you do not convert PDFs.

  • Let ImageMagick run under its own user or in its own container, which may only read and write the media directory and var/tmp. Then enter in paths.magick a small script that passes on the call and sets limits. It must be called magick and lie in its own directory; this is how camindo CMS recognizes ImageMagick 7:

    #!/bin/sh
    # /opt/camindo-magick/magick  -> paths.magick
    ulimit -t 150 -v 1048576          # CPU seconds, memory in KB
    exec sudo --preserve-env=MAGICK_CONFIGURE_PATH -u imagick /usr/bin/magick "$@"

    sudo needs an entry in the sudoers file for the web server user. It is important to use --preserve-env: without the variable MAGICK_CONFIGURE_PATH, the camindo policy is missing. The user imagick must be able to read var/config/magick/.

    For ImageMagick 6, place two such scripts, convert and identify, in the directory and enter the path to convert.

  • If you process only JPEG, PNG and GIF, the GD processor is sufficient. It needs no ImageMagick.

Logs

All logs are in var/log/, one file per day and type (for example 2026-09-22_php_errors.log, …_scheduler.log, …_security.log). Superusers see them in the module “Log files”.
php Core/cron/camindo.php status summarizes the state of the system.

The log files grow until they are deleted. The scheduler task “Clean up logs” (created by the installation, see scheduler) deletes old log files and stored sign-in attempts with IP address; you set the periods in the task. Independently of this, camindo CMS shortens the IP addresses in the log files after privacy.ip_anonymize_days days (see chapter 15).

Who changed what in the backend is not stated in the log files, but in the audit log.

Operating notices on the start page

Superusers see a block “Operational notices” beneath the module tiles on the start page as soon as something is wrong in operation. If everything is in order, it does not appear.

Notice Meaning
Cron job not running cron.php has not reported for more than 15 minutes. The scheduler (including all maintenance tasks) and thumbnails stand still
Temporary files too old var/tmp/ contains entries that are older than admin.tmp_retention_hours (plus two hours). The task “Delete temporary files” does not take effect
Maintenance tasks missing For one of the maintenance tasks (trash, temporary files, deletion policies, retention periods, check audit log, updates) there is no switched-on task in the scheduler
No database backup There is no switched-on task “Database backup”
Database backup failed / too old / unusable The last run reported an error, backup-1.sql is older than two days or unusable: smaller than 10 KB (then usually only an error message is in it) or without the final line -- Dump completed (dump aborted)
Tasks with errors Other scheduler tasks whose last run failed
Logs are not cleaned up There is no task “Clean up logs”
Frontend cache is not cleaned up Templates use the output cache, but there is no switched-on task “Clean up frontend cache”
Little free storage Less than 2 GB free for var/ (less than 512 MB: alarm)
Not writable var/tmp, var/log or the media directory
Time zone tables missing The database server has no time zone tables; camindo CMS uses the current UTC offset of the configured time zone, so after a daylight saving change, times are off by one hour. For the fix, see chapter 1
Email missing two-factor sign-in is on, but no sender address is set up
Development mode system.development_version is switched on
Cookie without HTTPS admin.cookie_secure is false
Key in CAMINDO_KEY outdated The environment variable CAMINDO_KEY does not contain the current key – usually after a key change. .camindo.key is used; enter its content into the variable
security.txt missing There is no reporting channel for security vulnerabilities (see chapter 14)
Audit log damaged The daily check has found a broken checksum chain in var/audit/, missing entries or entries of the database that do not match the files; details with php Core/cron/camindo.php audit verify
Security vulnerabilities / new version / updates Result of the daily update check: known vulnerabilities in installed packages (alarm), a new version of camindo CMS (warning); “Open” leads to “Info”

A click on “Open” leads, where possible, directly to the place where the notice can be remedied.

Checklist for live operation

  • [ ] HTTPS set up, admin.cookie_secure and user.cookie_secure set to true (default – false may no longer stand in .camindo.php)
  • [ ] system.development_version is false
  • [ ] ImageMagick up to date and, in the detail view of the converters, no warning about version and security policy
  • [ ] HSTS switched on (web server or security.hsts_max_age), as soon as HTTPS runs permanently
  • [ ] Email dispatch set up, two-factor sign-in switched on, all users with an email address (sign-in code and notice of new devices)
  • [ ] Superusers with an authenticator app – on live systems security.admin_2fa = 'totp_required'
  • [ ] Password fields in the frontend with the extension “Password policy”
  • [ ] Shops and member areas: second factor in the profile area at least “Code on a new device” (chapter 13)
  • [ ] Script policy of the website first “Report only”, after a clean log “Enforce” (chapter 3)
  • [ ] fixed backend domain entered (admin.backend_domain), own subdomain with HTTPS
  • [ ] behind a proxy or load balancer: security.trusted_proxies set
  • [ ] the cron job cron.php runs (php Core/cron/camindo.php status)
  • [ ] maintenance tasks present and switched on in the scheduler (php Core/cron/camindo.php scheduler list), times suitable
  • [ ] database backups additionally stored outside the server
  • [ ] templates use caching: task “Clean up frontend cache” set up in the scheduler
  • [ ] periods of the audit log (audit.*) coordinated with the data protection concept
  • [ ] public/.well-known/security.txt created
  • [ ] no operating notices on the start page
  • [ ] .camindo.key secured in a safe place (or the content of CAMINDO_KEY)
  • [ ] write permissions of the web server only on var/ and public/media/
  • [ ] instances with “Canonical requests” for search engines

License

camindo CMS is free software under the GNU AGPL version 3 or later. You find the license text in Core/LICENSE, the licenses of the bundled libraries in Core/THIRD-PARTY-NOTICES.md.

For operation, this means:

  • An unchanged installation you may operate without further obligations, also for customers and for a fee.
  • Your templates and all contents belong to you and may be under any license – an additional permission in Core/LICENSING.md expressly excludes them.
  • If you have changed the core or an AGPL component and the website is publicly reachable, you must offer visitors the source code of your modified version, for example with a link to a public repository.

13 Community features

With the community features, frontend users can register, sign in, maintain their profile and create contents themselves – comments, ratings, classified ads, images. Protected areas and files are open only to signed-in frontend users; a shopping cart remembers selected items.

Registered visitors are called frontend users in camindo CMS – in contrast to the users in the backend. A frontend user has no user account in the backend: their digital image in the CMS is a profile record, an ordinary record like their contributions. As a result, all tools of the editorial team apply to them – search, batch processing, export, rights.

Profile areas

An area of the type PROFILE contains one record per frontend user. The blueprint “Members” creates such an area with the fields name, email and password. Further profile fields – avatar, address, interests – you add as usual.

Under “Website sign-in” you define:

Setting Meaning
Name the field with the sign-in name, type “Short text” (also encrypted), for example the email address
Password the field of the type “Password”, against which the sign-in is checked
Second factor whether the sign-in additionally requires a code – see below

Both fields can be selected only after they have been created.

Assign the extension “Password policy” to the password field (section “Extension” of the field definition). Only then does camindo CMS reject weak passwords at registration and when changing the password. The rules are those from chapter 12; you can set individual values differently on the field. The template programmer displays the same rules in the form (CMS:USER_PASSWORD_RULES).

Without the extension, the password strength can be controlled only by minimum length (minimum) and pattern (RegEx) in the settings of the input field.

Contribution areas

An area of the type UGC (“user-generated content”) takes in contributions of signed-in frontend users. The blueprint “Comments” creates such an area with a name and comment text. Under “Authentication” you select the profile containers whose frontend users may create contributions here. Each contribution is linked to the profile of its author. A frontend user may change and delete only their own contributions.

The templates

Registration, sign-in, password reset and the saving of contributions are handled by templates with the commands CMS:USER_* – see the manual for template programmers.

The sequence of a registration:

  1. The visitor fills in the registration form. camindo CMS creates their profile with the status “Unconfirmed” and generates a one-time code; the template sends it as a confirmation link by email (command CMS:EMAIL).
  2. A click on the link enables the profile; the frontend user can sign in.
  3. If they forget their password, they request a link for resetting it in the same way.

The links are valid for a limited time (default: 24 hours for the confirmation, one hour for the password reset) and can be used only once.

Sign-in devices and passwords

  • New password only with the current one. In the customer account, a frontend user changes their password only if they enter the current one. Whoever gains access to an open session cannot take over the account in this way. The exception is the link from “Forgot password”: it counts as proof, the current password is no longer required.
  • “Forgot password” works on every device – common and sensible, because people often forget their password precisely on a new computer or phone. The security lies in the link: it goes only to the stored email address, is valid for one hour and only once.
  • After a new password all other sign-ins of the user are ended. If the user is to be informed about the password change (if they did not make it themselves), the template that is displayed after saving the profile must take care of the notification, for example by means of CMS:EMAIL.
  • Sign-in from a new device: The website can send the frontend user a note when their account signs in from an unknown browser. The template sends the email; alternatively a separate notification route, for example by SMS (chapter 12).

Second factor

A customer account should not depend on the password alone. With the setting “Second factor” in the profile area, sign-in additionally requires a six-digit code:

Setting Effect
Off as before, only the password
Optional frontend users can switch on an authenticator app or the code by email themselves in the customer account
Code for new device as “Optional”. In addition, the website sends a code by email if someone signs in from an unknown device (browser). On the usual devices, the password remains sufficient. Recommended for shops
Always every sign-in requires a code: by email or – if set up – from the app

The frontend user sets up an authenticator app (Google Authenticator, Microsoft Authenticator, 1Password …) in the customer account by scanning a QR code, typing in the displayed key or opening a link on the phone. They receive ten recovery codes in case they lose their phone. If they no longer have these either, reset the second factor in the backend: open the profile record, in the menu on the right choose “Second factor” → “Reset second factor” (write permission for the area required; the process appears in the audit log). Check beforehand whether the request really comes from the holder.

If an app is set up, the link from “Forgot password” also requires the code – otherwise access to the email mailbox would have sufficed to take over the account.

The code is sent by email – like all emails of the community features – by the template. If it is to go by SMS, the template programmer calls an SMS service there instead of the email.

The templates for this (CMS:USER_LOGIN_CODE, CMS:USER_2FA) must be present before you switch on the setting – otherwise every sign-in that would need a code fails.

Protecting the forms

“Forgot password” and registration send emails to addresses that someone has typed in. So that nobody floods foreign mailboxes via your website, camindo CMS limits the requests (throttling) – per entered address, per visitor (IP address) and per website. In addition there is a form protection against robots, with a block per IP address against the mass trying out of passwords. The limits are set in the configuration (below). The defaults suit most websites.

Registrations never confirmed are deleted after seven days – whoever enters someone else’s address blocks it only briefly in this way.

If the website runs behind a proxy or load balancer, enter it under security.trusted_proxies (chapter 12) – otherwise camindo CMS sees all visitors with the address of the proxy and blocks all of them at once during throttling.

Review and owner

Everything that a frontend user creates or changes is marked with “Needs review” and appears in the widget “Unreviewed content of frontend users” of the editorial team (see the manual for editors). Create the meta field “Needs review” in the profile and contribution areas, so that the editorial team can remove the marking after the review.

Each record needs an owner in the backend (see chapter 7). For records of frontend users, the template names it (parameter owner) or the configuration (user.default_owner_id). If neither exists, the command cannot save. Via the user group of this owner you control which editorial team is responsible for the contents of the frontend users.

Protected files

Upload and thumbnail fields have a section “Authentication”. With “Signed-in frontend users only” and the selection of the eligible profile containers, the directory of the field is blocked on the server. The files are then delivered only to signed-in frontend users from these profile areas – for example member images, documents for course participants or downloads for customers. Everyone else receives a “not found” response.

Data export and deletion

If a website user requests information about their stored data (Art. 15 GDPR) or a copy to take along (Art. 20 GDPR), you create an export of personal data: at the profile record in the menu on the right or in the module “Privacy” (see chapter 15). The export is a partial extract that you complete before passing it on. Website users cannot download their data themselves.

Deleting (Art. 17 GDPR): What happens when a profile and its contributions are deleted is determined by the deletion policy of the area – put into the trash, delete permanently, overwrite individual fields, also with a delay (see chapter 15). The contributions of the user are always deleted according to the policy of their own area (trigger “UGC: Profile was deleted”). The deletion dialog shows what the policy does with the record. If the trash of a user must be emptied immediately: “Tools” → “Trash of a user” (chapter 15). If all data including the change history (audit log) must be removed: camindo.php audit delete.

What camindo CMS stores in total about website users is described in chapter 15.

Configuration

The community settings are in .camindo.php under user (see chapter 12):

Key Default Meaning
user.max_inactive_sec 86400 automatic sign-out of a user after inactivity (seconds)
user.basket_days 30 retention of the shopping cart of visitors who are not signed in (days)
user.token_expiration 86400 validity of the confirmation link after registration (seconds)
user.password_token_expiration 3600 validity of the link “Forgot password” and of the period afterwards in which the new password can be set without the current one (seconds)
user.bruteforce_max / bruteforce_delay 5 / 600 permitted failed attempts at sign-in and blocking period (seconds)
user.cookie_secure true session cookie only over HTTPS. false only for a local test without SSL
user.default_owner_id 0 user to whom records of frontend users belong if the template names no owner
user.throttle see below throttling: per rule [at most, seconds], at most 0 switches it off
user.formguard_min_seconds / formguard_max_seconds 3 / 3600 form protection: at the earliest or at the latest this many seconds after the call may a protected form be sent. A template can set the minimum time differently per form
user.unconfirmed_days 7 delete registrations never confirmed after this many days (0 = never)
user.login_code_expiration 600 validity of the sign-in code (seconds)

Defaults of the throttling (user.throttle):

Rule Default counts
pw_value 3 per hour “Forgot password” per entered address
pw_ip 10 per hour “Forgot password” per visitor (IP)
register_ip 5 per hour registrations per visitor
register_value 3 per day registrations per email address
mail_instance 200 per hour both together per website
login_ip 30 per 10 minutes wrong passwords per visitor, across all accounts
login_code 3 per 10 minutes “Send new code” per account
email_ip 5 per hour contact forms with throttling (CMS:EMAIL throttle=true)
csp_report 60 per 10 minutes reports of the script policy per visitor
zip_ip 30 per hour newly created ZIP downloads (CMS:ZIP) per visitor

Change one rule individually, for example 'throttle' => ['pw_ip' => [20, 3600]].

14 Security, data protection and operation – for decision-makers

This chapter is aimed at everyone who decides on the use of camindo CMS or is responsible for it: IT management, data protection officers, information security and project managers. It describes what the system provides and which organizational tasks remain with the operator. The technical details are in the linked chapters.

Security at a glance

Area What camindo CMS provides More about it
Sign-in to the backend password policy (default: at least 12 characters), storage with Argon2id, two-factor sign-in with authenticator app (can be made mandatory for superusers) or email code, block after repeated failed attempts, note by email when signing in from a new device, automatic sign-out after inactivity chapter 12
Rights user groups with rights per module, website (instance), language and content area; records with owner; superuser modules (log files, audit log) for no one else chapter 7
Protection against manipulation rights and user accounts are signed; if someone changes them directly in the database, they do not take effect chapter 12
Sign-in to the website password policy, block after failed attempts per account and per visitor, second factor per profile area (authenticator app or code by email, also only on new devices), throttling and form protection for “Forgot password” and registration chapter 13
Attacks via the browser protection against cross-site request forgery (token) for backend and frontend forms; script policy (Content Security Policy) in the backend, in the frontend switchable per website; cleaning of HTML in contents; security headers chapter 3, chapter 12
Encryption fields of the type “encrypted” are stored encrypted in the database (AES-256-GCM), likewise email address and telephone numbers of the users. The key is kept outside the database (.camindo.key) chapter 5
Files protected files (“Signed-in frontend users only”) are blocked on the server and are delivered only after the session has been checked; image processing with a restricted ImageMagick policy chapter 8
Traceability audit log of all changes with before/after below

Limits you should know:

  • The two-factor sign-in by email code is only as secure as the email mailbox of the users. The authenticator app is stronger; for superusers it can be made mandatory (security.admin_2fa = 'totp_required', chapter 12).
  • The domain lock for media prevents files from being embedded via foreign websites. It is not access protection. Confidential files are protected only by the field option “Signed-in frontend users only”. Clients who must not reach each other’s files need separate installations (chapter 8).
  • Superusers have unrestricted access. Assign this role sparingly and only to named individuals, and never share access credentials.

Data backup and recovery

What must be backed up

Component Location Frequency (recommendation)
Database per scheduler to var/dbbackup/ daily
Media files public/media/ daily (incremental)
Project data var/templates/, var/custom/, var/extensions/, var/modules/, var/deleted/ daily
Audit log (contents) var/audit/ – belongs to the database backup of the same point in time daily
Configuration .camindo.php, composer.json, composer.lock with every change
Key file .camindo.key once, separately from all other backups

var/cache/, var/php/, var/tmp/, var/export/ and vendor/ need not be backed up – they are created anew.

Without the key file, encrypted contents and all signatures are unusable after a restoration. Whoever captures the key file together with a database backup can read the encrypted fields. Therefore keep it separately, for example in the IT password vault.

How to back up

camindo CMS creates the database backup itself: a task “Database backup” in the scheduler writes a complete dump to var/dbbackup/ every day and keeps the set number. In addition, camindo CMS backs up the database automatically before every update.

These backups lie on the same server. They protect against operating errors and failed updates, not against the loss of the server. For operation, the 3-2-1 rule applies: three copies, on two different media, one of them outside the data center. This is handled by the backup of your host or your IT, which regularly fetches var/dbbackup/ and the other directories from the server.

Every backup is checked after writing: if it is smaller than 10 KB (then it usually contains only an error message) or lacks the final line with which every complete dump ends, it is discarded and the run counts as failed. If a backup fails, superusers see this on the start page (operating notices): no backup task, failed run, backup older than two days or unusable.

Recovery

Individual contents editors recover themselves from the trash within the retention period (admin.trash_retention_days, default 7 days) – including files. How a record looked earlier is shown by the tab “Changes” (audit log); from it, individual values can be written back by hand.

Whole installation (for example after a server failure):

  1. Provide the server, install the code with composer install from the backed-up composer.lock.
  2. Restore .camindo.php and .camindo.key.
  3. Switch on maintenance mode: php Core/cron/camindo.php maintenance on.
  4. Create the database and import the backup, for example mysql camindo < var/dbbackup/backup-1.sql.
  5. Restore public/media/ and the project directories under var/.
  6. php Core/cron/camindo.php upgrade – reconciles components and rebuilds all caches.
  7. Set up the cron jobs (chapter 1), switch maintenance mode off, check the start page of the backend for operating notices.

Individual areas can additionally be backed up and restored selectively via XML export.

Practicing recovery

A backup whose recovery has never been practiced is a promise. Determine

  • how much data loss is acceptable at most (recovery point objective – with a daily backup up to 24 hours) and
  • how long the website may be down at most (recovery time objective),

and restore a backup on a test system according to the instructions above at least once a year.

Updates and security updates

Versions

camindo CMS follows semantic versioning MAJOR.MINOR.PATCH:

Level Meaning Effort
PATCH (9.0.1) only bug fixes, among them security corrections; no change to the database harmless, install promptly
MINOR (9.1.0) new functions; existing things continue to work; may extend the database install after a short check
MAJOR (10.0.0) changes that may require add-on components or templates to be adapted plan, check on a test system

Security corrections appear as a patch version for the current major version. The release notes of each version state whether it contains security-relevant corrections.

Installing updates

php Core/cron/camindo.php maintenance on
composer update
php Core/cron/camindo.php upgrade
php Core/cron/camindo.php maintenance off

upgrade backs up the database automatically beforehand. The way back is described in chapter 12.

Organizational

  • Determine who follows release notes and who installs updates (including deputies).
  • Install security corrections within a few days, other updates in the regular maintenance window.
  • Server software (PHP, database, web server, ImageMagick, ffmpeg) belongs to this as well. It is updated via the operating system or the host, not via camindo CMS.
  • Add-on components and Composer packages come along with composer update. Whether there are newer versions and whether security notices exist for installed packages, camindo CMS checks itself daily (composer outdated / composer audit); the result appears under “Info” in the main menu and as an operating notice on the start page (chapter 12).

Reporting security vulnerabilities

Vulnerabilities in camindo CMS

The procedure – reporting route, deadlines, supported versions – is described in the file Core/SECURITY.md. In short: please report security vulnerabilities in camindo CMS confidentially to camindo@camindo.de, not via public channels such as forums or issue trackers. Describe which version is affected and how the vulnerability can be reproduced.

A reporting route for your website: security.txt

For the websites that you run with camindo CMS, you are yourself the point of contact. The standard RFC 9116 provides for this a file /.well-known/security.txt. Create it under public/.well-known/security.txt, for example like this:

Contact: mailto:security@your-company.example
Expires: 2027-09-30T23:00:00.000Z
Preferred-Languages: en, de
Policy: https://www.your-company.example/security
  • Contact – an address that is read regularly (not a personal mailbox).
  • Expires – at most one year in the future; extend it in good time.
  • Policy – optional: how you handle reports, whether you name the persons reporting them.

As long as the file is missing, the start page of the backend reminds superusers of it.

Data protection (GDPR)

What personal data camindo CMS stores

The complete list – what, where and how long – is in chapter 15 – Data protection; here is the overview.

Persons Data Where
Users name, sign-in name, email, telephone (encrypted), password (as hash), personal settings, sessions, known devices with shortened IP address, activities (and, if switched on, sign-ins) in the audit log tables admin, admin_session, login_device, audit_log
Website users (community) all fields of the profile area that you define; their contributions; sessions, devices with shortened IP address, shopping cart profile and contribution areas, user_session, login_device, user_basket
Persons in contents what editors enter in content areas (for example contact persons, teams) content areas
All visitors IP address with failed sign-ins; technical logs admin_login_attempt, user_login_attempt, var/log/

camindo CMS sets in the frontend only technically necessary cookies (session, frontend editing, device recognition at sign-in) and does not embed third-party services. What your templates additionally load (fonts, maps, statistics) is your responsibility.

Retention periods

Data Period Setting
Deleted contents in the trash 7 days, then permanently including files admin.trash_retention_days
Deleted records, profiles and contributions as the deletion policy of the area determines (default: trash, after 7 days permanently; for a profile after 7 days all fields in the audit log emptied; after 1 year history deleted) module “Privacy” (chapter 15)
Audit log on existing contents unlimited (traceable back to creation) audit.retention_days_changes
Audit log on permanently deleted objects (except records) 365 days after deletion audit.retention_days_deleted
Known devices 180 days after last use security.device_days
Internal messages between users 60 days after sending privacy.message_days
Full IP addresses (audit log, sign-in attempts, log files) 7 days, then shortened; devices and audit files store them shortened from the start privacy.ip_anonymize_days, privacy.ip_v4_octets (chapter 15)
Sessions of website users 24 hours of inactivity user.max_inactive_sec
Anonymous shopping carts 30 days user.basket_days
Log files, security logs, failed sign-ins according to the setting of the task “Clean up logs” (default 90 / 365 / 30 days) scheduler
Database backups according to the set number scheduler, task “Database backup”

Align these periods with your record of processing activities. Keep in mind that deleted data lives on in database backups until the backup itself expires. This is permissible, but belongs in your deletion concept.

Rights of data subjects

Right Implementation in camindo CMS
Access (Art. 15) and data portability (Art. 20) “Export of personal data” in the module “Privacy” or at the profile record: a zip file with readable information, machine-readable JSON and the files, including contributions, devices, sessions, change log and retention periods. A partial extract that you complete with data outside the CMS (chapter 15)
Rectification (Art. 16) editing the profile in the backend or by the frontend user themselves, if the template offers it
Erasure (Art. 17) delete the profile – what then happens to profile, contributions, files and audit log is determined by the deletion policy; the trash of a user can be emptied immediately (module “Tools”). The deletion route for frontend users themselves is offered by CMS:USER_DELETE
Restriction (Art. 18) set the status of the profile to “Disabled”: sign-in is blocked, the data remain

The same applies analogously to users: their data are in the user management; deleted users remain as records so that the statement “created by” and “changed by” in contents and in the audit log remains valid. Their data export is created by whoever has the right to do so in the module “Privacy” (chapter 15).

Technical and organizational measures

For your documentation of technical and organizational measures (Art. 32) you can draw on the overview Security at a glance. The operator remains responsible for: hosting with a data processing agreement, HTTPS, access control to the server, backup outside the server, update process, role and rights concept.

Accountability: the audit log

The audit log answers the question “Who changed what, and when?” – for contents as well as for structure, templates, users and rights:

  • Every change with time, acting person (user, website user or system), origin (backend, frontend, API, AI assistant, import, batch processing, scheduler) and IP address.
  • Before and after of all changed fields, with texts compared paragraph by paragraph.
  • Failed sign-ins and personal data reports; successful sign-ins and sign-outs only if you switch them on (audit.log_logins).

Entries cannot be changed or deleted via the interface; they disappear only according to the set periods.

Storage and protection against forgery: The database contains only who did what, when, to which object, and which fields were affected. The contents – before and after – are in the file system under var/audit/, one file per object, to which only appending takes place (above 1 MB a further page begins). Every line carries a checksum that includes the previous one (HMAC chain): if a line is changed, removed or inserted, this is noticed – only someone with the key .camindo.key can forge the chain. Whoever has access only to the database can neither read nor change the contents; deleted, added or altered entries of the database are recognized by the comparison with the files – even if only the time, action, actor or designation has been rewritten. The check is performed every night by the scheduler task “Verify the audit log” (operating notice on the start page in case of findings), in the detail view of each entry, and at any time with php Core/cron/camindo.php audit verify. Its result replaces that of the nightly check; after audit reseal or the check of an individual object, the notice on the start page for this object is updated.

Restoring a database backup: The files under var/audit/ are not part of the database dump. After the restoration they contain entries that no longer exist in the database, and the database assigns their numbers anew. Therefore run immediately afterwards – before anyone works again – php Core/cron/camindo.php audit restore (with --dry-run only count). The command removes all entries that are newer than the state of the database, records this in the file as a control point, and first stores the affected files unchanged under var/audit-restore-{time}/.

Re-chaining a broken chain: If audit verify reports only “chain” for an object – the entries themselves are genuine, only their chaining is wrong, for example because a file was replaced or restored from a backup –, check the process and then re-chain: php Core/cron/camindo.php audit reseal template 433 --reason="…" (with --dry-run only display). The entries up to the break remain unchanged, the rest is re-chained behind a noted control point; the old files then lie under var/audit-reseal-{time}/. If an entry itself has been altered (“hmac”), the command refuses – with --force it works nevertheless, but the content of this entry is then considered unverified.

The audit log is thus tamper-detecting – “revision-proof” in the sense of GoBD it would only be with an additional, unchangeable storage. Editors see the latest changes of the records that they may read in the tab “Changes”; the complete research is reserved for superusers. Data exports are created only by users with the additional right “Export personal data” (module “Privacy”).

The audit log itself contains personal data (numbers of the acting persons, IP addresses, earlier contents). Name it in your record of processing activities, with the purpose “traceability and security” and the periods chosen.

Operational monitoring

  • Operating notices on the start page: Superusers see beneath the module tiles whether the cron job has stopped, temporary files are not being deleted, the database backup is missing or failed, storage space is running short, directories are not writable, the configuration does not suit live operation, or the update check reports security vulnerabilities or new versions (chapter 12).
  • Log files: errors, blocked sign-ins, emails that could not be delivered and security events in the module “Log files”, with a widget for the start page.
  • Command line: php Core/cron/camindo.php status for external monitoring.

The notices appear only when someone opens the start page. For a notification without sign-in, integrate the server into your existing monitoring (availability of the website, age of var/dbbackup/backup-1.sql, free storage space).

Tasks of the operator

  • Responsible persons named: operation, updates, data protection, security reports
  • Hosting with a data processing agreement, HTTPS, current server software
  • Daily database backup in the scheduler, collection of all backups outside the server, .camindo.key stored separately
  • Recovery practiced once a year
  • Update process defined; security corrections within a few days
  • public/.well-known/security.txt created and extended every year
  • Task “Clean up logs” set up, periods documented in the deletion concept
  • Periods of the audit log determined and stated in the record of processing activities
  • Process for requests for disclosure and erasure defined (who, how quickly, by which route the information is transmitted)
  • Rights concept: few superusers, named; group rights following the principle of least privilege; access of departed staff blocked immediately
  • Checklist for live operation worked through

15 Data protection

This chapter describes in detail which personal data camindo CMS stores, where they lie and how long they remain – separated by users (your staff in the backend), website users (frontend community) and visitors without an account. It is the basis for your record of processing activities and your privacy statement. The overview for decision-makers is in chapter 14.

All periods are defaults; the keys are in .camindo.php (see chapter 12). Deleted data live on in database backups until the backup itself expires – this belongs in your deletion concept.

Users (employees)

Data Where How long
Unencrypted user data sign-in name, names (first, display and sort name), user group, status (enabled/blocked/deleted) table admin as long as the account exists; deleted users remain, so that “created by/changed by” in contents and in the audit log remains valid – until they are anonymized
Encrypted user data email, mobile, telephone admin, encrypted as above
Password hash (not reversible)
day of the last change (without time)
admin as above
Two-factor data authenticator app, passkeys (designation, time), recovery codes admin (key encrypted), admin_passkey, admin_recovery_code (hashes only) until removal; passkeys and recovery codes with the deletion of the account; the key of the app when anonymizing
Personal settings language, time zone, widgets, reports … admin.settings as long as the account exists
Session data interface, last access admin_session until sign-out or admin.max_inactive_sec (30 minutes) of inactivity; with the deletion of the account immediately
Known devices browser/operating system, shortened IP address, time of first and last sign-in login_device security.device_days (180 days) after last use; with the deletion of the account immediately
Last sign-in day (without time) admin.login_date overwritten at every sign-in
Ongoing sign-in time at which the two-factor sign-in began (sending of the code) admin.uid_date only until the sign-in is completed, then emptied
Activities what the user changed when, with IP address audit log: audit_log (metadata), var/audit/ (contents) audit.retention_days_changes (unlimited); entries on records according to the deletion policy of their area; the IP address is shortened after privacy.ip_anonymize_days (7 days)
Failed sign-ins (audit log) number or entered name, IP address, time, interface (backend, PHP API, command line)
(under the account’s number if the account is recognized, otherwise with the entered name)
audit log audit.retention_days_logins (90 days)
“Created by”,
“Changed by”,
owner
times table record as long as the record exists
History recently opened records and templates, time history only the most recent entries; older ones drop out automatically; with the deletion of the account all of them
Internal messages sender, recipient, subject, text encrypted, time message privacy.message_days (60 days) after sending – for both sides, also unread; earlier, as soon as sender and recipient have deleted them
Successful sign-ins and sign-outs time, IP address, interface audit log – only with audit.log_logins = true default: not recorded; if switched on audit.retention_days_logins (90 days)
Failed sign-ins (block) time, entered name, IP address admin_login_attempt; for a block, a line in the log security in the table only for the blocking period admin.bruteforce_delay (10 minutes), in the log after the task “Clean up logs”; IP address shortened after privacy.ip_anonymize_days
Notification emails new device, password changed – with the full IP address mailbox of the user not stored in camindo CMS

User names that begin with admin_ are reserved for anonymized accounts and cannot be assigned again. The data export of a user is created by whoever has the right to do so in the module “Privacy” (see exporting).

Failed sign-ins always stand in the audit log, after several failed attempts additionally in the log security. Both are deleted after audit.retention_days_logins; in the data export, each entry shows the day of its deletion.

Sign-in times: Mere sign-ins without activity say nothing about changes, but they do say something about the working hours of your staff. Therefore camindo CMS records successful sign-ins and sign-outs only if you switch on audit.log_logins; the default setting is false.

What you as employer should consider: The audit log shows who changed what, and when. It is meant for traceability and security, but it can be used for performance and behavior monitoring. Regulate purpose and access (research only for superusers) in a works agreement or an internal policy, and inform your staff.

Former employees

A user account continues to exist after deletion, but no longer appears in the user list. Sign-in is no longer possible. Purpose: traceability – records, templates and the audit log refer to it.

A superuser can delete the messages of a “deleted” user: open the user → tab “Settings” → Internal messages → “Delete all messages”. Received messages are deleted permanently; sent ones disappear from their outbox, the recipients keep their version. The process appears in the log security. You remove devices and sessions in the tab “Devices and sessions”.

Anonymizing deleted users

As soon as this traceability is no longer needed (or the former employee demands the deletion), remove the person with Anonymize: module “Privacy” → tab “Deleted users” → click on the user → on their info page “Anonymize”. The button is visible to superusers and to user groups with the additional right “Anonymize deleted users” (for users of the groups that they see in the module “Users”).

Data Anonymized form
User name admin_{Id}, for example admin_42 – everywhere the name appears, also at “created by/changed by”
First, display and sort name, email, telephone, mobile, personal settings deleted
Password, authenticator app, passkeys, recovery codes, sessions, devices deleted – the account can no longer sign in
Audit log: entries in which they acted appear with admin_{Id} – the audit log stores acting persons only as a number and shows the current name; the changes themselves remain
Audit log of their account designation admin_{Id}, the values of the personal fields in before/after deleted
Failed sign-ins under their name admin_{Id} instead of the name

Because the audit log stores acting persons only as a number, anonymizing takes only a moment even for users with a long history. If it nevertheless aborts, simply start it again – only the name admin_{Id} shows that it has been completed.

This cannot be undone. The process appears in the audit log of the account (“Privacy”) and in the log security. The list shows anonymized users with the note “anonymized”.

Website users (community)

Data Where How long
Personal data encrypted all fields of the profile that are set up as encrypted fields (usually all with personal data, including user name, email address) profile area (tables record, data_enc) until the profile is deleted, then according to its deletion policy – with the standard policy: admin.trash_retention_days (7 days) in the trash, then permanently including files
Personal data unencrypted all fields of the profile that are set up as unencrypted fields, times of creation and last change profile area (tables record, data_*), media/ as above
Password hash profile area (tables record, data_pwd) as above
Contributions comments, ratings … and uploaded files, times of creation and last change contribution areas (tables record, data_*), media/ according to the deletion policy of the contribution area; with the standard policies like the profile, as soon as it is deleted
Records that refer to the profile links respective area according to your own rules (for example commercial and tax-law periods)
Known devices device, shortened IP address, time of first and last sign-in login_device security.device_days (180 days) after last use; with the deletion of the profile immediately, regardless of which policy
Session data session ID, last access user_session user.max_inactive_sec (24 hours) of inactivity; with the deletion of the profile immediately
Two-factor data type, time of first use user_second_factor until removal or deletion of the profile
Open confirmation and password links token, time user_token until use or expiry (user.token_expiration, user.password_token_expiration)
Registrations never confirmed details from the registration process, time profile area user.unconfirmed_days (7 days)
Shopping cart record IDs (products, subscriptions …), time of last change user_basket signed in: with the session; anonymous: user.basket_days (30 days)
Change log audit log of the profile record and contributions, with IP address, time audit log according to the deletion policy; with the standard policies for the profile after 7 days all fields emptied, after 1 year deleted. The user appears in it only with the number of their profile (anonymous, as soon as the profile is deleted). IP address shortened after privacy.ip_anonymize_days
Failed sign-ins sign-in name, time, IP only with user.bruteforce_ip user_login_attempt for the blocking period user.bruteforce_delay

Sign-in names that begin with user_ are reserved for anonymized profiles: they are rejected at registration and in the backend – only user_{record_id} of the profile itself is permitted, as a deletion policy uses it.

The data export of a website user is created by whoever has the right to do so in the module “Privacy” (see exporting and the manual for editors).

Visitors without an account

camindo CMS sets in the frontend only technically necessary cookies (session, frontend editing, device recognition at sign-in) and does not embed third-party services. What your templates additionally load (fonts, maps, statistics) is your responsibility.

Data Where How long
Throttling
(forms,
sign-in,
CSP reports)
IP addresses only as a hash, never in plain text table of the RateLimiter a few minutes to hours (time window of the throttling)
Blocked sign-ins,
rejected forms
sign-in name, IP address, time log security in var/log/ task “Clean up logs” (default 365 days); IP addresses shortened after privacy.ip_anonymize_days
Script violations (CSP reports) script URL, time, (no IP address) log csp as above
Form entries partly personal data depending on the template: as record or email, depending on the area encrypted / unencrypted according to the deletion rules of the area, or of the mailbox

IP addresses

IP addresses are personal data. The legitimate interest in the complete address – recognizing and warding off attacks – according to widespread opinion lasts only a few days. camindo CMS therefore handles it as follows:

Where What is stored
Known devices login_device from the start only the shortened address
Contents of the audit log var/audit/ from the start only the shortened address – the files are protected against forgery and are only appended to; shortening them afterwards would not work
Metadata of the audit log audit_log the full address, shortened after privacy.ip_anonymize_days (7 days)
Failed sign-ins admin_login_attempt, user_login_attempt the full address for the blocking period; shortened after the same period, if they stand longer
Log files var/log/ the full address; every log file whose day is older than the period is shortened
Throttling RateLimiter table only a hash, never the address

Which address is “the visitor’s address” is determined at all points by the same rule: REMOTE_ADDR, behind a registered proxy (security.trusted_proxies) the address from X-Forwarded-For. Without this entry, behind a proxy everywhere stands the address of the proxy.

How shortening works:

  • IPv4: the last octet is zeroed (203.0.113.45 → 203.0.113.0); with privacy.ip_v4_octets = 2 the last two (203.0.0.0).
  • IPv6: the last 80 bits are zeroed; the first 48 bits remain – the network of the provider, not the individual connection (2001:db8:abcd:12::1 → 2001:db8:abcd::).

Changing the setting later: If you raise privacy.ip_v4_octets from 1 to 2, the next run of the task “Apply retention periods” shortens all addresses that are already shortened further – in the database, the log files and the contents of the audit log (203.0.113.0 → 203.0.0.0, displayed as 203.0.xxx.xxx). Lowering does not bring anything back: what has been zeroed remains so; the display continues to mark both digits.

Displayed are the shortened digits as xxx – in the personal data report, in the research and in the tab “Changes” of the audit log and in the device list (203.0.113.xxx, 2001:db8:abcd:xxxx:xxxx:xxxx:xxxx:xxxx). In this way nobody takes the zero for part of the address. Stored remains the address with zeros: only in this way is it still a valid IP address that can be compared and – after a change of privacy.ip_v4_octets – shortened further.

The shortening is handled by the scheduler task “Apply retention periods” (set up by the installation, every night). Each run takes only the entries since the previous run and uses the indexes of the time columns. Log files are rewritten line by line; this keeps it fast even with large data holdings – under Linux as well as under Windows. The first run after the update passes once over the entire holdings, as does the first run after a change of privacy.ip_v4_octets.

Once after the update: Entries in var/audit/ that were written before this update still contain the full address. Shorten them, ideally in maintenance mode, with

php Core/cron/camindo.php audit anonymize-ip --dry-run   # only count
php Core/cron/camindo.php audit anonymize-ip

The command writes the files with a newly calculated checksum chain and notes this as a control point “anonymize”. Files whose chain was already broken beforehand, it leaves unchanged and names them; check them with audit verify (see chapter 14).

In the case of a concrete attack: If the full addresses are urgently needed for the further pursuit of an attack that has already been detected, temporarily raise privacy.ip_anonymize_days. This only stops the shortening – what has already been shortened does not come back. Secure the necessary entries (for example an export from the audit log, a copy of the log file) at a protected place, document the reason and duration, and set the period back afterwards.

The 7-day period is a default, not legal advice. Coordinate it with your data protection officer.

Deleting according to deletion policy

What “delete” means for a record is determined by the deletion policy of its content area. Every content area (records, profiles, contributions) has exactly one; you select it in the settings of the area (see chapter 4) and manage it in the module “Privacy” → “Deletion policies”.

The list shows for each policy the instance, the label and the categories of its blocks. A click opens the info page (with the areas that use the policy, and the tab “Changes”), a right-click the context menu with “Info”, “Edit” and “Delete”.

Structure of a policy

A policy has a label (text or snippet), applies to an instance (or to all) and consists of any number of blocks. Each block states: at which trigger, at which time, which action. For this you choose a category, which classifies the action – it serves only for overview and has no effect:

Category Typical for
Move to trash the trash
Remove data that is no longer strictly needed overwriting individual fields (data minimization)
Fully anonymize the entry overwriting fields in the audit log
Delete permanently permanent deletion, with the audit log; permanent deletion of the audit log
Trigger When
Record is deleted regardless of the way: deletion dialog of the input form, batch processing, API record.delete or CMS:USER_DELETE in the frontend – also for the sub-records that are deleted with it
UGC: Profile was deleted a profile was deleted – individually, by batch processing or with its whole profile area. This always and immediately triggers the policies of all records of this user (contributions, ratings …), each the policy of its own area. If it has no block with this trigger, the records remain. This cannot be undone: if the profile (or its area) is restored, you must retrieve contributions in the trash by hand
Area was deleted the whole content area was deleted; its records then already lie in the trash together with it. Therefore not for “Move to trash” and “Overwrite individual fields”. Blocks that are due immediately also run only with the scheduler task “Apply deletion policies”; if the area is restored, they lapse

Time: immediately (0) or after a number of days or years, counted from the triggering.

The actions are grouped in the selection by what they concern:

Group Action What happens
Record Move to trash (deleted permanently after … days) the record goes into the trash of the person deleting, and is permanently deleted after admin.trash_retention_days (default 7 days) – including media files. The selection names the set period
Record Overwrite individual fields fields of the record receive new values, for example status_id = "4", username = "user_{record_id}", account_number = "". Multilingual fields receive the value in all languages. An upload field accepts only "": the file is removed from the server immediately, together with its thumbnails (also for multilingual uploads in all languages)
Record Apply trigger template to the record the method onPrivacyAction() in the trigger template of the area receives the record – multilingual fields per language – and can change it; changes are saved. For everything that “Overwrite individual fields” cannot express: linked records, multilingual texts per language, own rules
Audit log Overwrite fields in the audit log in the history of the record (database and var/audit/), before and after of the named fields are replaced, for multilingual fields in all languages. Additionally: _label (designation of the entry) and * (all other fields – regardless of where the line stands). Later entries as well – for example the last state at permanent deletion – are written this way
Audit log Apply trigger template to the audit log onPrivacyAction() in the trigger template receives each entry in the history of the record and can change the designation and the values before/after; changes are written back
Audit log Delete the audit log permanently the entire history of the record
Everything Delete permanently, including the audit log without trash: the record, its sub-records, their files and their entire history in the audit log are gone

You write the fields per line as field = "value". Placeholders in the value: {record_id} (the record) and {user_id} (the user to whom the record belongs). A field that does not exist in the area is skipped; a thumbnail field alone cannot be emptied – it disappears with its upload field.

The two trigger actions require an identifier (lowercase letters, digits, _), which the template receives as $action – so it distinguishes several blocks. If the area has several trigger templates, onPrivacyAction() runs in each one that contains the method, one after another. If none has the method, the job fails and appears in the log privacy. camindo CMS does not check whether blocks contradict each other or duplicate each other – that is the responsibility of the person who sets up the policy.

Regardless of any policy, when a profile is deleted – also with its area –, its known devices and sessions are deleted immediately, and media files always disappear with the permanent deletion of a record.

Sub-records

Deleting a record also deletes its sub-records: the children in the tree, if you delete “with sub-records” (otherwise they move up one level and remain untouched), and always the hard-linked content (fields of the type “link”), also those of the children. Each sub-record receives the blocks of the policy of its own area for “Record is deleted” – with one exception: “Move to trash” and “Delete permanently, including the audit log” are handled for everyone by the main record. A sub-record for which a deletion is already running keeps its jobs.

Anonymous via the profile

The audit log stores who acted only as a number – for a website user the number of their profile record, never their name. This link always remains; no action removes it. Entries are anonymized via the profile: if it is permanently deleted, or if its personal fields have been emptied with “Overwrite individual fields”, the number leads to nobody – also not in the entries on contributions and other records on which the user worked. What is personal in the entries themselves (values before/after, designation) you overwrite with “Overwrite fields in the audit log”.

The standard policies

During installation, three policies arise for all instances. You can change them – as policies for all instances only by a superuser –, but not delete them; a new area receives the one of its type pre-assigned.

Policy Area type Blocks
Delete record records immediately into the trash (permanently after 7 days), also when the profile of the user is deleted; delete the audit log permanently after 1 year
Delete profile profiles immediately into the trash (permanently after 7 days); in the audit log after 7 days replace _label with user_{record_id} and empty all other fields; delete the audit log permanently after 1 year
Delete user-generated content contributions immediately into the trash (permanently after 7 days), also when the profile of the user is deleted; delete the audit log permanently after 1 year

The blocks in the audit log apply in all three standard policies also to the trigger “Area was deleted”.

With the standard policies, a deleted website user is anonymous after 7 days: the profile is permanently deleted, its own history emptied, and the entries on its contributions name only a number that no longer belongs to any profile.

Example: anonymizing a profile instead of deleting it

If a profile (for example because of orders) is to remain, but no longer reveal any person:

Trigger Time Action Fields
Record is deleted immediately Overwrite individual fields status_id = "4"
Record is deleted 7 days Overwrite individual fields username = "user_{record_id}", name = "", account_number = ""
Record is deleted 7 days Overwrite fields in the audit log _label = "user_{record_id}", * = ""
Record is deleted 1 year Delete permanently, including the audit log

The contributions of the user are deleted in this process according to the policy of their area with the trigger “UGC: Profile was deleted”. If they are to remain, remove this trigger from their policy.

Note in the deletion dialog

Whoever deletes a record in the backend sees beforehand what the policy does with the database – immediately and after periods, for a profile also with its contributions:

The profile record remains, but is adjusted as follows: – Status: “Deleted”

After 7 days: – User name: “user_{record_id}” · – Account number: “”

After 1 year: – Permanent deletion

72 contributions of the user (“Comments”) remain.

Batch processing shows the same per area. After the deletion, the message is, depending on the policy, “deleted”, “adjusted” or “scheduled”.

How it is carried out

Each block becomes, when deleting, a job (table privacy_job) with a due date; the immediately due ones run at once, the others with the scheduler task “Apply deletion policies” (default: every night; for periods in hours, schedule it more often). A job takes over the values of its block – a later change to the policy affects only future deletions. If a job fails (for example because someone is just editing the record), there are up to 20 attempts; everything appears in the log privacy. Actions of a policy count as system actions: mandatory fields and group rights are not checked, and in the audit log the change appears as “adjusted by deletion policy” without the previous values.

If a record is restored from the trash, its still open jobs lapse. A record for which a deletion is already running cannot be deleted again.

When an entire area is deleted, its records go into the trash together with the area, and camindo CMS schedules for each the blocks of the area policy with the trigger “Area was deleted” – with the standard policies: delete the audit log permanently after 1 year. If the policy has no block for this trigger, the history remains. Records for which there were never any jobs (for example deleted before the introduction of the deletion policies) receive at permanent deletion at least the audit log blocks of this trigger, with the periods counted from the deletion. The history of permanently deleted records in the audit log follows only the policies, not audit.retention_days_deleted (which continues to apply to areas, fields, templates and other objects).

Emptying a website user’s trash immediately

If a website user requests immediate deletion, a user with the right “Permanently empty the trash of a website user” (module “Tools”) empties their trash: “Tools” → “Trash of a user”. You select the profile via the search across all profile areas that you may read or – if it is itself already deleted – from the list of profiles in the trash. Profile and contributions that lie in the trash are permanently deleted at once. Users empty their own trash as before, themselves.

Deleting the audit log manually

The permanent deletion – also via “Tools” → “Database” → “Empty trash” – leaves the history of a record in the audit log; the audit log blocks of the deletion policy remove it after their period. If a history must disappear earlier – for example because a website user insists on it or test data were involved –, delete it on the command line:

php Core/cron/camindo.php audit delete record 4237 --dry-run   # only count
php Core/cron/camindo.php audit delete record 4237
php Core/cron/camindo.php audit delete --area=141 --force      # all records of an area

The entire history is deleted: the entries of the database, the files under var/audit/ and the jobs of the deletion policy for this record. Open jobs remain as long as the record exists – otherwise it would never be fully deleted; later they also remove the entry of the permanent deletion. --area covers every record that lies in the area or, according to the audit log, lay there, including deleted ones; without --force the command only names the number. Instead of record, any other object type of the audit log works, for example admin 34 for the history of a user account. The process is recorded in the log security with the count, but without contents. Entries in which a website user acted on other records are not covered by audit delete; they name them only with the number of their profile (see anonymous via the profile).

Exporting personal data

For a request for disclosure (Art. 15 GDPR) or for data portability (Art. 20), camindo CMS creates an export: a zip file with report.html for reading and data.json machine-readable; for website users additionally files/ with their uploaded files. It is a partial extract – before passing it on to the data subject, you complete it with everything that camindo CMS does not know: legal bases, server log files outside the CMS, data in accounting, email and other systems.

The export is created by whoever has the additional right “Export personal data” in the module “Privacy” (superusers always), under “Privacy” → “Data export”:

Person Who may How
Website user whoever may read the profile area search for the profile (all readable profile areas) or choose from the profiles in the trash – or on the profile record in the menu ⋮ “Export personal data”
User users of the groups whose users the group may see in the module “Users” (own group with “Read user details: own user group”, other groups with “Read user details: other user groups”) choose the user from the list

Users cannot download their export themselves.

Before creating, camindo CMS checks the templates:

  • If there is no template for website users for the instance of the profile – neither for its profile area nor for all areas – (for a user: neither for their group nor for all groups), a notice appears and no button for creating – with the right to do so, you create one immediately via “Create template”.
  • If there is exactly one, it is used.
  • If there are templates in several languages, you choose the language of the report. For users you choose among the user templates of all your instances.

Below this, a warning notice appears: a personal data report may only be created by an authorized person – as a rule the data protection officer. And: every personal data report is logged; in every later disclosure, the data subject learns that one was created about them today. Only when you tick “I am authorized to create the personal data report.” does the export come into being with “Create personal data report”.

The export of a website user contains:

  • all fields of the profile in all languages (encrypted ones in plain text, passwords only as a note),
  • their contributions, sorted by area, including files – contributions in the trash are marked as “deleted, still in the trash”,
  • permanently deleted contributions that the audit log still links to them, and what the deletion policy still plans for them (for example “delete audit log permanently: 2027-10-08”),
  • records related to the profile (for example orders),
  • known devices and sessions – directly below them their storage period –, second factor, open links, shopping cart,
  • failed sign-in attempts under their sign-in name or their account, each with the day of deletion,
  • entries in log files that clearly belong to them: blocks after too many failed attempts (log security) and lines of the mail log with their email address, for example emails that could not be delivered,
  • the change log, the storage periods and the deletion policy of their profile area.

Names of other persons do not appear in the report of a website user: who acted appears only as “You”, “Admin”, “User” (another website user) or “System”. Shortened IP addresses appear with xxx instead of the zeroed digits (203.0.113.xxx), so that nobody takes the zero for part of the address.

The export of a user contains: account data (contact data in plain text, of the password only the day of the last change), devices and sessions with their storage period, second factor (without keys or codes), sign-ins (only with audit.log_logins) and failed sign-ins under their name – each with the day of their deletion –, their activities in the audit log with IP address (the latest 1000), changes to their account, internal messages to them (date, sender, subject), the number of records with their name and the storage periods.

Every export is recorded in the audit log. The zip file lies in var/export/ until it is downloaded, is deleted during the download, and is removed after admin.export_retention_hours (24 hours) at the latest.

Templates of the personal data report

What report.html looks like and which text surrounds the data is determined by an HTML template that you design yourself – for example with your logo and the section “Storage period” of your privacy statement. There are separate templates for website users and for users, each per instance and per language – and, if you wish, per profile area or per user group, for example:

Type Instance Applies to Language
Website user Shop Customers English
Website user Shop Community English
Website user Shop All profile areas German
User Shop Editorial team English
User Shop All user groups English

On export, the specific template wins: for a profile in the area “Customers”, the templates for “Customers” apply; if there are none, the ones for all profile areas. For a user, the templates for their user group apply (from all your instances), otherwise those for all groups. You maintain the templates under “Privacy” → “Templates”; whoever has the additional right “Maintain templates of the personal data report” and may edit the instance (superusers always) may create, change and delete them.

With “New” you first choose type (website user or user) and instance; in the form then “Applies to” (profile area or user group or all) and the language. A second template for the same combination cannot be saved. The new template is pre-filled with the template of the core for this type (Core/install/privacy/privacy_user_en_US.html or privacy_admin_en_US.html, in English; they list all placeholders at the beginning). Without a template for website users, there is no data export for the website users of an instance; for a user you choose among the user templates of all your instances.

The report is created in the language of the template: snippets and the labels of the tables appear in this language, regardless of your backend language. Because types, profile areas and user groups have separate templates, no distinction is needed within a template.

Notation Effect
{name} placeholder – values are inserted masked, blocks as finished HTML tables
<!-- if:name -->…<!-- endif:name --> section only if the condition applies; can be nested
{#module.key} language snippet in the language of the template, for example {#record.gdpr_col_device}
<!-- comment --> explanation for you – dropped in the export

Conditions: audit, audit_changes_limited / audit_changes_unlimited, audit_deleted_limited, logcleanup / no_logcleanup (task “Clean up logs” set up), dbbackup / no_dbbackup (task “Database backup”), messages_limited, ip_v4_one / ip_v4_two.

Periods (in both types): {trash_days}, {device_days}, {ip_days}, {ip_v4_octets}, {message_days}, {audit_changes_days}, {audit_deleted_days}, {session_hours} (website users), {session_minutes} (users), {basket_days}, {unconfirmed_days}, {export_hours}, {backups} (retained database backups), {log_days}, {security_days}, {attempt_days} (from the task “Clean up logs”). In this way the text always matches the settings – if you change a period, the export changes with it.

Header: {subject}, {subject_id}, {domain} (main domain of the instance), {instance} (designation of the instance), {created}, {lang}, {dir}.

Data in the template for website users: {profile}, {contents} (sorted by area), {purged} (permanently deleted contributions that the audit log still knows), {references}, {devices}, {sessions} (each with the storage period below), {second_factor}, {tokens}, {basket}, {login_attempts}, {log_lines}, {files}, {audit}, {retention} (the periods as a finished list), {delete_policy} (the deletion policy of the profile area, one sentence per block).

Data in the template for users: {account}, {devices}, {sessions}, {second_factor}, {logins}, {activity}, {activity_note}, {account_changes}, {messages}, {content_counts}, {retention}.

If you leave out a data block, it is missing from the export – but the report must be complete under Art. 15 GDPR. Therefore check a changed template with an export for a test account. data.json always contains all data, regardless of the template.

What lies outside camindo CMS

camindo CMS can shorten and delete only what it stores itself. On your list there are also:

  • Access logs of the web server (Apache, nginx, IIS) – they contain the full IP address of every call. Shorten them in the web server (for example with an anonymizing log format) or delete them after a few days via log rotation.
  • PHP and database logs outside var/log/.
  • Database backups and server backups – they contain the state at the time of the backup, therefore also the unshortened addresses of the last days.
  • Emails that camindo CMS has sent (notes about new devices, forms).
  • Proxies, load balancers, CDNs and firewalls in front of the website.

Checklist

  • [ ] periods (privacy.ip_anonymize_days, security.device_days, audit.*, admin.trash_retention_days, task “Clean up logs”) coordinated with the data protection officer and entered in the record of processing activities
  • [ ] scheduler task “Clean up logs” set up
  • [ ] audit anonymize-ip run once after the update
  • [ ] deletion policies checked: each content area has the appropriate one; data that must be retained (orders, invoices) not in the trash, but anonymized
  • [ ] templates of the personal data report created (“Privacy” → “Templates”): for website users per instance with community (if needed per profile area), for users at least one (if needed per user group) – adjusted and checked with a test account
  • [ ] rights granted: “Export personal data”, “Create and change deletion policies”, “Maintain templates of the personal data report” (module “Privacy”), “Permanently empty the trash of a website user” (module “Tools”)
  • [ ] decided whether successful sign-ins are recorded (audit.log_logins, default no) and how long sign-ins remain (audit.retention_days_logins)
  • [ ] period of the internal messages (privacy.message_days) determined; for former employees, messages deleted
  • [ ] access logs of the web server shortened or deleted at short notice
  • [ ] behind a proxy: security.trusted_proxies set – otherwise everywhere stands the address of the proxy
  • [ ] staff informed about the audit log, works agreement or policy in place
  • [ ] privacy statement of the website names cookies, forms, community features
  • [ ] process for requests for disclosure and erasure defined (who, deadline one month)

As of camindo CMS 9.1.3