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.
Completed
Pull Request — integration (#2604)
by Brendan
05:01
created

SectionEvent   F

Complexity

Total Complexity 94

Size/Duplication

Total Lines 765
Duplicated Lines 14.12 %

Coupling/Cohesion

Components 1
Dependencies 19

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 108
loc 765
rs 1.263
wmc 94
lcom 1
cbo 19

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __reduceType() 0 8 3
C execute() 0 66 16
B buildFilterElement() 0 15 5
D __doit() 8 121 20
C processPreSaveFilters() 7 71 7
C appendErrors() 0 39 8
A createError() 0 11 2
F processSendMailFilter() 0 119 15
C replaceFieldToken() 0 33 8
B processPostSaveFilters() 43 44 5
B processFinalSaveFilters() 50 51 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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
    /**
8
     * The `SectionEvent` class provides methods required to save
9
     * data entered on the frontend to a corresponding Symphony section.
10
     *
11
     * @since Symphony 2.3.1
12
     * @link http://getsymphony.com/learn/concepts/view/events/
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
         *
20
         * @var array
21
         */
22
        public $filter_results = array();
23
24
        /**
25
         * An associative array of errors from the filters that have run
26
         * on this event.
27
         *
28
         * @var array
29
         */
30
        public $filter_errors = array();
31
32
        /**
33
         * Helper method to determine if a field is missing, or if the data
34
         * provided was invalid. Used in conjunction with `array_reduce`.
35
         *
36
         * @param array $a ,
37
         * @param array $b
38
         * @return string
39
         *  'missing' or 'invalid'
40
         */
41
        public function __reduceType($a, $b)
42
        {
43
            if (is_array($b)) {
44
                return array_reduce($b, array('SectionEvent', '__reduceType'));
45
            }
46
47
            return (strlen(trim($b)) === 0) ? 'missing' : 'invalid';
48
        }
49
50
        /**
51
         * This function will process the core Filters, Admin Only and Expect
52
         * Multiple, before invoking the `__doit` function, which actually
53
         * processes the Event. Once the Event has executed, this function will
54
         * determine if the user should be redirected to a URL, or to just return
55
         * the XML.
56
         *
57
         * @throws Exception
58
         * @return XMLElement|void
59
         *  If `$_REQUEST{'redirect']` is set, and the Event executed successfully,
60
         *  the user will be redirected to the given location. If `$_REQUEST['redirect']`
61
         *  is not set, or the Event encountered errors, an XMLElement of the Event
62
         *  result will be returned.
63
         */
64
        public function execute()
65
        {
66
            if (!isset($this->eParamFILTERS) || !is_array($this->eParamFILTERS)) {
67
                $this->eParamFILTERS = array();
0 ignored issues
show
Bug introduced by
The property eParamFILTERS does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
68
            }
69
70
            $result = new XMLElement($this->ROOTELEMENT);
0 ignored issues
show
Bug introduced by
The property ROOTELEMENT does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
71
72
            if (in_array('admin-only', $this->eParamFILTERS) && !Symphony::Engine()->isLoggedIn()) {
73
                $result->setAttribute('result', 'error');
74
                $result->appendChild(new XMLElement('message', __('Entry encountered errors when saving.'), array(
75
                    'message-id' => EventMessages::ENTRY_ERRORS
76
                )));
77
                $result->appendChild(self::buildFilterElement('admin-only', 'failed'));
78
79
                return $result;
80
            }
81
82
            $entry_id = $position = $fields = null;
83
            $post = General::getPostData();
84
            $success = true;
85
            if (!is_array($post['fields'])) {
86
                $post['fields'] = array();
87
            }
88
89
            if (in_array('expect-multiple', $this->eParamFILTERS)) {
90
                foreach ($post['fields'] as $position => $fields) {
91
                    if (isset($post['id'][$position]) && is_numeric($post['id'][$position])) {
92
                        $entry_id = $post['id'][$position];
93
                    } else {
94
                        $entry_id = null;
95
                    }
96
97
                    $entry = new XMLElement('entry', null, array('position' => $position));
98
99
                    // Reset errors for each entry execution
100
                    $this->filter_results = $this->filter_errors = array();
101
102
                    // Ensure that we are always dealing with an array.
103
                    if (!is_array($fields)) {
104
                        $fields = array();
105
                    }
106
107
                    // Execute the event for this entry
108
                    if (!$this->__doit($fields, $entry, $position, $entry_id)) {
109
                        $success = false;
110
                    }
111
112
                    $result->appendChild($entry);
113
                }
114
            } else {
115
                $fields = $post['fields'];
116
117
                if (isset($post['id']) && is_numeric($post['id'])) {
118
                    $entry_id = $post['id'];
119
                }
120
121
                $success = $this->__doit($fields, $result, null, $entry_id);
122
            }
123
124
            if ($success && isset($_REQUEST['redirect'])) {
125
                redirect($_REQUEST['redirect']);
126
            }
127
128
            return $result;
129
        }
130
131
        /**
132
         * This method will construct XML that represents the result of
133
         * an Event filter.
134
         *
135
         * @param string $name
136
         *  The name of the filter
137
         * @param string $status
138
         *  The status of the filter, either passed or failed.
139
         * @param XMLElement|string $message
140
         *  Optionally, an XMLElement or string to be appended to this
141
         *  `<filter>` element. XMLElement allows for more complex return
142
         *  types.
143
         * @param array $attributes
144
         *  An associative array of additional attributes to add to this
145
         *  `<filter>` element
146
         * @return XMLElement
147
         */
148
        public static function buildFilterElement($name, $status, $message = null, array $attributes = null)
149
        {
150
            $filter = new XMLElement('filter', (!$message || is_object($message) ? null : $message),
151
                array('name' => $name, 'status' => $status));
152
153
            if ($message instanceof XMLElement) {
154
                $filter->appendChild($message);
155
            }
156
157
            if (is_array($attributes)) {
158
                $filter->setAttributeArray($attributes);
159
            }
160
161
            return $filter;
162
        }
163
164
        /**
165
         * This function does the bulk of processing the Event, from running the delegates
166
         * to validating the data and eventually saving the data into Symphony. The result
167
         * of the Event is returned via the `$result` parameter.
168
         *
169
         * @param array $fields
170
         *  An array of $_POST data, to process and add/edit an entry.
171
         * @param XMLElement $result
172
         *  The XMLElement contains the result of the Event, it is passed by
173
         *  reference.
174
         * @param integer $position
175
         *  When the Expect Multiple filter is added, this event should expect
176
         *  to deal with adding (or editing) multiple entries at once.
177
         * @param integer $entry_id
178
         *  If this Event is editing an existing entry, that Entry ID will
179
         *  be passed to this function.
180
         * @throws Exception
181
         * @return XMLElement
182
         *  The result of the Event
183
         */
184
        public function __doit(array $fields = array(), XMLElement &$result, $position = null, $entry_id = null)
0 ignored issues
show
Unused Code introduced by
The parameter $position is not used and could be removed.

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

Loading history...
Coding Style introduced by
__doit uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
185
        {
186
            $post_values = new XMLElement('post-values');
187
188
            if (!is_array($this->eParamFILTERS)) {
189
                $this->eParamFILTERS = array();
190
            }
191
192
            // Check to see if the Section of this Event is valid.
193
            if (!$section = SectionManager::fetch($this->getSource())) {
194
                $result->setAttribute('result', 'error');
195
                $result->appendChild(new XMLElement('message',
196
                    __('The Section, %s, could not be found.', array($this->getSource())), array(
197
                        'message-id' => EventMessages::SECTION_MISSING
198
                    )));
199
200
                return false;
201
            }
202
203
            // Create the post data element
204
            if (!empty($fields)) {
205
                General::array_to_xml($post_values, $fields, true);
206
            }
207
208
            // If the EventPreSaveFilter fails, return early
209
            if ($this->processPreSaveFilters($result, $fields, $post_values, $entry_id) === false) {
210
                return false;
211
            }
212
213
            // If the `$entry_id` is provided, check to see if it exists.
214
            // @todo If this was moved above PreSaveFilters, we can pass the
215
            // Entry object to the delegate meaning extensions don't have to
216
            // do that step.
217
            if (isset($entry_id)) {
218
                $entry = EntryManager::fetch($entry_id);
219
                $entry = $entry[0];
220
221 View Code Duplication
                if (!is_object($entry)) {
222
                    $result->setAttribute('result', 'error');
223
                    $result->appendChild(new XMLElement('message',
224
                        __('The Entry, %s, could not be found.', array($entry_id)), array(
225
                            'message-id' => EventMessages::ENTRY_MISSING
226
                        )));
227
228
                    return false;
229
                }
230
231
                // `$entry_id` wasn't provided, create a new Entry object.
232
            } else {
233
                $entry = EntryManager::create();
234
                $entry->set('section_id', $this->getSource());
235
            }
236
237
            // Validate the data. `$entry->checkPostData` loops over all fields calling
238
            // their `checkPostFieldData` function. If the return of the function is
239
            // `Entry::__ENTRY_FIELD_ERROR__` then abort the event and add the error
240
            // messages to the `$result`.
241
            if (Entry::__ENTRY_FIELD_ERROR__ === $entry->checkPostData($fields, $errors,
242
                    ($entry->get('id') ? true : false))
243
            ) {
244
                $result = self::appendErrors($result, $fields, $errors, $post_values);
0 ignored issues
show
Bug introduced by
The variable $errors does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
245
246
                return false;
247
248
                // If the data is good, process the data, almost ready to save it to the
249
                // Database. If processing fails, abort the event and display the errors
250
            } elseif (Entry::__ENTRY_OK__ !== $entry->setDataFromPost($fields, $errors, false,
251
                    ($entry->get('id') ? true : false))
252
            ) {
253
                $result = self::appendErrors($result, $fields, $errors, $post_values);
254
255
                return false;
256
257
                // Data is checked, data has been processed, by trying to save the
258
                // Entry caused an error to occur, so abort and return.
259
            } elseif ($entry->commit() === false) {
260
                $result->setAttribute('result', 'error');
261
                $result->appendChild(new XMLElement('message', __('Unknown errors where encountered when saving.'),
262
                    array(
263
                        'message-id' => EventMessages::UNKNOWN_ERROR
264
                    )));
265
266
                if (isset($post_values) && is_object($post_values)) {
267
                    $result->appendChild($post_values);
268
                }
269
270
                return false;
271
272
                // Entry was created, add the good news to the return `$result`
273
            } else {
274
                $result->setAttributeArray(array(
275
                    'result' => 'success',
276
                    'type' => (isset($entry_id) ? 'edited' : 'created'),
277
                    'id' => $entry->get('id')
278
                ));
279
280
                if (isset($entry_id)) {
281
                    $result->appendChild(new XMLElement('message', __('Entry edited successfully.'), array(
282
                        'message-id' => EventMessages::ENTRY_EDITED_SUCCESS
283
                    )));
284
                } else {
285
                    $result->appendChild(new XMLElement('message', __('Entry created successfully.'), array(
286
                        'message-id' => EventMessages::ENTRY_CREATED_SUCCESS
287
                    )));
288
                }
289
            }
290
291
            // PASSIVE FILTERS ONLY AT THIS STAGE. ENTRY HAS ALREADY BEEN CREATED.
292
            if (in_array('send-email', $this->eParamFILTERS) && !in_array('expect-multiple', $this->eParamFILTERS)) {
293
                $result = $this->processSendMailFilter($result, $_POST['send-email'], $fields, $section, $entry);
0 ignored issues
show
Bug introduced by
It seems like $section defined by \SectionManager::fetch($this->getSource()) on line 193 can also be of type array; however, SectionEvent::processSendMailFilter() does only seem to accept object<Section>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
294
            }
295
296
            $result = $this->processPostSaveFilters($result, $fields, $entry);
297
            $result = $this->processFinalSaveFilters($result, $fields, $entry);
298
299
            if (isset($post_values) && is_object($post_values)) {
300
                $result->appendChild($post_values);
301
            }
302
303
            return true;
304
        }
305
306
        /**
307
         * Processes all extensions attached to the `EventPreSaveFilter` delegate
308
         *
309
         * @uses EventPreSaveFilter
310
         *
311
         * @param XMLElement $result
312
         * @param array $fields
313
         * @param XMLElement $post_values
314
         * @param integer $entry_id
315
         * @return boolean
316
         */
317
        protected function processPreSaveFilters(
318
            XMLElement $result,
319
            array &$fields,
320
            XMLElement &$post_values,
321
            $entry_id = null
322
        ) {
323
            $can_proceed = true;
324
325
            /**
326
             * Prior to saving entry from the front-end. This delegate will
327
             * force the Event to terminate if it populates the `$filter_results`
328
             * array. All parameters are passed by reference.
329
             *
330
             * @delegate EventPreSaveFilter
331
             * @param string $context
332
             * '/frontend/'
333
             * @param array $fields
334
             * @param Event $this
335
             * @param array $messages
336
             *  An associative array of array's which contain 4 values,
337
             *  the name of the filter (string), the status (boolean),
338
             *  the message (string) an optionally an associative array
339
             *  of additional attributes to add to the filter element.
340
             * @param XMLElement $post_values
341
             * @param integer $entry_id
342
             *  If editing an entry, this parameter will be an integer,
343
             *  otherwise null.
344
             */
345
            Symphony::ExtensionManager()->notifyMembers(
346
                'EventPreSaveFilter',
347
                '/frontend/',
348
                array(
349
                    'fields' => &$fields,
350
                    'event' => &$this,
351
                    'messages' => &$this->filter_results,
352
                    'post_values' => &$post_values,
353
                    'entry_id' => $entry_id
354
                )
355
            );
356
357
            // Logic taken from `event.section.php` to fail should any `$this->filter_results`
358
            // be returned. This delegate can cause the event to exit early.
359
            if (is_array($this->filter_results) && !empty($this->filter_results)) {
360
                $can_proceed = true;
361
362
                foreach ($this->filter_results as $fr) {
363
                    list($name, $status, $message, $attributes) = $fr;
364
365
                    $result->appendChild(
366
                        self::buildFilterElement($name, ($status ? 'passed' : 'failed'), $message, $attributes)
367
                    );
368
369
                    if ($status === false) {
370
                        $can_proceed = false;
371
                    }
372
                }
373
374 View Code Duplication
                if ($can_proceed !== true) {
375
                    $result->appendChild($post_values);
376
                    $result->setAttribute('result', 'error');
377
                    $result->appendChild(new XMLElement('message', __('Entry encountered errors when saving.'), array(
378
                        'message-id' => EventMessages::FILTER_FAILED
379
                    )));
380
                }
381
            }
382
383
            // Reset the filter results to prevent duplicates. RE: #2179
384
            $this->filter_results = array();
385
386
            return $can_proceed;
387
        }
388
389
        /**
390
         * Appends errors generated from fields during the execution of an Event
391
         *
392
         * @param XMLElement $result
393
         * @param array $fields
394
         * @param array $errors
395
         * @param object $post_values
396
         * @throws Exception
397
         * @return XMLElement
398
         */
399
        public static function appendErrors(XMLElement $result, array $fields, $errors, $post_values)
0 ignored issues
show
Coding Style introduced by
appendErrors uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
400
        {
401
            $result->setAttribute('result', 'error');
402
            $result->appendChild(new XMLElement('message', __('Entry encountered errors when saving.'), array(
403
                'message-id' => EventMessages::ENTRY_ERRORS
404
            )));
405
406
            foreach ($errors as $field_id => $message) {
407
                $field = FieldManager::fetch($field_id);
408
409
                // Do a little bit of a check for files so that we can correctly show
410
                // whether they are 'missing' or 'invalid'. If it's missing, then we
411
                // want to remove the data so `__reduceType` will correctly resolve to
412
                // missing instead of invalid.
413
                // @see https://github.com/symphonists/s3upload_field/issues/17
414
                if (isset($_FILES['fields']['error'][$field->get('element_name')])) {
415
                    $upload = $_FILES['fields']['error'][$field->get('element_name')];
416
417
                    if ($upload === UPLOAD_ERR_NO_FILE) {
418
                        unset($fields[$field->get('element_name')]);
419
                    }
420
                }
421
422
                if (is_array($fields[$field->get('element_name')])) {
423
                    $type = array_reduce($fields[$field->get('element_name')], array('SectionEvent', '__reduceType'));
424
                } else {
425
                    $type = ($fields[$field->get('element_name')] === '') ? 'missing' : 'invalid';
426
                }
427
428
                $error = self::createError($field, $type, $message);
0 ignored issues
show
Bug introduced by
It seems like $field defined by \FieldManager::fetch($field_id) on line 407 can also be of type array; however, SectionEvent::createError() does only seem to accept object<Field>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
429
                $result->appendChild($error);
430
            }
431
432
            if (isset($post_values) && is_object($post_values)) {
433
                $result->appendChild($post_values);
434
            }
435
436
            return $result;
437
        }
438
439
        /**
440
         * Given a Field instance, the type of error, and the message, this function
441
         * creates an XMLElement node so that it can be added to the `?debug` for the
442
         * Event
443
         *
444
         * @since Symphony 2.5.0
445
         * @param Field $field
446
         * @param string $type
447
         *  At the moment 'missing' or 'invalid' accepted
448
         * @param string $message
449
         * @return XMLElement
450
         */
451
        public static function createError(Field $field, $type, $message = null)
452
        {
453
            $error = new XMLElement($field->get('element_name'), null, array(
454
                'label' => General::sanitize($field->get('label')),
455
                'type' => $type,
456
                'message-id' => ($type === 'missing') ? EventMessages::FIELD_MISSING : EventMessages::FIELD_INVALID,
457
                'message' => General::sanitize($message)
458
            ));
459
460
            return $error;
461
        }
462
463
        /**
464
         * This function handles the Send Mail filter which will send an email
465
         * to each specified recipient informing them that an Entry has been
466
         * created.
467
         *
468
         * @param XMLElement $result
469
         *  The XMLElement of the XML that is going to be returned as part
470
         *  of this event to the page.
471
         * @param array $send_email
472
         *  Associative array of `send-mail` parameters.*  Associative array of `send-mail` parameters.
473
         * @param array $fields
474
         *  Array of post data to extract the values from
475
         * @param Section $section
476
         *  This current Entry that has just been updated or created
477
         * @param Entry $entry
478
         * @throws Exception
479
         * @return XMLElement
480
         *  The modified `$result` with the results of the filter.
481
         */
482
        public function processSendMailFilter(
483
            XMLElement $result,
484
            array $send_email,
485
            array &$fields,
486
            Section $section,
487
            Entry $entry
488
        ) {
489
            $fields['recipient'] = self::replaceFieldToken($send_email['recipient'], $fields);
490
            $fields['recipient'] = preg_split('/\,/i', $fields['recipient'], -1, PREG_SPLIT_NO_EMPTY);
491
            $fields['recipient'] = array_map('trim', $fields['recipient']);
492
493
            $fields['subject'] = self::replaceFieldToken($send_email['subject'], $fields,
494
                __('[Symphony] A new entry was created on %s',
495
                    array(Symphony::Configuration()->get('sitename', 'general'))));
496
            $fields['body'] = self::replaceFieldToken($send_email['body'], $fields, null, false, false);
497
            $fields['sender-email'] = self::replaceFieldToken($send_email['sender-email'], $fields);
498
            $fields['sender-name'] = self::replaceFieldToken($send_email['sender-name'], $fields);
499
500
            $fields['reply-to-name'] = self::replaceFieldToken($send_email['reply-to-name'], $fields);
501
            $fields['reply-to-email'] = self::replaceFieldToken($send_email['reply-to-email'], $fields);
502
503
            $edit_link = SYMPHONY_URL . '/publish/' . $section->get('handle') . '/edit/' . $entry->get('id') . '/';
504
            $language = Symphony::Configuration()->get('lang', 'symphony');
505
            $template_path = Event::getNotificationTemplate($language);
506
            $body = sprintf(file_get_contents($template_path), $section->get('name'), $edit_link);
507
508
            if (is_array($fields['body'])) {
509
                foreach ($fields['body'] as $field_handle => $value) {
510
                    $body .= "// $field_handle" . PHP_EOL . $value . PHP_EOL . PHP_EOL;
511
                }
512
            } else {
513
                $body .= $fields['body'];
514
            }
515
516
            // Loop over all the recipients and attempt to send them an email
517
            // Errors will be appended to the Event XML
518
            $errors = array();
519
520
            foreach ($fields['recipient'] as $recipient) {
521
                $author = AuthorManager::fetchByUsername($recipient);
522
523
                if (empty($author)) {
524
                    $errors['recipient'][$recipient] = __('Recipient not found');
525
                    continue;
526
                }
527
528
                $email = Email::create();
529
530
                // Huib: Exceptions are also thrown in the settings functions, not only in the send function.
531
                // Those Exceptions should be caught too.
532
                try {
533
                    $email->recipients = array(
0 ignored issues
show
Bug introduced by
The property recipients does not seem to exist. Did you mean _recipients?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
534
                        $author->get('first_name') => $author->get('email')
535
                    );
536
537
                    if ($fields['sender-name'] !== null) {
538
                        $email->sender_name = $fields['sender-name'];
0 ignored issues
show
Bug introduced by
The property sender_name does not seem to exist. Did you mean _sender_name?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
539
                    }
540
541
                    if ($fields['sender-email'] !== null) {
542
                        $email->sender_email_address = $fields['sender-email'];
0 ignored issues
show
Bug introduced by
The property sender_email_address does not seem to exist. Did you mean _sender_email_address?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
543
                    }
544
545
                    if ($fields['reply-to-name'] !== null) {
546
                        $email->reply_to_name = $fields['reply-to-name'];
0 ignored issues
show
Bug introduced by
The property reply_to_name does not seem to exist. Did you mean _reply_to_name?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
547
                    }
548
549
                    if ($fields['reply-to-email'] !== null) {
550
                        $email->reply_to_email_address = $fields['reply-to-email'];
0 ignored issues
show
Bug introduced by
The property reply_to_email_address does not seem to exist. Did you mean _reply_to_email_address?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
551
                    }
552
553
                    $email->text_plain = str_replace('<!-- RECIPIENT NAME -->', $author->get('first_name'), $body);
0 ignored issues
show
Bug introduced by
The property text_plain does not seem to exist. Did you mean _text_plain?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
554
                    $email->subject = $fields['subject'];
0 ignored issues
show
Bug introduced by
The property subject does not seem to exist. Did you mean _subject?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
555
                    $email->send();
556
                } catch (EmailValidationException $e) {
557
                    $errors['address'][$author->get('email')] = $e->getMessage();
558
559
                    // The current error array does not permit custom tags.
560
                    // Therefore, it is impossible to set a "proper" error message.
561
                    // Will return the failed email address instead.
562
                } catch (EmailGatewayException $e) {
563
                    $errors['gateway'][$author->get('email')] = $e->getMessage();
564
565
                    // Because we don't want symphony to break because it can not send emails,
566
                    // all exceptions are logged silently.
567
                    // Any custom event can change this behaviour.
568
                } catch (EmailException $e) {
569
                    $errors['email'][$author->get('email')] = $e->getMessage();
570
                }
571
            }
572
573
            // If there were errors, output them to the event
574
            if (!empty($errors)) {
575
                $xml = self::buildFilterElement('send-email', 'failed');
576
577
                foreach ($errors as $type => $messages) {
578
                    $xType = new XMLElement('error');
579
                    $xType->setAttribute('error-type', $type);
580
581
                    foreach ($messages as $recipient => $message) {
582
                        $xType->appendChild(
583
                            new XMLElement('message', $message, array(
584
                                'recipient' => $recipient
585
                            ))
586
                        );
587
                    }
588
589
                    $xml->appendChild($xType);
590
                }
591
592
                $result->appendChild($xml);
593
            } else {
594
                $result->appendChild(
595
                    self::buildFilterElement('send-email', 'passed')
596
                );
597
            }
598
599
            return $result;
600
        }
601
602
        /**
603
         * This function searches the `$haystack` for the given `$needle`,
604
         * where the needle is a string representation of where the desired
605
         * value exists in the `$haystack` array. For example `fields[name]`
606
         * would look in the `$haystack` for the key of `fields` that has the
607
         * key `name` and return the value.
608
         *
609
         * @param string $needle
610
         *  The needle, ie. `fields[name]`.
611
         * @param array $haystack
612
         *  Associative array to find the needle, ie.
613
         *      `array('fields' => array(
614
         *          'name' => 'Bob',
615
         *          'age' => '10'
616
         *      ))`
617
         * @param string $default
618
         *  If the `$needle` is not found, return this value. Defaults to null.
619
         * @param boolean $discard_field_name
620
         *  When matches are found in the `$haystack`, they are added to results
621
         *  array. This parameter defines if this should be an associative array
622
         *  or just an array of the matches. Used in conjunction with `$collapse`
623
         * @param boolean $collapse
624
         *  If multiple values are found, this will cause them to be reduced
625
         *  to single string with ' ' as the separator. Defaults to true.
626
         * @return string|array
627
         */
628
        public static function replaceFieldToken(
629
            $needle,
630
            $haystack,
631
            $default = null,
632
            $discard_field_name = true,
633
            $collapse = true
634
        ) {
635
            if (preg_match('/^(fields\[[^\]]+\],?)+$/i', $needle)) {
636
                $parts = preg_split('/\,/i', $needle, -1, PREG_SPLIT_NO_EMPTY);
637
                $parts = array_map('trim', $parts);
638
639
                $stack = array();
640
641
                foreach ($parts as $p) {
642
                    $field = str_replace(array('fields[', ']'), '', $p);
643
                    ($discard_field_name ? $stack[] = $haystack[$field] : $stack[$field] = $haystack[$field]);
644
                }
645
646
                if (is_array($stack) && !empty($stack)) {
647
                    return $collapse ? implode(' ', $stack) : $stack;
648
                } else {
649
                    $needle = null;
650
                }
651
            }
652
653
            $needle = trim($needle);
654
655
            if (empty($needle)) {
656
                return $default;
657
            } else {
658
                return $needle;
659
            }
660
        }
661
662
        /**
663
         * Processes all extensions attached to the `EventPostSaveFilter` delegate
664
         *
665
         * @uses EventPostSaveFilter
666
         *
667
         * @param XMLElement $result
668
         * @param array $fields
669
         * @param Entry $entry
670
         * @return XMLElement
671
         */
672 View Code Duplication
        protected function processPostSaveFilters(XMLElement $result, array $fields, Entry $entry = null)
673
        {
674
            /**
675
             * After saving entry from the front-end. This delegate will not force
676
             * the Events to terminate if it populates the `$filter_results` array.
677
             * Provided with references to this object, the `$_POST` data and also
678
             * the error array
679
             *
680
             * @delegate EventPostSaveFilter
681
             * @param string $context
682
             * '/frontend/'
683
             * @param integer $entry_id
684
             * @param array $fields
685
             * @param Entry $entry
686
             * @param Event $this
687
             * @param array $messages
688
             *  An associative array of array's which contain 4 values,
689
             *  the name of the filter (string), the status (boolean),
690
             *  the message (string) an optionally an associative array
691
             *  of additional attributes to add to the filter element.
692
             */
693
            Symphony::ExtensionManager()->notifyMembers('EventPostSaveFilter', '/frontend/', array(
694
                'entry_id' => $entry->get('id'),
0 ignored issues
show
Bug introduced by
It seems like $entry is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
695
                'fields' => $fields,
696
                'entry' => $entry,
697
                'event' => &$this,
698
                'messages' => &$this->filter_results
699
            ));
700
701
            if (is_array($this->filter_results) && !empty($this->filter_results)) {
702
                foreach ($this->filter_results as $fr) {
703
                    list($name, $status, $message, $attributes) = $fr;
704
705
                    $result->appendChild(
706
                        self::buildFilterElement($name, ($status ? 'passed' : 'failed'), $message, $attributes)
707
                    );
708
                }
709
            }
710
711
            // Reset the filter results to prevent duplicates. RE: #2179
712
            $this->filter_results = array();
713
714
            return $result;
715
        }
716
717
        /**
718
         * Processes all extensions attached to the `EventFinalSaveFilter` delegate
719
         *
720
         * @uses EventFinalSaveFilter
721
         *
722
         * @param XMLElement $result
723
         * @param array $fields
724
         * @param Entry $entry
725
         * @return XMLElement
726
         */
727 View Code Duplication
        protected function processFinalSaveFilters(XMLElement $result, array $fields, Entry $entry = null)
728
        {
729
            /**
730
             * This delegate that lets extensions know the final status of the
731
             * current Event. It is triggered when everything has processed correctly.
732
             * The `$messages` array contains the results of the previous filters that
733
             * have executed, and the `$errors` array contains any errors that have
734
             * occurred as a result of this delegate. These errors cannot stop the
735
             * processing of the Event, as that has already been done.
736
             *
737
             *
738
             * @delegate EventFinalSaveFilter
739
             * @param string $context
740
             * '/frontend/'
741
             * @param array $fields
742
             * @param Event $this
743
             * @param array $messages
744
             *  An associative array of array's which contain 4 values,
745
             *  the name of the filter (string), the status (boolean),
746
             *  the message (string) an optionally an associative array
747
             *  of additional attributes to add to the filter element.
748
             * @param array $errors
749
             *  An associative array of array's which contain 4 values,
750
             *  the name of the filter (string), the status (boolean),
751
             *  the message (string) an optionally an associative array
752
             *  of additional attributes to add to the filter element.
753
             * @param Entry $entry
754
             */
755
            Symphony::ExtensionManager()->notifyMembers('EventFinalSaveFilter', '/frontend/', array(
756
                'fields' => $fields,
757
                'event' => $this,
758
                'messages' => $this->filter_results,
759
                'errors' => &$this->filter_errors,
760
                'entry' => $entry
761
            ));
762
763
            if (is_array($this->filter_errors) && !empty($this->filter_errors)) {
764
                foreach ($this->filter_errors as $fr) {
765
                    list($name, $status, $message, $attributes) = $fr;
766
767
                    $result->appendChild(
768
                        self::buildFilterElement($name, ($status ? 'passed' : 'failed'), $message, $attributes)
769
                    );
770
                }
771
            }
772
773
            // Reset the filter results to prevent duplicates. RE: #2179
774
            $this->filter_results = array();
775
776
            return $result;
777
        }
778
    }
779
780
    /**
781
     * Basic lookup class for Event messages, allows for frontend developers
782
     * to localise and change event messages without relying on string
783
     * comparision.
784
     *
785
     * @since Symphony 2.4
786
     */
787
    class EventMessages
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
788
    {
789
        const UNKNOWN_ERROR = 0;
790
791
        const ENTRY_CREATED_SUCCESS = 100;
792
        const ENTRY_EDITED_SUCCESS = 101;
793
        const ENTRY_ERRORS = 102;
794
        const ENTRY_MISSING = 103;
795
        const ENTRY_NOT_UNIQUE = 104;
796
797
        const SECTION_MISSING = 200;
798
799
        const FIELD_MISSING = 301;
800
        const FIELD_INVALID = 302;
801
        const FIELD_NOT_UNIQUE = 303;
802
803
        const FILTER_FAILED = 400;
804
805
        const SECURITY_XSRF = 500;
806
    }
807