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.
Passed
Pull Request — master (#2835)
by
unknown
06:01
created

SectionEvent::processSendMailFilter()   F

Complexity

Conditions 15
Paths 1020

Size

Total Lines 111
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 60
c 0
b 0
f 0
nc 1020
nop 5
dl 0
loc 111
rs 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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)
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 = (new FieldManager)->select()->field($field_id)->execute()->next();
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);
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)
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)
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]);
173
            }
174
175
            if (is_array($stack) && !empty($stack)) {
176
                return $collapse ? implode(' ', $stack) : $stack;
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)
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
        $section = (new SectionManager)
319
            ->select()
320
            ->section($this->getSource())
321
            ->execute()
322
            ->next();
323
324
        if (!$section) {
0 ignored issues
show
introduced by
$section is of type Section, thus it always evaluated to true.
Loading history...
325
            $result->setAttribute('result', 'error');
326
            $result->appendChild(new XMLElement('message', __('The Section, %s, could not be found.', array($this->getSource())), array(
327
                'message-id' => EventMessages::SECTION_MISSING
328
            )));
329
            return false;
330
        }
331
332
        // Create the post data element
333
        if (!empty($fields)) {
334
            General::array_to_xml($post_values, $fields, true);
335
        }
336
337
        // If the EventPreSaveFilter fails, return early
338
        if ($this->processPreSaveFilters($result, $fields, $post_values, $entry_id) === false) {
339
            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...
340
        }
341
342
        // If the `$entry_id` is provided, check to see if it exists.
343
        // @todo If this was moved above PreSaveFilters, we can pass the
344
        // Entry object to the delegate meaning extensions don't have to
345
        // do that step.
346
        if (isset($entry_id)) {
347
            $entry = (new EntryManager)->select()->entry($entry_id)->execute()->next();
348
349
            if (!$entry) {
0 ignored issues
show
introduced by
$entry is of type Entry, thus it always evaluated to true.
Loading history...
350
                $result->setAttribute('result', 'error');
351
                $result->appendChild(new XMLElement('message', __('The Entry, %s, could not be found.', array($entry_id)), array(
352
                    'message-id' => EventMessages::ENTRY_MISSING
353
                )));
354
355
                return false;
356
            }
357
358
            // `$entry_id` wasn't provided, create a new Entry object.
359
        } else {
360
            $entry = EntryManager::create();
361
            $entry->set('section_id', $this->getSource());
362
        }
363
364
        // Validate the data. `$entry->checkPostData` loops over all fields calling
365
        // their `checkPostFieldData` function. If the return of the function is
366
        // `Entry::__ENTRY_FIELD_ERROR__` then abort the event and add the error
367
        // messages to the `$result`.
368
        if (Entry::__ENTRY_FIELD_ERROR__ == $entry->checkPostData($fields, $errors, ($entry->get('id') ? true : false))) {
369
            $result = self::appendErrors($result, $fields, $errors, $post_values);
370
            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...
371
372
            // If the data is good, process the data, almost ready to save it to the
373
            // Database. If processing fails, abort the event and display the errors
374
        } elseif (Entry::__ENTRY_OK__ != $entry->setDataFromPost($fields, $errors, false, ($entry->get('id') ? true : false))) {
375
            $result = self::appendErrors($result, $fields, $errors, $post_values);
376
            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...
377
378
            // Data is checked, data has been processed, by trying to save the
379
            // Entry caused an error to occur, so abort and return.
380
        } elseif ($entry->commit() === false) {
381
            $result->setAttribute('result', 'error');
382
            $result->appendChild(new XMLElement('message', __('Unknown errors where encountered when saving.'), array(
383
                '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...
384
            )));
385
386
            if (isset($post_values) && is_object($post_values)) {
387
                $result->appendChild($post_values);
388
            }
389
390
            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...
391
392
            // Entry was created, add the good news to the return `$result`
393
        } else {
394
            $result->setAttributeArray(array(
395
                'result' => 'success',
396
                'type' => (isset($entry_id) ? 'edited' : 'created'),
397
                'id' => $entry->get('id')
398
            ));
399
400
            if (isset($entry_id)) {
401
                $result->appendChild(new XMLElement('message', __('Entry edited successfully.'), array(
402
                    'message-id' => EventMessages::ENTRY_EDITED_SUCCESS
403
                )));
404
            } else {
405
                $result->appendChild(new XMLElement('message', __('Entry created successfully.'), array(
406
                    'message-id' => EventMessages::ENTRY_CREATED_SUCCESS
407
                )));
408
            }
409
        }
410
411
        // PASSIVE FILTERS ONLY AT THIS STAGE. ENTRY HAS ALREADY BEEN CREATED.
412
        if (in_array('send-email', $this->eParamFILTERS) && !in_array('expect-multiple', $this->eParamFILTERS)) {
413
            $result = $this->processSendMailFilter($result, $_POST['send-email'], $fields, $section, $entry);
414
        }
415
416
        $result = $this->processPostSaveFilters($result, $fields, $entry);
417
        $result = $this->processFinalSaveFilters($result, $fields, $entry);
418
419
        if (isset($post_values) && is_object($post_values)) {
420
            $result->appendChild($post_values);
421
        }
422
423
        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...
424
    }
425
426
    /**
427
     * Processes all extensions attached to the `EventPreSaveFilter` delegate
428
     *
429
     * @uses EventPreSaveFilter
430
     *
431
     * @param XMLElement $result
432
     * @param array $fields
433
     * @param XMLElement $post_values
434
     * @param integer $entry_id
435
     * @return boolean
436
     */
437
    protected function processPreSaveFilters(XMLElement $result, array &$fields, XMLElement &$post_values, $entry_id = null)
438
    {
439
        $can_proceed = true;
440
441
        /**
442
         * Prior to saving entry from the front-end. This delegate will
443
         * force the Event to terminate if it populates the `$filter_results`
444
         * array. All parameters are passed by reference.
445
         *
446
         * @delegate EventPreSaveFilter
447
         * @param string $context
448
         * '/frontend/'
449
         * @param array $fields
450
         * @param Event $this
451
         * @param array $messages
452
         *  An associative array of array's which contain 4 values,
453
         *  the name of the filter (string), the status (boolean),
454
         *  the message (string) an optionally an associative array
455
         *  of additional attributes to add to the filter element.
456
         * @param XMLElement $post_values
457
         * @param integer $entry_id
458
         *  If editing an entry, this parameter will be an integer,
459
         *  otherwise null.
460
         */
461
        Symphony::ExtensionManager()->notifyMembers(
462
            'EventPreSaveFilter',
463
            '/frontend/',
464
            array(
465
                'fields' => &$fields,
466
                'event' => &$this,
467
                'messages' => &$this->filter_results,
468
                'post_values' => &$post_values,
469
                'entry_id' => $entry_id
470
            )
471
        );
472
473
        // Logic taken from `event.section.php` to fail should any `$this->filter_results`
474
        // be returned. This delegate can cause the event to exit early.
475
        if (is_array($this->filter_results) && !empty($this->filter_results)) {
476
            $can_proceed = true;
477
478
            foreach ($this->filter_results as $fr) {
479
                list($name, $status, $message, $attributes) = array_pad($fr, 4, null);
480
481
                $result->appendChild(
482
                    self::buildFilterElement($name, ($status ? 'passed' : 'failed'), $message, $attributes)
483
                );
484
485
                if ($status === false) {
486
                    $can_proceed = false;
487
                }
488
            }
489
490
            if ($can_proceed !== true) {
491
                $result->appendChild($post_values);
492
                $result->setAttribute('result', 'error');
493
                $result->appendChild(new XMLElement('message', __('Entry encountered errors when saving.'), array(
494
                    'message-id' => EventMessages::FILTER_FAILED
495
                )));
496
            }
497
        }
498
499
        // Reset the filter results to prevent duplicates. RE: #2179
500
        $this->filter_results = array();
501
        return $can_proceed;
502
    }
503
504
    /**
505
     * Processes all extensions attached to the `EventPostSaveFilter` delegate
506
     *
507
     * @uses EventPostSaveFilter
508
     *
509
     * @param XMLElement $result
510
     * @param array $fields
511
     * @param Entry $entry
512
     * @return XMLElement
513
     */
514
    protected function processPostSaveFilters(XMLElement $result, array $fields, Entry $entry = null)
515
    {
516
        /**
517
         * After saving entry from the front-end. This delegate will not force
518
         * the Events to terminate if it populates the `$filter_results` array.
519
         * Provided with references to this object, the `$_POST` data and also
520
         * the error array
521
         *
522
         * @delegate EventPostSaveFilter
523
         * @param string $context
524
         * '/frontend/'
525
         * @param integer $entry_id
526
         * @param array $fields
527
         * @param Entry $entry
528
         * @param Event $this
529
         * @param array $messages
530
         *  An associative array of array's which contain 4 values,
531
         *  the name of the filter (string), the status (boolean),
532
         *  the message (string) an optionally an associative array
533
         *  of additional attributes to add to the filter element.
534
         */
535
        Symphony::ExtensionManager()->notifyMembers('EventPostSaveFilter', '/frontend/', array(
536
            '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

536
            '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...
537
            'fields' => $fields,
538
            'entry' => $entry,
539
            'event' => &$this,
540
            'messages' => &$this->filter_results
541
        ));
542
543
        if (is_array($this->filter_results) && !empty($this->filter_results)) {
544
            foreach ($this->filter_results as $fr) {
545
                list($name, $status, $message, $attributes) = $fr;
546
547
                $result->appendChild(
548
                    self::buildFilterElement($name, ($status ? 'passed' : 'failed'), $message, $attributes)
549
                );
550
            }
551
        }
552
553
        // Reset the filter results to prevent duplicates. RE: #2179
554
        $this->filter_results = array();
555
        return $result;
556
    }
557
558
    /**
559
     * Processes all extensions attached to the `EventFinalSaveFilter` delegate
560
     *
561
     * @uses EventFinalSaveFilter
562
     *
563
     * @param XMLElement $result
564
     * @param array $fields
565
     * @param Entry $entry
566
     * @return XMLElement
567
     */
568
    protected function processFinalSaveFilters(XMLElement $result, array $fields, Entry $entry = null)
569
    {
570
        /**
571
         * This delegate that lets extensions know the final status of the
572
         * current Event. It is triggered when everything has processed correctly.
573
         * The `$messages` array contains the results of the previous filters that
574
         * have executed, and the `$errors` array contains any errors that have
575
         * occurred as a result of this delegate. These errors cannot stop the
576
         * processing of the Event, as that has already been done.
577
         *
578
         *
579
         * @delegate EventFinalSaveFilter
580
         * @param string $context
581
         * '/frontend/'
582
         * @param array $fields
583
         * @param Event $this
584
         * @param array $messages
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 array $errors
590
         *  An associative array of array's which contain 4 values,
591
         *  the name of the filter (string), the status (boolean),
592
         *  the message (string) an optionally an associative array
593
         *  of additional attributes to add to the filter element.
594
         * @param Entry $entry
595
         */
596
        Symphony::ExtensionManager()->notifyMembers('EventFinalSaveFilter', '/frontend/', array(
597
            'fields'    => $fields,
598
            'event'     => $this,
599
            'messages'  => $this->filter_results,
600
            'errors'    => &$this->filter_errors,
601
            'entry'     => $entry
602
        ));
603
604
        if (is_array($this->filter_errors) && !empty($this->filter_errors)) {
605
            foreach ($this->filter_errors as $fr) {
606
                list($name, $status, $message, $attributes) = $fr;
607
608
                $result->appendChild(
609
                    self::buildFilterElement($name, ($status ? 'passed' : 'failed'), $message, $attributes)
610
                );
611
            }
612
        }
613
614
        // Reset the filter results to prevent duplicates. RE: #2179
615
        $this->filter_results = array();
616
        return $result;
617
    }
618
619
    /**
620
     * This function handles the Send Mail filter which will send an email
621
     * to each specified recipient informing them that an Entry has been
622
     * created.
623
     *
624
     * @param XMLElement $result
625
     *  The XMLElement of the XML that is going to be returned as part
626
     *  of this event to the page.
627
     * @param array $send_email
628
     *  Associative array of `send-mail` parameters.*  Associative array of `send-mail` parameters.
629
     * @param array $fields
630
     *  Array of post data to extract the values from
631
     * @param Section $section
632
     *  This current Entry that has just been updated or created
633
     * @param Entry $entry
634
     * @throws Exception
635
     * @return XMLElement
636
     *  The modified `$result` with the results of the filter.
637
     */
638
    public function processSendMailFilter(XMLElement $result, array $send_email, array &$fields, Section $section, Entry $entry)
639
    {
640
        $fields['recipient']        = self::replaceFieldToken($send_email['recipient'], $fields);
641
        $fields['recipient']        = preg_split('/\,/i', $fields['recipient'], -1, PREG_SPLIT_NO_EMPTY);
642
        $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

642
        $fields['recipient']        = array_map('trim', /** @scrutinizer ignore-type */ $fields['recipient']);
Loading history...
643
644
        $fields['subject']          = self::replaceFieldToken($send_email['subject'], $fields, __('[Symphony] A new entry was created on %s', array(Symphony::Configuration()->get('sitename', 'general'))));
645
        $fields['body']             = self::replaceFieldToken($send_email['body'], $fields, null, false, false);
646
        $fields['sender-email']     = self::replaceFieldToken($send_email['sender-email'], $fields);
647
        $fields['sender-name']      = self::replaceFieldToken($send_email['sender-name'], $fields);
648
649
        $fields['reply-to-name']    = self::replaceFieldToken($send_email['reply-to-name'], $fields);
650
        $fields['reply-to-email']   = self::replaceFieldToken($send_email['reply-to-email'], $fields);
651
652
        $edit_link = SYMPHONY_URL . '/publish/' . $section->get('handle') . '/edit/' . $entry->get('id').'/';
0 ignored issues
show
Bug introduced by
Are you sure $entry->get('id') of type null|array|mixed 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

652
        $edit_link = SYMPHONY_URL . '/publish/' . $section->get('handle') . '/edit/' . /** @scrutinizer ignore-type */ $entry->get('id').'/';
Loading history...
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 string|array 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

652
        $edit_link = SYMPHONY_URL . '/publish/' . /** @scrutinizer ignore-type */ $section->get('handle') . '/edit/' . $entry->get('id').'/';
Loading history...
653
        $language = Symphony::Configuration()->get('lang', 'symphony');
654
        $template_path = Event::getNotificationTemplate($language);
0 ignored issues
show
Bug introduced by
It seems like $language can also be of type array; however, parameter $language of Event::getNotificationTemplate() 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

654
        $template_path = Event::getNotificationTemplate(/** @scrutinizer ignore-type */ $language);
Loading history...
655
        $body = sprintf(file_get_contents($template_path), $section->get('name'), $edit_link);
0 ignored issues
show
Bug introduced by
It seems like $template_path can also be of type false; however, parameter $filename of file_get_contents() 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

655
        $body = sprintf(file_get_contents(/** @scrutinizer ignore-type */ $template_path), $section->get('name'), $edit_link);
Loading history...
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

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