Diese Seite wurde noch nicht vollständig übersetzt. Bitte helfen Sie bei der Übersetzung.
(diesen Absatz entfernen, wenn die Übersetzung abgeschlossen wurde)
Ab Admidio 5.0 können alle Änderungen an Objekten, die in der Admidio-Datenbank gespeichert wie etwa Benutzer, Veranstaltungen, Gruppen/Rollen, Weblinks, Alben, Ordner/Dateien, etc., sowie Einstellungen protokolliert und im Änderungsverlauf angezeigt werden. Die Protokollierung kann in den Einstellungen für jeden Objekttyp (=Datenbanktabelle) aktiviert werden. Jedes Objekt oder jede Liste mit aktivierter Änderungshistorie zeigt eine Schaltfläche an, um das Historie anzuzeigen:
Das Changelog basiert auf der Verwendung der Entity
-Klasse (früher TableAccess
) und ihrer abgeleiteten Klassen für den Datenbankzugriff. Direkte Änderungen an der Datenbank durch SQL-Anweisungen werden nicht protokolliert. Die Entity-Klasse verwendet setValue
-Methoden, um Datenbankspalten zu ändern, und die save
-Methode, um sie zu speichern. Hier setzt das Changelog an: Neben der Speicherung der Änderung in der Datenbank erkennt die save
-Methode die Änderung und fügt entsprechende Einträge in die Tabelle adm_log_changes
ein (mithilfe der Klasse LogChanges
).
Entity::logCreation()
aufgerufen, um einen Erstellungsdatensatz im Log zu speichern.Entity::logModifications($logChanges)
aufgerufen (diese erstellt für jede geänderte Spalte einen separaten Changelog-Eintrag).Entity::logDeletion()
aufgerufen.
Diese Methoden sorgen für die Erstellung von LogChanges
-Einträgen und deren Speicherung in der Tabelle adm_log_changes
. In der Regel ist es nicht notwendig, diese Kernfunktionen zu ändern. Nachfolgend wird erklärt, wie sich alle Aspekte der Changelog-Erstellung und -Anzeige anpassen lassen.
Zusätzlich werden folgende Methoden der Entity
-Klasse verwendet:
Entity::readableName()
: Gibt eine lesbare Darstellung des Datenbankeintrags zurück. Standardmäßig wird die Spalte 'prefix_name' oder 'prefix_headline' verwendet, falls vorhanden. Andernfalls wird die Primärschlüssel-Spalte der Tabelle zurückgegeben. Klassen wie User
können dies überschreiben, um z. B. “Nachname, Vorname” als Darstellung zurückzugeben.static Entity::setLoggingEnabled($enabled)
: Temporäre, systemweite Aktivierung oder Deaktivierung der Protokollierung (bis zur nächsten Aufruf oder Anfrage). Dieser Zustand wird nicht gespeichert und hat keinen Einfluss auf nachfolgende Seitenaufrufe.Entity::getIgnoredLogColumns()
: Gibt eine Liste von Spalten zurück, die nicht protokolliert werden sollen (z. B. Erstellungs-/Änderungszeitstempel oder Benutzer-IDs).Entity::adjustLogEntry(LogChanges $logEntry)
: Ermöglicht die Anpassung des Log-Eintrags nach der Erstellung durch logCreation/logModification/logDeletion, um z. B. ein verknüpftes Objekt hinzuzufügen (Gruppenmitgliedschaften haben den Benutzer als geänderten Eintrag und die Gruppe als zugehöriges Objekt) oder das Standardverhalten zu ändern.
Abgeleitete Unterklassen der Entity
-Basisklasse können diese Methoden überschreiben, um die generierten Changelog-Einträge zu optimieren oder zu unterdrücken.
Die Changelog-Einträge werden in der Tabelle adm_log_changes
gespeichert und enthalten alle relevanten Informationen zum betroffenen Datensatz (ID, UUID und Name), ggf. einen verknüpften Datensatz (ID/UUID und Name), die geänderte Spalte/Datenbankspalte (Spaltenname und lesbarer Name) sowie die vorherigen und neuen Werte.
Die Tabelle enthält folgende Spalten, die meisten davon werden automatisch durch die Methoden der Entity
-Klasse gefüllt:
log_id
(Auto-Increment-Zähler), log_table
(die betroffene Datenbanktabelle ohne das 'adm_'-Präfix)log_record_id
, log_record_uuid
, log_record_name
: Die ID, UUID und die lesbare Darstellung des betroffenen Datensatzeslog_record_linkid
: Manche Tabellen haben keine eigene Ansicht für ihre Einträge. Dann wird stattdessen eine verknüpfte Entität verwendet (z. B. verweist die adm_members
-Tabelle nicht auf eine Mitgliedschaftsansicht, sondern auf den Benutzer und die Gruppe als relevante Objekte).log_related_id
, log_related_name
: Falls vorhanden, die ID/UUID und der lesbare Name eines verknüpften Datensatzeslog_field
, log_field_name
: Name der Datenbankspalte und deren lesbare Bezeichnunglog_action
: MODIFY, CREATED oder DELETEDlog_value_old
, log_value_new
: Vorheriger und neuer Wert der Spalte.log_user_id_create
, log_timestamp_create
: Benutzer-ID und Zeit der Änderung (automatisch aus dem aktuellen Benutzer und dem aktuellen Datum/Zeit gefüllt)
Standardmäßig können alle Datenbankzugriffe über die Entity
-Klasse protokolliert werden, sofern das entsprechende Einstellungs-Flag gesetzt ist. Kernmodule von Admidio haben eigene Einstellungen zur Aktivierung der Protokollierung. Alle anderen Tabellen (Plugins oder zukünftige Module ohne explizite Changelog-Implementierung) werden über die Einstellung changelog_table_others
(Einstellungen → “Änderungsverlauf” → “Inhaltsmodule” → “Alle anderen (others)”) verwaltet.
Weitere Anpassungen wie das Ignorieren bestimmter Tabellen und Spalten oder das Anpassen von Changelog-Darstellungen sind ebenfalls möglich.
Starting with Admidio 5.0, all changes to objects (Users, Events, Groups/Roles, Weblinks, Albums, Folders/Files, …) and settings that are saved in Admidio's database can be logged and the changes displayed in the Change History screen. Logging can be enabled per object type (=database table) in the preferences. Each object or list with changelogs enabled will display a changelog button to view it:
The changelog depends on the use of the Entity
class (previously TableAccess
) and its derived classes for all database access. Direct modifications of the database using SQL statements will not be logged. The Entity class uses setValue
methods to change database columns and the save
method to store them to the database. This is where the changelog hooks in: In addition to storing the change to the database, the save method will also detect and insert a new entry for each changed column into the adm_log_changes database table (using the LogChanges class derived from Entity).
Entity::logCreation()
method is called to insert a creation record into the log.Entity::logModifications($logChanges)
method is called (which will in turn insert a separate changelog entry for each modified column).Entity::logDeletion()
method is called.These methods handle the creation of LogChanges records and storing them to the adm_log_changes database table. Usually it is not needed to change these core functions. See below for instructions how to adjust all aspects of the changelog generation and display.
In addition, the following methods of the Entity class are used:
Entity::readableName()
: Returns a human-readable representation of the database record. By default, if a column 'prefix_name' or 'prefix_headline' exists, it will be used, otherwise the table's key column is returned. Classes like User
can override this to return e.g. a string of the form “Lastname, Firstname”.static Entity::setLoggingEnabled($enabled)
: Temporarily enable or disable logging (until called again, or a new request is handled). This state is not persisted, so this function will not affect subsequent page loadings.Entity::getIngoredLogColumns()
: Returns a list of column names, which should not be logged (e.g. the creation / modification time stamps or user IDs)Entity::adjustLogEntry(LogChanges $logEntry)
: After the logCration/logModification/logDeletion methods set up the new LogChanges record, this method can adjust it for custom behaviour, e.g. to add a related record (group memberships have the user as modified record and the group as a related record) or completely change the record away from the default behavior.Derived subclasses of the Entity base class can override these methods to tweak the changelog entries generated (or even suppress or fundamentally change them).
The changelog entries are stored in the database in the adm_log_changes table and contain all information of the affected record (ID, UUID and name), a potentially affected related record (ID/UUID and name), the modified field / dabase column (column name and human-readable name), as well as the previous and the new values.
The table has the following columns, most of which will be automatically filled the methods in the Entity class:
log_id
(auto-increment counter), log_table
(the affected database table without the 'adm_' prefix)log_record_id
, log_record_uuid
, log_record_name
: The record ID, UUID and a human-readable representation of the affected recordlog_record_linkid
: Some tables have no corresponding display page for its records, so we want to link to a different object (e.g. the adm_members table has no view for its records. Instead the affected record should be the user and the group is the related record → the linkid for html links is the user UUID rather than the membership ID or UUID)log_related_id
, log_related_name
: For records that relate to others (e.g. group memberships relate a user to a group, a folder or album potentially relates to its parent folder/album, etc.) these columns give the ID/UUID and the human-readable name of the related namelog_field
, log_field_name
: table column name and human-readable representation of the modified columnlog_action
: MODIFY, CREATED or DELETEDlog_value_old
, log_value_new
: The previous and the new value of the field.log_user_id_create
, log_timestamp_create
: user ID and time of the change (both automatically filled from the current user and date/time)
By default, all database access via the Entity class can and will be logged, as long as the corresponding preference flag is set. The core admidio tables have their own preference setting, so each table can be individually turned on/off. All other tables (third party modules/plugins or future core modules that have not yet explicitly implemented its logging) are controlled collectively by the preference changelog_table_others
(Preferences → “Change History” → header “Content modules” → “All others (others)”).
All uncustomized tables will log all changes to all columns (except the creation/modification user ids and timestamps) with the raw table column name as field. The changelog entry will not have any links and the human-readable representation will use value returned by the record's Entity::readableName()
method. For example, without any custom implementation, the forum modul would create the following changelog entries out of the box when adding a new topic and a new post:
As one can see, the creation of the topic and the post with ID 1 is properly logged, as well as setting its category and title. For a user, however, this display can be improved by using category and topic names for display, linking to the corresponding pages, and converting the raw database column names to comprehensible labels.
See below for instructions how to add tables for individual selection and customize their logging.
Some tables (like the adm_auto_login or adm_sessions tables) are meant as transient temporary data storage and should never be logged in the changelog. Others like adm_log_changes are clearly also not meant to be logged in the changelog. Third-party plugins can also use their own table to cache data temporarily without logging.
Admidio holds a global static table of database table names that should not be logged in the static array Entity::$noLogTables
array. If a third-party plugin or module has a table that should never generate changelog entries, you can add the corresponding table names (WITHOUT the table name prefix!) in the constructor of your modules class or even in the main body of a module.
The following code will add the adm_myplugin_cache and adm_session_data tables to the list of tables that should not be logged. This will only affect the current execution and will NOT persist to the next execution. So this code really needs to be executed each time your plugin/module loads.
use Admidio\Changelog\Entity\LogChanges; array_push(LogChanges::$noLogTables, 'myplugin_cache', 'session_data');
Core modules and plugins can directly modify the LogChanges::$noLogTables default setting in the src/Changelog/Entity/LogChanges.php
file.
By default, the uuid (prefix_uuid) as well as the the create/change timestamp (prefix_timestamp_create/change) and user ID (prefix_usr_id_create/change) columns are ignored. To ignore other columns as well, one must use an Entity-derived subclass for your database record and override the getIgnoredLogColumns()
methods, like the User class does::
use Admidio\Infrastructure\Entity\Entity; class User extends Entity { ... public function getIgnoredLogColumns(): array { return array_merge(parent::getIgnoredLogColumns(), ['usr_pw_reset_id', 'usr_pw_reset_timestamp', 'usr_last_login']); } }
Of course, you then need to use this class instead of Entity to create / modify the database.
All tweaks to the changelog record generation depend on the use of an Entity-drived class for your record creation/modification, similar to the code above to ignore certain database columns in the changelog!
By default, each object (database record) uses the 'prefix_name' column as its display name in the changelog view, if such a column exists (if not, 'prefix_title' and 'prefix_headline' are used, too). To change this, one can simply override the Entity::readableName()
method, like for the User class, which uses a label of the form “Lastname, Firstname” as its display string:
class User extends Entity { ... public function readableName(): string { return $this->mProfileFieldsData->getValue('LAST_NAME') . ', ' . $this->mProfileFieldsData->getValue('FIRST_NAME'); } }
Override the Entity::adjustLogEntry(LogChanges $logEntry)
method in your subclass and call $logEntry→setLogRelated(..)
to add the link to the related object. Here is the code for the File class to set the corresponding Folder as the related object (the UUID and the name of the related object are needed):
<?php use Admidio\Changelog\Entity\LogChanges; class File extends Entity { ... protected function adjustLogEntry(LogChanges $logEntry) { $folEntry = new Folder($this->db, $this->getValue('fil_fol_id')); $logEntry->setLogRelated($folEntry->getValue('fol_uuid'), $folEntry->getValue('fol_name')); } }
By default, the changelog view will link to the same object as the main object (e.g. if a folder links to parent folder, it will work out of the box). To link to a different object type, one needs to modify the changelog display code. See below for instructions.
Some database records do not describe objects per se, but relations between records or even more abstract data. For these, the database record should not be logged as a separate record, but rather as a change to a completely different record. E.g. the creation of a Membership record (table adm_members) should not be logged as the creation of a membership record, with each column as a separate modification, but rather as a modification of the corresponding User records with the related group/role. This fundamental modification of the changelog record can also be done in the adjustLogEntry method. E.g. the Membership::ajustLogEntry method set the user as the object for HTML links in the change log, inserts the group as a releated object and also suppresses individual logging of the mem_rol_id, mem_usr_id and mem_uuid columns. As a consequence, the creation of a membership object is logged like a change of the user object related to the group.
class Membership extends Entity { ... public function getIgnoredLogColumns(): array { return array_merge(parent::getIgnoredLogColumns(), ['mem_rol_id', 'mem_usr_id', 'mem_uuid'}]); } protected function adjustLogEntry(LogChanges $logEntry) { global $gDb, $gProfileFields; $usrId = (int)$this->getValue('mem_usr_id'); $user = new User($this->db, $gProfileFields, $usrId); $logEntry->setValue('log_record_name', $user->readableName()); $logEntry->setValue('log_record_uuid', $user->getValue('usr_uuid')); $logEntry->setLogLinkID($usrId); $rolId = $this->getValue('mem_rol_id'); $role = new Role($this->db, $rolId); $logEntry->setLogRelated($role->getValue('rol_uuid'), $role->getValue('rol_name')); } }
The page '/adm_program/modules/changelog.php' is used to display the changelog, either for only one or more particular tables (parameter table=table1
or table=table1%2Ctable2%2Ctable3
) or even only one object (parameter uuid=7a854ed2-50db-49ee-9379-31d07f467d47
). If no parameters are given, the whole changelog from all tables is displayed.
While individual modification or list pages show the “Change History” button for the particular object or object type, sometimes it can be useful to see the complete changelog of all changes. The easiest way is to create an admin menu item for this. Simply to to “Menu” and create a new item with the following settings:
The changelog page (code in adm_program/modules/changelog.php
) first checks, whether the current user has either admin rights or at least edit rights for the corresponding tables or object.
It loads all entries from the adm_log_changes table and displays them:
static ChangelogService::getObjectForTable(string $module)
is used to load the object from the database. It's readableName is then used in the headline. E.g. if $module='users'
, a User object is created, if $module='photos'
, an Album object, etc.static ChangelogService::getTableLabel(string $table)
methodLanguage::translateIfTranslationStrId
. It then tries to create a link to the object using the static ChangelogService::createLink(string $text, string $module, int|string $id, string $uuid = '')
method. If the 'log_record_linkid' is set, then this ID is used in the link rather than the original record ID/UUID.static ChangelogService::getRelatedTable
method to modify the object type for the related object.static ChangelogService::getFieldTranslations()
method defines a mapping table.return array(..., 'rol_name' => 'SYS_NAME', 'rol_description' => 'SYS_DESCRIPTION', 'rol_cat_id' => array('name' => 'SYS_CATEGORY', 'type' => 'CATEGORY'), ... 'lnk_name' => 'SYS_LINK_NAME', 'lnk_description' => 'SYS_DESCRIPTION', 'lnk_url' => array('name' => 'SYS_LINK_ADDRESS', 'type' => 'URL'), 'lnk_cat_id' => array('name' => 'SYS_CATEGORY', 'type' => 'CATEGORY'), }
'type'
key, its value determines the formatting. The actual formatting is done with the static ChangelogService::formatValue($value, $type, $entries = [])
method. New columns can be simply added to the getFieldTranslations() method. 'column_name' => 'translatable string'
suffices.'column_name' => array('name' => 'translatable string', 'type' => 'BOOL')
formatValue
method. See below.
The abovementioned methods of the ChangelogService
class have all existing database tables properly implemented. New modules or new database tables simply need to add code to these functions for proper support, if required (not all objects have a page to link to, some database columns can use the default formatting, etc.).
If a new object type and thus a new database table is added to Admidio (either by the core, but included modules or plugins or by third-party extensions), the table's (translatable) name is added to the ChangelogService::getTableLabel(string $table)
array and if a list view for objects of the new table exists, a link can be added in the ChangelogService::createLink
method (file src/Changelog/Service/ChangelogService.php
):
class ChangelogService { ... public static function createLink(string $text, string $module, $id, $uuid = '') { switch ($module) { ... case 'rooms': $url = SecurityUtils::encodeUrl(ADMIDIO_URL.FOLDER_MODULES.'/rooms/rooms_new.php', array('room_uuid' => $uuid)); break; ... } }
By default, if a record has a related object, it will be formatted and linked with the same object type. E.g. if a folder has a parent folder (set as related record in the changelog table), the parent folder will also be formatted as a folder and a link to the folder page created. In many cases this is not desired, e.g. a File is related to its parent folder, which cannot be formatted as File, but rather as folder. This needs to be added to the method ChangelogService::getRelatedTable
(file src/Changelog/Service/ChangelogService.php
):
case 'files': return 'folders';
To add a new data type for field value formatting (both the previous and the new value), you define the column in the ChangelogService::getFieldTranslations
method (file src/Changelog/Service/ChangelogService.php
) with the new data type you desire:
'fil_fol_id' => array('name' => 'SYS_FOLDER', 'type' => 'FOLDER'),
In addition, you also need to implement the HTML output for values this datatype 'FOLDER' in the ChangelogService::formatValue($value, $type, $entries = [])
function (file src/Changelog/Service/ChangelogService.php
), with the $value typically holding the ID of the object:
public static function formatValue($value, $type, $entries = []) { ... switch ($type) { ... case 'FOLDER': $obj = new Folder($gDb, $value); $htmlValue = self::createLink($obj->readableName(), 'folders', $obj->getValue('fol_id'), $obj->getValue('fol_uuid')); break; ... } }
It is very easy to add a changelog button to each list page for a certain type of object, as well as to individual edit pages. The changelog on a list page will display all changes to the objects of that type, while the changelog of a particular edit page will filter the changelog to display only changes to the current object. Both variants are handled by a call to the method public static ChangelogService::displayHistoryButton(PagePresenter $page, string $area, string|array $table, bool $condition = true, array $params = array())
. This method adds a history button (only if $condition
is true) to the current page $page
. The param $table
defines the database table(s), e.g. 'users,user_data,members'
for user profile data, or 'rooms' for rooms), while $params
defines additional filters that are directly passed on as URL parameters to the link for adm_program/modules/changelog.php.
Supported key are 'id', 'uuid' and 'related_to', which all correspond to the columns in the adm_log_changes table.
An example for the changelog button on the contacts page (showing all changes to all contacts, if the user has the neccessary permissions) is:
ChangelogService::displayHistoryButton($page, 'contacts', 'users,user_data,members');
The changlog button on the profile edit page of a particular contact is:
// show link to view profile field change history, if we have a user ID and the current user has permissions ChangelogService::displayHistoryButton($page, 'profile', 'users,user_data,user_relations,members', !empty($getUserUuid) && $gCurrentUser->hasRightEditProfile($user), array('uuid' => $getUserUuid));
At the example of the Forum module's tables (adm_forum_posts and adm_forum_topics), these are the steps to implement full support. Basic changelog support already works out of the box with no required code changes (enabled with the changelog_table_others
preference flag for all unknown or third-party database table). All changes described here are only required to get a nicer changelog view with links and easy-to-understand labels.
The example of the forum module is taken directly from the admidio code tree (commit ff61d0a on github)
ChangelogService::getTableLabel
translation array (file src/Changelog/Service/ChangelogService.php
)'forum_topics' => 'SYS_FORUM_TOPIC', 'forum_posts' => 'SYS_FORUM_POST',
adm_program/languages/en.xml
<string name="SYS_FORUM_POST">Forum post</string> <string name="SYS_FORUM_TOPIC">Forum topic</string>
install/db_scripts/preferences.php
'changelog_table_forum_topics' => '0', 'changelog_table_forum_posts' => '0',
src/UI/View/Preferences.php
, Preferences::createChangelogForm:array( 'title' => $gL10n->get('SYS_HEADER_CONTENT_MODULES'), 'id' => 'content_modules', 'tables' => array('files', 'folders', 'photos', 'announcements', 'events', 'rooms', 'forum_topics', 'forum_posts', 'links', 'others') ),
src/Changelog/Service/ChangelogService.php
)use Admidio\Forum\Entity\Topic; use Admidio\Forum\Entity\Post;
Topic::getIgnoredLogColumns()
(file src/Forum/Entity/Topic.php
):class Topic extends Entity { ... public function getIgnoredLogColumns(): array { return array_merge(parent::getIgnoredLogColumns(), [$this->columnPrefix . '_views'], ($this->newRecord)?[$this->columnPrefix.'_title']:[]); } }
Post::getIgnoredLogColumns()
(file src/Forum/Entity/Post.php
):class Post extends Entity { ... public function getIgnoredLogColumns(): array { return array_merge(parent::getIgnoredLogColumns(), ['fop_fot_id'], ($this->newRecord)?[$this->columnPrefix.'_text']:[] ); } }
Topic::adjustLogEntry()
(file src/Forum/Entity/Topic.php
):class Topic extends Entity { ... protected function adjustLogEntry(LogChanges $logEntry): void { $fotEntry = new Post($this->db, (int)$this->getValue('fot_fop_id_first_post')); $logEntry->setLogRelated($fotEntry->getValue('fop_uuid'), $fotEntry->getValue('fop_text')); } }
Post::adjustLogEntry()
(file src/Forum/Entity/Post.php
):class Post extends Entity { ... protected function adjustLogEntry(LogChanges $logEntry): void { $fotEntry = new Topic($this->db, $this->getValue('fop_fot_id')); $logEntry->setLogRelated($fotEntry->getValue('fot_uuid'), $fotEntry->getValue('fot_title')); } }
ChangelogService::getFieldTranslations
, add the column definitions to the return list (file src/Changelog/Service/ChangelogService.php
):'fot_cat_id' => array('name' => 'SYS_CATEGORY', 'type' => 'CATEGORY'), 'fot_fop_id_first_post' => array('name' => 'SYS_FORUM_POST', 'type' => 'POST'), 'fot_title' => 'SYS_TITLE', 'fop_text' => 'SYS_TEXT', 'fop_fot_id' => array('name' => 'SYS_FORUM_TOPIC', 'type' => 'TOPIC'),
ChangelogService::formatValue
method inside the switch statement (file src/Changelog/Service/ChangelogService.php
):case 'TOPIC': $obj = new Topic($gDb, $value); $htmlValue = self::createLink($obj->readableName(), 'forum_topics', $obj->getValue('fot_id'), $obj->getValue('fot_uuid')); break; case 'POST': $obj = new POST($gDb, $value); $htmlValue = self::createLink($obj->readableName(), 'forum_posts', $obj->getValue('fop_id'), $obj->getValue('fop_uuid')); break;
ChangelogService::createLink
, add the link definitions in the switch statement (file src/Changelog/Service/ChangelogService.php
):case 'forum_topics' : $url = SecurityUtils::encodeUrl( ADMIDIO_URL.FOLDER_MODULES.'/forum.php', array('mode' => 'topic', 'topic_uuid' => $uuid)); break; case 'forum_posts' : $url = SecurityUtils::encodeUrl( ADMIDIO_URL.FOLDER_MODULES.'/forum.php', array('mode' => 'post_edit', 'post_uuid' => $uuid)); break;
ChangelogService::getRelatedTable
, tinside the switch statement, add the type of the related object for posts to topic and vice versa (file src/Changelog/Service/ChangelogService.php
):case 'forum_posts': return 'forum_topics'; case 'forum_topics: return 'forum_posts';
src/Changelog/Service/ChangelogService.php
)ChangelogServer::getObjectForTable(string $module)
:case 'forum_topic': return new Topic($gDb); case 'forum_post': return new Post($gDb);
src/UI/Presenter/ForumPresenter.php
, use Admidio\Changelog\Service\ChangelogService;
method ForumPresenter::createSharedHeader
ChangelogService::displayHistoryButton($this, 'forum', 'forum_topics,forum_posts', $gCurrentUser->administrateForum());
src/UI/Presenter/ForumTopicPresenter.php
use Admidio\Changelog\Service\ChangelogService;
In ForumTopicPresenter::createCards method (after this→addPageFunctionsMenuItem call):
global $gCurrentUser; ChangelogService::displayHistoryButton($this, 'forum', 'forum_topics,forum_posts', $gCurrentUser->administrateForum(), ['uuid' => $this->topicUUID]);
In ForumTopicPresenter::createEditForm (after new FormPresenter); The changelog button should be hidden when a new topic is created (i.e. no uuid exists yet!):
ChangelogService::displayHistoryButton($this, 'forum', 'forum_topics,forum_posts', $this->topicUUID !== '' && $gCurrentUser->administrateForum(), ['uuid' => $this->topicUUID]);
src/UI/Presenter/ForumPostPresenter.php
use Admidio\Changelog\Service\ChangelogService;
In ForumPostPresenter::createEditFormmethod (after new FormPresenter):
global $gCurrentUser; ChangelogService::displayHistoryButton($this, 'forum', 'forum_posts', $this->postUUID !== '' && $gCurrentUser->administrateForum(), ['uuid' => $this->postUUID]);
Adding support for changelogs in third-party extensions, where modifying the core Admidio code is not possible, works similar to the above steps. Modifying the changelog entry creation is implemented inside the extension's Entity-derived database access classes, so this part is similar to core modules. However, the formatting in the Change history page view is implemented in the class ChangelogService
for the core modules, which is not directly available for change to third-party extension developers.
However, the ChanglogService class additionally provides a way to register mapping tables for table/column names or general callback functions for all the methods that need modifications as described above. The callback functions are registered with the method
/** * Register a callback function or value for the changelog functionality. If the callback is a value (string, array, etc.), it will * be returned. If the callback is a function, it will be executed and if the return value is not empty, it will be returned. If the * function returns a null or empty value, the next callback or the default processing of the ChangelogService method will proceed. * @param string $function The method of the ChangelogService class that should be customized. One of * 'getTableLabel', 'getTableLabelArray', 'getObjectForTable', 'getFieldTranslations', 'createLink', * 'formatValue', 'getRelatedTable', 'getPermittedTables' * @param string $moduleOrKey The module or type that should be customized. If empty, the callback will be * executed for all values and it will be used if it evaluates to a non-empty value. * @param mixed $callback The callback function or value. A value will be returned unchanged, a function will be executed (arguments are identical to the ChangelogService's methods) */ static ChangelogService::registerCallback(string $function, string $moduleOrKey, mixed $callback)
Using these callback mechanisms, the forum changelog described above could also be implemented with the following code. It should be executed somewhere during php startup when the third-party module is loaded, and before either a changelog page can be displayed or before any of the third-party extension's database records are modified (i.e. before the extension writes data to the database).
## Translation of database tables ChangelogService::registerCallback('getTableLabelArray', 'forum_topics', 'SYS_FORUM_TOPIC'); ChangelogService::registerCallback('getTableLabelArray', 'forum_posts', 'SYS_FORUM_POST'); ## Translations and type definitions of database columns ChangelogService::registerCallback('getFieldTranslations', '', [ 'fot_cat_id' => array('name' => 'SYS_CATEGORY', 'type' => 'CATEGORY'), 'fot_fop_id_first_post' => array('name' => 'SYS_FORUM_POST', 'type' => 'POST'), 'fot_title' => 'SYS_TITLE', 'fop_text' => 'SYS_TEXT', 'fop_fot_id' => array('name' => 'SYS_FORUM_TOPIC', 'type' => 'TOPIC') ]); ## Formatting of new database column types (in many cases not needed) ChangelogService::registerCallback('formatValue', 'TOPIC', function($value, $type, $entries = []) { global $gDb; if (empty($value)) return ''; $obj = new Topic($gDb, $value??0); return ChangelogService::createLink($obj->readableName(), 'forum_topics', $obj->getValue('fot_id'), $obj->getValue('fot_uuid')); }); ChangelogService::registerCallback('formatValue', 'POST', function($value, $type, $entries = []) { global $gDb; if (empty($value)) return ''; $obj = new POST($gDb, $value??0); return ChangelogService::createLink($obj->readableName(), 'forum_posts', $obj->getValue('fop_id'), $obj->getValue('fop_uuid')); }); ## Create HTML links to the object's list view and edit pages ChangelogService::registerCallback('createLink', 'forum_topics', function(string $text, string $module, int|string $id, string $uuid = '') { return SecurityUtils::encodeUrl( ADMIDIO_URL.FOLDER_MODULES.'/forum.php', array('mode' => 'topic', 'topic_uuid' => $uuid)); }); ChangelogService::registerCallback('createLink', 'forum_posts', function(string $text, string $module, int|string $id, string $uuid = '') { return SecurityUtils::encodeUrl( ADMIDIO_URL.FOLDER_MODULES.'/forum.php', array('mode' => 'post_edit', 'post_uuid' => $uuid)); }); ## Object types of related objects (if object relations are used at all!) ChangelogService::registerCallback('getRelatedTable', 'forum_topics', 'forum_posts'); ChangelogService::registerCallback('getRelatedTable', 'forum_posts', 'forum_topics'); ## Create Entity-derived objects to create headlines with proper object names ChangelogService::registerCallback('getObjectForTable', 'forum_topics', function() {global $gDb; return new Topic($gDb);}); ChangelogService::registerCallback('getObjectForTable', 'forum_posts', function() {global $gDb; return new Post($gDb);}); ## Enable per-user detection of access permissions to the tables (based on user's role permission); Admin is always allowed ChangelogService::registerCallback('getPermittedTables', '', function(User $user) { if ($user->administrateForum()) return ['forum_topics', 'forum_posts']; });