updateFormSubmissionFolderPermissions()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 8
rs 10
1
<?php
2
3
namespace SilverStripe\UserForms\Control;
4
5
use SilverStripe\Admin\LeftAndMain;
6
use SilverStripe\Assets\Folder;
7
use SilverStripe\CMS\Controllers\CMSMain;
8
use SilverStripe\Control\HTTPRequest;
9
use SilverStripe\Control\HTTPResponse;
10
use SilverStripe\Control\HTTPResponse_Exception;
11
use SilverStripe\Forms\FieldList;
12
use SilverStripe\Forms\Form;
13
use SilverStripe\Forms\FormAction;
14
use SilverStripe\Forms\HiddenField;
15
use SilverStripe\Forms\LiteralField;
16
use SilverStripe\Forms\OptionsetField;
17
use SilverStripe\Forms\RequiredFields;
18
use SilverStripe\Forms\Schema\FormSchema;
19
use SilverStripe\Forms\TextField;
20
use SilverStripe\Forms\TreeDropdownField;
21
use SilverStripe\ORM\ValidationException;
22
use SilverStripe\Security\Group;
23
use SilverStripe\Security\InheritedPermissions;
24
use SilverStripe\Security\Permission;
25
use SilverStripe\Security\PermissionFailureException;
26
use SilverStripe\Security\Security;
27
use SilverStripe\UserForms\Model\EditableFormField;
28
use SilverStripe\UserForms\Model\EditableFormField\EditableFileField;
29
use SilverStripe\UserForms\Model\UserDefinedForm;
30
use SilverStripe\Versioned\Versioned;
31
32
/**
33
 * Provides a few endpoints the user form CMS UI targets with some AJAX request.
34
 *
35
 * @note While this is a LeftAndMain controller, it doesn't actually appear in the Left side CMS navigation.
36
 */
37
class UserDefinedFormAdmin extends LeftAndMain
38
{
39
    private static $allowed_actions = [
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
40
        'confirmfolderformschema',
41
        'ConfirmFolderForm',
42
        'confirmfolder',
43
        'getfoldergrouppermissions',
44
    ];
45
46
    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...
47
48
    private static $url_segment = 'user-forms';
0 ignored issues
show
introduced by
The private property $url_segment is not used, and could be removed.
Loading history...
49
50
    /**
51
     * @var string The name of the folder where form submissions will be placed by default
52
     */
53
    private static $form_submissions_folder = 'Form-submissions';
0 ignored issues
show
introduced by
The private property $form_submissions_folder is not used, and could be removed.
Loading history...
54
55
    /**
56
     * Returns a TextField for entering a folder name.
57
     * @param string $folder The current folder to set the field to
58
     * @param string $title The title of the text field
59
     * @return TextField
60
     */
61
    private static function getRestrictedAccessField(string $folder, string $title)
62
    {
63
        /** @var TextField $textField */
64
        $textField = TextField::create('CreateFolder', '');
65
66
        /** @var Folder $formSubmissionsFolder */
67
        $formSubmissionsFolder = Folder::find($folder);
68
        $textField->setDescription(EditableFileField::getFolderPermissionString($formSubmissionsFolder));
69
        $textField->addExtraClass('pt-2');
70
        $textField->setSchemaData([
71
            'data' => [
72
                'prefix' => static::config()->get('form_submissions_folder') . '/',
73
            ],
74
            'attributes' => [
75
                'placeholder' => $title
76
            ]
77
        ]);
78
79
        return $textField;
80
    }
81
82
83
    public function index($request)
84
    {
85
        // Don't serve anythign under the main URL.
86
        return $this->httpError(404);
87
    }
88
89
    /**
90
     * This returns a Confirm Folder form schema used to verify the upload folder for EditableFileFields
91
     * @param HTTPRequest $request
92
     * @return HTTPResponse
93
     */
94
    public function confirmfolderformschema(HTTPRequest $request)
95
    {
96
        // Retrieve editable form field by its ID
97
        $id = $request->requestVar('ID');
98
        if (!$id) {
99
            throw new HTTPResponse_Exception(_t(__CLASS__.'.INVALID_REQUEST', 'This request was invalid.'), 400);
100
        }
101
        $editableFormField = EditableFormField::get()->byID($id);
102
        if (!$editableFormField) {
0 ignored issues
show
introduced by
$editableFormField is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
103
            $editableFormField = Versioned::get_by_stage(EditableFormField::class, Versioned::DRAFT)
104
                ->byID($id);
105
        }
106
        if (!$editableFormField) {
0 ignored issues
show
introduced by
$editableFormField is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
107
            throw new HTTPResponse_Exception(_t(__CLASS__.'.INVALID_REQUEST', 'This request was invalid.'), 400);
108
        }
109
110
        // Retrieve the editable form fields Parent
111
        $userForm = $editableFormField->Parent();
112
        if (!$userForm) {
113
            throw new HTTPResponse_Exception(_t(__CLASS__.'.INVALID_REQUEST', 'This request was invalid.'), 400);
114
        }
115
        if (!$userForm->canEdit()) {
116
            throw new PermissionFailureException();
117
        }
118
119
        // Get the folder we want to associate to this EditableFileField
120
        $folderId = 0;
121
        if ($editableFormField instanceof EditableFileField) {
122
            $folderId = $editableFormField->FolderID;
123
        }
124
        /** @var Folder $folder */
125
        $folder = Folder::get()->byID($folderId);
126
        if (!$folder) {
0 ignored issues
show
introduced by
$folder is of type SilverStripe\Assets\Folder, thus it always evaluated to true.
Loading history...
127
            $folder = $this->getFormSubmissionFolder();
128
            $folderId = $folder->ID;
129
        }
130
131
        $form = $this->buildConfirmFolderForm(
132
            $userForm->Title ?: '',
133
            EditableFileField::getFolderPermissionString($folder)
134
        );
135
        $form->loadDataFrom(['FolderID' => $folderId, 'ID' => $id]);
136
137
        // Convert the EditableFormField to an EditableFileField if it's not already one.
138
        if (!$editableFormField instanceof EditableFileField) {
139
            $editableFormField = $editableFormField->newClassInstance(EditableFileField::class);
140
            $editableFormField->write();
141
        }
142
143
        // create the schema response
144
        $parts = $this->getRequest()->getHeader(static::SCHEMA_HEADER);
145
        $schemaID = $this->getRequest()->getURL();
146
        $data = FormSchema::singleton()->getMultipartSchema($parts, $schemaID, $form);
147
148
        // return the schema response
149
        $response = HTTPResponse::create(json_encode($data));
150
        $response->addHeader('Content-Type', 'application/json');
151
        return $response;
152
    }
153
154
    /**
155
     * Return the ConfirmFolderForm. This is only exposed so the treeview has somewhere to direct it's AJAX calss.
156
     * @return Form
157
     */
158
    public function ConfirmFolderForm(): Form
159
    {
160
        return $this->buildConfirmFolderForm();
161
    }
162
163
    /**
164
     * Build the ConfirmFolderForm
165
     * @param string $suggestedFolderName Suggested name for the folder name field
166
     * @param string $permissionFolderString Description to append to the treeview field
167
     * @return Form
168
     */
169
    private function buildConfirmFolderForm(string $suggestedFolderName = '', string $permissionFolderString = ''): Form
170
    {
171
        // Build our Field list for the Form we will return to the front end.
172
        $fields = FieldList::create(
173
            LiteralField::create(
174
                'LabelA',
175
                _t(__CLASS__.'.CONFIRM_FOLDER_LABEL_A', 'Files that your users upload should be stored carefully to reduce the risk of exposing sensitive data. Ensure the folder you select can only be viewed by appropriate parties. Folder permissions can be managed within the Files area.')
176
            )->addExtraClass(' mb-2'),
177
            LiteralField::create(
178
                'LabelB',
179
                _t(__CLASS__.'.CONFIRM_FOLDER_LABEL_B', 'The folder selected will become the default for this form. This can be changed on an individual basis in the <i>File upload field.</i>')
180
            )->addExtraClass(' mb-3'),
181
            static::getRestrictedAccessField($this->config()->get('form_submissions_folder'), $suggestedFolderName),
182
            OptionsetField::create('FolderOptions', _t(__CLASS__.'.FOLDER_OPTIONS_TITLE', 'Form folder options'), [
183
                "new" => _t(__CLASS__.'.FOLDER_OPTIONS_NEW', 'Create a new folder (recommended)'),
184
                "existing" => _t(__CLASS__.'.FOLDER_OPTIONS_EXISTING', 'Use an existing folder')
185
            ], "new"),
186
            TreeDropdownField::create('FolderID', '', Folder::class)
187
                ->addExtraClass('pt-1')
188
                ->setDescription($permissionFolderString),
189
            HiddenField::create('ID')
190
        );
191
192
        $actions = FieldList::create(
193
            FormAction::create('confirmfolder', _t(__CLASS__.'.FORM_ACTION_CONFIRM', 'Save and continue'))
194
                ->setUseButtonTag(false)
195
                ->addExtraClass('btn btn-primary'),
196
            FormAction::create("cancel", _t(CMSMain::class . '.Cancel', "Cancel"))
197
                ->addExtraClass('btn btn-secondary')
198
                ->setUseButtonTag(true)
199
        );
200
201
        return Form::create($this, 'ConfirmFolderForm', $fields, $actions, RequiredFields::create('ID'))
202
            ->setFormAction($this->Link('ConfirmFolderForm'))
203
            ->addExtraClass('form--no-dividers');
204
    }
205
206
    /**
207
     * Sets the selected folder as the upload folder for an EditableFileField
208
     * @param array $data
209
     * @param Form $form
210
     * @param HTTPRequest $request
211
     * @return HTTPResponse
212
     * @throws ValidationException
213
     */
214
    public function confirmfolder(array $data, Form $form, HTTPRequest $request)
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

214
    public function confirmfolder(array $data, /** @scrutinizer ignore-unused */ Form $form, HTTPRequest $request)

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

Loading history...
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

214
    public function confirmfolder(array $data, Form $form, /** @scrutinizer ignore-unused */ HTTPRequest $request)

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

Loading history...
215
    {
216
        if (!Permission::checkMember(null, "CMS_ACCESS_AssetAdmin")) {
217
            throw new PermissionFailureException();
218
        }
219
220
        // retrieve the EditableFileField
221
        $id = $data['ID'];
222
        if (!$id) {
223
            throw new HTTPResponse_Exception(_t(__CLASS__.'.INVALID_REQUEST', 'This request was invalid.'), 400);
224
        }
225
        /** @var EditableFileField $editableFileField */
226
        $editableFormField = EditableFormField::get()->byID($id);
227
        if (!$editableFormField) {
0 ignored issues
show
introduced by
$editableFormField is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
228
            $editableFormField = Versioned::get_by_stage(EditableFormField::class, Versioned::DRAFT)->byID($id);
229
        }
230
        if (!$editableFormField) {
0 ignored issues
show
introduced by
$editableFormField is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
231
            throw new HTTPResponse_Exception(_t(__CLASS__.'.INVALID_REQUEST', 'This request was invalid.'), 400);
232
        }
233
        // change the class if it is incorrect
234
        if (!$editableFormField instanceof EditableFileField) {
235
            $editableFormField = $editableFormField->newClassInstance(EditableFileField::class);
236
        }
237
        if (!$editableFormField) {
238
            throw new HTTPResponse_Exception(_t(__CLASS__.'.INVALID_REQUEST', 'This request was invalid.'), 400);
239
        }
240
        $editableFileField = $editableFormField;
241
242
        if (!$editableFileField->canEdit()) {
243
            throw new PermissionFailureException();
244
        }
245
246
        // check if we're creating a new folder or using an existing folder
247
        $option = isset($data['FolderOptions']) ? $data['FolderOptions'] : '';
248
        if ($option === 'existing') {
249
            // set existing folder
250
            $folderID = $data['FolderID'];
251
            if ($folderID != 0) {
252
                $folder = Folder::get()->byID($folderID);
253
                if (!$folder) {
0 ignored issues
show
introduced by
$folder is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
254
                    throw new HTTPResponse_Exception(_t(__CLASS__.'.INVALID_REQUEST', 'This request was invalid.'), 400);
255
                }
256
            }
257
        } else {
258
            // create the folder
259
            $createFolder = isset($data['CreateFolder']) ? $data['CreateFolder'] : $editableFormField->Parent()->Title;
260
            $folder = $this->getFormSubmissionFolder($createFolder);
261
        }
262
263
        // assign the folder
264
        $editableFileField->FolderID = isset($folder) ? $folder->ID : 0;
265
        $editableFileField->write();
266
267
        // respond
268
        return HTTPResponse::create(json_encode([]))->addHeader('Content-Type', 'application/json');
269
    }
270
271
    /**
272
     * Get the permission for a specific folder
273
     * @return HTTPResponse
274
     */
275
    public function getfoldergrouppermissions()
276
    {
277
        $folderID = $this->getRequest()->requestVar('FolderID');
278
        if ($folderID) {
279
            /** @var Folder $folder */
280
            $folder = Folder::get()->byID($folderID);
281
            if (!$folder) {
0 ignored issues
show
introduced by
$folder is of type SilverStripe\Assets\Folder, thus it always evaluated to true.
Loading history...
282
                throw new HTTPResponse_Exception(_t(__CLASS__.'.INVALID_REQUEST', 'This request was invalid.'), 400);
283
            }
284
            if (!$folder->canView()) {
285
                throw new PermissionFailureException();
286
            }
287
        } else {
288
            $folder = null;
289
        }
290
291
        // respond
292
        $response = HTTPResponse::create(json_encode(EditableFileField::getFolderPermissionString($folder)));
293
        $response->addHeader('Content-Type', 'application/json');
294
        return $response;
295
    }
296
297
    /**
298
     * Set the permission for the default submisison folder.
299
     * @throws ValidationException
300
     */
301
    private static function updateFormSubmissionFolderPermissions()
302
    {
303
        // ensure the FormSubmissions folder is only accessible to Administrators
304
        $formSubmissionsFolder = Folder::find(self::config()->get('form_submissions_folder'));
305
        $formSubmissionsFolder->CanViewType = InheritedPermissions::ONLY_THESE_USERS;
306
        $formSubmissionsFolder->ViewerGroups()->removeAll();
307
        $formSubmissionsFolder->ViewerGroups()->add(Group::get_one(Group::class, ['"Code"' => 'administrators']));
308
        $formSubmissionsFolder->write();
309
    }
310
311
    /**
312
     * Returns the form submission folder or a sub folder if provided.
313
     * Creates the form submission folder if it doesn't exist.
314
     * Updates the form submission folder permissions if it is created.
315
     * @param string $subFolder Sub-folder to be created or returned.
316
     * @return Folder
317
     * @throws ValidationException
318
     */
319
    public static function getFormSubmissionFolder(string $subFolder = null): ?Folder
320
    {
321
        $folderPath = self::config()->get('form_submissions_folder');
322
        if ($subFolder) {
323
            $folderPath .= '/' . $subFolder;
324
        }
325
        $formSubmissionsFolderExists = !!Folder::find(self::config()->get('form_submissions_folder'));
326
        $folder = Folder::find_or_make($folderPath);
327
328
        // Set default permissions if this is the first time we create the form submission folder
329
        if (!$formSubmissionsFolderExists) {
330
            self::updateFormSubmissionFolderPermissions();
331
            // Make sure we return the folder with the latest permission
332
            $folder = Folder::find($folderPath);
333
        }
334
335
        return $folder;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $folder could return the type SilverStripe\Assets\File which includes types incompatible with the type-hinted return SilverStripe\Assets\Folder|null. Consider adding an additional type-check to rule them out.
Loading history...
336
    }
337
}
338