Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@
->setClassName(Driver::class)
->setInitializationData(['source' => $container->getName()])
->setVersioningEnabled(false);
// MetaModels\CoreBundle\EventListener\DcGeneral\LogPersistedItemsListener logs items
// with their rendered label instead - logging both would duplicate every entry under
// two different wordings. A separate statement, not chained onto the calls above:
// setVersioningEnabled() returns the wider DataProviderInformationInterface, which
// does not declare setLoggingEnabled().
$providerInformation->setLoggingEnabled(false);

Check failure on line 104 in src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/DataProviderBuilder.php

View workflow job for this annotation

GitHub Actions / PHP: 8.5 Contao: ~5.7.0

UndefinedMethod: Method ContaoCommunityAlliance\DcGeneral\Contao\Dca\ContaoDataProviderInformation::setLoggingEnabled does not exist (reported by psalm)

Check failure on line 104 in src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/DataProviderBuilder.php

View workflow job for this annotation

GitHub Actions / PHP: 8.4 Contao: ~5.7.0

UndefinedMethod: Method ContaoCommunityAlliance\DcGeneral\Contao\Dca\ContaoDataProviderInformation::setLoggingEnabled does not exist (reported by psalm)

Check failure on line 104 in src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/DataProviderBuilder.php

View workflow job for this annotation

GitHub Actions / PHP: 8.4 Contao: ~5.7.0

UndefinedMethod: Method ContaoCommunityAlliance\DcGeneral\Contao\Dca\ContaoDataProviderInformation::setLoggingEnabled does not exist (reported by psalm)

Check failure on line 104 in src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/DataProviderBuilder.php

View workflow job for this annotation

GitHub Actions / PHP: 8.5 Contao: ~5.7.0

UndefinedMethod: Method ContaoCommunityAlliance\DcGeneral\Contao\Dca\ContaoDataProviderInformation::setLoggingEnabled does not exist (reported by psalm)
$basicDefinition->setDataProvider($container->getName());
}

Expand Down
159 changes: 159 additions & 0 deletions src/CoreBundle/EventListener/DcGeneral/LogPersistedItemsListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<?php

/**
* This file is part of MetaModels/core.
*
* (c) 2012-2026 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* This project is provided in good faith and hope to be usable by anyone.
*
* @package MetaModels/core
* @author Ingolf Steinhardt <info@e-spin.de>
* @copyright 2012-2026 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/

declare(strict_types=1);

namespace MetaModels\CoreBundle\EventListener\DcGeneral;

use ContaoCommunityAlliance\DcGeneral\Data\ModelInterface;
use ContaoCommunityAlliance\DcGeneral\Event\PostDeleteModelEvent;
use ContaoCommunityAlliance\DcGeneral\Event\PostDuplicateModelEvent;
use ContaoCommunityAlliance\DcGeneral\Event\PostPersistModelEvent;
use MetaModels\CoreBundle\Backend\ItemLabelRenderer;
use MetaModels\IFactory;
use MetaModels\ViewCombination\ViewCombination;
use Psr\Log\LoggerInterface;

/**
* Logs creating, duplicating and deleting a MetaModel item to the Contao system log (tl_log), the
* way dc-general's own generic LogPersistedModelsListener does for every other DC_General table -
* except with the item named the same way the edit mask headline and breadcrumb already do
* (ItemLabelRenderer, the input screen's "subheadline" pattern), instead of a bare table+id. See
* ".claude/dcg-systemlog.md".
*
* MetaModels items opt out of the generic listener entirely (DataProviderBuilder sets
* setLoggingEnabled(false) on their provider information) so that a create/duplicate/delete is not
* logged twice under two different wordings. The three MetaModels configuration tables
* (tl_metamodel_rendersettings and friends) are unaffected and keep using the generic listener - a
* bare table+id is all that is meaningful for those anyway.
*
* Deliberately not "edit", for the same reason as the generic listener: Contao's own tables do not
* log edits either, the version history is what covers that.
*/
final class LogPersistedItemsListener
{
public function __construct(
private readonly IFactory $factory,
private readonly ViewCombination $viewCombination,
private readonly ItemLabelRenderer $labelRenderer,
private readonly LoggerInterface $logger,
) {
}

/**
* Log the creation of a new item.
*
* @param PostPersistModelEvent $event The event.
*
* @return void
*/
public function onPersist(PostPersistModelEvent $event): void
{
// Edits fire this same event with the previously stored data as original model. A create
// is not signalled by a null original model - CreateHandler passes an empty one
// (getEmptyModel()), never a literal null - it has no id yet, which is what actually tells
// the two apart.
$originalModel = $event->getOriginalModel();
if (null !== $originalModel && null !== $originalModel->getId()) {
return;
}

$this->log(
$event->getModel(),
fn (string $label): string => \sprintf('A new entry "%s" has been created', $label)
);
}

/**
* Log the creation of an item by duplicating another one.
*
* @param PostDuplicateModelEvent $event The event.
*
* @return void
*/
public function onDuplicate(PostDuplicateModelEvent $event): void
{
$sourceLabel = $this->describe($event->getSourceModel());

$this->log(
$event->getModel(),
fn (string $label): string => \sprintf(
'A new entry "%s" has been created by duplicating record "%s"',
$label,
$sourceLabel
)
);
}

/**
* Log the deletion of an item.
*
* @param PostDeleteModelEvent $event The event.
*
* @return void
*/
public function onDelete(PostDeleteModelEvent $event): void
{
$this->log($event->getModel(), fn (string $label): string => \sprintf('Deleted entry "%s"', $label));
}

/**
* Write the log entry for a model, unless it is not a MetaModel item or logging is off for it.
*
* @param ModelInterface $model The model.
* @param callable $message Builds the log message from the model's rendered label.
*
* @return void
*/
private function log(ModelInterface $model, callable $message): void
{
$tableName = $model->getProviderName();
if (!\in_array($tableName, $this->factory->collectNames(), true)) {
return;
}

$metaModel = $this->factory->getMetaModel($tableName);
if (null === $metaModel || !(bool) $metaModel->get('enableLogging')) {
return;
}

$this->logger->info($message($this->describe($model)));
}

/**
* Name a model the same way its edit mask headline and breadcrumb do.
*
* @param ModelInterface $model The model.
*
* @return string
*/
private function describe(ModelInterface $model): string
{
$tableName = $model->getProviderName();
$metaModel = $this->factory->getMetaModel($tableName);
$modelName = null !== $metaModel ? $metaModel->getName() : $tableName;

/** @var array<string, mixed>|null $screen */
$screen = $this->viewCombination->getScreen($tableName);
$pattern = (string) ($screen['meta']['subheadline'] ?? '');
$label = $this->labelRenderer->render($pattern, $model->getPropertiesAsArray());

return $modelName . ': ' . ('' !== $label ? $label : (string) $model->getId());
}
}
18 changes: 18 additions & 0 deletions src/CoreBundle/Resources/config/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,24 @@ services:
- "@router"
- "@request_stack"

metamodels.listener.dcgeneral.log_persisted_items:
class: MetaModels\CoreBundle\EventListener\DcGeneral\LogPersistedItemsListener
arguments:
- "@metamodels.factory"
- "@metamodels.view_combination"
- "@metamodels.backend.item_label_renderer"
- "@monolog.logger.contao.general"
tags:
- name: kernel.event_listener
event: dc-general.model.post-persist
method: onPersist
- name: kernel.event_listener
event: dc-general.model.post-duplicate
method: onDuplicate
- name: kernel.event_listener
event: dc-general.model.post-delete
method: onDelete

metamodels.assets.icon_builder:
class: MetaModels\CoreBundle\Assets\IconBuilder
arguments:
Expand Down
16 changes: 15 additions & 1 deletion src/CoreBundle/Resources/contao/dca/tl_metamodel.php
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,8 @@
],
'advanced' => [
':hide',
'varsupport'
'varsupport',
'enableLogging'
],
]
],
Expand Down Expand Up @@ -532,5 +533,18 @@
],
'sql' => "char(1) NOT NULL default ''"
],
// Mirrors what Contao logs for its own tables (create/duplicate/delete) into tl_log - see
// .claude/dcg-systemlog.md. Default on, matching Contao's own tables, which cannot be
// switched off either.
'enableLogging' => [
'label' => 'enableLogging.label',
'description' => 'enableLogging.description',
'exclude' => true,
'inputType' => 'checkbox',
'eval' => [
'tl_class' => 'clr w50 cbx m12'
],
'sql' => "char(1) NOT NULL default '1'"
],
],
];
8 changes: 8 additions & 0 deletions src/CoreBundle/Resources/translations/tl_metamodel.de.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@
<source>Check if this MetaModel shall support language territory at locale.</source>
<target>Mit der Checkbox wird die Territory-Angabe bei dem Sprachkey unterstützt.</target>
</trans-unit>
<trans-unit id="enableLogging.label" resname="enableLogging.label">
<source>Log changes to the system log</source>
<target>Änderungen im Systemlog protokollieren</target>
</trans-unit>
<trans-unit id="enableLogging.description" resname="enableLogging.description">
<source>Writes creating, duplicating and deleting an item of this MetaModel to the Contao system log, the same way Contao does for its own tables. Editing an existing item is not logged, matching Contao - the version history covers that instead.</source>
<target>Schreibt Anlegen, Duplizieren und Löschen eines Items dieses MetaModels ins Contao-Systemlog, genauso wie Contao es für seine eigenen Tabellen tut. Das Bearbeiten eines vorhandenen Items wird - wie bei Contao - nicht protokolliert, dafür ist die Versionierung da.</target>
</trans-unit>
<trans-unit id="sorting.label" resname="sorting.label">
<source>Sorting</source>
<target>Sortierung</target>
Expand Down
6 changes: 6 additions & 0 deletions src/CoreBundle/Resources/translations/tl_metamodel.en.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@
<trans-unit id="localeterritorysupport.description" resname="localeterritorysupport.description">
<source>Check if this MetaModel shall support language territory at locale.</source>
</trans-unit>
<trans-unit id="enableLogging.label" resname="enableLogging.label">
<source>Log changes to the system log</source>
</trans-unit>
<trans-unit id="enableLogging.description" resname="enableLogging.description">
<source>Writes creating, duplicating and deleting an item of this MetaModel to the Contao system log, the same way Contao does for its own tables. Editing an existing item is not logged, matching Contao - the version history covers that instead.</source>
</trans-unit>
<trans-unit id="sorting.label" resname="sorting.label">
<source>Sorting</source>
</trans-unit>
Expand Down
Loading
Loading