mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 09:35:49 +02:00
Show a table with the old data in log entry details page
This commit is contained in:
parent
4c6ceab8e8
commit
1534f780aa
12 changed files with 435 additions and 84 deletions
|
@ -24,6 +24,7 @@ namespace App\Services;
|
||||||
|
|
||||||
use App\Entity\Attachments\Attachment;
|
use App\Entity\Attachments\Attachment;
|
||||||
use App\Entity\Attachments\AttachmentType;
|
use App\Entity\Attachments\AttachmentType;
|
||||||
|
use App\Entity\Base\AbstractDBElement;
|
||||||
use App\Entity\Contracts\NamedElementInterface;
|
use App\Entity\Contracts\NamedElementInterface;
|
||||||
use App\Entity\ProjectSystem\Project;
|
use App\Entity\ProjectSystem\Project;
|
||||||
use App\Entity\LabelSystem\LabelProfile;
|
use App\Entity\LabelSystem\LabelProfile;
|
||||||
|
@ -43,17 +44,20 @@ use App\Entity\ProjectSystem\ProjectBOMEntry;
|
||||||
use App\Entity\UserSystem\Group;
|
use App\Entity\UserSystem\Group;
|
||||||
use App\Entity\UserSystem\User;
|
use App\Entity\UserSystem\User;
|
||||||
use App\Exceptions\EntityNotSupportedException;
|
use App\Exceptions\EntityNotSupportedException;
|
||||||
|
use Doctrine\ORM\Mapping\Entity;
|
||||||
use function get_class;
|
use function get_class;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
class ElementTypeNameGenerator
|
class ElementTypeNameGenerator
|
||||||
{
|
{
|
||||||
protected TranslatorInterface $translator;
|
protected TranslatorInterface $translator;
|
||||||
|
private EntityURLGenerator $entityURLGenerator;
|
||||||
protected array $mapping;
|
protected array $mapping;
|
||||||
|
|
||||||
public function __construct(TranslatorInterface $translator)
|
public function __construct(TranslatorInterface $translator, EntityURLGenerator $entityURLGenerator)
|
||||||
{
|
{
|
||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
|
$this->entityURLGenerator = $entityURLGenerator;
|
||||||
|
|
||||||
//Child classes has to become before parent classes
|
//Child classes has to become before parent classes
|
||||||
$this->mapping = [
|
$this->mapping = [
|
||||||
|
@ -132,4 +136,81 @@ class ElementTypeNameGenerator
|
||||||
|
|
||||||
return $type.': '.$entity->getName();
|
return $type.': '.$entity->getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a HTML formatted label for the given enitity in the format "Type: Name" (on elements with a name) and
|
||||||
|
* "Type: ID" (on elements without a name). If possible the value is given as a link to the element.
|
||||||
|
* @param AbstractDBElement $entity The entity for which the label should be generated
|
||||||
|
* @param bool $include_associated If set to true, the associated entity (like the part belonging to a part lot) is included in the label to give further information
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function formatLabelHTMLForEntity(AbstractDBElement $entity, bool $include_associated = false): string
|
||||||
|
{
|
||||||
|
//The element is existing
|
||||||
|
if ($entity instanceof NamedElementInterface && !empty($entity->getName())) {
|
||||||
|
try {
|
||||||
|
$tmp = sprintf(
|
||||||
|
'<a href="%s">%s</a>',
|
||||||
|
$this->entityURLGenerator->infoURL($entity),
|
||||||
|
$this->getTypeNameCombination($entity, true)
|
||||||
|
);
|
||||||
|
} catch (EntityNotSupportedException $exception) {
|
||||||
|
$tmp = $this->getTypeNameCombination($entity, true);
|
||||||
|
}
|
||||||
|
} else { //Target does not have a name
|
||||||
|
$tmp = sprintf(
|
||||||
|
'<i>%s</i>: %s',
|
||||||
|
$this->getLocalizedTypeLabel($entity),
|
||||||
|
$entity->getID()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add a hint to the associated element if possible
|
||||||
|
if ($include_associated) {
|
||||||
|
if ($entity instanceof Attachment && null !== $entity->getElement()) {
|
||||||
|
$on = $entity->getElement();
|
||||||
|
} elseif ($entity instanceof AbstractParameter && null !== $entity->getElement()) {
|
||||||
|
$on = $entity->getElement();
|
||||||
|
} elseif ($entity instanceof PartLot && null !== $entity->getPart()) {
|
||||||
|
$on = $entity->getPart();
|
||||||
|
} elseif ($entity instanceof Orderdetail && null !== $entity->getPart()) {
|
||||||
|
$on = $entity->getPart();
|
||||||
|
} elseif ($entity instanceof Pricedetail && null !== $entity->getOrderdetail() && null !== $entity->getOrderdetail()->getPart()) {
|
||||||
|
$on = $entity->getOrderdetail()->getPart();
|
||||||
|
} elseif ($entity instanceof ProjectBOMEntry && null !== $entity->getProject()) {
|
||||||
|
$on = $entity->getProject();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($on) && is_object($on)) {
|
||||||
|
try {
|
||||||
|
$tmp .= sprintf(
|
||||||
|
' (<a href="%s">%s</a>)',
|
||||||
|
$this->entityURLGenerator->infoURL($on),
|
||||||
|
$this->getTypeNameCombination($on, true)
|
||||||
|
);
|
||||||
|
} catch (EntityNotSupportedException $exception) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a HTML formatted label for a deleted element of which we only know the class and the ID.
|
||||||
|
* Please note that it is not checked if the element really not exists anymore, so you have to do this yourself.
|
||||||
|
* @param string $class
|
||||||
|
* @param int $id
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function formatElementDeletedHTML(string $class, int $id): string
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'<i>%s</i>: %s [%s]',
|
||||||
|
$this->getLocalizedTypeLabel($class),
|
||||||
|
$id,
|
||||||
|
$this->translator->trans('log.target_deleted')
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
146
src/Services/LogSystem/LogDataFormatter.php
Normal file
146
src/Services/LogSystem/LogDataFormatter.php
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Services\LogSystem;
|
||||||
|
|
||||||
|
use App\Entity\LogSystem\AbstractLogEntry;
|
||||||
|
use App\Services\ElementTypeNameGenerator;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
|
class LogDataFormatter
|
||||||
|
{
|
||||||
|
private const STRING_MAX_LENGTH = 1024;
|
||||||
|
|
||||||
|
private TranslatorInterface $translator;
|
||||||
|
private EntityManagerInterface $entityManager;
|
||||||
|
private ElementTypeNameGenerator $elementTypeNameGenerator;
|
||||||
|
|
||||||
|
public function __construct(TranslatorInterface $translator, EntityManagerInterface $entityManager, ElementTypeNameGenerator $elementTypeNameGenerator)
|
||||||
|
{
|
||||||
|
$this->translator = $translator;
|
||||||
|
$this->entityManager = $entityManager;
|
||||||
|
$this->elementTypeNameGenerator = $elementTypeNameGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the given data of a log entry as HTML
|
||||||
|
* @param mixed $data
|
||||||
|
* @param AbstractLogEntry $logEntry
|
||||||
|
* @param string $fieldName
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function formatData($data, AbstractLogEntry $logEntry, string $fieldName): string
|
||||||
|
{
|
||||||
|
if (is_string($data)) {
|
||||||
|
return '"' . mb_strimwidth(htmlspecialchars($data), 0, self::STRING_MAX_LENGTH, ) . '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_bool($data)) {
|
||||||
|
return $this->formatBool($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_int($data)) {
|
||||||
|
return (string) $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_float($data)) {
|
||||||
|
return (string) $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_null($data)) {
|
||||||
|
return '<i>null</i>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($data)) {
|
||||||
|
//If the array contains only one element with the key @id, it is a reference to another entity (foreign key)
|
||||||
|
if (isset($data['@id'])) {
|
||||||
|
return $this->formatForeignKey($data, $logEntry, $fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
//If the array contains a "date", "timezone_type" and "timezone" key, it is a DateTime object
|
||||||
|
if (isset($data['date'], $data['timezone_type'], $data['timezone'])) {
|
||||||
|
return $this->formatDateTime($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return htmlspecialchars(json_encode($data, JSON_PRETTY_PRINT));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
throw new \RuntimeException('Type of $data not supported (' . gettype($data) . ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatForeignKey(array $data, AbstractLogEntry $logEntry, string $fieldName): string
|
||||||
|
{
|
||||||
|
//Extract the id from the @id key
|
||||||
|
$id = $data['@id'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
//Retrieve the class type from the logEntry and retrieve the doctrine metadata
|
||||||
|
$classMetadata = $this->entityManager->getClassMetadata($logEntry->getTargetClass());
|
||||||
|
$fkTargetClass = $classMetadata->getAssociationTargetClass($fieldName);
|
||||||
|
|
||||||
|
//Try to retrieve the entity from the database
|
||||||
|
$entity = $this->entityManager->getRepository($fkTargetClass)->find($id);
|
||||||
|
|
||||||
|
//If the entity was found, return a label for this entity
|
||||||
|
if ($entity) {
|
||||||
|
return $this->elementTypeNameGenerator->formatLabelHTMLForEntity($entity, true);
|
||||||
|
} else { //Otherwise the entity was deleted, so return the id
|
||||||
|
return $this->elementTypeNameGenerator->formatElementDeletedHTML($fkTargetClass, $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (\InvalidArgumentException|\ReflectionException $exception) {
|
||||||
|
return '<i>unknown target class</i>: ' . $id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatDateTime(array $data): string
|
||||||
|
{
|
||||||
|
if (!isset($data['date'], $data['timezone_type'], $data['timezone'])) {
|
||||||
|
return '<i>unknown DateTime format</i>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$date = $data['date'];
|
||||||
|
$timezoneType = $data['timezone_type'];
|
||||||
|
$timezone = $data['timezone'];
|
||||||
|
|
||||||
|
if (!is_string($date) || !is_int($timezoneType) || !is_string($timezone)) {
|
||||||
|
return '<i>unknown DateTime format</i>';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$dateTime = new \DateTime($date, new \DateTimeZone($timezone));
|
||||||
|
} catch (\Exception $exception) {
|
||||||
|
return '<i>unknown DateTime format</i>';
|
||||||
|
}
|
||||||
|
|
||||||
|
//Format it to the users locale
|
||||||
|
$formatter = new \IntlDateFormatter(null, \IntlDateFormatter::MEDIUM, \IntlDateFormatter::MEDIUM);
|
||||||
|
return $formatter->format($dateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatBool(bool $data): string
|
||||||
|
{
|
||||||
|
return $data ? $this->translator->trans('true') : $this->translator->trans('false');
|
||||||
|
}
|
||||||
|
}
|
|
@ -78,64 +78,17 @@ class LogTargetHelper
|
||||||
/** @var AbstractLogEntry $context */
|
/** @var AbstractLogEntry $context */
|
||||||
$target = $this->entryRepository->getTargetElement($context);
|
$target = $this->entryRepository->getTargetElement($context);
|
||||||
|
|
||||||
$tmp = '';
|
//If the target is null and the context has a target, that means that the target was deleted. Show it that way.
|
||||||
|
if ($target === null) {
|
||||||
//The element is existing
|
if ($context->hasTarget()) {
|
||||||
if ($target instanceof NamedElementInterface && !empty($target->getName())) {
|
return $this->elementTypeNameGenerator->formatElementDeletedHTML($context->getTargetClass(),
|
||||||
try {
|
$context->getTargetId());
|
||||||
$tmp = sprintf(
|
|
||||||
'<a href="%s">%s</a>',
|
|
||||||
$this->entityURLGenerator->infoURL($target),
|
|
||||||
$this->elementTypeNameGenerator->getTypeNameCombination($target, true)
|
|
||||||
);
|
|
||||||
} catch (EntityNotSupportedException $exception) {
|
|
||||||
$tmp = $this->elementTypeNameGenerator->getTypeNameCombination($target, true);
|
|
||||||
}
|
}
|
||||||
} elseif ($target instanceof AbstractDBElement) { //Target does not have a name
|
//If no target is set, we can't do anything
|
||||||
$tmp = sprintf(
|
return '';
|
||||||
'<i>%s</i>: %s',
|
|
||||||
$this->elementTypeNameGenerator->getLocalizedTypeLabel($target),
|
|
||||||
$target->getID()
|
|
||||||
);
|
|
||||||
} elseif (null === $target && $context->hasTarget()) { //Element was deleted
|
|
||||||
$tmp = sprintf(
|
|
||||||
'<i>%s</i>: %s [%s]',
|
|
||||||
$this->elementTypeNameGenerator->getLocalizedTypeLabel($context->getTargetClass()),
|
|
||||||
$context->getTargetID(),
|
|
||||||
$this->translator->trans('log.target_deleted')
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add a hint to the associated element if possible
|
//Otherwise we can return a label for the target
|
||||||
if (null !== $target && $options['show_associated']) {
|
return $this->elementTypeNameGenerator->formatLabelHTMLForEntity($target, $options['show_associated']);
|
||||||
if ($target instanceof Attachment && null !== $target->getElement()) {
|
|
||||||
$on = $target->getElement();
|
|
||||||
} elseif ($target instanceof AbstractParameter && null !== $target->getElement()) {
|
|
||||||
$on = $target->getElement();
|
|
||||||
} elseif ($target instanceof PartLot && null !== $target->getPart()) {
|
|
||||||
$on = $target->getPart();
|
|
||||||
} elseif ($target instanceof Orderdetail && null !== $target->getPart()) {
|
|
||||||
$on = $target->getPart();
|
|
||||||
} elseif ($target instanceof Pricedetail && null !== $target->getOrderdetail() && null !== $target->getOrderdetail()->getPart()) {
|
|
||||||
$on = $target->getOrderdetail()->getPart();
|
|
||||||
} elseif ($target instanceof ProjectBOMEntry && null !== $target->getProject()) {
|
|
||||||
$on = $target->getProject();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($on) && is_object($on)) {
|
|
||||||
try {
|
|
||||||
$tmp .= sprintf(
|
|
||||||
' (<a href="%s">%s</a>)',
|
|
||||||
$this->entityURLGenerator->infoURL($on),
|
|
||||||
$this->elementTypeNameGenerator->getTypeNameCombination($on, true)
|
|
||||||
);
|
|
||||||
} catch (EntityNotSupportedException $exception) {
|
|
||||||
$tmp .= ' ('.$this->elementTypeNameGenerator->getTypeNameCombination($target, true).')';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Log is not associated with an element
|
|
||||||
return $tmp;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
40
src/Twig/LogExtension.php
Normal file
40
src/Twig/LogExtension.php
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Twig;
|
||||||
|
|
||||||
|
use App\Services\LogSystem\LogDataFormatter;
|
||||||
|
use Twig\Extension\AbstractExtension;
|
||||||
|
use Twig\TwigFunction;
|
||||||
|
|
||||||
|
class LogExtension extends AbstractExtension
|
||||||
|
{
|
||||||
|
public function __construct(LogDataFormatter $logDataFormatter)
|
||||||
|
{
|
||||||
|
$this->logDataFormatter = $logDataFormatter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFunctions()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new TwigFunction('format_log_data', [$this->logDataFormatter, 'formatData'], ['is_safe' => ['html']])
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{# @var entry \App\Entity\LogSystem\ElementCreatedLogEntry #}
|
||||||
|
|
||||||
|
{% import "log_system/details/helper.macro.html.twig" as log_helper %}
|
||||||
|
|
||||||
|
{{ log_helper.comment_field(entry) }}
|
||||||
|
{% if entry.creationInstockValue %}
|
||||||
|
<p>
|
||||||
|
<b>{% trans %}log.element_created.original_instock{% endtrans %}:</b>
|
||||||
|
{{ entry.creationInstockValue }}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{# @var entry \App\Entity\LogSystem\ElementDeletedLogEntry #}
|
||||||
|
|
||||||
|
{% import "log_system/details/helper.macro.html.twig" as log_helper %}
|
||||||
|
|
||||||
|
<span class="badge badge-notice"></span>
|
||||||
|
|
||||||
|
{{ log_helper.comment_field(entry) }}
|
||||||
|
{{ log_helper.data_change_table(entry) }}
|
11
templates/log_system/details/_extra_element_edited.html.twig
Normal file
11
templates/log_system/details/_extra_element_edited.html.twig
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{# @var entry \App\Entity\LogSystem\ElementDeletedLogEntry #}
|
||||||
|
|
||||||
|
{% import "log_system/details/helper.macro.html.twig" as log_helper %}
|
||||||
|
|
||||||
|
{% if entry.undoEvent %}
|
||||||
|
<span class="badge badge-info bg-info">Test</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{{ log_helper.comment_field(entry) }}
|
||||||
|
|
||||||
|
{{ log_helper.data_change_table(entry) }}
|
77
templates/log_system/details/helper.macro.html.twig
Normal file
77
templates/log_system/details/helper.macro.html.twig
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
|
||||||
|
{% macro comment_field(entry) %}
|
||||||
|
{# @var entry \App\Entity\Contracts\LogWithComment #}
|
||||||
|
<p class="mb-0">
|
||||||
|
<b>{% trans %}edit.log_comment{% endtrans %}:</b>
|
||||||
|
{% if entry.comment %}
|
||||||
|
{{ entry.comment }}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">{% trans %}log.no_comment{% endtrans %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro data_change_table(entry) %}
|
||||||
|
{# @var entry \App\Entity\LogSystem\ElementEditedLogEntry|\App\Entity\LogSystem\ElementDeletedLogEntry entry #}
|
||||||
|
|
||||||
|
{% set fields, old_data, new_data = {}, {}, {} %}
|
||||||
|
|
||||||
|
{# For log entries where only the changed fields are saved, this is the last executed assignment #}
|
||||||
|
{% if attribute(entry, 'changedFieldInfo') is defined and entry.changedFieldsInfo %}
|
||||||
|
{% set fields = entry.changedFields %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# For log entries, where we know the old data, this is the last exectuted assignment #}
|
||||||
|
{% if attribute(entry, 'oldDataInformations') is defined and entry.oldDataInformations %}
|
||||||
|
{# We have to use the keys of oldData here, as changedFields might not be available #}
|
||||||
|
{% set fields = entry.oldData | keys %}
|
||||||
|
{% set old_data = entry.oldData %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if fields is not empty %}
|
||||||
|
<table class="table table-hover table-striped table-sm table-bordered mt-2">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans %}log.element_changed.field{% endtrans %}</th>
|
||||||
|
{% if old_data is not empty %}
|
||||||
|
<th>{% trans %}log.element_changed.data_before{% endtrans %}</th>
|
||||||
|
{% endif %}
|
||||||
|
{% if new_data is not empty %}
|
||||||
|
<th>{% trans %}log.element_changed.data_after{% endtrans %}</th>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for field in fields %}
|
||||||
|
<tr>
|
||||||
|
<td title="{{ field }}">
|
||||||
|
{% set trans_key = 'log.element_edited.changed_fields.'~field %}
|
||||||
|
{# If the translation key is not found, the translation key is returned, and we dont show the translation #}
|
||||||
|
{% if trans_key|trans != trans_key %}
|
||||||
|
{{ ('log.element_edited.changed_fields.'~field) | trans }}
|
||||||
|
<span class="text-muted">({{ field }})</span>
|
||||||
|
{% else %}
|
||||||
|
{{ field }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</td>
|
||||||
|
{% if old_data is not empty %}
|
||||||
|
<td>
|
||||||
|
{% if old_data[field] is defined %}
|
||||||
|
{{ format_log_data(old_data[field], entry, field) }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
|
{% if new_data is not empty %}
|
||||||
|
<td>
|
||||||
|
{% if new_data[field] is defined %}
|
||||||
|
{{ format_log_data(new_data[field], entry, field) }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
{% endmacro %}
|
|
@ -62,6 +62,12 @@
|
||||||
{% set entry = log_entry %}
|
{% set entry = log_entry %}
|
||||||
{% if log_entry is instanceof('App\\Entity\\LogSystem\\DatabaseUpdatedLogEntry') %}
|
{% if log_entry is instanceof('App\\Entity\\LogSystem\\DatabaseUpdatedLogEntry') %}
|
||||||
{% include "log_system/details/_extra_database_updated.html.twig" %}
|
{% include "log_system/details/_extra_database_updated.html.twig" %}
|
||||||
|
{% elseif log_entry is instanceof('App\\Entity\\LogSystem\\ElementCreatedLogEntry') %}
|
||||||
|
{% include "log_system/details/_extra_element_created.html.twig" %}
|
||||||
|
{% elseif log_entry is instanceof('App\\Entity\\LogSystem\\ElementEditedLogEntry') %}
|
||||||
|
{% include "log_system/details/_extra_element_edited.html.twig" %}
|
||||||
|
{% elseif log_entry is instanceof('App\\Entity\\LogSystem\\ElementDeletedLogEntry') %}
|
||||||
|
{% include "log_system/details/_extra_element_deleted.html.twig" %}
|
||||||
{% elseif log_entry is instanceof('App\\Entity\\LogSystem\\UserLoginLogEntry')
|
{% elseif log_entry is instanceof('App\\Entity\\LogSystem\\UserLoginLogEntry')
|
||||||
or log_entry is instanceof('App\\Entity\\LogSystem\\UserLogoutLogEntry') %}
|
or log_entry is instanceof('App\\Entity\\LogSystem\\UserLogoutLogEntry') %}
|
||||||
{% include "log_system/details/_extra_user_login.html.twig" %}
|
{% include "log_system/details/_extra_user_login.html.twig" %}
|
||||||
|
|
|
@ -10960,31 +10960,31 @@ Element 3</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="Qt585vm" name="attachment.max_file_size">
|
<unit id="Qt585vm" name="attachment.max_file_size">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>attachment.max_file_size</source>
|
<source>attachment.max_file_size</source>
|
||||||
<target>Maximum file size</target>
|
<target>Maximum file size</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="tkkbiag" name="user.saml_user">
|
<unit id="tkkbiag" name="user.saml_user">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>user.saml_user</source>
|
<source>user.saml_user</source>
|
||||||
<target>SSO / SAML user</target>
|
<target>SSO / SAML user</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="fhepjKr" name="user.saml_user.pw_change_hint">
|
<unit id="fhepjKr" name="user.saml_user.pw_change_hint">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>user.saml_user.pw_change_hint</source>
|
<source>user.saml_user.pw_change_hint</source>
|
||||||
<target>Your user uses single sign-on (SSO). You can not change the password and 2FA settings here. Configure them on your central SSO provider instead!</target>
|
<target>Your user uses single sign-on (SSO). You can not change the password and 2FA settings here. Configure them on your central SSO provider instead!</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="32beTBH" name="login.sso_saml_login">
|
<unit id="32beTBH" name="login.sso_saml_login">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>login.sso_saml_login</source>
|
<source>login.sso_saml_login</source>
|
||||||
<target>Single Sign-On Login (SSO)</target>
|
<target>Single Sign-On Login (SSO)</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="wnMLanX" name="login.local_login_hint">
|
<unit id="wnMLanX" name="login.local_login_hint">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>login.local_login_hint</source>
|
<source>login.local_login_hint</source>
|
||||||
<target>The form below is only for log in with a local user. If you want to log in via single sign-on, press the button above.</target>
|
<target>The form below is only for log in with a local user. If you want to log in via single sign-on, press the button above.</target>
|
||||||
</segment>
|
</segment>
|
||||||
|
@ -11188,103 +11188,103 @@ Element 3</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="qDVJg2i" name="measurement_unit.new">
|
<unit id="qDVJg2i" name="measurement_unit.new">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>measurement_unit.new</source>
|
<source>measurement_unit.new</source>
|
||||||
<target>New Measurement Unit</target>
|
<target>New Measurement Unit</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="WKzr7h0" name="measurement_unit.edit">
|
<unit id="WKzr7h0" name="measurement_unit.edit">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>measurement_unit.edit</source>
|
<source>measurement_unit.edit</source>
|
||||||
<target>Edit Measurement Unit</target>
|
<target>Edit Measurement Unit</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="gRatnCn" name="user.aboutMe.label">
|
<unit id="gRatnCn" name="user.aboutMe.label">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>user.aboutMe.label</source>
|
<source>user.aboutMe.label</source>
|
||||||
<target>About Me</target>
|
<target>About Me</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="WsHXARp" name="storelocation.owner.label">
|
<unit id="WsHXARp" name="storelocation.owner.label">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>storelocation.owner.label</source>
|
<source>storelocation.owner.label</source>
|
||||||
<target>Owner</target>
|
<target>Owner</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="VQ97Dh0" name="storelocation.part_owner_must_match.label">
|
<unit id="VQ97Dh0" name="storelocation.part_owner_must_match.label">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>storelocation.part_owner_must_match.label</source>
|
<source>storelocation.part_owner_must_match.label</source>
|
||||||
<target>Part Lot owner must match storage location owner</target>
|
<target>Part Lot owner must match storage location owner</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="sddE1L." name="part_lot.owner">
|
<unit id="sddE1L." name="part_lot.owner">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>part_lot.owner</source>
|
<source>part_lot.owner</source>
|
||||||
<target>Owner</target>
|
<target>Owner</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="XKIMr8j" name="part_lot.owner.help">
|
<unit id="XKIMr8j" name="part_lot.owner.help">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>part_lot.owner.help</source>
|
<source>part_lot.owner.help</source>
|
||||||
<target>Only the owner can withdraw or add stock to this lot.</target>
|
<target>Only the owner can withdraw or add stock to this lot.</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="61.yfNy" name="log.element_edited.changed_fields.owner">
|
<unit id="61.yfNy" name="log.element_edited.changed_fields.owner">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>log.element_edited.changed_fields.owner</source>
|
<source>log.element_edited.changed_fields.owner</source>
|
||||||
<target>Owner</target>
|
<target>Owner</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="YkZAIS8" name="log.element_edited.changed_fields.instock_unknown">
|
<unit id="YkZAIS8" name="log.element_edited.changed_fields.instock_unknown">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>log.element_edited.changed_fields.instock_unknown</source>
|
<source>log.element_edited.changed_fields.instock_unknown</source>
|
||||||
<target>Amount unknown</target>
|
<target>Amount unknown</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="xf7NNZ9" name="log.element_edited.changed_fields.needs_refill">
|
<unit id="xf7NNZ9" name="log.element_edited.changed_fields.needs_refill">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>log.element_edited.changed_fields.needs_refill</source>
|
<source>log.element_edited.changed_fields.needs_refill</source>
|
||||||
<target>Refill needed</target>
|
<target>Refill needed</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="Gfw_MWL" name="part.withdraw.access_denied">
|
<unit id="Gfw_MWL" name="part.withdraw.access_denied">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>part.withdraw.access_denied</source>
|
<source>part.withdraw.access_denied</source>
|
||||||
<target>Not allowed to do the desired action. Please check your permissions and the owner of the part lots.</target>
|
<target>Not allowed to do the desired action. Please check your permissions and the owner of the part lots.</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="Dwo4KWP" name="part.info.amount.less_than_desired">
|
<unit id="Dwo4KWP" name="part.info.amount.less_than_desired">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>part.info.amount.less_than_desired</source>
|
<source>part.info.amount.less_than_desired</source>
|
||||||
<target>Less than desired</target>
|
<target>Less than desired</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="cdnsW4q" name="log.cli_user">
|
<unit id="cdnsW4q" name="log.cli_user">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>log.cli_user</source>
|
<source>log.cli_user</source>
|
||||||
<target>CLI user</target>
|
<target>CLI user</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="4GTAJ9E" name="log.element_edited.changed_fields.part_owner_must_match">
|
<unit id="4GTAJ9E" name="log.element_edited.changed_fields.part_owner_must_match">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>log.element_edited.changed_fields.part_owner_must_match</source>
|
<source>log.element_edited.changed_fields.part_owner_must_match</source>
|
||||||
<target>Part owner must match storage location owner</target>
|
<target>Part owner must match storage location owner</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="u6qFa_j" name="part.filter.lessThanDesired">
|
<unit id="u6qFa_j" name="part.filter.lessThanDesired">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>part.filter.lessThanDesired</source>
|
<source>part.filter.lessThanDesired</source>
|
||||||
<target>In stock less than desired (total amount < min. amount)</target>
|
<target><![CDATA[In stock less than desired (total amount < min. amount)]]></target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="lHTN.a1" name="part.filter.lotOwner">
|
<unit id="lHTN.a1" name="part.filter.lotOwner">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>part.filter.lotOwner</source>
|
<source>part.filter.lotOwner</source>
|
||||||
<target>Lot owner</target>
|
<target>Lot owner</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="47OCK_W" name="user.show_email_on_profile.label">
|
<unit id="47OCK_W" name="user.show_email_on_profile.label">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>user.show_email_on_profile.label</source>
|
<source>user.show_email_on_profile.label</source>
|
||||||
<target>Show email on public profile page</target>
|
<target>Show email on public profile page</target>
|
||||||
</segment>
|
</segment>
|
||||||
|
@ -11319,5 +11319,23 @@ Element 3</target>
|
||||||
<target>The request was blocked. No action should be required.</target>
|
<target>The request was blocked. No action should be required.</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
|
<unit id="JVE17kW" name="log.no_comment">
|
||||||
|
<segment>
|
||||||
|
<source>log.no_comment</source>
|
||||||
|
<target>No comment</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="5xvPvME" name="log.element_changed.field">
|
||||||
|
<segment>
|
||||||
|
<source>log.element_changed.field</source>
|
||||||
|
<target>Field</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="vufWYhV" name="log.element_changed.data_before">
|
||||||
|
<segment>
|
||||||
|
<source>log.element_changed.data_before</source>
|
||||||
|
<target>Data before change</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="Dpb9AmY" name="saml.error.cannot_login_local_user_per_saml">
|
<unit id="Dpb9AmY" name="saml.error.cannot_login_local_user_per_saml">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>saml.error.cannot_login_local_user_per_saml</source>
|
<source>saml.error.cannot_login_local_user_per_saml</source>
|
||||||
<target>You can not login as local user via SSO! Use your local user password instead.</target>
|
<target>You can not login as local user via SSO! Use your local user password instead.</target>
|
||||||
</segment>
|
</segment>
|
||||||
|
|
|
@ -300,19 +300,19 @@
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="m8kMFhf" name="validator.attachment.name_not_blank">
|
<unit id="m8kMFhf" name="validator.attachment.name_not_blank">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>validator.attachment.name_not_blank</source>
|
<source>validator.attachment.name_not_blank</source>
|
||||||
<target>Set a value here, or upload a file to automatically use its filename as name for the attachment.</target>
|
<target>Set a value here, or upload a file to automatically use its filename as name for the attachment.</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="nwGaNBW" name="validator.part_lot.owner_must_match_storage_location_owner">
|
<unit id="nwGaNBW" name="validator.part_lot.owner_must_match_storage_location_owner">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>validator.part_lot.owner_must_match_storage_location_owner</source>
|
<source>validator.part_lot.owner_must_match_storage_location_owner</source>
|
||||||
<target>The owner of this lot must match the owner of the selected storage location (%owner_name%)!</target>
|
<target>The owner of this lot must match the owner of the selected storage location (%owner_name%)!</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="HXSz3nQ" name="validator.part_lot.owner_must_not_be_anonymous">
|
<unit id="HXSz3nQ" name="validator.part_lot.owner_must_not_be_anonymous">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>validator.part_lot.owner_must_not_be_anonymous</source>
|
<source>validator.part_lot.owner_must_not_be_anonymous</source>
|
||||||
<target>A lot owner must not be the anonymous user!</target>
|
<target>A lot owner must not be the anonymous user!</target>
|
||||||
</segment>
|
</segment>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue