GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

SectionEvent   F
last analyzed

Complexity

Total Complexity 94

Size/Duplication

Total Lines 730
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 252
c 2
b 0
f 0
dl 0
loc 730
rs 2
wmc 94

11 Methods

Rating   Name   Duplication   Size   Complexity  
A createError() 0 10 2
B appendErrors() 0 38 8
B replaceFieldToken() 0 26 8
F processSendMailFilter() 0 111 15
D __doit() 0 110 20
A __reduceType() 0 7 3
A processFinalSaveFilters() 0 49 5
C execute() 0 64 16
B processPreSaveFilters() 0 65 7
A processPostSaveFilters() 0 42 5
A buildFilterElement() 0 13 5

How to fix   Complexity   

Complex Class

Complex classes like SectionEvent often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SectionEvent, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @package events
5
 */
6
/**
7
 * The `SectionEvent` class provides methods required to save
8
 * data entered on the frontend to a corresponding Symphony section.
9
 *
10
 * @since Symphony 2.3.1
11
 * @link http://getsymphony.com/learn/concepts/view/events/
12
 */
13
14
abstract class SectionEvent extends Event
15
{
16
    /**
17
     * An associative array of results from the filters that have run
18
     * on this event.
19
     * @var array
20
     */
21
    public $filter_results = array();
22
23
    /**
24
     * An associative array of errors from the filters that have run
25
     * on this event.
26
     * @var array
27
     */
28
    public $filter_errors = array();
29
30
    /**
31
     * This method will construct XML that represents the result of
32
     * an Event filter.
33
     *
34
     * @param string $name
35
     *  The name of the filter
36
     * @param string $status
37
     *  The status of the filter, either passed or failed.
38
     * @param XMLElement|string $message
39
     *  Optionally, an XMLElement or string to be appended to this
40
     *  `<filter>` element. XMLElement allows for more complex return
41
     *  types.
42
     * @param array $attributes
43
     *  An associative array of additional attributes to add to this
44
     *  `<filter>` element
45
     * @return XMLElement
46
     */
47
    public static function buildFilterElement($name, $status, $message = null, array $attributes = null)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$message" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$message"; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between argument "$attributes" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$attributes"; expected 0 but found 1
Loading history...
48
    {
49
        $filter = new XMLElement('filter', (!$message || is_object($message) ? null : $message), array('name' => $name, 'status' => $status));
50
51
        if ($message instanceof XMLElement) {
52
            $filter->appendChild($message);
53
        }
54
55
        if (is_array($attributes)) {
56
            $filter->setAttributeArray($attributes);
57
        }
58
59
        return $filter;
60
    }
61
62
    /**
63
     * Appends errors generated from fields during the execution of an Event
64
     *
65
     * @param XMLElement $result
66
     * @param array $fields
67
     * @param array $errors
68
     * @param object $post_values
69
     * @throws Exception
70
     * @return XMLElement
71
     */
72
    public static function appendErrors(XMLElement $result, array $fields, $errors, $post_values)
73
    {
74
        $result->setAttribute('result', 'error');
75
        $result->appendChild(new XMLElement('message', __('Entry encountered errors when saving.'), array(
76
            'message-id' => EventMessages::ENTRY_ERRORS
77
        )));
78
79
        foreach ($errors as $field_id => $message) {
80
            $field = FieldManager::fetch($field_id);
81
82
            // Do a little bit of a check for files so that we can correctly show
83
            // whether they are 'missing' or 'invalid'. If it's missing, then we
84
            // want to remove the data so `__reduceType` will correctly resolve to
85
            // missing instead of invalid.
86
            // @see https://github.com/symphonists/s3upload_field/issues/17
87
            if (isset($_FILES['fields']['error'][$field->get('element_name')])) {
88
                $upload = $_FILES['fields']['error'][$field->get('element_name')];
89
90
                if ($upload === UPLOAD_ERR_NO_FILE) {
91
                    unset($fields[$field->get('element_name')]);
92
                }
93
            }
94
95
            if (is_array($fields[$field->get('element_name')])) {
96
                $type = array_reduce($fields[$field->get('element_name')], array('SectionEvent', '__reduceType'));
97
            } else {
98
                $type = ($fields[$field->get('element_name')] == '') ? 'missing' : 'invalid';
99
            }
100
101
            $error = self::createError($field, $type, $message);
0 ignored issues
show
Bug introduced by
$field of type array is incompatible with the type Field expected by parameter $field of SectionEvent::createError(). ( Ignorable by Annotation )

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

101
            $error = self::createError(/** @scrutinizer ignore-type */ $field, $type, $message);
Loading history...
102
            $result->appendChild($error);
103
        }
104
105
        if (isset($post_values) && is_object($post_values)) {
106
            $result->appendChild($post_values);
107
        }
108
109
        return $result;
110
    }
111
112
    /**
113
     * Given a Field instance, the type of error, and the message, this function
114
     * creates an XMLElement node so that it can be added to the `?debug` for the
115
     * Event
116
     *
117
     * @since Symphony 2.5.0
118
     * @param Field $field
119
     * @param string $type
120
     *  At the moment 'missing' or 'invalid' accepted
121
     * @param string $message
122
     * @return XMLElement
123
     */
124
    public static function createError(Field $field, $type, $message = null)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$message" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$message"; expected 0 but found 1
Loading history...
125
    {
126
        $error = new XMLElement($field->get('element_name'), null, array(
0 ignored issues
show
Bug introduced by
It seems like $field->get('element_name') can also be of type array; however, parameter $name of XMLElement::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

126
        $error = new XMLElement(/** @scrutinizer ignore-type */ $field->get('element_name'), null, array(
Loading history...
127
            'label' => General::sanitize($field->get('label')),
0 ignored issues
show
Bug introduced by
It seems like $field->get('label') can also be of type array; however, parameter $source of General::sanitize() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

127
            'label' => General::sanitize(/** @scrutinizer ignore-type */ $field->get('label')),
Loading history...
128
            'type' => $type,
129
            'message-id' => ($type === 'missing') ? EventMessages::FIELD_MISSING : EventMessages::FIELD_INVALID,
130
            'message' => General::sanitize($message)
131
        ));
132
133
        return $error;
134
    }
135
136
    /**
137
     * This function searches the `$haystack` for the given `$needle`,
138
     * where the needle is a string representation of where the desired
139
     * value exists in the `$haystack` array. For example `fields[name]`
140
     * would look in the `$haystack` for the key of `fields` that has the
141
     * key `name` and return the value.
142
     *
143
     * @param string $needle
144
     *  The needle, ie. `fields[name]`.
145
     * @param array $haystack
146
     *  Associative array to find the needle, ie.
147
     *      `array('fields' => array(
148
     *          'name' => 'Bob',
149
     *          'age' => '10'
150
     *      ))`
151
     * @param string $default
152
     *  If the `$needle` is not found, return this value. Defaults to null.
153
     * @param boolean $discard_field_name
154
     *  When matches are found in the `$haystack`, they are added to results
155
     *  array. This parameter defines if this should be an associative array
156
     *  or just an array of the matches. Used in conjunction with `$collapse`
157
     * @param boolean $collapse
158
     *  If multiple values are found, this will cause them to be reduced
159
     *  to single string with ' ' as the separator. Defaults to true.
160
     * @return string|array
161
     */
162
    public static function replaceFieldToken($needle, $haystack, $default = null, $discard_field_name = true, $collapse = true)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$default" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$default"; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between argument "$discard_field_name" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$discard_field_name"; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between argument "$collapse" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$collapse"; expected 0 but found 1
Loading history...
163
    {
164
        if (preg_match('/^(fields\[[^\]]+\],?)+$/i', $needle)) {
165
            $parts = preg_split('/\,/i', $needle, -1, PREG_SPLIT_NO_EMPTY);
166
            $parts = array_map('trim', $parts);
0 ignored issues
show
Bug introduced by
It seems like $parts can also be of type false; however, parameter $arr1 of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

166
            $parts = array_map('trim', /** @scrutinizer ignore-type */ $parts);
Loading history...
167
168
            $stack = array();
169
170
            foreach ($parts as $p) {
171
                $field = str_replace(array('fields[', ']'), '', $p);
172
                ($discard_field_name ? $stack[] = $haystack[$field] : $stack[$field] = $haystack[$field]);
0 ignored issues
show
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
173
            }
174
175
            if (is_array($stack) && !empty($stack)) {
176
                return $collapse ? implode(' ', $stack) : $stack;
0 ignored issues
show
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
177
            } else {
178
                $needle = null;
179
            }
180
        }
181
182
        $needle = trim($needle);
183
184
        if (empty($needle)) {
185
            return $default;
186
        } else {
187
            return $needle;
188
        }
189
    }
190
191
    /**
192
     * Helper method to determine if a field is missing, or if the data
193
     * provided was invalid. Used in conjunction with `array_reduce`.
194
     *
195
     * @param array $a,
196
     * @param array $b
197
     * @return string
198
     *  'missing' or 'invalid'
199
     */
200
    public static function __reduceType($a, $b)
201
    {
202
        if (is_array($b)) {
0 ignored issues
show
introduced by
The condition is_array($b) is always true.
Loading history...
203
            return array_reduce($b, array('SectionEvent', '__reduceType'));
204
        }
205
206
        return (strlen(trim($b)) === 0) ? 'missing' : 'invalid';
207
    }
208
209
    /**
210
     * This function will process the core Filters, Admin Only and Expect
211
     * Multiple, before invoking the `__doit` function, which actually
212
     * processes the Event. Once the Event has executed, this function will
213
     * determine if the user should be redirected to a URL, or to just return
214
     * the XML.
215
     *
216
     * @throws Exception
217
     * @return XMLElement|void
218
     *  If `$_REQUEST{'redirect']` is set, and the Event executed successfully,
219
     *  the user will be redirected to the given location. If `$_REQUEST['redirect']`
220
     *  is not set, or the Event encountered errors, an XMLElement of the Event
221
     *  result will be returned.
222
     */
223
    public function execute()
224
    {
225
        if (!isset($this->eParamFILTERS) || !is_array($this->eParamFILTERS)) {
226
            $this->eParamFILTERS = array();
0 ignored issues
show
Bug Best Practice introduced by
The property eParamFILTERS does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
227
        }
228
229
        $result = new XMLElement($this->ROOTELEMENT);
0 ignored issues
show
Bug Best Practice introduced by
The property ROOTELEMENT does not exist on SectionEvent. Did you maybe forget to declare it?
Loading history...
230
231
        if (in_array('admin-only', $this->eParamFILTERS) && !Symphony::Engine()->isLoggedIn()) {
232
            $result->setAttribute('result', 'error');
233
            $result->appendChild(new XMLElement('message', __('Entry encountered errors when saving.'), array(
234
                'message-id' => EventMessages::ENTRY_ERRORS
235
            )));
236
            $result->appendChild(self::buildFilterElement('admin-only', 'failed'));
237
            return $result;
238
        }
239
240
        $entry_id = $position = $fields = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $fields is dead and can be removed.
Loading history...
Unused Code introduced by
The assignment to $position is dead and can be removed.
Loading history...
241
        $post = General::getPostData();
242
        $success = true;
243
        if (!is_array($post['fields'])) {
244
            $post['fields'] = array();
245
        }
246
247
        if (in_array('expect-multiple', $this->eParamFILTERS)) {
248
            foreach ($post['fields'] as $position => $fields) {
249
                if (isset($post['id'][$position]) && is_numeric($post['id'][$position])) {
250
                    $entry_id = $post['id'][$position];
251
                } else {
252
                    $entry_id = null;
253
                }
254
255
                $entry = new XMLElement('entry', null, array('position' => $position));
256
257
                // Reset errors for each entry execution
258
                $this->filter_results = $this->filter_errors = array();
259
260
                // Ensure that we are always dealing with an array.
261
                if (!is_array($fields)) {
262
                    $fields = array();
263
                }
264
265
                // Execute the event for this entry
266
                if (!$this->__doit($fields, $entry, $position, $entry_id)) {
267
                    $success = false;
268
                }
269
270
                $result->appendChild($entry);
271
            }
272
        } else {
273
            $fields = $post['fields'];
274
275
            if (isset($post['id']) && is_numeric($post['id'])) {
276
                $entry_id = $post['id'];
277
            }
278
279
            $success = $this->__doit($fields, $result, null, $entry_id);
280
        }
281
282
        if ($success && isset($_REQUEST['redirect'])) {
283
            redirect($_REQUEST['redirect']);
284
        }
285
286
        return $result;
287
    }
288
289
    /**
290
     * This function does the bulk of processing the Event, from running the delegates
291
     * to validating the data and eventually saving the data into Symphony. The result
292
     * of the Event is returned via the `$result` parameter.
293
     *
294
     * @param array $fields
295
     *  An array of $_POST data, to process and add/edit an entry.
296
     * @param XMLElement $result
297
     *  The XMLElement contains the result of the Event, it is passed by
298
     *  reference.
299
     * @param integer $position
300
     *  When the Expect Multiple filter is added, this event should expect
301
     *  to deal with adding (or editing) multiple entries at once.
302
     * @param integer $entry_id
303
     *  If this Event is editing an existing entry, that Entry ID will
304
     *  be passed to this function.
305
     * @throws Exception
306
     * @return XMLElement
307
     *  The result of the Event
308
     */
309
    public function __doit(array $fields = array(), XMLElement &$result, $position = null, $entry_id = null)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$fields" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$fields"; expected 0 but found 1
Loading history...
Coding Style introduced by
Parameters which have default values should be placed at the end.

If you place a parameter with a default value before a parameter with a default value, the default value of the first parameter will never be used as it will always need to be passed anyway:

// $a must always be passed; it's default value is never used.
function someFunction($a = 5, $b) { }
Loading history...
Coding Style introduced by
Incorrect spacing between argument "$position" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$position"; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between argument "$entry_id" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$entry_id"; expected 0 but found 1
Loading history...
310
    {
311
        $post_values = new XMLElement('post-values');
312
313
        if (!is_array($this->eParamFILTERS)) {
314
            $this->eParamFILTERS = array();
0 ignored issues
show
Bug Best Practice introduced by
The property eParamFILTERS does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
315
        }
316
317
        // Check to see if the Section of this Event is valid.
318
        if (!$section = SectionManager::fetch($this->getSource())) {
0 ignored issues
show
Bug introduced by
The method getSource() does not exist on SectionEvent. ( Ignorable by Annotation )

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

318
        if (!$section = SectionManager::fetch($this->/** @scrutinizer ignore-call */ getSource())) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
319
            $result->setAttribute('result', 'error');
320
            $result->appendChild(new XMLElement('message', __('The Section, %s, could not be found.', array($this->getSource())), array(
321
                'message-id' => EventMessages::SECTION_MISSING
322
            )));
323
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type XMLElement.
Loading history...
324
        }
325
326
        // Create the post data element
327
        if (!empty($fields)) {
328
            General::array_to_xml($post_values, $fields, true);
329
        }
330
331
        // If the EventPreSaveFilter fails, return early
332
        if ($this->processPreSaveFilters($result, $fields, $post_values, $entry_id) === false) {
333
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type XMLElement.
Loading history...
334
        }
335
336
        // If the `$entry_id` is provided, check to see if it exists.
337
        // @todo If this was moved above PreSaveFilters, we can pass the
338
        // Entry object to the delegate meaning extensions don't have to
339
        // do that step.
340
        if (isset($entry_id)) {
341
            $entry = EntryManager::fetch($entry_id);
342
            $entry = $entry[0];
343
344
            if (!is_object($entry)) {
345
                $result->setAttribute('result', 'error');
346
                $result->appendChild(new XMLElement('message', __('The Entry, %s, could not be found.', array($entry_id)), array(
347
                    'message-id' => EventMessages::ENTRY_MISSING
348
                )));
349
350
                return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type XMLElement.
Loading history...
351
            }
352
353
            // `$entry_id` wasn't provided, create a new Entry object.
354
        } else {
355
            $entry = EntryManager::create();
356
            $entry->set('section_id', $this->getSource());
357
        }
358
359
        // Validate the data. `$entry->checkPostData` loops over all fields calling
360
        // their `checkPostFieldData` function. If the return of the function is
361
        // `Entry::__ENTRY_FIELD_ERROR__` then abort the event and add the error
362
        // messages to the `$result`.
363
        if (Entry::__ENTRY_FIELD_ERROR__ == $entry->checkPostData($fields, $errors, ($entry->get('id') ? true : false))) {
364
            $result = self::appendErrors($result, $fields, $errors, $post_values);
365
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type XMLElement.
Loading history...
366
367
            // If the data is good, process the data, almost ready to save it to the
368
            // Database. If processing fails, abort the event and display the errors
369
        } elseif (Entry::__ENTRY_OK__ != $entry->setDataFromPost($fields, $errors, false, ($entry->get('id') ? true : false))) {
370
            $result = self::appendErrors($result, $fields, $errors, $post_values);
371
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type XMLElement.
Loading history...
372
373
            // Data is checked, data has been processed, by trying to save the
374
            // Entry caused an error to occur, so abort and return.
375
        } elseif ($entry->commit() === false) {
376
            $result->setAttribute('result', 'error');
377
            $result->appendChild(new XMLElement('message', __('Unknown errors where encountered when saving.'), array(
378
                'message-id' => EventMessages::ENTRY_UNKNOWN
0 ignored issues
show
Bug introduced by
The constant EventMessages::ENTRY_UNKNOWN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
379
            )));
380
381
            if (isset($post_values) && is_object($post_values)) {
382
                $result->appendChild($post_values);
383
            }
384
385
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type XMLElement.
Loading history...
386
387
            // Entry was created, add the good news to the return `$result`
388
        } else {
389
            $result->setAttributeArray(array(
390
                'result' => 'success',
391
                'type' => (isset($entry_id) ? 'edited' : 'created'),
392
                'id' => $entry->get('id')
393
            ));
394
395
            if (isset($entry_id)) {
396
                $result->appendChild(new XMLElement('message', __('Entry edited successfully.'), array(
397
                    'message-id' => EventMessages::ENTRY_EDITED_SUCCESS
398
                )));
399
            } else {
400
                $result->appendChild(new XMLElement('message', __('Entry created successfully.'), array(
401
                    'message-id' => EventMessages::ENTRY_CREATED_SUCCESS
402
                )));
403
            }
404
        }
405
406
        // PASSIVE FILTERS ONLY AT THIS STAGE. ENTRY HAS ALREADY BEEN CREATED.
407
        if (in_array('send-email', $this->eParamFILTERS) && !in_array('expect-multiple', $this->eParamFILTERS)) {
408
            $result = $this->processSendMailFilter($result, $_POST['send-email'], $fields, $section, $entry);
0 ignored issues
show
Bug introduced by
It seems like $section can also be of type array; however, parameter $section of SectionEvent::processSendMailFilter() does only seem to accept Section, maybe add an additional type check? ( Ignorable by Annotation )

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

408
            $result = $this->processSendMailFilter($result, $_POST['send-email'], $fields, /** @scrutinizer ignore-type */ $section, $entry);
Loading history...
409
        }
410
411
        $result = $this->processPostSaveFilters($result, $fields, $entry);
412
        $result = $this->processFinalSaveFilters($result, $fields, $entry);
413
414
        if (isset($post_values) && is_object($post_values)) {
415
            $result->appendChild($post_values);
416
        }
417
418
        return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the documented return type XMLElement.
Loading history...
419
    }
420
421
    /**
422
     * Processes all extensions attached to the `EventPreSaveFilter` delegate
423
     *
424
     * @uses EventPreSaveFilter
425
     *
426
     * @param XMLElement $result
427
     * @param array $fields
428
     * @param XMLElement $post_values
429
     * @param integer $entry_id
430
     * @return boolean
431
     */
432
    protected function processPreSaveFilters(XMLElement $result, array &$fields, XMLElement &$post_values, $entry_id = null)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$entry_id" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$entry_id"; expected 0 but found 1
Loading history...
433
    {
434
        $can_proceed = true;
435
436
        /**
437
         * Prior to saving entry from the front-end. This delegate will
438
         * force the Event to terminate if it populates the `$filter_results`
439
         * array. All parameters are passed by reference.
440
         *
441
         * @delegate EventPreSaveFilter
442
         * @param string $context
443
         * '/frontend/'
444
         * @param array $fields
445
         * @param Event $this
446
         * @param array $messages
447
         *  An associative array of array's which contain 4 values,
448
         *  the name of the filter (string), the status (boolean),
449
         *  the message (string) an optionally an associative array
450
         *  of additional attributes to add to the filter element.
451
         * @param XMLElement $post_values
452
         * @param integer $entry_id
453
         *  If editing an entry, this parameter will be an integer,
454
         *  otherwise null.
455
         */
456
        Symphony::ExtensionManager()->notifyMembers(
457
            'EventPreSaveFilter',
458
            '/frontend/',
459
            array(
460
                'fields' => &$fields,
461
                'event' => &$this,
462
                'messages' => &$this->filter_results,
463
                'post_values' => &$post_values,
464
                'entry_id' => $entry_id
465
            )
466
        );
467
468
        // Logic taken from `event.section.php` to fail should any `$this->filter_results`
469
        // be returned. This delegate can cause the event to exit early.
470
        if (is_array($this->filter_results) && !empty($this->filter_results)) {
471
            $can_proceed = true;
472
473
            foreach ($this->filter_results as $fr) {
474
                list($name, $status, $message, $attributes) = array_pad($fr, 4, null);
475
476
                $result->appendChild(
477
                    self::buildFilterElement($name, ($status ? 'passed' : 'failed'), $message, $attributes)
0 ignored issues
show
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
478
                );
479
480
                if ($status === false) {
481
                    $can_proceed = false;
482
                }
483
            }
484
485
            if ($can_proceed !== true) {
486
                $result->appendChild($post_values);
487
                $result->setAttribute('result', 'error');
488
                $result->appendChild(new XMLElement('message', __('Entry encountered errors when saving.'), array(
489
                    'message-id' => EventMessages::FILTER_FAILED
490
                )));
491
            }
492
        }
493
494
        // Reset the filter results to prevent duplicates. RE: #2179
495
        $this->filter_results = array();
496
        return $can_proceed;
497
    }
498
499
    /**
500
     * Processes all extensions attached to the `EventPostSaveFilter` delegate
501
     *
502
     * @uses EventPostSaveFilter
503
     *
504
     * @param XMLElement $result
505
     * @param array $fields
506
     * @param Entry $entry
507
     * @return XMLElement
508
     */
509
    protected function processPostSaveFilters(XMLElement $result, array $fields, Entry $entry = null)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$entry" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$entry"; expected 0 but found 1
Loading history...
510
    {
511
        /**
512
         * After saving entry from the front-end. This delegate will not force
513
         * the Events to terminate if it populates the `$filter_results` array.
514
         * Provided with references to this object, the `$_POST` data and also
515
         * the error array
516
         *
517
         * @delegate EventPostSaveFilter
518
         * @param string $context
519
         * '/frontend/'
520
         * @param integer $entry_id
521
         * @param array $fields
522
         * @param Entry $entry
523
         * @param Event $this
524
         * @param array $messages
525
         *  An associative array of array's which contain 4 values,
526
         *  the name of the filter (string), the status (boolean),
527
         *  the message (string) an optionally an associative array
528
         *  of additional attributes to add to the filter element.
529
         */
530
        Symphony::ExtensionManager()->notifyMembers('EventPostSaveFilter', '/frontend/', array(
531
            'entry_id' => $entry->get('id'),
0 ignored issues
show
Bug introduced by
The method get() does not exist on null. ( Ignorable by Annotation )

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

531
            'entry_id' => $entry->/** @scrutinizer ignore-call */ get('id'),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
532
            'fields' => $fields,
533
            'entry' => $entry,
534
            'event' => &$this,
535
            'messages' => &$this->filter_results
536
        ));
537
538
        if (is_array($this->filter_results) && !empty($this->filter_results)) {
539
            foreach ($this->filter_results as $fr) {
540
                list($name, $status, $message, $attributes) = $fr;
541
542
                $result->appendChild(
543
                    self::buildFilterElement($name, ($status ? 'passed' : 'failed'), $message, $attributes)
0 ignored issues
show
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
544
                );
545
            }
546
        }
547
548
        // Reset the filter results to prevent duplicates. RE: #2179
549
        $this->filter_results = array();
550
        return $result;
551
    }
552
553
    /**
554
     * Processes all extensions attached to the `EventFinalSaveFilter` delegate
555
     *
556
     * @uses EventFinalSaveFilter
557
     *
558
     * @param XMLElement $result
559
     * @param array $fields
560
     * @param Entry $entry
561
     * @return XMLElement
562
     */
563
    protected function processFinalSaveFilters(XMLElement $result, array $fields, Entry $entry = null)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$entry" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$entry"; expected 0 but found 1
Loading history...
564
    {
565
        /**
566
         * This delegate that lets extensions know the final status of the
567
         * current Event. It is triggered when everything has processed correctly.
568
         * The `$messages` array contains the results of the previous filters that
569
         * have executed, and the `$errors` array contains any errors that have
570
         * occurred as a result of this delegate. These errors cannot stop the
571
         * processing of the Event, as that has already been done.
572
         *
573
         *
574
         * @delegate EventFinalSaveFilter
575
         * @param string $context
576
         * '/frontend/'
577
         * @param array $fields
578
         * @param Event $this
579
         * @param array $messages
580
         *  An associative array of array's which contain 4 values,
581
         *  the name of the filter (string), the status (boolean),
582
         *  the message (string) an optionally an associative array
583
         *  of additional attributes to add to the filter element.
584
         * @param array $errors
585
         *  An associative array of array's which contain 4 values,
586
         *  the name of the filter (string), the status (boolean),
587
         *  the message (string) an optionally an associative array
588
         *  of additional attributes to add to the filter element.
589
         * @param Entry $entry
590
         */
591
        Symphony::ExtensionManager()->notifyMembers('EventFinalSaveFilter', '/frontend/', array(
592
            'fields'    => $fields,
593
            'event'     => $this,
594
            'messages'  => $this->filter_results,
595
            'errors'    => &$this->filter_errors,
596
            'entry'     => $entry
597
        ));
598
599
        if (is_array($this->filter_errors) && !empty($this->filter_errors)) {
600
            foreach ($this->filter_errors as $fr) {
601
                list($name, $status, $message, $attributes) = $fr;
602
603
                $result->appendChild(
604
                    self::buildFilterElement($name, ($status ? 'passed' : 'failed'), $message, $attributes)
0 ignored issues
show
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
605
                );
606
            }
607
        }
608
609
        // Reset the filter results to prevent duplicates. RE: #2179
610
        $this->filter_results = array();
611
        return $result;
612
    }
613
614
    /**
615
     * This function handles the Send Mail filter which will send an email
616
     * to each specified recipient informing them that an Entry has been
617
     * created.
618
     *
619
     * @param XMLElement $result
620
     *  The XMLElement of the XML that is going to be returned as part
621
     *  of this event to the page.
622
     * @param array $send_email
623
     *  Associative array of `send-mail` parameters.*  Associative array of `send-mail` parameters.
624
     * @param array $fields
625
     *  Array of post data to extract the values from
626
     * @param Section $section
627
     *  This current Entry that has just been updated or created
628
     * @param Entry $entry
629
     * @throws Exception
630
     * @return XMLElement
631
     *  The modified `$result` with the results of the filter.
632
     */
633
    public function processSendMailFilter(XMLElement $result, array $send_email, array &$fields, Section $section, Entry $entry)
634
    {
635
        $fields['recipient']        = self::replaceFieldToken($send_email['recipient'], $fields);
636
        $fields['recipient']        = preg_split('/\,/i', $fields['recipient'], -1, PREG_SPLIT_NO_EMPTY);
637
        $fields['recipient']        = array_map('trim', $fields['recipient']);
0 ignored issues
show
Bug introduced by
It seems like $fields['recipient'] can also be of type false; however, parameter $arr1 of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

637
        $fields['recipient']        = array_map('trim', /** @scrutinizer ignore-type */ $fields['recipient']);
Loading history...
638
639
        $fields['subject']          = self::replaceFieldToken($send_email['subject'], $fields, __('[Symphony] A new entry was created on %s', array(Symphony::Configuration()->get('sitename', 'general'))));
640
        $fields['body']             = self::replaceFieldToken($send_email['body'], $fields, null, false, false);
641
        $fields['sender-email']     = self::replaceFieldToken($send_email['sender-email'], $fields);
642
        $fields['sender-name']      = self::replaceFieldToken($send_email['sender-name'], $fields);
643
644
        $fields['reply-to-name']    = self::replaceFieldToken($send_email['reply-to-name'], $fields);
645
        $fields['reply-to-email']   = self::replaceFieldToken($send_email['reply-to-email'], $fields);
646
647
        $edit_link = SYMPHONY_URL . '/publish/' . $section->get('handle') . '/edit/' . $entry->get('id').'/';
0 ignored issues
show
Bug introduced by
The constant SYMPHONY_URL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
Are you sure $section->get('handle') of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

647
        $edit_link = SYMPHONY_URL . '/publish/' . /** @scrutinizer ignore-type */ $section->get('handle') . '/edit/' . $entry->get('id').'/';
Loading history...
Bug introduced by
Are you sure $entry->get('id') of type array|mixed|null can be used in concatenation? ( Ignorable by Annotation )

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

647
        $edit_link = SYMPHONY_URL . '/publish/' . $section->get('handle') . '/edit/' . /** @scrutinizer ignore-type */ $entry->get('id').'/';
Loading history...
648
        $language = Symphony::Configuration()->get('lang', 'symphony');
649
        $template_path = Event::getNotificationTemplate($language);
0 ignored issues
show
Bug introduced by
The method getNotificationTemplate() does not exist on Event. ( Ignorable by Annotation )

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

649
        /** @scrutinizer ignore-call */ 
650
        $template_path = Event::getNotificationTemplate($language);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
650
        $body = sprintf(file_get_contents($template_path), $section->get('name'), $edit_link);
0 ignored issues
show
Bug introduced by
It seems like $section->get('name') can also be of type array; however, parameter $args of sprintf() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

650
        $body = sprintf(file_get_contents($template_path), /** @scrutinizer ignore-type */ $section->get('name'), $edit_link);
Loading history...
651
652
        if (is_array($fields['body'])) {
653
            foreach ($fields['body'] as $field_handle => $value) {
654
                $body .= "// $field_handle" . PHP_EOL . $value . PHP_EOL . PHP_EOL;
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $field_handle instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
655
            }
656
        } else {
657
            $body .= $fields['body'];
658
        }
659
660
        // Loop over all the recipients and attempt to send them an email
661
        // Errors will be appended to the Event XML
662
        $errors = array();
663
664
        foreach ($fields['recipient'] as $recipient) {
665
            $author = AuthorManager::fetchByUsername($recipient);
666
667
            if (empty($author)) {
668
                $errors['recipient'][$recipient] = __('Recipient not found');
669
                continue;
670
            }
671
672
            $email = Email::create();
673
674
            // Exceptions are also thrown in the settings functions, not only in the send function.
675
            // Those Exceptions should be caught too.
676
            try {
677
                $email->recipients = array(
0 ignored issues
show
Bug Best Practice introduced by
The property recipients does not exist on EmailGateway. Since you implemented __set, consider adding a @property annotation.
Loading history...
678
                    $author->get('first_name') => $author->get('email')
679
                );
680
681
                if ($fields['sender-name'] != null) {
682
                    $email->sender_name = $fields['sender-name'];
0 ignored issues
show
Bug Best Practice introduced by
The property sender_name does not exist on EmailGateway. Since you implemented __set, consider adding a @property annotation.
Loading history...
683
                }
684
685
                if ($fields['sender-email'] != null) {
686
                    $email->sender_email_address = $fields['sender-email'];
0 ignored issues
show
Bug Best Practice introduced by
The property sender_email_address does not exist on EmailGateway. Since you implemented __set, consider adding a @property annotation.
Loading history...
687
                }
688
689
                if ($fields['reply-to-name'] != null) {
690
                    $email->reply_to_name = $fields['reply-to-name'];
0 ignored issues
show
Bug Best Practice introduced by
The property reply_to_name does not exist on EmailGateway. Since you implemented __set, consider adding a @property annotation.
Loading history...
691
                }
692
693
                if ($fields['reply-to-email'] != null) {
694
                    $email->reply_to_email_address = $fields['reply-to-email'];
0 ignored issues
show
Bug Best Practice introduced by
The property reply_to_email_address does not exist on EmailGateway. Since you implemented __set, consider adding a @property annotation.
Loading history...
695
                }
696
697
                $email->text_plain = str_replace('<!-- RECIPIENT NAME -->', $author->get('first_name'), $body);
0 ignored issues
show
Bug Best Practice introduced by
The property text_plain does not exist on EmailGateway. Since you implemented __set, consider adding a @property annotation.
Loading history...
698
                $email->subject = $fields['subject'];
0 ignored issues
show
Bug Best Practice introduced by
The property subject does not exist on EmailGateway. Since you implemented __set, consider adding a @property annotation.
Loading history...
699
                $email->send();
700
            } catch (EmailValidationException $e) {
701
                $errors['address'][$author->get('email')] = $e->getMessage();
702
703
                // The current error array does not permit custom tags.
704
                // Therefore, it is impossible to set a "proper" error message.
705
                // Will return the failed email address instead.
706
            } catch (EmailGatewayException $e) {
707
                $errors['gateway'][$author->get('email')] = $e->getMessage();
708
709
                // Because we don't want symphony to break because it can not send emails,
710
                // all exceptions are logged silently.
711
                // Any custom event can change this behaviour.
712
            } catch (EmailException $e) {
713
                $errors['email'][$author->get('email')] = $e->getMessage();
714
            }
715
        }
716
717
        // If there were errors, output them to the event
718
        if (!empty($errors)) {
719
            $xml = self::buildFilterElement('send-email', 'failed');
720
721
            foreach ($errors as $type => $messages) {
722
                $xType = new XMLElement('error');
723
                $xType->setAttribute('error-type', $type);
724
725
                foreach ($messages as $recipient => $message) {
726
                    $xType->appendChild(
727
                        new XMLElement('message', General::wrapInCDATA($message), array(
728
                            'recipient' => $recipient
729
                        ))
730
                    );
731
                }
732
733
                $xml->appendChild($xType);
734
            }
735
736
            $result->appendChild($xml);
737
        } else {
738
            $result->appendChild(
739
                self::buildFilterElement('send-email', 'passed')
740
            );
741
        }
742
743
        return $result;
744
    }
745
}
746
747
/**
748
 * Basic lookup class for Event messages, allows for frontend developers
749
 * to localise and change event messages without relying on string
750
 * comparision.
751
 *
752
 * @since Symphony 2.4
753
 */
754
class EventMessages
755
{
756
    const UNKNOWN_ERROR = 0;
757
758
    const ENTRY_CREATED_SUCCESS = 100;
759
    const ENTRY_EDITED_SUCCESS = 101;
760
    const ENTRY_ERRORS = 102;
761
    const ENTRY_MISSING = 103;
762
    const ENTRY_NOT_UNIQUE = 104;
763
764
    const SECTION_MISSING = 200;
765
766
    const FIELD_MISSING = 301;
767
    const FIELD_INVALID = 302;
768
    const FIELD_NOT_UNIQUE = 303;
769
770
    const FILTER_FAILED = 400;
771
772
    const SECURITY_XSRF = 500;
773
}
774