Issues (2)

Classes/ViewHelpers/DiffViewHelper.php (1 issue)

1
<?php
2
namespace AE\History\ViewHelpers;
3
4
use Neos\ContentRepository\Domain\Model\NodeType;
5
use Neos\ContentRepository\Domain\Service\NodeTypeManager;
6
use Neos\Diff\Diff;
7
use Neos\Diff\Renderer\Html\HtmlArrayRenderer;
8
use Neos\Flow\Annotations as Flow;
9
use Neos\FluidAdaptor\Core\ViewHelper\AbstractViewHelper;
10
use Neos\FluidAdaptor\Core\ViewHelper\Exception as ViewHelperException;
11
use Neos\Media\Domain\Model\AssetInterface;
12
use Neos\Media\Domain\Model\ImageInterface;
13
use Neos\Neos\EventLog\Domain\Model\NodeEvent;
14
15
/**
16
 * Renders the difference between the original and the changed content of the given node and returns it, along with meta
17
 * information, in an array.
18
 */
19
class DiffViewHelper extends AbstractViewHelper
20
{
21
    /**
22
     * @var boolean
23
     */
24
    protected $escapeOutput = false;
25
26
    /**
27
     * @Flow\Inject
28
     * @var NodeTypeManager
29
     */
30
    protected $nodeTypeManager;
31
32
    /**
33
     * @return void
34
     *
35
     * @throws ViewHelperException
36
     */
37
    public function initializeArguments() : void
38
    {
39
        parent::initializeArguments();
40
41
        $this->registerArgument('nodeEvent', NodeEvent::class, 'The NodeEvent to extract the diff from.', true);
42
    }
43
44
    /**
45
     * @return string
46
     * @throws \Neos\ContentRepository\Exception\NodeTypeNotFoundException
47
     */
48
    public function render() : string
49
    {
50
        $data = $this->arguments['nodeEvent']->getData();
51
        $old = $data['old'];
52
        $new = $data['new'];
53
        $nodeType = $this->nodeTypeManager->getNodeType($data['nodeType']);
54
        $changeNodePropertiesDefaults = $nodeType->getDefaultValuesForProperties();
55
56
        $renderer = new HtmlArrayRenderer();
57
        $changes = [];
58
        foreach ($new as $propertyName => $changedPropertyValue) {
59
            if (($old === null && empty($changedPropertyValue))
60
                || (isset($changeNodePropertiesDefaults[$propertyName])
61
                    && $changedPropertyValue === $changeNodePropertiesDefaults[$propertyName]
62
                )
63
            ) {
64
                continue;
65
            }
66
67
            $originalPropertyValue = ($old === null ? null : $old[$propertyName]);
68
69
            if (!is_object($originalPropertyValue) && !is_object($changedPropertyValue)) {
70
                $originalSlimmedDownContent = $this->renderSlimmedDownContent($originalPropertyValue);
71
                $changedSlimmedDownContent = $this->renderSlimmedDownContent($changedPropertyValue);
72
73
                $diff = new Diff(
74
                    explode("\n", $originalSlimmedDownContent),
75
                    explode("\n", $changedSlimmedDownContent),
76
                    ['context' => 1]
77
                );
78
                /** @var array $diffArray */
79
                $diffArray = $diff->render($renderer);
80
                $this->postProcessDiffArray($diffArray);
81
                if ($diffArray !== []) {
82
                    $changes[$propertyName] = [
83
                        'diff' => $diffArray,
84
                        'propertyLabel' => $this->getPropertyLabel($propertyName, $nodeType),
85
                        'type' => 'text',
86
                    ];
87
                }
88
            } elseif ($originalPropertyValue instanceof ImageInterface
89
                || $changedPropertyValue instanceof ImageInterface
90
            ) {
91
                $changes[$propertyName] = [
92
                    'changed' => $changedPropertyValue,
93
                    'original' => $originalPropertyValue,
94
                    'propertyLabel' => $this->getPropertyLabel($propertyName, $nodeType),
95
                    'type' => 'image',
96
                ];
97
            } elseif ($originalPropertyValue instanceof AssetInterface
98
                || $changedPropertyValue instanceof AssetInterface
99
            ) {
100
                $changes[$propertyName] = [
101
                    'changed' => $changedPropertyValue,
102
                    'original' => $originalPropertyValue,
103
                    'propertyLabel' => $this->getPropertyLabel($propertyName, $nodeType),
104
                    'type' => 'asset',
105
                ];
106
            } elseif ($originalPropertyValue instanceof \DateTimeInterface
107
                && $changedPropertyValue instanceof \DateTimeInterface
108
            ) {
109
                if ($changedPropertyValue->getTimestamp() !== $originalPropertyValue->getTimestamp()) {
110
                    $changes[$propertyName] = [
111
                        'changed' => $changedPropertyValue,
112
                        'original' => $originalPropertyValue,
113
                        'propertyLabel' => $this->getPropertyLabel($propertyName, $nodeType),
114
                        'type' => 'datetime',
115
                    ];
116
                }
117
            }
118
        }
119
        $this->templateVariableContainer->add('changes', $changes);
120
        $content = $this->renderChildren();
121
        $this->templateVariableContainer->remove('changes');
122
        return $content;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $content could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
123
    }
124
125
    /**
126
     * Tries to determine a label for the specified property
127
     *
128
     * @param string $propertyName
129
     * @param NodeType $nodeType
130
     *
131
     * @return string
132
     */
133
    protected function getPropertyLabel(string $propertyName, NodeType $nodeType) : string
134
    {
135
        return $nodeType->getProperties()[$propertyName]['ui']['label'] ?? $propertyName;
136
    }
137
138
    /**
139
     * Renders a slimmed down representation of a property of the given node. The output will be HTML, but does not
140
     * contain any markup from the original content.
141
     *
142
     * Note: It's clear that this method needs to be extracted and moved to a more universal service at some point.
143
     * However, since we only implemented diff-view support for this particular controller at the moment, it stays
144
     * here for the time being. Once we start displaying diffs elsewhere, we should refactor the diff rendering part.
145
     *
146
     * @param mixed $propertyValue
147
     *
148
     * @return string
149
     */
150
    protected function renderSlimmedDownContent($propertyValue) : string
151
    {
152
        $content = '';
153
        if (is_string($propertyValue)) {
154
            $contentSnippet = str_replace('&nbsp;', ' ', $propertyValue);
155
            $contentSnippet = preg_replace('/<br[^>]*>/', "\n", $contentSnippet);
156
            $contentSnippet = preg_replace(['/<[^>]*>/', '/ {2,}/'], ' ', $contentSnippet);
157
            $content = trim($contentSnippet);
158
        }
159
        return $content;
160
    }
161
162
    /**
163
     * A workaround for some missing functionality in the Diff Renderer:
164
     *
165
     * This method will check if content in the given diff array is either completely new or has been completely
166
     * removed and wraps the respective part in <ins> or <del> tags, because the Diff Renderer currently does not
167
     * do that in these cases.
168
     *
169
     * @param array $diffArray
170
     *
171
     * @return void
172
     */
173
    protected function postProcessDiffArray(array &$diffArray)
174
    {
175
        foreach ($diffArray as $index => $blocks) {
176
            foreach ($blocks as $blockIndex => $block) {
177
                $baseLines = trim(implode('', $block['base']['lines']), " \t\n\r\0\xC2\xA0");
178
                $changedLines = trim(implode('', $block['changed']['lines']), " \t\n\r\0\xC2\xA0");
179
                if ($baseLines === '') {
180
                    foreach ($block['changed']['lines'] as $lineIndex => $line) {
181
                        $diffArray[$index][$blockIndex]['changed']['lines'][$lineIndex] = '<ins>' . $line . '</ins>';
182
                    }
183
                }
184
                if ($changedLines === '') {
185
                    foreach ($block['base']['lines'] as $lineIndex => $line) {
186
                        $diffArray[$index][$blockIndex]['base']['lines'][$lineIndex] = '<del>' . $line . '</del>';
187
                    }
188
                }
189
            }
190
        }
191
    }
192
}
193