Completed
Pull Request — master (#1749)
by Damian
02:35
created

CMSPageHistoryController::transformReadonly()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 7
nc 3
nop 1
1
<?php
2
3
namespace SilverStripe\CMS\Controllers;
4
5
use SilverStripe\CMS\Model\SiteTree;
6
use SilverStripe\Control\Controller;
7
use SilverStripe\Control\HTTPRequest;
8
use SilverStripe\Control\HTTPResponse;
9
use SilverStripe\Forms\CheckboxField;
10
use SilverStripe\Forms\CompositeField;
11
use SilverStripe\Forms\FieldList;
12
use SilverStripe\Forms\Form;
13
use SilverStripe\Forms\FormAction;
14
use SilverStripe\Forms\HiddenField;
15
use SilverStripe\Forms\HTMLReadonlyField;
16
use SilverStripe\Forms\LiteralField;
17
use SilverStripe\ORM\FieldType\DBField;
18
use SilverStripe\ORM\FieldType\DBHTMLText;
19
use SilverStripe\ORM\Versioning\Versioned;
20
use SilverStripe\Security\Security;
21
use SilverStripe\View\ArrayData;
22
use SilverStripe\View\ViewableData;
23
24
class CMSPageHistoryController extends CMSMain
25
{
26
27
    private static $url_segment = 'pages/history';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
28
29
    private static $url_rule = '/$Action/$ID/$VersionID/$OtherVersionID';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
30
31
    private static $url_priority = 42;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
32
33
    private static $menu_title = 'History';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
34
35
    private static $required_permission_codes = 'CMS_ACCESS_CMSMain';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
36
37
    private static $allowed_actions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
38
        'VersionsForm',
39
        'CompareVersionsForm',
40
        'show',
41
        'compare'
42
    );
43
44
    private static $url_handlers = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
45
        '$Action/$ID/$VersionID/$OtherVersionID' => 'handleAction'
46
    );
47
48
    public function getResponseNegotiator()
49
    {
50
        $negotiator = parent::getResponseNegotiator();
51
        $controller = $this;
52
        $negotiator->setCallback('CurrentForm', function () use (&$controller) {
53
            $form = $controller->ShowVersionForm($controller->getRequest()->param('VersionID'));
54
            if ($form) {
55
                return $form->forTemplate();
56
            } else {
57
                return $controller->renderWith($controller->getTemplatesWithSuffix('_Content'));
58
            }
59
        });
60
        $negotiator->setCallback('default', function () use (&$controller) {
61
            return $controller->renderWith($controller->getViewer('show'));
62
        });
63
        return $negotiator;
64
    }
65
66
    /**
67
     * @param HTTPRequest $request
68
     * @return array
69
     */
70 View Code Duplication
    public function show($request)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
71
    {
72
        $form = $this->ShowVersionForm($request->param('VersionID'));
73
74
        $negotiator = $this->getResponseNegotiator();
75
        $controller = $this;
76
        $negotiator->setCallback('CurrentForm', function () use (&$controller, &$form) {
77
            return $form ? $form->forTemplate() : $controller->renderWith($controller->getTemplatesWithSuffix('_Content'));
78
        });
79
        $negotiator->setCallback('default', function () use (&$controller, &$form) {
80
            return $controller->customise(array('EditForm' => $form))->renderWith($controller->getViewer('show'));
81
        });
82
83
        return $negotiator->respond($request);
84
    }
85
86
    /**
87
     * @param HTTPRequest $request
88
     * @return array
89
     */
90 View Code Duplication
    public function compare($request)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
91
    {
92
        $form = $this->CompareVersionsForm(
93
            $request->param('VersionID'),
94
            $request->param('OtherVersionID')
95
        );
96
97
        $negotiator = $this->getResponseNegotiator();
98
        $controller = $this;
99
        $negotiator->setCallback('CurrentForm', function () use (&$controller, &$form) {
100
            return $form ? $form->forTemplate() : $controller->renderWith($controller->getTemplatesWithSuffix('_Content'));
101
        });
102
        $negotiator->setCallback('default', function () use (&$controller, &$form) {
103
            return $controller->customise(array('EditForm' => $form))->renderWith($controller->getViewer('show'));
104
        });
105
106
        return $negotiator->respond($request);
107
    }
108
109
    public function getSilverStripeNavigator()
110
    {
111
        $record = $this->getRecord($this->currentPageID(), $this->getRequest()->param('VersionID'));
112
        if ($record) {
113
            $navigator = new SilverStripeNavigator($record);
114
            return $navigator->renderWith($this->getTemplatesWithSuffix('_SilverStripeNavigator'));
115
        } else {
116
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method SilverStripe\Admin\LeftA...etSilverStripeNavigator of type SilverStripe\ORM\FieldType\DBHTMLText.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
117
        }
118
    }
119
120
    /**
121
     * Returns the read only version of the edit form. Detaches all {@link FormAction}
122
     * instances attached since only action relates to revert.
123
     *
124
     * Permission checking is done at the {@link CMSMain::getEditForm()} level.
125
     *
126
     * @param int $id ID of the record to show
127
     * @param array $fields optional
128
     * @param int $versionID
129
     * @param int $compareID Compare mode
130
     *
131
     * @return Form
132
     */
133
    public function getEditForm($id = null, $fields = null, $versionID = null, $compareID = null)
134
    {
135
        if (!$id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
136
            $id = $this->currentPageID();
137
        }
138
139
        $record = $this->getRecord($id, $versionID);
140
        $versionID = ($record) ? $record->Version : $versionID;
0 ignored issues
show
Bug introduced by
The property Version does not seem to exist. Did you mean versioning?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
141
142
        $form = parent::getEditForm($record, ($record) ? $record->getCMSFields() : null);
0 ignored issues
show
Documentation introduced by
$record is of type object<SilverStripe\CMS\Model\SiteTree>, but the function expects a integer|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
143
        // Respect permission failures from parent implementation
144
        if (!($form instanceof Form)) {
145
            return $form;
146
        }
147
148
        // TODO: move to the SilverStripeNavigator structure so the new preview can pick it up.
149
        //$nav = new SilverStripeNavigatorItem_ArchiveLink($record);
150
151
        $form->setActions(new FieldList(
152
            $revert = FormAction::create('doRollback', _t('CMSPageHistoryController.REVERTTOTHISVERSION', 'Revert to this version'))->setUseButtonTag(true)
153
        ));
154
155
        $fields = $form->Fields();
156
        $fields->removeByName("Status");
157
        $fields->push(new HiddenField("ID"));
158
        $fields->push(new HiddenField("Version"));
159
160
        $fields = $fields->makeReadonly();
161
162
        if ($compareID) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $compareID of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
163
            $link = Controller::join_links(
164
                $this->Link('show'),
165
                $id
166
            );
167
168
            $view = _t('CMSPageHistoryController.VIEW', "view");
169
170
            $message = _t(
171
                'CMSPageHistoryController.COMPARINGVERSION',
172
                "Comparing versions {version1} and {version2}.",
173
                array(
174
                    'version1' => sprintf('%s (<a href="%s">%s</a>)', $versionID, Controller::join_links($link, $versionID), $view),
175
                    'version2' => sprintf('%s (<a href="%s">%s</a>)', $compareID, Controller::join_links($link, $compareID), $view)
176
                )
177
            );
178
179
            $revert->setReadonly(true);
180
        } else {
181
            if ($record->isLatestVersion()) {
0 ignored issues
show
Documentation Bug introduced by
The method isLatestVersion does not exist on object<SilverStripe\CMS\Model\SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
182
                $message = _t('CMSPageHistoryController.VIEWINGLATEST', 'Currently viewing the latest version.');
183
            } else {
184
                $message = _t(
185
                    'CMSPageHistoryController.VIEWINGVERSION',
186
                    "Currently viewing version {version}.",
187
                    array('version' => $versionID)
188
                );
189
            }
190
        }
191
192
        $fields->fieldByName('Root.Main')->unshift(
193
            new LiteralField('CurrentlyViewingMessage', ArrayData::create(array(
194
                'Content' => DBField::create_field('HTMLFragment', $message),
195
                'Classes' => 'notice'
196
            ))->renderWith($this->getTemplatesWithSuffix('_notice')))
197
        );
198
199
        $form->setFields($fields->makeReadonly());
200
        $form->loadDataFrom(array(
201
            "ID" => $id,
202
            "Version" => $versionID,
203
        ));
204
205
        if (($record && $record->isLatestVersion())) {
0 ignored issues
show
Documentation Bug introduced by
The method isLatestVersion does not exist on object<SilverStripe\CMS\Model\SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
206
            $revert->setReadonly(true);
207
        }
208
209
        $form->removeExtraClass('cms-content');
210
211
        return $form;
212
    }
213
214
215
    /**
216
     * Version select form. Main interface between selecting versions to view
217
     * and comparing multiple versions.
218
     *
219
     * Because we can reload the page directly to a compare view (history/compare/1/2/3)
220
     * this form has to adapt to those parameters as well.
221
     *
222
     * @return Form
223
     */
224
    public function VersionsForm()
225
    {
226
        $id = $this->currentPageID();
227
        $page = $this->getRecord($id);
228
        $versionsHtml = '';
229
230
        $action = $this->getRequest()->param('Action');
231
        $versionID = $this->getRequest()->param('VersionID');
232
        $otherVersionID = $this->getRequest()->param('OtherVersionID');
233
234
        $showUnpublishedChecked = 0;
235
        $compareModeChecked = ($action == "compare");
236
237
        if ($page) {
238
            $versions = $page->allVersions();
0 ignored issues
show
Documentation Bug introduced by
The method allVersions does not exist on object<SilverStripe\CMS\Model\SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
239
            $versionID = (!$versionID) ? $page->Version : $versionID;
0 ignored issues
show
Bug introduced by
The property Version does not seem to exist. Did you mean versioning?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
240
241
            if ($versions) {
242
                foreach ($versions as $k => $version) {
243
                    $active = false;
244
245
                    if ($version->Version == $versionID || $version->Version == $otherVersionID) {
246
                        $active = true;
247
248
                        if (!$version->WasPublished) {
249
                            $showUnpublishedChecked = 1;
250
                        }
251
                    }
252
253
                    $version->Active = ($active);
254
                }
255
            }
256
257
            $vd = new ViewableData();
258
259
            $versionsHtml = $vd->customise(array(
260
                'Versions' => $versions
261
            ))->renderWith($this->getTemplatesWithSuffix('_versions'));
262
        }
263
264
        $fields = new FieldList(
265
            new CheckboxField(
266
                'ShowUnpublished',
267
                _t('CMSPageHistoryController.SHOWUNPUBLISHED', 'Show unpublished versions'),
268
                $showUnpublishedChecked
269
            ),
270
            new CheckboxField(
271
                'CompareMode',
272
                _t('CMSPageHistoryController.COMPAREMODE', 'Compare mode (select two)'),
273
                $compareModeChecked
274
            ),
275
            new LiteralField('VersionsHtml', $versionsHtml),
0 ignored issues
show
Bug introduced by
It seems like $versionsHtml defined by $vd->customise(array('Ve...ithSuffix('_versions')) on line 259 can also be of type object<SilverStripe\ORM\FieldType\DBHTMLText>; however, SilverStripe\Forms\LiteralField::__construct() does only seem to accept string|object<SilverStripe\Forms\FormField>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
276
            $hiddenID = new HiddenField('ID', false, "")
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a null|string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
277
        );
278
279
        $actions = new FieldList(
280
            new FormAction(
281
                'doCompare',
282
                _t('CMSPageHistoryController.COMPAREVERSIONS', 'Compare Versions')
283
            ),
284
            new FormAction(
285
                'doShowVersion',
286
                _t('CMSPageHistoryController.SHOWVERSION', 'Show Version')
287
            )
288
        );
289
290
        // Use <button> to allow full jQuery UI styling
291
        foreach ($actions->dataFields() as $action) {
292
            /** @var FormAction $action */
293
            $action->setUseButtonTag(true);
294
        }
295
296
        $form = Form::create(
297
            $this,
298
            'VersionsForm',
299
            $fields,
300
            $actions
301
        )->setHTMLID('Form_VersionsForm');
302
        $form->loadDataFrom($this->getRequest()->requestVars());
303
        $hiddenID->setValue($id);
304
        $form->unsetValidator();
305
306
        $form
307
            ->addExtraClass('cms-versions-form') // placeholder, necessary for $.metadata() to work
308
            ->setAttribute('data-link-tmpl-compare', Controller::join_links($this->Link('compare'), '%s', '%s', '%s'))
309
            ->setAttribute('data-link-tmpl-show', Controller::join_links($this->Link('show'), '%s', '%s'));
310
311
        return $form;
312
    }
313
314
    /**
315
     * Process the {@link VersionsForm} compare function between two pages.
316
     *
317
     * @param array $data
318
     * @param Form $form
319
     * @return HTTPResponse|DBHTMLText
320
     */
321
    public function doCompare($data, $form)
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
322
    {
323
        $versions = $data['Versions'];
324
        if (count($versions) < 2) {
325
            return null;
326
        }
327
328
        $version1 = array_shift($versions);
329
        $version2 = array_shift($versions);
330
331
        $form = $this->CompareVersionsForm($version1, $version2);
332
333
        // javascript solution, render into template
334 View Code Duplication
        if ($this->getRequest()->isAjax()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
335
            return $this->customise(array(
336
                "EditForm" => $form
337
            ))->renderWith(array(
338
                static::class . '_EditForm',
339
                'LeftAndMain_Content'
340
            ));
341
        }
342
343
        // non javascript, redirect the user to the page
344
        return $this->redirect(Controller::join_links(
345
            $this->Link('compare'),
346
            $version1,
347
            $version2
348
        ));
349
    }
350
351
    /**
352
     * Process the {@link VersionsForm} show version function. Only requires
353
     * one page to be selected.
354
     *
355
     * @param array
356
     * @param Form
357
     *
358
     * @return DBHTMLText|HTTPResponse
359
     */
360
    public function doShowVersion($data, $form)
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
361
    {
362
        $versionID = null;
363
364
        if (isset($data['Versions']) && is_array($data['Versions'])) {
365
            $versionID  = array_shift($data['Versions']);
366
        }
367
368
        if (!$versionID) {
369
            return null;
370
        }
371
372
        $request = $this->getRequest();
373 View Code Duplication
        if ($request->isAjax()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
374
            return $this->customise(array(
375
                "EditForm" => $this->ShowVersionForm($versionID)
376
            ))->renderWith(array(
377
                static::class . '_EditForm',
378
                'LeftAndMain_Content'
379
            ));
380
        }
381
382
        // non javascript, redirect the user to the page
383
        return $this->redirect(Controller::join_links(
384
            $this->Link('version'),
385
            $versionID
386
        ));
387
    }
388
389
    /**
390
     * @param int|null $versionID
391
     * @return Form
392
     */
393
    public function ShowVersionForm($versionID = null)
394
    {
395
        if (!$versionID) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $versionID of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
396
            return null;
397
        }
398
399
        $id = $this->currentPageID();
400
        $form = $this->getEditForm($id, null, $versionID);
401
402
        return $form;
403
    }
404
405
    /**
406
     * @param int $versionID
407
     * @param int $otherVersionID
408
     * @return mixed
409
     */
410
    public function CompareVersionsForm($versionID, $otherVersionID)
411
    {
412
        if ($versionID > $otherVersionID) {
413
            $toVersion = $versionID;
414
            $fromVersion = $otherVersionID;
415
        } else {
416
            $toVersion = $otherVersionID;
417
            $fromVersion = $versionID;
418
        }
419
420
        if (!$toVersion || !$fromVersion) {
421
            return null;
422
        }
423
424
        $id = $this->currentPageID();
425
        /** @var SiteTree $page */
426
        $page = SiteTree::get()->byID($id);
427
428
        $record = null;
429
        if ($page && $page->exists()) {
430
            if (!$page->canView()) {
431
                return Security::permissionFailure($this);
432
            }
433
434
            $record = $page->compareVersions($fromVersion, $toVersion);
0 ignored issues
show
Documentation Bug introduced by
The method compareVersions does not exist on object<SilverStripe\CMS\Model\SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
435
        }
436
437
        $fromVersionRecord = Versioned::get_version('SilverStripe\\CMS\\Model\\SiteTree', $id, $fromVersion);
438
        $toVersionRecord = Versioned::get_version('SilverStripe\\CMS\\Model\\SiteTree', $id, $toVersion);
439
440
        if (!$fromVersionRecord) {
441
            user_error("Can't find version $fromVersion of page $id", E_USER_ERROR);
442
        }
443
444
        if (!$toVersionRecord) {
445
            user_error("Can't find version $toVersion of page $id", E_USER_ERROR);
446
        }
447
448
        if (!$record) {
449
            return null;
450
        }
451
        $form = $this->getEditForm($id, null, $fromVersion, $toVersion);
452
        $form->setActions(new FieldList());
453
        $form->addExtraClass('compare');
454
455
        $form->loadDataFrom($record);
456
        $form->loadDataFrom(array(
457
            "ID" => $id,
458
            "Version" => $fromVersion,
459
        ));
460
461
        // Comparison views shouldn't be editable.
462
        // As the comparison output is HTML and not valid values for the various field types
463
        $readonlyFields = $this->transformReadonly($form->Fields());
464
        $form->setFields($readonlyFields);
465
466
        return $form;
467
    }
468
469
    public function Breadcrumbs($unlinked = false)
470
    {
471
        $crumbs = parent::Breadcrumbs($unlinked);
472
        $crumbs[0]->Title = _t('CMSPagesController.MENUTITLE', 'Pages');
473
        return $crumbs;
474
    }
475
476
    /**
477
     * Replace all data fields with HTML readonly fields to display diff
478
     *
479
     * @param FieldList $fields
480
     * @return FieldList
481
     */
482
    public function transformReadonly(FieldList $fields)
483
    {
484
        foreach ($fields->dataFields() as $field) {
485
            if ($field instanceof HiddenField) {
486
                continue;
487
            }
488
            $newField = $field->castedCopy(HTMLReadonlyField::class);
489
            $fields->replaceField($field->getName(), $newField);
490
        }
491
        return $fields;
492
    }
493
}
494