Completed
Pull Request — master (#12)
by Simon
08:01 queued 06:43
created

createOrUpdateSubmission()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 30
rs 9.1288
c 0
b 0
f 0
cc 5
nc 16
nop 1
1
<?php
2
3
namespace Firesphere\PartialUserforms\Controllers;
4
5
use Page;
6
use Firesphere\PartialUserforms\Models\PartialFormSubmission;
7
use SilverStripe\Control\HTTPRequest;
8
use SilverStripe\Control\Middleware\HTTPCacheControlMiddleware;
9
use SilverStripe\ORM\DataObject;
10
use SilverStripe\ORM\FieldType\DBField;
11
use SilverStripe\UserForms\Control\UserDefinedFormController;
12
13
/**
14
 * Class PartialUserFormController
15
 *
16
 * @package Firesphere\PartialUserforms\Controllers
17
 */
18
class PartialUserFormController extends UserDefinedFormController
19
{
20
    /**
21
     * Session key name
22
     */
23
    public const SESSION_KEY = 'PartialSubmissionID';
24
25
    /**
26
     * @var array
27
     */
28
    private static $url_handlers = [
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...
29
        '$Key/$Token' => 'partial',
30
    ];
31
32
    /**
33
     * @var array
34
     */
35
    private static $allowed_actions = [
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
        'partial',
37
    ];
38
39
    /**
40
     * @param HTTPRequest $request
41
     * @return int|mixed|void
42
     * @throws ValidationException
43
     * @throws \SilverStripe\Control\HTTPResponse_Exception
44
     */
45
    public function savePartialSubmission(HTTPRequest $request)
46
    {
47
        if (!$request->isPOST()) {
48
            return $this->httpError(404);
49
        }
50
51
        $postVars = $request->postVars();
52
        $editableField = null;
53
54
        // We don't want SecurityID and/or the process Action stored as a thing
55
        unset($postVars['SecurityID'], $postVars['action_process']);
56
        $submissionID = $request->getSession()->get(self::SESSION_KEY);
57
58
        /** @var PartialFormSubmission $partialSubmission */
59
        $partialSubmission = PartialFormSubmission::get()->byID($submissionID);
60
61
        if (!$submissionID || !$partialSubmission) {
62
            $partialSubmission = PartialFormSubmission::create();
63
            $submissionID = $partialSubmission->write();
64
        }
65
        $request->getSession()->set(self::SESSION_KEY, $submissionID);
66
        foreach ($postVars as $field => $value) {
67
            /** @var EditableFormField $editableField */
68
            $editableField = $this->createOrUpdateSubmission([
69
                'Name'            => $field,
70
                'Value'           => $value,
71
                'SubmittedFormID' => $submissionID
72
            ]);
73
        }
74
75
        if ($editableField instanceof EditableFormField && !$partialSubmission->UserDefinedFormID) {
0 ignored issues
show
Bug introduced by
The class Firesphere\PartialUserfo...llers\EditableFormField does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
76
            $partialSubmission->update([
77
                'UserDefinedFormID'    => $editableField->Parent()->ID,
78
                'ParentID'             => $editableField->Parent()->ID,
79
                'ParentClass'          => $editableField->Parent()->ClassName,
80
                'UserDefinedFormClass' => $editableField->Parent()->ClassName
81
            ]);
82
            $partialSubmission->write();
83
        }
84
85
        return $submissionID;
86
    }
87
88
    /**
89
     * @param $formData
90
     * @return DataObject|EditableFormField
91
     * @throws ValidationException
92
     */
93
    protected function createOrUpdateSubmission($formData)
94
    {
95
        if (is_array($formData['Value'])) {
96
            $formData['Value'] = implode(', ', $formData['Value']);
97
        }
98
99
        $filter = [
100
            'Name'            => $formData['Name'],
101
            'SubmittedFormID' => $formData['SubmittedFormID'],
102
        ];
103
104
        $exists = PartialFieldSubmission::get()->filter($filter)->first();
105
        // Set the title
106
        $editableField = EditableFormField::get()->filter(['Name' => $formData['Name']])->first();
107
        if ($editableField) {
108
            $formData['Title'] = $editableField->Title;
109
            $formData['ParentClass'] = $editableField->Parent()->ClassName;
110
        }
111
        if (!$exists) {
112
            $exists = PartialFieldSubmission::create($formData);
113
        } else {
114
            $exists->update($formData);
115
        }
116
        if ($formData['Value'] !== '') {
117
            $exists->write();
118
        }
119
120
        // Return the ParentID to link the PartialSubmission to it's proper thingy
121
        return $editableField;
122
    }
123
124
    /**
125
     * Partial form
126
     *
127
     * @param HTTPRequest $request
128
     * @return \SilverStripe\ORM\FieldType\DBHTMLText|void
129
     * @throws \SilverStripe\Control\HTTPResponse_Exception
130
     */
131
    public function partial(HTTPRequest $request)
132
    {
133
        // Ensure this URL doesn't get picked up by HTTP caches
134
        HTTPCacheControlMiddleware::singleton()->disableCache();
135
136
        $key = $request->param('Key');
137
        $token = $request->param('Token');
138
139
        $partial = PartialFormSubmission::get()->find('Token', $token);
140
        if (!$token || !$partial || !$partial->UserDefinedFormID) {
141
            return $this->httpError(404);
142
        }
143
144
        // TODO: Recognize visitor with the password
145
        if ($partial->generateKey($token) === $key) {
146
            // Set the session if the last session has expired
147
            if (!$request->getSession()->get(PartialSubmissionController::SESSION_KEY)) {
148
                $request->getSession()->set(PartialSubmissionController::SESSION_KEY, $partial->ID);
149
            }
150
151
            // Set data record and load the form
152
            $this->dataRecord = DataObject::get_by_id($partial->UserDefinedFormClass, $partial->UserDefinedFormID);
0 ignored issues
show
Documentation Bug introduced by
It seems like \SilverStripe\ORM\DataOb...ial->UserDefinedFormID) can also be of type object<SilverStripe\ORM\DataObject>. However, the property $dataRecord is declared as type object<SilverStripe\CMS\Model\SiteTree>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
153
            $this->setFailover($this->dataRecord);
0 ignored issues
show
Bug introduced by
It seems like $this->dataRecord can be null; however, setFailover() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
154
155
            $form = $this->Form();
156
            $fields = $partial->PartialFields()->map('Name', 'Value')->toArray();
157
            $form->loadDataFrom($fields);
0 ignored issues
show
Documentation Bug introduced by
The method loadDataFrom does not exist on object<Firesphere\Partia...tialUserFormController>? 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...
158
159
            // Copied from {@link UserDefinedFormController}
160
            if ($this->Content && $form && !$this->config()->disable_form_content_shortcode) {
0 ignored issues
show
Documentation introduced by
The property Content does not exist on object<Firesphere\Partia...tialUserFormController>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
161
                $hasLocation = stristr($this->Content, '$UserDefinedForm');
0 ignored issues
show
Documentation introduced by
The property Content does not exist on object<Firesphere\Partia...tialUserFormController>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
162
                if ($hasLocation) {
163
                    /** @see Requirements_Backend::escapeReplacement */
164
                    $formEscapedForRegex = addcslashes($form->forTemplate(), '\\$');
0 ignored issues
show
Documentation Bug introduced by
The method forTemplate does not exist on object<Firesphere\Partia...tialUserFormController>? 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...
165
                    $content = preg_replace(
166
                        '/(<p[^>]*>)?\\$UserDefinedForm(<\\/p>)?/i',
167
                        $formEscapedForRegex,
168
                        $this->Content
0 ignored issues
show
Documentation introduced by
The property Content does not exist on object<Firesphere\Partia...tialUserFormController>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
169
                    );
170
171
                    return $this->customise([
172
                        'Content' => DBField::create_field('HTMLText', $content),
173
                        'Form' => '',
174
                        'PartialLink' => $partial->getPartialLink()
175
                    ])->renderWith([static::class, Page::class]);
176
                }
177
            }
178
179
            return $this->customise([
180
                'Content' => DBField::create_field('HTMLText', $this->Content),
0 ignored issues
show
Documentation introduced by
The property Content does not exist on object<Firesphere\Partia...tialUserFormController>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
181
                'Form' => $form,
182
                'PartialLink' => $partial->getPartialLink()
183
            ])->renderWith([static::class, Page::class]);
184
        } else {
185
            return $this->httpError(404);
186
        }
187
    }
188
}
189