A plugin is a folder in public/plugins/<name>/ with a <name>.plugin.php file. It is activated in the Plugins Manager and updated together with the core.
Header
<?php
/*
Plugin Name: Booking
Description: Table booking [booking]
Version: 1.0.0
Author: …
*/
cms39_i18n_domain('booking', __DIR__ . '/languages');
add_shortcode('booking', 'booking_render');Every change bumps the Version and is recorded in the plugin CHANGELOG.md, so sites know what an update brought.
Admin page and ajax
// cp_loader?page=mod_admin_booking — only functions prefixed mod_admin_
function mod_admin_booking()
{
check_authorise();
check_level(8);
return '<h1>Bookings</h1>…';
}
// cp_ajax?f=mod_ajax_booking_save — cp_ajax does not require login: the function checks it
function mod_ajax_booking_save()
{
cms39_ajax_admin_only(8);
cms39_csrf_check();
// …
}
HookManager::addHook('cms39_admin_sidebar_menu', function (array $groups) {
$groups['data']['items'][] = ['label' => 'Bookings', 'icon' => 'bi bi-calendar', 'url' => cp_URL . 'cp_loader?page=mod_admin_booking'];
return $groups;
});Settings
Plugin settings appear under Settings. The default goes into cms39_conf_defaults(), labels and help into the cms39_settings_ui filter. Code reads them with cms39_conf('booking.slots', 20).
Database tables
$p = ['v' => '1.1.0', 'slug' => 'booking', 'schema' => __DIR__ . '/schema.php'];
cms39_plugin_installation_check($p);
// schema.php
$plugin_schema = [
'tables' => ['cms39_booking' => 'CREATE TABLE IF NOT EXISTS `cms39_booking` (…) ENGINE=InnoDB'],
'migrations' => [
'1.1.0' => ['ALTER TABLE `cms39_booking` ADD COLUMN IF NOT EXISTS `notes` TEXT NULL'],
],
];Tables are created on the first request after installation. Each new version runs only the missing migrations. Always write IF NOT EXISTS so a step can run again without errors.
E-MAIL or by phone at (+30) +30.2152151245-8