Passed
Push — master ( b101db...a45b24 )
by Robbie
05:17
created

HistoryViewerController::generateSchemaForForm()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 20
nc 3
nop 2
dl 0
loc 29
rs 9.6
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\VersionedAdmin\Controllers;
4
5
use InvalidArgumentException;
6
use SilverStripe\Admin\LeftAndMain;
7
use SilverStripe\Admin\LeftAndMainFormRequestHandler;
8
use SilverStripe\Control\HTTPRequest;
9
use SilverStripe\Control\HTTPResponse;
10
use SilverStripe\Core\Injector\Injector;
11
use SilverStripe\Forms\Form;
12
use SilverStripe\Forms\FormFactory;
13
use SilverStripe\ORM\DataObject;
14
use SilverStripe\Versioned\Versioned;
15
use SilverStripe\VersionedAdmin\Forms\DataObjectVersionFormFactory;
16
use SilverStripe\VersionedAdmin\Forms\DiffTransformation;
17
18
/**
19
 * The HistoryViewerController provides AJAX endpoints for React to enable functionality, such as retrieving the form
20
 * schema.
21
 */
22
class HistoryViewerController extends LeftAndMain
23
{
24
    /**
25
     * @var string
26
     */
27
    const FORM_NAME_VERSION = 'versionForm';
28
29
    /**
30
     * @var string
31
     */
32
    const FORM_NAME_COMPARE = 'compareForm';
33
34
    private static $url_segment = 'historyviewer';
0 ignored issues
show
introduced by
The private property $url_segment is not used, and could be removed.
Loading history...
35
36
    private static $url_rule = '/$Action';
0 ignored issues
show
introduced by
The private property $url_rule is not used, and could be removed.
Loading history...
37
38
    private static $url_priority = 10;
0 ignored issues
show
introduced by
The private property $url_priority is not used, and could be removed.
Loading history...
39
40
    private static $required_permission_codes = 'CMS_ACCESS_CMSMain';
0 ignored issues
show
introduced by
The private property $required_permission_codes is not used, and could be removed.
Loading history...
41
42
    private static $allowed_actions = [
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
43
        self::FORM_NAME_VERSION,
44
        self::FORM_NAME_COMPARE,
45
        'schema',
46
    ];
47
48
    /**
49
     * An array of supported form names that can be requested through the schema
50
     *
51
     * @var string[]
52
     */
53
    protected $formNames = [self::FORM_NAME_VERSION, self::FORM_NAME_COMPARE];
54
55
    public function getClientConfig()
56
    {
57
        $clientConfig = parent::getClientConfig();
58
59
        foreach ($this->formNames as $formName) {
60
            $clientConfig['form'][$formName] = [
61
                'schemaUrl' => $this->Link('schema/' . $formName),
62
            ];
63
        }
64
65
        return $clientConfig;
66
    }
67
68
    /**
69
     * Gets a JSON schema representing the current version detail form.
70
     *
71
     * WARNING: Experimental API.
72
     * @internal
73
     * @param HTTPRequest $request
74
     * @return HTTPResponse
75
     */
76
    public function schema($request)
77
    {
78
        $formName = $request->param('FormName');
79
        if (!in_array($formName, $this->formNames)) {
80
            return parent::schema($request);
81
        }
82
83
        return $this->generateSchemaForForm($formName, $request);
84
    }
85
86
    /**
87
     * Checks the requested schema name and returns a scaffolded {@link Form}. An exception is thrown
88
     * if an unexpected value is provided.
89
     *
90
     * @param string $formName
91
     * @param HTTPRequest $request
92
     * @return HTTPResponse
93
     * @throws InvalidArgumentException
94
     */
95
    protected function generateSchemaForForm($formName, HTTPRequest $request)
96
    {
97
        switch ($formName) {
98
            // Get schema for history form
99
            case self::FORM_NAME_VERSION:
100
                $form = $this->getVersionForm([
101
                    'RecordClass' => $request->getVar('RecordClass'),
102
                    'RecordID' => $request->getVar('RecordID'),
103
                    'RecordVersion' => $request->getVar('RecordVersion'),
104
                ]);
105
                break;
106
            case self::FORM_NAME_COMPARE:
107
                $form = $this->getCompareForm([
108
                    'RecordClass' => $request->getVar('RecordClass'),
109
                    'RecordID' => $request->getVar('RecordID'),
110
                    'RecordVersionFrom' => $request->getVar('RecordVersionFrom'),
111
                    'RecordVersionTo' => $request->getVar('RecordVersionTo'),
112
                ]);
113
                break;
114
            default:
115
                throw new InvalidArgumentException('Invalid form name passed to generate schema: ' . $formName);
116
        }
117
118
        // Respond with this schema
119
        $response = $this->getResponse();
120
        $response->addHeader('Content-Type', 'application/json');
121
        $schemaID = $this->getRequest()->getURL();
122
123
        return $this->getSchemaResponse($schemaID, $form);
124
    }
125
126
    /**
127
     * Returns a {@link Form} showing the version details for a given version of a record
128
     *
129
     * @param array $context
130
     * @return Form
131
     */
132
    public function getVersionForm(array $context)
133
    {
134
        $this->validateInput($context, ['RecordClass', 'RecordID', 'RecordVersion']);
135
136
        $recordClass = $context['RecordClass'];
137
        $recordId = $context['RecordID'];
138
        $recordVersion = $context['RecordVersion'];
139
140
        // Load record and perform a canView check
141
        $record = $this->getRecordVersion($recordClass, $recordId, $recordVersion);
142
        if (!$record) {
0 ignored issues
show
introduced by
$record is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
143
            return null;
144
        }
145
146
        $effectiveContext = array_merge($context, ['Record' => $record]);
147
        return $this->scaffoldForm(self::FORM_NAME_VERSION, $effectiveContext, [
148
            $recordClass,
149
            $recordId,
150
            $recordVersion
151
        ]);
152
    }
153
154
    /**
155
     * Fetches record version and checks canView permission for result
156
     *
157
     * @param string $recordClass
158
     * @param int $recordId
159
     * @param int $recordVersion
160
     * @return DataObject|null
161
     */
162
    protected function getRecordVersion($recordClass, $recordId, $recordVersion)
163
    {
164
        $record = Versioned::get_version($recordClass, $recordId, $recordVersion);
165
166
        if (!$record) {
0 ignored issues
show
introduced by
$record is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
167
            $this->jsonError(404);
168
            return null;
169
        }
170
171
        if (!$record->canView()) {
172
            $this->jsonError(403, _t(
173
                __CLASS__.'.ErrorItemViewPermissionDenied',
174
                "You don't have the necessary permissions to view {ObjectTitle}",
175
                ['ObjectTitle' => $record->i18n_singular_name()]
176
            ));
177
            return null;
178
        }
179
180
        return $record;
181
    }
182
183
    /**
184
     * Returns a {@link Form} containing the comparison {@link DiffTransformation} view for a record
185
     * between two specified versions.
186
     *
187
     * @param array $context
188
     * @return Form
189
     */
190
    public function getCompareForm(array $context)
191
    {
192
        $this->validateInput($context, ['RecordClass', 'RecordID', 'RecordVersionFrom', 'RecordVersionTo']);
193
194
        $recordClass = $context['RecordClass'];
195
        $recordId = $context['RecordID'];
196
        $recordVersionFrom = $context['RecordVersionFrom'];
197
        $recordVersionTo = $context['RecordVersionTo'];
198
199
        // Load record and perform a canView check
200
        $recordFrom = $this->getRecordVersion($recordClass, $recordId, $recordVersionFrom);
201
        $recordTo = $this->getRecordVersion($recordClass, $recordId, $recordVersionTo);
202
        if (!$recordFrom || !$recordTo) {
0 ignored issues
show
introduced by
$recordFrom is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
introduced by
$recordTo is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
203
            return null;
204
        }
205
206
        $effectiveContext = array_merge($context, ['Record' => $recordTo]);
207
208
        $form = $this->scaffoldForm(self::FORM_NAME_COMPARE, $effectiveContext, [
209
            $recordClass,
210
            $recordId,
211
            $recordVersionFrom,
212
            $recordVersionTo,
213
        ]);
214
215
        // Enable the "compare mode" diff view
216
        $comparisonTransformation = DiffTransformation::create();
217
        $form->transform($comparisonTransformation);
218
        $form->loadDataFrom($recordFrom);
219
220
        return $form;
221
    }
222
223
    public function versionForm(HTTPRequest $request = null)
224
    {
225
        if (!$request) {
226
            $this->jsonError(400);
227
            return null;
228
        }
229
230
        try {
231
            return $this->getVersionForm([
232
                'RecordClass' => $request->getVar('RecordClass'),
233
                'RecordID' => $request->getVar('RecordID'),
234
                'RecordVersion' => $request->getVar('RecordVersion'),
235
            ]);
236
        } catch (InvalidArgumentException $ex) {
237
            $this->jsonError(400);
238
        }
239
    }
240
241
    public function compareForm(HTTPRequest $request = null)
242
    {
243
        if (!$request) {
244
            $this->jsonError(400);
245
            return null;
246
        }
247
248
        try {
249
            return $this->getCompareForm([
250
                'RecordClass' => $request->getVar('RecordClass'),
251
                'RecordID' => $request->getVar('RecordID'),
252
                'RecordVersionFrom' => $request->getVar('RecordVersionFrom'),
253
                'RecordVersionTo' => $request->getVar('RecordVersionTo'),
254
            ]);
255
        } catch (InvalidArgumentException $ex) {
256
            $this->jsonError(400);
257
        }
258
    }
259
260
    /**
261
     * Perform some centralised validation checks on the input request and data within it
262
     *
263
     * @param array $context
264
     * @param string[] $requiredFields
265
     * @return bool
266
     * @throws InvalidArgumentException
267
     */
268
    protected function validateInput(array $context, array $requiredFields = [])
269
    {
270
        foreach ($requiredFields as $requiredField) {
271
            if (empty($context[$requiredField])) {
272
                throw new InvalidArgumentException('Missing required field ' . $requiredField);
273
            }
274
        }
275
        return true;
276
    }
277
278
    /**
279
     * Given some context, scaffold a form using the FormFactory and return it
280
     *
281
     * @param string $formName The name for the returned {@link Form}
282
     * @param array $context Context arguments for the {@link FormFactory}
283
     * @param array $extra Context arguments for the {@link LeftAndMainFormRequestHandler}
284
     * @return Form
285
     */
286
    protected function scaffoldForm($formName, array $context = [], array $extra = [])
287
    {
288
        /** @var FormFactory $scaffolder */
289
        $scaffolder = Injector::inst()->get(DataObjectVersionFormFactory::class);
290
        $form = $scaffolder->getForm($this, $formName, $context);
291
292
        // Set form handler with class name, ID and VersionID
293
        return $form->setRequestHandler(
294
            LeftAndMainFormRequestHandler::create($form, $extra)
295
        );
296
    }
297
}
298