onAfterRevertToLive()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\UserForms\Extension;
4
5
use SilverStripe\Forms\FieldList;
6
use SilverStripe\Forms\GridField\GridFieldPaginator;
7
use SilverStripe\Forms\Tab;
8
use SilverStripe\Forms\GridField\GridField;
9
use SilverStripe\Forms\GridField\GridFieldButtonRow;
10
use SilverStripe\Forms\GridField\GridFieldConfig;
11
use SilverStripe\Forms\GridField\GridFieldEditButton;
12
use SilverStripe\Forms\GridField\GridFieldDeleteAction;
13
use SilverStripe\Forms\GridField\GridFieldDetailForm;
14
use SilverStripe\Forms\GridField\GridFieldToolbarHeader;
15
use SilverStripe\ORM\DataExtension;
16
use SilverStripe\UserForms\Form\GridFieldAddClassesButton;
17
use SilverStripe\UserForms\Model\EditableFormField;
18
use SilverStripe\UserForms\Model\EditableFormField\EditableFieldGroup;
19
use SilverStripe\UserForms\Model\EditableFormField\EditableFieldGroupEnd;
20
use SilverStripe\UserForms\Model\EditableFormField\EditableFileField;
21
use SilverStripe\UserForms\Model\EditableFormField\EditableFormStep;
22
use SilverStripe\UserForms\Model\EditableFormField\EditableTextField;
23
use SilverStripe\Versioned\Versioned;
24
use SilverStripe\View\Requirements;
25
use Symbiote\GridFieldExtensions\GridFieldEditableColumns;
26
use Symbiote\GridFieldExtensions\GridFieldOrderableRows;
27
28
/**
29
 * @method DataList|EditableFormField[] Fields()
30
 */
31
class UserFormFieldEditorExtension extends DataExtension
32
{
33
    /**
34
     * @var array
35
     */
36
    private static $has_many = array(
0 ignored issues
show
introduced by
The private property $has_many is not used, and could be removed.
Loading history...
37
        'Fields' => EditableFormField::class
38
    );
39
40
    private static $owns = [
0 ignored issues
show
introduced by
The private property $owns is not used, and could be removed.
Loading history...
41
        'Fields'
42
    ];
43
44
    private static $cascade_deletes = [
0 ignored issues
show
introduced by
The private property $cascade_deletes is not used, and could be removed.
Loading history...
45
        'Fields'
46
    ];
47
48
    /**
49
     * Adds the field editor to the page.
50
     *
51
     * @return FieldList
52
     */
53
    public function updateCMSFields(FieldList $fields)
54
    {
55
        $fieldEditor = $this->getFieldEditorGrid();
56
57
        $fields->insertAfter(new Tab('FormFields', _t(__CLASS__.'.FORMFIELDS', 'Form Fields')), 'Main');
0 ignored issues
show
Bug introduced by
'Main' of type string is incompatible with the type SilverStripe\Forms\FormField expected by parameter $item of SilverStripe\Forms\FieldList::insertAfter(). ( Ignorable by Annotation )

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

57
        $fields->insertAfter(new Tab('FormFields', _t(__CLASS__.'.FORMFIELDS', 'Form Fields')), /** @scrutinizer ignore-type */ 'Main');
Loading history...
58
        $fields->addFieldToTab('Root.FormFields', $fieldEditor);
59
60
        return $fields;
61
    }
62
63
    /**
64
     * Gets the field editor, for adding and removing EditableFormFields.
65
     *
66
     * @return GridField
67
     */
68
    public function getFieldEditorGrid()
69
    {
70
        Requirements::javascript('silverstripe/admin:client/dist/js/vendor.js');
71
        Requirements::javascript('silverstripe/admin:client/dist/js/bundle.js');
72
        Requirements::javascript('silverstripe/userforms:client/dist/js/userforms-cms.js');
73
74
        $fields = $this->owner->Fields();
75
76
        $this->createInitialFormStep(true);
77
78
        $editableColumns = new GridFieldEditableColumns();
79
        $fieldClasses = singleton(EditableFormField::class)->getEditableFieldClasses();
80
        $editableColumns->setDisplayFields([
81
            'ClassName' => function ($record, $column, $grid) use ($fieldClasses) {
0 ignored issues
show
Unused Code introduced by
The parameter $grid 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

81
            'ClassName' => function ($record, $column, /** @scrutinizer ignore-unused */ $grid) use ($fieldClasses) {

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...
82
                if ($record instanceof EditableFormField) {
83
                    $field = $record->getInlineClassnameField($column, $fieldClasses);
84
                    if ($record instanceof EditableFileField) {
85
                        $field->setAttribute('data-folderconfirmed', $record->FolderConfirmed ? 1 : 0);
86
                    }
87
                    return $field;
88
                }
89
            },
90
            'Title' => function ($record, $column, $grid) {
0 ignored issues
show
Unused Code introduced by
The parameter $grid 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

90
            'Title' => function ($record, $column, /** @scrutinizer ignore-unused */ $grid) {

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...
91
                if ($record instanceof EditableFormField) {
92
                    return $record->getInlineTitleField($column);
93
                }
94
            }
95
        ]);
96
97
        $config = GridFieldConfig::create()
98
            ->addComponents(
99
                $editableColumns,
100
                new GridFieldButtonRow(),
101
                (new GridFieldAddClassesButton(EditableTextField::class))
0 ignored issues
show
Bug introduced by
SilverStripe\UserForms\M...ditableTextField::class of type string is incompatible with the type array expected by parameter $classes of SilverStripe\UserForms\F...esButton::__construct(). ( Ignorable by Annotation )

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

101
                (new GridFieldAddClassesButton(/** @scrutinizer ignore-type */ EditableTextField::class))
Loading history...
102
                    ->setButtonName(_t(__CLASS__.'.ADD_FIELD', 'Add Field'))
103
                    ->setButtonClass('btn-primary'),
104
                (new GridFieldAddClassesButton(EditableFormStep::class))
105
                    ->setButtonName(_t(__CLASS__.'.ADD_PAGE_BREAK', 'Add Page Break'))
106
                    ->setButtonClass('btn-secondary'),
107
                (new GridFieldAddClassesButton([EditableFieldGroup::class, EditableFieldGroupEnd::class]))
108
                    ->setButtonName(_t(__CLASS__.'.ADD_FIELD_GROUP', 'Add Field Group'))
109
                    ->setButtonClass('btn-secondary'),
110
                $editButton = new GridFieldEditButton(),
111
                new GridFieldDeleteAction(),
112
                new GridFieldToolbarHeader(),
113
                new GridFieldOrderableRows('Sort'),
114
                new GridFieldDetailForm(),
115
                // Betterbuttons prev and next is enabled by adding a GridFieldPaginator component
116
                new GridFieldPaginator(999)
117
            );
118
119
        $editButton->removeExtraClass('grid-field__icon-action--hidden-on-hover');
120
121
        $fieldEditor = GridField::create(
122
            'Fields',
123
            '',
124
            $fields,
125
            $config
126
        )->addExtraClass('uf-field-editor');
127
128
        return $fieldEditor;
129
    }
130
131
    /**
132
     * A UserForm must have at least one step.
133
     * If no steps exist, create an initial step, and put all fields inside it.
134
     *
135
     * @param bool $force
136
     * @return void
137
     */
138
    public function createInitialFormStep($force = false)
139
    {
140
        // Only invoke once saved
141
        if (!$this->owner->exists()) {
142
            return;
143
        }
144
145
        // Check if first field is a step
146
        $fields = $this->owner->Fields();
147
        $firstField = $fields->first();
148
        if ($firstField instanceof EditableFormStep) {
149
            return;
150
        }
151
152
        // Don't create steps on write if there are no formfields, as this
153
        // can create duplicate first steps during publish of new records
154
        if (!$force && !$firstField) {
155
            return;
156
        }
157
158
        // Re-apply sort to each field starting at 2
159
        $next = 2;
160
        foreach ($fields as $field) {
161
            $field->Sort = $next++;
162
            $field->write();
163
        }
164
165
        // Add step
166
        $step = EditableFormStep::create();
167
        $step->Title = _t('SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFormStep.TITLE_FIRST', 'First Page');
168
        $step->Sort = 1;
169
        $step->write();
170
        $fields->add($step);
171
    }
172
173
    /**
174
     * Ensure that at least one page exists at the start
175
     */
176
    public function onAfterWrite()
177
    {
178
        $this->createInitialFormStep();
179
    }
180
181
    /**
182
     * Remove any orphaned child records on publish
183
     */
184
    public function onAfterPublish()
185
    {
186
        // store IDs of fields we've published
187
        $seenIDs = [];
188
        foreach ($this->owner->Fields() as $field) {
189
            // store any IDs of fields we publish so we don't unpublish them
190
            $seenIDs[] = $field->ID;
191
            $field->doPublish(Versioned::DRAFT, Versioned::LIVE);
192
            $field->destroy();
193
        }
194
195
        // fetch any orphaned live records
196
        $live = Versioned::get_by_stage(EditableFormField::class, Versioned::LIVE)
197
            ->filter([
198
                'ParentID' => $this->owner->ID,
199
            ]);
200
201
        if (!empty($seenIDs)) {
202
            $live = $live->exclude([
203
                'ID' => $seenIDs,
204
            ]);
205
        }
206
207
        // delete orphaned records
208
        foreach ($live as $field) {
209
            $field->deleteFromStage(Versioned::LIVE);
210
            $field->destroy();
211
        }
212
    }
213
214
    /**
215
     * Remove all fields from the live stage when unpublishing the page
216
     */
217
    public function onAfterUnpublish()
218
    {
219
        foreach ($this->owner->Fields() as $field) {
220
            $field->deleteFromStage(Versioned::LIVE);
221
        }
222
    }
223
224
    /**
225
     * When duplicating a UserDefinedForm, duplicate all of its fields and display rules
226
     *
227
     * @see \SilverStripe\ORM\DataObject::duplicate
228
     * @param \SilverStripe\ORM\DataObject $oldPage
229
     * @param bool $doWrite
230
     * @param string $manyMany
231
     * @return \SilverStripe\ORM\DataObject
232
     */
233
    public function onAfterDuplicate($oldPage, $doWrite, $manyMany)
0 ignored issues
show
Unused Code introduced by
The parameter $doWrite 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

233
    public function onAfterDuplicate($oldPage, /** @scrutinizer ignore-unused */ $doWrite, $manyMany)

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 $manyMany 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

233
    public function onAfterDuplicate($oldPage, $doWrite, /** @scrutinizer ignore-unused */ $manyMany)

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...
234
    {
235
        // List of EditableFieldGroups, where the key of the array is the ID of the old end group
236
        $fieldGroups = [];
237
        foreach ($oldPage->Fields() as $field) {
238
            /** @var EditableFormField $newField */
239
            $newField = $field->duplicate(false);
240
            $newField->ParentID = $this->owner->ID;
0 ignored issues
show
Bug Best Practice introduced by
The property ParentID does not exist on SilverStripe\UserForms\Model\EditableFormField. Since you implemented __set, consider adding a @property annotation.
Loading history...
241
            $newField->ParentClass = $this->owner->ClassName;
0 ignored issues
show
Bug Best Practice introduced by
The property ParentClass does not exist on SilverStripe\UserForms\Model\EditableFormField. Since you implemented __set, consider adding a @property annotation.
Loading history...
242
            $newField->Version = 0;
0 ignored issues
show
Bug Best Practice introduced by
The property Version does not exist on SilverStripe\UserForms\Model\EditableFormField. Since you implemented __set, consider adding a @property annotation.
Loading history...
243
            $newField->write();
244
245
            // If we encounter a group start, record it for later use
246
            if ($field instanceof EditableFieldGroup) {
247
                $fieldGroups[$field->EndID] = $newField;
0 ignored issues
show
Bug Best Practice introduced by
The property EndID does not exist on SilverStripe\UserForms\M...ield\EditableFieldGroup. Since you implemented __get, consider adding a @property annotation.
Loading history...
248
            }
249
250
            // If we encounter an end group, link it back to the group start
251
            if ($field instanceof EditableFieldGroupEnd && isset($fieldGroups[$field->ID])) {
252
                $groupStart = $fieldGroups[$field->ID];
253
                $groupStart->EndID = $newField->ID;
254
                $groupStart->write();
255
            }
256
257
            foreach ($field->DisplayRules() as $customRule) {
258
                $newRule = $customRule->duplicate(false);
259
                $newRule->ParentID = $newField->ID;
260
                $newRule->Version = 0;
261
                $newRule->write();
262
            }
263
        }
264
    }
265
266
    /**
267
     * Checks child fields to see if any are modified in draft as well. The owner of this extension will still
268
     * use the Versioned method to determine its own status.
269
     *
270
     * @see Versioned::isModifiedOnDraft
271
     *
272
     * @return boolean|null
273
     */
274
    public function isModifiedOnDraft()
275
    {
276
        foreach ($this->owner->Fields() as $field) {
277
            if ($field->isModifiedOnDraft()) {
278
                return true;
279
            }
280
        }
281
    }
282
283
    /**
284
     * @see Versioned::doRevertToLive
285
     */
286
    public function onAfterRevertToLive()
287
    {
288
        foreach ($this->owner->Fields() as $field) {
289
            $field->copyVersionToStage(Versioned::LIVE, Versioned::DRAFT, false);
290
            $field->writeWithoutVersion();
291
        }
292
    }
293
}
294