Completed
Branch master (fbc6d5)
by
unknown
15:54 queued 10:26
created
core/db_classes/EE_CSV.class.php 1 patch
Indentation   +660 added lines, -660 removed lines patch added patch discarded remove patch
@@ -13,664 +13,664 @@
 block discarded – undo
13 13
  */
14 14
 class EE_CSV
15 15
 {
16
-    // instance of the EE_CSV object
17
-    private static $_instance = null;
18
-
19
-
20
-    // multidimensional array to store update & error messages
21
-    // var $_notices = array( 'updates' => array(), 'errors' => array() );
22
-
23
-
24
-    private $_primary_keys;
25
-
26
-    /**
27
-     * @var EE_Registry
28
-     */
29
-    private $EE;
30
-    /**
31
-     * string used for 1st cell in exports, which indicates that the following 2 rows will be metadata keys and values
32
-     */
33
-    const metadata_header = 'Event Espresso Export Meta Data';
34
-
35
-    /**
36
-     * private constructor to prevent direct creation
37
-     *
38
-     * @return void
39
-     */
40
-    private function __construct()
41
-    {
42
-        global $wpdb;
43
-
44
-        $this->_primary_keys = array(
45
-            $wpdb->prefix . 'esp_answer'                  => array('ANS_ID'),
46
-            $wpdb->prefix . 'esp_attendee'                => array('ATT_ID'),
47
-            $wpdb->prefix . 'esp_datetime'                => array('DTT_ID'),
48
-            $wpdb->prefix . 'esp_event_question_group'    => array('EQG_ID'),
49
-            $wpdb->prefix . 'esp_message_template'        => array('MTP_ID'),
50
-            $wpdb->prefix . 'esp_payment'                 => array('PAY_ID'),
51
-            $wpdb->prefix . 'esp_price'                   => array('PRC_ID'),
52
-            $wpdb->prefix . 'esp_price_type'              => array('PRT_ID'),
53
-            $wpdb->prefix . 'esp_question'                => array('QST_ID'),
54
-            $wpdb->prefix . 'esp_question_group'          => array('QSG_ID'),
55
-            $wpdb->prefix . 'esp_question_group_question' => array('QGQ_ID'),
56
-            $wpdb->prefix . 'esp_question_option'         => array('QSO_ID'),
57
-            $wpdb->prefix . 'esp_registration'            => array('REG_ID'),
58
-            $wpdb->prefix . 'esp_status'                  => array('STS_ID'),
59
-            $wpdb->prefix . 'esp_transaction'             => array('TXN_ID'),
60
-            $wpdb->prefix . 'esp_transaction'             => array('TXN_ID'),
61
-            $wpdb->prefix . 'events_detail'               => array('id'),
62
-            $wpdb->prefix . 'events_category_detail'      => array('id'),
63
-            $wpdb->prefix . 'events_category_rel'         => array('id'),
64
-            $wpdb->prefix . 'events_venue'                => array('id'),
65
-            $wpdb->prefix . 'events_venue_rel'            => array('emeta_id'),
66
-            $wpdb->prefix . 'events_locale'               => array('id'),
67
-            $wpdb->prefix . 'events_locale_rel'           => array('id'),
68
-            $wpdb->prefix . 'events_personnel'            => array('id'),
69
-            $wpdb->prefix . 'events_personnel_rel'        => array('id'),
70
-        );
71
-    }
72
-
73
-
74
-    /**
75
-     * singleton method used to instantiate class object
76
-     *
77
-     * @return EE_CSV
78
-     */
79
-    public static function instance()
80
-    {
81
-        // check if class object is instantiated
82
-        if (self::$_instance === null or ! is_object(self::$_instance) or ! (self::$_instance instanceof EE_CSV)) {
83
-            self::$_instance = new self();
84
-        }
85
-        return self::$_instance;
86
-    }
87
-
88
-    /**
89
-     * Opens a unicode or utf file (normal file_get_contents has difficulty reading ga unicode file)
90
-     * @see http://stackoverflow.com/questions/15092764/how-to-read-unicode-text-file-in-php
91
-     *
92
-     * @param string $file_path
93
-     * @return string
94
-     * @throws EE_Error
95
-     */
96
-    private function read_unicode_file($file_path)
97
-    {
98
-        $fc = "";
99
-        $fh = fopen($file_path, "rb");
100
-        if (! $fh) {
101
-            throw new EE_Error(sprintf(esc_html__("Cannot open file for read: %s<br>\n", 'event_espresso'), $file_path));
102
-        }
103
-        $flen = filesize($file_path);
104
-        $bc = fread($fh, $flen);
105
-        for ($i = 0; $i < $flen; $i++) {
106
-            $c = substr($bc, $i, 1);
107
-            if ((ord($c) != 0) && (ord($c) != 13)) {
108
-                $fc = $fc . $c;
109
-            }
110
-        }
111
-        if ((ord(substr($fc, 0, 1)) == 255) && (ord(substr($fc, 1, 1)) == 254)) {
112
-            $fc = substr($fc, 2);
113
-        }
114
-        return ($fc);
115
-    }
116
-
117
-
118
-    /**
119
-     * Generic CSV-functionality to turn an entire CSV file into a single array that's
120
-     * NOT in a specific format to EE. It's just a 2-level array, with top-level arrays
121
-     * representing each row in the CSV file, and the second-level arrays being each column in that row
122
-     *
123
-     * @param string $path_to_file
124
-     * @return array of arrays. Top-level array has rows, second-level array has each item
125
-     */
126
-    public function import_csv_to_multi_dimensional_array($path_to_file)
127
-    {
128
-        // needed to deal with Mac line endings
129
-        ini_set('auto_detect_line_endings', true);
130
-
131
-        // because fgetcsv does not correctly deal with backslashed quotes such as \"
132
-        // we'll read the file into a string
133
-        $file_contents = $this->read_unicode_file($path_to_file);
134
-        // replace backslashed quotes with CSV enclosures
135
-        $file_contents = str_replace('\\"', '"""', $file_contents);
136
-        // HEY YOU! PUT THAT FILE BACK!!!
137
-        file_put_contents($path_to_file, $file_contents);
138
-
139
-        if (($file_handle = fopen($path_to_file, "r")) !== false) {
140
-            # Set the parent multidimensional array key to 0.
141
-            $nn = 0;
142
-            $csvarray = array();
143
-
144
-            // in PHP 5.3 fgetcsv accepts a 5th parameter, but the pre 5.3 versions of fgetcsv choke if passed more than 4 - is that crazy or what?
145
-            if (version_compare(PHP_VERSION, '5.3.0') < 0) {
146
-                //  PHP 5.2- version
147
-                // loop through each row of the file
148
-                while (($data = fgetcsv($file_handle, 0, ',', '"')) !== false) {
149
-                    $csvarray[] = $data;
150
-                }
151
-            } else {
152
-                // loop through each row of the file
153
-                while (($data = fgetcsv($file_handle, 0, ',', '"', '\\')) !== false) {
154
-                    $csvarray[] = $data;
155
-                }
156
-            }
157
-            # Close the File.
158
-            fclose($file_handle);
159
-            return $csvarray;
160
-        } else {
161
-            EE_Error::add_error(
162
-                sprintf(esc_html__("An error occurred - the file: %s could not opened.", "event_espresso"), $path_to_file),
163
-                __FILE__,
164
-                __FUNCTION__,
165
-                __LINE__
166
-            );
167
-            return false;
168
-        }
169
-    }
170
-
171
-
172
-    /**
173
-     * Import contents of csv file and store values in an array to be manipulated by other functions
174
-     * @param string  $path_to_file         - the csv file to be imported including the path to it's location.
175
-     *                                      If $model_name is provided, assumes that each row in the CSV represents a
176
-     *                                      model object for that model If $model_name ISN'T provided, assumes that
177
-     *                                      before model object data, there is a row where the first entry is simply
178
-     *                                      'MODEL', and next entry is the model's name, (untranslated) like Event, and
179
-     *                                      then maybe a row of headers, and then the model data. Eg.
180
-     *                                      '<br>MODEL,Event,<br>EVT_ID,EVT_name,...<br>1,Monkey
181
-     *                                      Party,...<br>2,Llamarama,...<br>MODEL,Venue,<br>VNU_ID,VNU_name<br>1,The
182
-     *                                      Forest
183
-     * @param string  $model_name           model name if we know what model we're importing
184
-     * @param boolean $first_row_is_headers - whether the first row of data is headers or not - TRUE = headers, FALSE =
185
-     *                                      data
186
-     * @return mixed - array on success - multi dimensional with headers as keys (if headers exist) OR string on fail -
187
-     *               error message like the following array('Event'=>array( array('EVT_ID'=>1,'EVT_name'=>'bob
188
-     *               party',...), array('EVT_ID'=>2,'EVT_name'=>'llamarama',...),
189
-     *                                      ...
190
-     *                                      )
191
-     *                                      'Venue'=>array(
192
-     *                                      array('VNU_ID'=>1,'VNU_name'=>'the shack',...),
193
-     *                                      array('VNU_ID'=>2,'VNU_name'=>'tree house',...),
194
-     *                                      ...
195
-     *                                      )
196
-     *                                      ...
197
-     *                                      )
198
-     */
199
-    public function import_csv_to_model_data_array($path_to_file, $model_name = false, $first_row_is_headers = true)
200
-    {
201
-        $multi_dimensional_array = $this->import_csv_to_multi_dimensional_array($path_to_file);
202
-        if (! $multi_dimensional_array) {
203
-            return false;
204
-        }
205
-        // gotta start somewhere
206
-        $row = 1;
207
-        // array to store csv data in
208
-        $ee_formatted_data = array();
209
-        // array to store headers (column names)
210
-        $headers = array();
211
-        foreach ($multi_dimensional_array as $data) {
212
-            // if first cell is MODEL, then second cell is the MODEL name
213
-            if ($data[0] == 'MODEL') {
214
-                $model_name = $data[1];
215
-                // don't bother looking for model data in this row. The rest of this
216
-                // row should be blank
217
-                // AND pretend this is the first row again
218
-                $row = 1;
219
-                // reset headers
220
-                $headers = array();
221
-                continue;
222
-            }
223
-            if (strpos($data[0], EE_CSV::metadata_header) !== false) {
224
-                $model_name = EE_CSV::metadata_header;
225
-                // store like model data, we just won't try importing it etc.
226
-                $row = 1;
227
-                continue;
228
-            }
229
-
230
-
231
-            // how many columns are there?
232
-            $columns = count($data);
233
-
234
-            $model_entry = array();
235
-            // loop through each column
236
-            for ($i = 0; $i < $columns; $i++) {
237
-                // replace csv_enclosures with backslashed quotes
238
-                $data[ $i ] = str_replace('"""', '\\"', $data[ $i ]);
239
-                // do we need to grab the column names?
240
-                if ($row === 1) {
241
-                    if ($first_row_is_headers) {
242
-                        // store the column names to use for keys
243
-                        $column_name = $data[ $i ];
244
-                        // check it's not blank... sometimes CSV editign programs adda bunch of empty columns onto the end...
245
-                        if (! $column_name) {
246
-                            continue;
247
-                        }
248
-                        $matches = array();
249
-                        if ($model_name == EE_CSV::metadata_header) {
250
-                            $headers[ $i ] = $column_name;
251
-                        } else {
252
-                            // now get the db table name from it (the part between square brackets)
253
-                            $success = preg_match('~(.*)\[(.*)\]~', $column_name, $matches);
254
-                            if (! $success) {
255
-                                EE_Error::add_error(
256
-                                    sprintf(
257
-                                        esc_html__(
258
-                                            "The column titled %s is invalid for importing. It must be be in the format of 'Nice Name[model_field_name]' in row %s",
259
-                                            "event_espresso"
260
-                                        ),
261
-                                        $column_name,
262
-                                        implode(",", $data)
263
-                                    ),
264
-                                    __FILE__,
265
-                                    __FUNCTION__,
266
-                                    __LINE__
267
-                                );
268
-                                return false;
269
-                            }
270
-                            $headers[ $i ] = $matches[2];
271
-                        }
272
-                    } else {
273
-                        // no column names means our final array will just use counters for keys
274
-                        $model_entry[ $headers[ $i ] ] = $data[ $i ];
275
-                        $headers[ $i ] = $i;
276
-                    }
277
-                    // and we need to store csv data
278
-                } else {
279
-                    // this column isn' ta header, store it if there is a header for it
280
-                    if (isset($headers[ $i ])) {
281
-                        $model_entry[ $headers[ $i ] ] = $data[ $i ];
282
-                    }
283
-                }
284
-            }
285
-            // save the row's data IF it's a non-header-row
286
-            if (! $first_row_is_headers || ($first_row_is_headers && $row > 1)) {
287
-                $ee_formatted_data[ $model_name ][] = $model_entry;
288
-            }
289
-            // advance to next row
290
-            $row++;
291
-        }
292
-
293
-        // delete the uploaded file
294
-        unlink($path_to_file);
295
-        // echo '<pre style="height:auto;border:2px solid lightblue;">' . print_r( $ee_formatted_data, TRUE ) . '</pre><br /><span style="font-size:10px;font-weight:normal;">' . __FILE__ . '<br />line no: ' . __LINE__ . '</span>';
296
-        // die();
297
-
298
-        // it's good to give back
299
-        return $ee_formatted_data;
300
-    }
301
-
302
-
303
-    public function save_csv_to_db($csv_data_array, $model_name = false)
304
-    {
305
-        EE_Error::doing_it_wrong(
306
-            'save_csv_to_db',
307
-            esc_html__(
308
-                'Function moved to EE_Import and renamed to save_csv_data_array_to_db',
309
-                'event_espresso'
310
-            ),
311
-            '4.6.7'
312
-        );
313
-        return EE_Import::instance()->save_csv_data_array_to_db($csv_data_array, $model_name);
314
-    }
315
-
316
-    /**
317
-     * Sends HTTP headers to indicate that the browser should download a file,
318
-     * and starts writing the file to PHP's output. Returns the file handle so other functions can
319
-     * also write to it
320
-     *
321
-     * @param string $new_filename the name of the file that the user will download
322
-     * @return resource, like the results of fopen(), which can be used for fwrite, fputcsv2, etc.
323
-     */
324
-    public function begin_sending_csv($filename)
325
-    {
326
-        // grab file extension
327
-        $ext = substr(strrchr($filename, '.'), 1);
328
-        if ($ext == '.csv' or $ext == '.xls') {
329
-            str_replace($ext, '', $filename);
330
-        }
331
-        $filename .= '.csv';
332
-
333
-        // if somebody's been naughty and already started outputting stuff, trash it
334
-        // and start writing our stuff.
335
-        if (ob_get_length()) {
336
-            @ob_flush();
337
-            @flush();
338
-            @ob_end_flush();
339
-        }
340
-        @ob_start();
341
-        header("Pragma: public");
342
-        header("Expires: 0");
343
-        header("Pragma: no-cache");
344
-        header("Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
345
-        // header("Content-Type: application/force-download");
346
-        // header("Content-Type: application/octet-stream");
347
-        // header("Content-Type: application/download");
348
-        header('Content-disposition: attachment; filename=' . $filename);
349
-        header("Content-Type: text/csv; charset=utf-8");
350
-        do_action('AHEE__EE_CSV__begin_sending_csv__headers');
351
-        echo apply_filters(
352
-            'FHEE__EE_CSV__begin_sending_csv__start_writing',
353
-            "\xEF\xBB\xBF"
354
-        ); // makes excel open it as UTF-8. UTF-8 BOM, see http://stackoverflow.com/a/4440143/2773835
355
-        $fh = fopen('php://output', 'w');
356
-        return $fh;
357
-    }
358
-
359
-    /**
360
-     * Writes some meta data to the CSV as a bunch of columns. Initially we're only
361
-     * mentioning the version and timezone
362
-     *
363
-     * @param resource $filehandle
364
-     */
365
-    public function write_metadata_to_csv($filehandle)
366
-    {
367
-        $data_row = array(EE_CSV::metadata_header);// do NOT translate because this exact string is used when importing
368
-        $this->fputcsv2($filehandle, $data_row);
369
-        $meta_data = array(
370
-            0 => array(
371
-                'version'        => espresso_version(),
372
-                'timezone'       => EEH_DTT_Helper::get_timezone(),
373
-                'time_of_export' => current_time('mysql'),
374
-                'site_url'       => is_ssl() ? str_replace('http://', 'https://', site_url()) : site_url(),
375
-            ),
376
-        );
377
-        $this->write_data_array_to_csv($filehandle, $meta_data);
378
-    }
379
-
380
-
381
-    /**
382
-     * Writes $data to the csv file open in $filehandle. uses the array indices of $data for column headers
383
-     *
384
-     * @param array   $data                 2D array, first numerically-indexed, and next-level-down preferably indexed
385
-     *                                      by string
386
-     * @param boolean $add_csv_column_names whether or not we should add the keys in the bottom-most array as a row for
387
-     *                                      headers in the CSV. Eg, if $data looked like
388
-     *                                      array(0=>array('EVT_ID'=>1,'EVT_name'=>'monkey'...), 1=>array(...),...))
389
-     *                                      then the first row we'd write to the CSV would be "EVT_ID,EVT_name,..."
390
-     * @return boolean if we successfully wrote to the CSV or not. If there's no $data, we consider that a success
391
-     *                 (because we wrote everything there was...nothing)
392
-     */
393
-    public function write_data_array_to_csv($filehandle, $data)
394
-    {
395
-
396
-
397
-        // determine if $data is actually a 2d array
398
-        if ($data && is_array($data) && is_array(EEH_Array::get_one_item_from_array($data))) {
399
-            // make sure top level is numerically indexed,
400
-
401
-            if (EEH_Array::is_associative_array($data)) {
402
-                throw new EE_Error(
403
-                    sprintf(
404
-                        esc_html__(
405
-                            "top-level array must be numerically indexed. Does these look like numbers to you? %s",
406
-                            "event_espresso"
407
-                        ),
408
-                        implode(",", array_keys($data))
409
-                    )
410
-                );
411
-            }
412
-            $item_in_top_level_array = EEH_Array::get_one_item_from_array($data);
413
-            // now, is the last item in the top-level array of $data an associative or numeric array?
414
-            if (EEH_Array::is_associative_array($item_in_top_level_array)) {
415
-                // its associative, so we want to output its keys as column headers
416
-                $keys = array_keys($item_in_top_level_array);
417
-                $this->fputcsv2($filehandle, $keys);
418
-            }
419
-            // start writing data
420
-            foreach ($data as $data_row) {
421
-                $this->fputcsv2($filehandle, $data_row);
422
-            }
423
-            return true;
424
-        } else {
425
-            // no data TO write... so we can assume that's a success
426
-            return true;
427
-        }
428
-        // //if 2nd level is indexed by strings, use those as csv column headers (ie, the first row)
429
-        //
430
-        //
431
-        // $no_table = TRUE;
432
-        //
433
-        // // loop through data and add each row to the file/stream as csv
434
-        // foreach ( $data as $model_name => $model_data ) {
435
-        // // test first row to see if it is data or a model name
436
-        // $model = EE_Registry::instance();->load_model($model_name);
437
-        // //if the model really exists,
438
-        // if ( $model ) {
439
-        //
440
-        // // we have a table name
441
-        // $no_table = FALSE;
442
-        //
443
-        // // put the tablename into an array cuz that's how fputcsv rolls
444
-        // $model_name_row = array( 'MODEL', $model_name );
445
-        //
446
-        // // add table name to csv output
447
-        // echo self::fputcsv2($filehandle, $model_name_row);
448
-        //
449
-        // // now get the rest of the data
450
-        // foreach ( $model_data as $row ) {
451
-        // // output the row
452
-        // echo self::fputcsv2($filehandle, $row);
453
-        // }
454
-        //
455
-        // }
456
-        //
457
-        // if ( $no_table ) {
458
-        // // no table so just put the data
459
-        // echo self::fputcsv2($filehandle, $model_data);
460
-        // }
461
-        //
462
-        // } // END OF foreach ( $data )
463
-    }
464
-
465
-    /**
466
-     * Should be called after begin_sending_csv(), and one or more write_data_array_to_csv()s.
467
-     * Calls exit to prevent polluting the CSV file with other junk
468
-     *
469
-     * @param resource $fh filehandle where we're writing the CSV to
470
-     */
471
-    public function end_sending_csv($fh)
472
-    {
473
-        fclose($fh);
474
-        exit(0);
475
-    }
476
-
477
-    /**
478
-     * Given an open file, writes all the model data to it in the format the importer expects.
479
-     * Usually preceded by begin_sending_csv($filename), and followed by end_sending_csv($filehandle).
480
-     *
481
-     * @param resource $filehandle
482
-     * @param array    $model_data_array is assumed to be a 3d array: 1st layer has keys of model names (eg 'Event'),
483
-     *                                   next layer is numerically indexed to represent each model object (eg, each
484
-     *                                   individual event), and the last layer has all the attributes o fthat model
485
-     *                                   object (eg, the event's id, name, etc)
486
-     * @return boolean success
487
-     */
488
-    public function write_model_data_to_csv($filehandle, $model_data_array)
489
-    {
490
-        $this->write_metadata_to_csv($filehandle);
491
-        foreach ($model_data_array as $model_name => $model_instance_arrays) {
492
-            // first: output a special row stating the model
493
-            $this->fputcsv2($filehandle, array('MODEL', $model_name));
494
-            // if we have items to put in the CSV, do it normally
495
-
496
-            if (! empty($model_instance_arrays)) {
497
-                $this->write_data_array_to_csv($filehandle, $model_instance_arrays);
498
-            } else {
499
-                // echo "no data to write... so just write the headers";
500
-                // so there's actually NO model objects for that model.
501
-                // probably still want to show the columns
502
-                $model = EE_Registry::instance()->load_model($model_name);
503
-                $column_names = array();
504
-                foreach ($model->field_settings() as $field) {
505
-                    $column_names[ $field->get_nicename() . "[" . $field->get_name() . "]" ] = null;
506
-                }
507
-                $this->write_data_array_to_csv($filehandle, array($column_names));
508
-            }
509
-        }
510
-    }
511
-
512
-    /**
513
-     * Writes the CSV file to the output buffer, with rows corresponding to $model_data_array,
514
-     * and dies (in order to avoid other plugins from messing up the csv output)
515
-     *
516
-     * @param string $filename         the filename you want to give the file
517
-     * @param array  $model_data_array 3d array, as described in EE_CSV::write_model_data_to_csv()
518
-     * @return bool | void writes CSV file to output and dies
519
-     */
520
-    public function export_multiple_model_data_to_csv($filename, $model_data_array)
521
-    {
522
-        $filehandle = $this->begin_sending_csv($filename);
523
-        $this->write_model_data_to_csv($filehandle, $model_data_array);
524
-        $this->end_sending_csv($filehandle);
525
-    }
526
-
527
-    /**
528
-     * Export contents of an array to csv file
529
-     * @param array  $data     - the array of data to be converted to csv and exported
530
-     * @param string $filename - name for newly created csv file
531
-     * @return TRUE on success, FALSE on fail
532
-     */
533
-    public function export_array_to_csv($data = false, $filename = false)
534
-    {
535
-
536
-        // no data file?? get outta here
537
-        if (! $data or ! is_array($data) or empty($data)) {
538
-            return false;
539
-        }
540
-
541
-        // no filename?? get outta here
542
-        if (! $filename) {
543
-            return false;
544
-        }
545
-
546
-
547
-        // somebody told me i might need this ???
548
-        global $wpdb;
549
-        $prefix = $wpdb->prefix;
550
-
551
-
552
-        $fh = $this->begin_sending_csv($filename);
553
-
554
-
555
-        $this->end_sending_csv($fh);
556
-    }
557
-
558
-
559
-    /**
560
-     * Determine the maximum upload file size based on php.ini settings
561
-     * @param int $percent_of_max - desired percentage of the max upload_mb
562
-     * @return int KB
563
-     */
564
-    public function get_max_upload_size($percent_of_max = false)
565
-    {
566
-
567
-        $max_upload = (int) (ini_get('upload_max_filesize'));
568
-        $max_post = (int) (ini_get('post_max_size'));
569
-        $memory_limit = (int) (ini_get('memory_limit'));
570
-
571
-        // determine the smallest of the three values from above
572
-        $upload_mb = min($max_upload, $max_post, $memory_limit);
573
-
574
-        // convert MB to KB
575
-        $upload_mb = $upload_mb * 1024;
576
-
577
-        // don't want the full monty? then reduce the max uplaod size
578
-        if ($percent_of_max) {
579
-            // is percent_of_max like this -> 50 or like this -> 0.50 ?
580
-            if ($percent_of_max > 1) {
581
-                // chnages 50 to 0.50
582
-                $percent_of_max = $percent_of_max / 100;
583
-            }
584
-            // make upload_mb a percentage of the max upload_mb
585
-            $upload_mb = $upload_mb * $percent_of_max;
586
-        }
587
-
588
-        return $upload_mb;
589
-    }
590
-
591
-
592
-    /**
593
-     * Drop   in replacement for PHP's fputcsv function - but this one works!!!
594
-     * @param resource $fh         - file handle - what we are writing to
595
-     * @param array    $row        - individual row of csv data
596
-     * @param string   $delimiter  - csv delimiter
597
-     * @param string   $enclosure  - csv enclosure
598
-     * @param string   $mysql_null - allows php NULL to be overridden with MySQl's insertable NULL value
599
-     * @return void
600
-     */
601
-    private function fputcsv2($fh, array $row, $delimiter = ',', $enclosure = '"', $mysql_null = false)
602
-    {
603
-        // Allow user to filter the csv delimiter and enclosure for other countries csv standards
604
-        $delimiter = apply_filters('FHEE__EE_CSV__fputcsv2__delimiter', $delimiter);
605
-        $enclosure = apply_filters('FHEE__EE_CSV__fputcsv2__enclosure', $enclosure);
606
-
607
-        $delimiter_esc = preg_quote($delimiter, '/');
608
-        $enclosure_esc = preg_quote($enclosure, '/');
609
-
610
-        $output = array();
611
-        foreach ($row as $field_value) {
612
-            if (is_object($field_value) || is_array($field_value)) {
613
-                $field_value = serialize($field_value);
614
-            }
615
-            if ($field_value === null && $mysql_null) {
616
-                $output[] = 'NULL';
617
-                continue;
618
-            }
619
-
620
-            $output[] = preg_match("/(?:${delimiter_esc}|${enclosure_esc}|\s)/", $field_value) ?
621
-                ($enclosure . str_replace($enclosure, $enclosure . $enclosure, $field_value) . $enclosure)
622
-                : $field_value;
623
-        }
624
-
625
-        fwrite($fh, join($delimiter, $output) . PHP_EOL);
626
-    }
627
-
628
-
629
-    // /**
630
-    //  * CSV    Import / Export messages
631
-    //  * @return void
632
-    //  */
633
-    // public function csv_admin_notices()
634
-    // {
635
-    //
636
-    //     // We play both kinds of music here! Country AND Western! - err... I mean, cycle through both types of notices
637
-    //     foreach (array('updates', 'errors') as $type) {
638
-    //
639
-    //         // if particular notice type is not empty, then "You've got Mail"
640
-    //         if (! empty($this->_notices[ $type ])) {
641
-    //
642
-    //             // is it an update or an error ?
643
-    //             $msg_class = $type == 'updates' ? 'updated' : 'error';
644
-    //             echo '<div id="message" class="' . $msg_class . '">';
645
-    //             // display each notice, however many that may be
646
-    //             foreach ($this->_notices[ $type ] as $message) {
647
-    //                 echo '<p>' . $message . '</p>';
648
-    //             }
649
-    //             // wrap it up
650
-    //             echo '</div>';
651
-    //         }
652
-    //     }
653
-    // }
654
-
655
-    /**
656
-     * Gets the date format to use in teh csv. filterable
657
-     *
658
-     * @param string $current_format
659
-     * @return string
660
-     */
661
-    public function get_date_format_for_csv($current_format = null)
662
-    {
663
-        return apply_filters('FHEE__EE_CSV__get_date_format_for_csv__format', 'Y-m-d', $current_format);
664
-    }
665
-
666
-    /**
667
-     * Gets the time format we want to use in CSV reports. Filterable
668
-     *
669
-     * @param string $current_format
670
-     * @return string
671
-     */
672
-    public function get_time_format_for_csv($current_format = null)
673
-    {
674
-        return apply_filters('FHEE__EE_CSV__get_time_format_for_csv__format', 'H:i:s', $current_format);
675
-    }
16
+	// instance of the EE_CSV object
17
+	private static $_instance = null;
18
+
19
+
20
+	// multidimensional array to store update & error messages
21
+	// var $_notices = array( 'updates' => array(), 'errors' => array() );
22
+
23
+
24
+	private $_primary_keys;
25
+
26
+	/**
27
+	 * @var EE_Registry
28
+	 */
29
+	private $EE;
30
+	/**
31
+	 * string used for 1st cell in exports, which indicates that the following 2 rows will be metadata keys and values
32
+	 */
33
+	const metadata_header = 'Event Espresso Export Meta Data';
34
+
35
+	/**
36
+	 * private constructor to prevent direct creation
37
+	 *
38
+	 * @return void
39
+	 */
40
+	private function __construct()
41
+	{
42
+		global $wpdb;
43
+
44
+		$this->_primary_keys = array(
45
+			$wpdb->prefix . 'esp_answer'                  => array('ANS_ID'),
46
+			$wpdb->prefix . 'esp_attendee'                => array('ATT_ID'),
47
+			$wpdb->prefix . 'esp_datetime'                => array('DTT_ID'),
48
+			$wpdb->prefix . 'esp_event_question_group'    => array('EQG_ID'),
49
+			$wpdb->prefix . 'esp_message_template'        => array('MTP_ID'),
50
+			$wpdb->prefix . 'esp_payment'                 => array('PAY_ID'),
51
+			$wpdb->prefix . 'esp_price'                   => array('PRC_ID'),
52
+			$wpdb->prefix . 'esp_price_type'              => array('PRT_ID'),
53
+			$wpdb->prefix . 'esp_question'                => array('QST_ID'),
54
+			$wpdb->prefix . 'esp_question_group'          => array('QSG_ID'),
55
+			$wpdb->prefix . 'esp_question_group_question' => array('QGQ_ID'),
56
+			$wpdb->prefix . 'esp_question_option'         => array('QSO_ID'),
57
+			$wpdb->prefix . 'esp_registration'            => array('REG_ID'),
58
+			$wpdb->prefix . 'esp_status'                  => array('STS_ID'),
59
+			$wpdb->prefix . 'esp_transaction'             => array('TXN_ID'),
60
+			$wpdb->prefix . 'esp_transaction'             => array('TXN_ID'),
61
+			$wpdb->prefix . 'events_detail'               => array('id'),
62
+			$wpdb->prefix . 'events_category_detail'      => array('id'),
63
+			$wpdb->prefix . 'events_category_rel'         => array('id'),
64
+			$wpdb->prefix . 'events_venue'                => array('id'),
65
+			$wpdb->prefix . 'events_venue_rel'            => array('emeta_id'),
66
+			$wpdb->prefix . 'events_locale'               => array('id'),
67
+			$wpdb->prefix . 'events_locale_rel'           => array('id'),
68
+			$wpdb->prefix . 'events_personnel'            => array('id'),
69
+			$wpdb->prefix . 'events_personnel_rel'        => array('id'),
70
+		);
71
+	}
72
+
73
+
74
+	/**
75
+	 * singleton method used to instantiate class object
76
+	 *
77
+	 * @return EE_CSV
78
+	 */
79
+	public static function instance()
80
+	{
81
+		// check if class object is instantiated
82
+		if (self::$_instance === null or ! is_object(self::$_instance) or ! (self::$_instance instanceof EE_CSV)) {
83
+			self::$_instance = new self();
84
+		}
85
+		return self::$_instance;
86
+	}
87
+
88
+	/**
89
+	 * Opens a unicode or utf file (normal file_get_contents has difficulty reading ga unicode file)
90
+	 * @see http://stackoverflow.com/questions/15092764/how-to-read-unicode-text-file-in-php
91
+	 *
92
+	 * @param string $file_path
93
+	 * @return string
94
+	 * @throws EE_Error
95
+	 */
96
+	private function read_unicode_file($file_path)
97
+	{
98
+		$fc = "";
99
+		$fh = fopen($file_path, "rb");
100
+		if (! $fh) {
101
+			throw new EE_Error(sprintf(esc_html__("Cannot open file for read: %s<br>\n", 'event_espresso'), $file_path));
102
+		}
103
+		$flen = filesize($file_path);
104
+		$bc = fread($fh, $flen);
105
+		for ($i = 0; $i < $flen; $i++) {
106
+			$c = substr($bc, $i, 1);
107
+			if ((ord($c) != 0) && (ord($c) != 13)) {
108
+				$fc = $fc . $c;
109
+			}
110
+		}
111
+		if ((ord(substr($fc, 0, 1)) == 255) && (ord(substr($fc, 1, 1)) == 254)) {
112
+			$fc = substr($fc, 2);
113
+		}
114
+		return ($fc);
115
+	}
116
+
117
+
118
+	/**
119
+	 * Generic CSV-functionality to turn an entire CSV file into a single array that's
120
+	 * NOT in a specific format to EE. It's just a 2-level array, with top-level arrays
121
+	 * representing each row in the CSV file, and the second-level arrays being each column in that row
122
+	 *
123
+	 * @param string $path_to_file
124
+	 * @return array of arrays. Top-level array has rows, second-level array has each item
125
+	 */
126
+	public function import_csv_to_multi_dimensional_array($path_to_file)
127
+	{
128
+		// needed to deal with Mac line endings
129
+		ini_set('auto_detect_line_endings', true);
130
+
131
+		// because fgetcsv does not correctly deal with backslashed quotes such as \"
132
+		// we'll read the file into a string
133
+		$file_contents = $this->read_unicode_file($path_to_file);
134
+		// replace backslashed quotes with CSV enclosures
135
+		$file_contents = str_replace('\\"', '"""', $file_contents);
136
+		// HEY YOU! PUT THAT FILE BACK!!!
137
+		file_put_contents($path_to_file, $file_contents);
138
+
139
+		if (($file_handle = fopen($path_to_file, "r")) !== false) {
140
+			# Set the parent multidimensional array key to 0.
141
+			$nn = 0;
142
+			$csvarray = array();
143
+
144
+			// in PHP 5.3 fgetcsv accepts a 5th parameter, but the pre 5.3 versions of fgetcsv choke if passed more than 4 - is that crazy or what?
145
+			if (version_compare(PHP_VERSION, '5.3.0') < 0) {
146
+				//  PHP 5.2- version
147
+				// loop through each row of the file
148
+				while (($data = fgetcsv($file_handle, 0, ',', '"')) !== false) {
149
+					$csvarray[] = $data;
150
+				}
151
+			} else {
152
+				// loop through each row of the file
153
+				while (($data = fgetcsv($file_handle, 0, ',', '"', '\\')) !== false) {
154
+					$csvarray[] = $data;
155
+				}
156
+			}
157
+			# Close the File.
158
+			fclose($file_handle);
159
+			return $csvarray;
160
+		} else {
161
+			EE_Error::add_error(
162
+				sprintf(esc_html__("An error occurred - the file: %s could not opened.", "event_espresso"), $path_to_file),
163
+				__FILE__,
164
+				__FUNCTION__,
165
+				__LINE__
166
+			);
167
+			return false;
168
+		}
169
+	}
170
+
171
+
172
+	/**
173
+	 * Import contents of csv file and store values in an array to be manipulated by other functions
174
+	 * @param string  $path_to_file         - the csv file to be imported including the path to it's location.
175
+	 *                                      If $model_name is provided, assumes that each row in the CSV represents a
176
+	 *                                      model object for that model If $model_name ISN'T provided, assumes that
177
+	 *                                      before model object data, there is a row where the first entry is simply
178
+	 *                                      'MODEL', and next entry is the model's name, (untranslated) like Event, and
179
+	 *                                      then maybe a row of headers, and then the model data. Eg.
180
+	 *                                      '<br>MODEL,Event,<br>EVT_ID,EVT_name,...<br>1,Monkey
181
+	 *                                      Party,...<br>2,Llamarama,...<br>MODEL,Venue,<br>VNU_ID,VNU_name<br>1,The
182
+	 *                                      Forest
183
+	 * @param string  $model_name           model name if we know what model we're importing
184
+	 * @param boolean $first_row_is_headers - whether the first row of data is headers or not - TRUE = headers, FALSE =
185
+	 *                                      data
186
+	 * @return mixed - array on success - multi dimensional with headers as keys (if headers exist) OR string on fail -
187
+	 *               error message like the following array('Event'=>array( array('EVT_ID'=>1,'EVT_name'=>'bob
188
+	 *               party',...), array('EVT_ID'=>2,'EVT_name'=>'llamarama',...),
189
+	 *                                      ...
190
+	 *                                      )
191
+	 *                                      'Venue'=>array(
192
+	 *                                      array('VNU_ID'=>1,'VNU_name'=>'the shack',...),
193
+	 *                                      array('VNU_ID'=>2,'VNU_name'=>'tree house',...),
194
+	 *                                      ...
195
+	 *                                      )
196
+	 *                                      ...
197
+	 *                                      )
198
+	 */
199
+	public function import_csv_to_model_data_array($path_to_file, $model_name = false, $first_row_is_headers = true)
200
+	{
201
+		$multi_dimensional_array = $this->import_csv_to_multi_dimensional_array($path_to_file);
202
+		if (! $multi_dimensional_array) {
203
+			return false;
204
+		}
205
+		// gotta start somewhere
206
+		$row = 1;
207
+		// array to store csv data in
208
+		$ee_formatted_data = array();
209
+		// array to store headers (column names)
210
+		$headers = array();
211
+		foreach ($multi_dimensional_array as $data) {
212
+			// if first cell is MODEL, then second cell is the MODEL name
213
+			if ($data[0] == 'MODEL') {
214
+				$model_name = $data[1];
215
+				// don't bother looking for model data in this row. The rest of this
216
+				// row should be blank
217
+				// AND pretend this is the first row again
218
+				$row = 1;
219
+				// reset headers
220
+				$headers = array();
221
+				continue;
222
+			}
223
+			if (strpos($data[0], EE_CSV::metadata_header) !== false) {
224
+				$model_name = EE_CSV::metadata_header;
225
+				// store like model data, we just won't try importing it etc.
226
+				$row = 1;
227
+				continue;
228
+			}
229
+
230
+
231
+			// how many columns are there?
232
+			$columns = count($data);
233
+
234
+			$model_entry = array();
235
+			// loop through each column
236
+			for ($i = 0; $i < $columns; $i++) {
237
+				// replace csv_enclosures with backslashed quotes
238
+				$data[ $i ] = str_replace('"""', '\\"', $data[ $i ]);
239
+				// do we need to grab the column names?
240
+				if ($row === 1) {
241
+					if ($first_row_is_headers) {
242
+						// store the column names to use for keys
243
+						$column_name = $data[ $i ];
244
+						// check it's not blank... sometimes CSV editign programs adda bunch of empty columns onto the end...
245
+						if (! $column_name) {
246
+							continue;
247
+						}
248
+						$matches = array();
249
+						if ($model_name == EE_CSV::metadata_header) {
250
+							$headers[ $i ] = $column_name;
251
+						} else {
252
+							// now get the db table name from it (the part between square brackets)
253
+							$success = preg_match('~(.*)\[(.*)\]~', $column_name, $matches);
254
+							if (! $success) {
255
+								EE_Error::add_error(
256
+									sprintf(
257
+										esc_html__(
258
+											"The column titled %s is invalid for importing. It must be be in the format of 'Nice Name[model_field_name]' in row %s",
259
+											"event_espresso"
260
+										),
261
+										$column_name,
262
+										implode(",", $data)
263
+									),
264
+									__FILE__,
265
+									__FUNCTION__,
266
+									__LINE__
267
+								);
268
+								return false;
269
+							}
270
+							$headers[ $i ] = $matches[2];
271
+						}
272
+					} else {
273
+						// no column names means our final array will just use counters for keys
274
+						$model_entry[ $headers[ $i ] ] = $data[ $i ];
275
+						$headers[ $i ] = $i;
276
+					}
277
+					// and we need to store csv data
278
+				} else {
279
+					// this column isn' ta header, store it if there is a header for it
280
+					if (isset($headers[ $i ])) {
281
+						$model_entry[ $headers[ $i ] ] = $data[ $i ];
282
+					}
283
+				}
284
+			}
285
+			// save the row's data IF it's a non-header-row
286
+			if (! $first_row_is_headers || ($first_row_is_headers && $row > 1)) {
287
+				$ee_formatted_data[ $model_name ][] = $model_entry;
288
+			}
289
+			// advance to next row
290
+			$row++;
291
+		}
292
+
293
+		// delete the uploaded file
294
+		unlink($path_to_file);
295
+		// echo '<pre style="height:auto;border:2px solid lightblue;">' . print_r( $ee_formatted_data, TRUE ) . '</pre><br /><span style="font-size:10px;font-weight:normal;">' . __FILE__ . '<br />line no: ' . __LINE__ . '</span>';
296
+		// die();
297
+
298
+		// it's good to give back
299
+		return $ee_formatted_data;
300
+	}
301
+
302
+
303
+	public function save_csv_to_db($csv_data_array, $model_name = false)
304
+	{
305
+		EE_Error::doing_it_wrong(
306
+			'save_csv_to_db',
307
+			esc_html__(
308
+				'Function moved to EE_Import and renamed to save_csv_data_array_to_db',
309
+				'event_espresso'
310
+			),
311
+			'4.6.7'
312
+		);
313
+		return EE_Import::instance()->save_csv_data_array_to_db($csv_data_array, $model_name);
314
+	}
315
+
316
+	/**
317
+	 * Sends HTTP headers to indicate that the browser should download a file,
318
+	 * and starts writing the file to PHP's output. Returns the file handle so other functions can
319
+	 * also write to it
320
+	 *
321
+	 * @param string $new_filename the name of the file that the user will download
322
+	 * @return resource, like the results of fopen(), which can be used for fwrite, fputcsv2, etc.
323
+	 */
324
+	public function begin_sending_csv($filename)
325
+	{
326
+		// grab file extension
327
+		$ext = substr(strrchr($filename, '.'), 1);
328
+		if ($ext == '.csv' or $ext == '.xls') {
329
+			str_replace($ext, '', $filename);
330
+		}
331
+		$filename .= '.csv';
332
+
333
+		// if somebody's been naughty and already started outputting stuff, trash it
334
+		// and start writing our stuff.
335
+		if (ob_get_length()) {
336
+			@ob_flush();
337
+			@flush();
338
+			@ob_end_flush();
339
+		}
340
+		@ob_start();
341
+		header("Pragma: public");
342
+		header("Expires: 0");
343
+		header("Pragma: no-cache");
344
+		header("Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
345
+		// header("Content-Type: application/force-download");
346
+		// header("Content-Type: application/octet-stream");
347
+		// header("Content-Type: application/download");
348
+		header('Content-disposition: attachment; filename=' . $filename);
349
+		header("Content-Type: text/csv; charset=utf-8");
350
+		do_action('AHEE__EE_CSV__begin_sending_csv__headers');
351
+		echo apply_filters(
352
+			'FHEE__EE_CSV__begin_sending_csv__start_writing',
353
+			"\xEF\xBB\xBF"
354
+		); // makes excel open it as UTF-8. UTF-8 BOM, see http://stackoverflow.com/a/4440143/2773835
355
+		$fh = fopen('php://output', 'w');
356
+		return $fh;
357
+	}
358
+
359
+	/**
360
+	 * Writes some meta data to the CSV as a bunch of columns. Initially we're only
361
+	 * mentioning the version and timezone
362
+	 *
363
+	 * @param resource $filehandle
364
+	 */
365
+	public function write_metadata_to_csv($filehandle)
366
+	{
367
+		$data_row = array(EE_CSV::metadata_header);// do NOT translate because this exact string is used when importing
368
+		$this->fputcsv2($filehandle, $data_row);
369
+		$meta_data = array(
370
+			0 => array(
371
+				'version'        => espresso_version(),
372
+				'timezone'       => EEH_DTT_Helper::get_timezone(),
373
+				'time_of_export' => current_time('mysql'),
374
+				'site_url'       => is_ssl() ? str_replace('http://', 'https://', site_url()) : site_url(),
375
+			),
376
+		);
377
+		$this->write_data_array_to_csv($filehandle, $meta_data);
378
+	}
379
+
380
+
381
+	/**
382
+	 * Writes $data to the csv file open in $filehandle. uses the array indices of $data for column headers
383
+	 *
384
+	 * @param array   $data                 2D array, first numerically-indexed, and next-level-down preferably indexed
385
+	 *                                      by string
386
+	 * @param boolean $add_csv_column_names whether or not we should add the keys in the bottom-most array as a row for
387
+	 *                                      headers in the CSV. Eg, if $data looked like
388
+	 *                                      array(0=>array('EVT_ID'=>1,'EVT_name'=>'monkey'...), 1=>array(...),...))
389
+	 *                                      then the first row we'd write to the CSV would be "EVT_ID,EVT_name,..."
390
+	 * @return boolean if we successfully wrote to the CSV or not. If there's no $data, we consider that a success
391
+	 *                 (because we wrote everything there was...nothing)
392
+	 */
393
+	public function write_data_array_to_csv($filehandle, $data)
394
+	{
395
+
396
+
397
+		// determine if $data is actually a 2d array
398
+		if ($data && is_array($data) && is_array(EEH_Array::get_one_item_from_array($data))) {
399
+			// make sure top level is numerically indexed,
400
+
401
+			if (EEH_Array::is_associative_array($data)) {
402
+				throw new EE_Error(
403
+					sprintf(
404
+						esc_html__(
405
+							"top-level array must be numerically indexed. Does these look like numbers to you? %s",
406
+							"event_espresso"
407
+						),
408
+						implode(",", array_keys($data))
409
+					)
410
+				);
411
+			}
412
+			$item_in_top_level_array = EEH_Array::get_one_item_from_array($data);
413
+			// now, is the last item in the top-level array of $data an associative or numeric array?
414
+			if (EEH_Array::is_associative_array($item_in_top_level_array)) {
415
+				// its associative, so we want to output its keys as column headers
416
+				$keys = array_keys($item_in_top_level_array);
417
+				$this->fputcsv2($filehandle, $keys);
418
+			}
419
+			// start writing data
420
+			foreach ($data as $data_row) {
421
+				$this->fputcsv2($filehandle, $data_row);
422
+			}
423
+			return true;
424
+		} else {
425
+			// no data TO write... so we can assume that's a success
426
+			return true;
427
+		}
428
+		// //if 2nd level is indexed by strings, use those as csv column headers (ie, the first row)
429
+		//
430
+		//
431
+		// $no_table = TRUE;
432
+		//
433
+		// // loop through data and add each row to the file/stream as csv
434
+		// foreach ( $data as $model_name => $model_data ) {
435
+		// // test first row to see if it is data or a model name
436
+		// $model = EE_Registry::instance();->load_model($model_name);
437
+		// //if the model really exists,
438
+		// if ( $model ) {
439
+		//
440
+		// // we have a table name
441
+		// $no_table = FALSE;
442
+		//
443
+		// // put the tablename into an array cuz that's how fputcsv rolls
444
+		// $model_name_row = array( 'MODEL', $model_name );
445
+		//
446
+		// // add table name to csv output
447
+		// echo self::fputcsv2($filehandle, $model_name_row);
448
+		//
449
+		// // now get the rest of the data
450
+		// foreach ( $model_data as $row ) {
451
+		// // output the row
452
+		// echo self::fputcsv2($filehandle, $row);
453
+		// }
454
+		//
455
+		// }
456
+		//
457
+		// if ( $no_table ) {
458
+		// // no table so just put the data
459
+		// echo self::fputcsv2($filehandle, $model_data);
460
+		// }
461
+		//
462
+		// } // END OF foreach ( $data )
463
+	}
464
+
465
+	/**
466
+	 * Should be called after begin_sending_csv(), and one or more write_data_array_to_csv()s.
467
+	 * Calls exit to prevent polluting the CSV file with other junk
468
+	 *
469
+	 * @param resource $fh filehandle where we're writing the CSV to
470
+	 */
471
+	public function end_sending_csv($fh)
472
+	{
473
+		fclose($fh);
474
+		exit(0);
475
+	}
476
+
477
+	/**
478
+	 * Given an open file, writes all the model data to it in the format the importer expects.
479
+	 * Usually preceded by begin_sending_csv($filename), and followed by end_sending_csv($filehandle).
480
+	 *
481
+	 * @param resource $filehandle
482
+	 * @param array    $model_data_array is assumed to be a 3d array: 1st layer has keys of model names (eg 'Event'),
483
+	 *                                   next layer is numerically indexed to represent each model object (eg, each
484
+	 *                                   individual event), and the last layer has all the attributes o fthat model
485
+	 *                                   object (eg, the event's id, name, etc)
486
+	 * @return boolean success
487
+	 */
488
+	public function write_model_data_to_csv($filehandle, $model_data_array)
489
+	{
490
+		$this->write_metadata_to_csv($filehandle);
491
+		foreach ($model_data_array as $model_name => $model_instance_arrays) {
492
+			// first: output a special row stating the model
493
+			$this->fputcsv2($filehandle, array('MODEL', $model_name));
494
+			// if we have items to put in the CSV, do it normally
495
+
496
+			if (! empty($model_instance_arrays)) {
497
+				$this->write_data_array_to_csv($filehandle, $model_instance_arrays);
498
+			} else {
499
+				// echo "no data to write... so just write the headers";
500
+				// so there's actually NO model objects for that model.
501
+				// probably still want to show the columns
502
+				$model = EE_Registry::instance()->load_model($model_name);
503
+				$column_names = array();
504
+				foreach ($model->field_settings() as $field) {
505
+					$column_names[ $field->get_nicename() . "[" . $field->get_name() . "]" ] = null;
506
+				}
507
+				$this->write_data_array_to_csv($filehandle, array($column_names));
508
+			}
509
+		}
510
+	}
511
+
512
+	/**
513
+	 * Writes the CSV file to the output buffer, with rows corresponding to $model_data_array,
514
+	 * and dies (in order to avoid other plugins from messing up the csv output)
515
+	 *
516
+	 * @param string $filename         the filename you want to give the file
517
+	 * @param array  $model_data_array 3d array, as described in EE_CSV::write_model_data_to_csv()
518
+	 * @return bool | void writes CSV file to output and dies
519
+	 */
520
+	public function export_multiple_model_data_to_csv($filename, $model_data_array)
521
+	{
522
+		$filehandle = $this->begin_sending_csv($filename);
523
+		$this->write_model_data_to_csv($filehandle, $model_data_array);
524
+		$this->end_sending_csv($filehandle);
525
+	}
526
+
527
+	/**
528
+	 * Export contents of an array to csv file
529
+	 * @param array  $data     - the array of data to be converted to csv and exported
530
+	 * @param string $filename - name for newly created csv file
531
+	 * @return TRUE on success, FALSE on fail
532
+	 */
533
+	public function export_array_to_csv($data = false, $filename = false)
534
+	{
535
+
536
+		// no data file?? get outta here
537
+		if (! $data or ! is_array($data) or empty($data)) {
538
+			return false;
539
+		}
540
+
541
+		// no filename?? get outta here
542
+		if (! $filename) {
543
+			return false;
544
+		}
545
+
546
+
547
+		// somebody told me i might need this ???
548
+		global $wpdb;
549
+		$prefix = $wpdb->prefix;
550
+
551
+
552
+		$fh = $this->begin_sending_csv($filename);
553
+
554
+
555
+		$this->end_sending_csv($fh);
556
+	}
557
+
558
+
559
+	/**
560
+	 * Determine the maximum upload file size based on php.ini settings
561
+	 * @param int $percent_of_max - desired percentage of the max upload_mb
562
+	 * @return int KB
563
+	 */
564
+	public function get_max_upload_size($percent_of_max = false)
565
+	{
566
+
567
+		$max_upload = (int) (ini_get('upload_max_filesize'));
568
+		$max_post = (int) (ini_get('post_max_size'));
569
+		$memory_limit = (int) (ini_get('memory_limit'));
570
+
571
+		// determine the smallest of the three values from above
572
+		$upload_mb = min($max_upload, $max_post, $memory_limit);
573
+
574
+		// convert MB to KB
575
+		$upload_mb = $upload_mb * 1024;
576
+
577
+		// don't want the full monty? then reduce the max uplaod size
578
+		if ($percent_of_max) {
579
+			// is percent_of_max like this -> 50 or like this -> 0.50 ?
580
+			if ($percent_of_max > 1) {
581
+				// chnages 50 to 0.50
582
+				$percent_of_max = $percent_of_max / 100;
583
+			}
584
+			// make upload_mb a percentage of the max upload_mb
585
+			$upload_mb = $upload_mb * $percent_of_max;
586
+		}
587
+
588
+		return $upload_mb;
589
+	}
590
+
591
+
592
+	/**
593
+	 * Drop   in replacement for PHP's fputcsv function - but this one works!!!
594
+	 * @param resource $fh         - file handle - what we are writing to
595
+	 * @param array    $row        - individual row of csv data
596
+	 * @param string   $delimiter  - csv delimiter
597
+	 * @param string   $enclosure  - csv enclosure
598
+	 * @param string   $mysql_null - allows php NULL to be overridden with MySQl's insertable NULL value
599
+	 * @return void
600
+	 */
601
+	private function fputcsv2($fh, array $row, $delimiter = ',', $enclosure = '"', $mysql_null = false)
602
+	{
603
+		// Allow user to filter the csv delimiter and enclosure for other countries csv standards
604
+		$delimiter = apply_filters('FHEE__EE_CSV__fputcsv2__delimiter', $delimiter);
605
+		$enclosure = apply_filters('FHEE__EE_CSV__fputcsv2__enclosure', $enclosure);
606
+
607
+		$delimiter_esc = preg_quote($delimiter, '/');
608
+		$enclosure_esc = preg_quote($enclosure, '/');
609
+
610
+		$output = array();
611
+		foreach ($row as $field_value) {
612
+			if (is_object($field_value) || is_array($field_value)) {
613
+				$field_value = serialize($field_value);
614
+			}
615
+			if ($field_value === null && $mysql_null) {
616
+				$output[] = 'NULL';
617
+				continue;
618
+			}
619
+
620
+			$output[] = preg_match("/(?:${delimiter_esc}|${enclosure_esc}|\s)/", $field_value) ?
621
+				($enclosure . str_replace($enclosure, $enclosure . $enclosure, $field_value) . $enclosure)
622
+				: $field_value;
623
+		}
624
+
625
+		fwrite($fh, join($delimiter, $output) . PHP_EOL);
626
+	}
627
+
628
+
629
+	// /**
630
+	//  * CSV    Import / Export messages
631
+	//  * @return void
632
+	//  */
633
+	// public function csv_admin_notices()
634
+	// {
635
+	//
636
+	//     // We play both kinds of music here! Country AND Western! - err... I mean, cycle through both types of notices
637
+	//     foreach (array('updates', 'errors') as $type) {
638
+	//
639
+	//         // if particular notice type is not empty, then "You've got Mail"
640
+	//         if (! empty($this->_notices[ $type ])) {
641
+	//
642
+	//             // is it an update or an error ?
643
+	//             $msg_class = $type == 'updates' ? 'updated' : 'error';
644
+	//             echo '<div id="message" class="' . $msg_class . '">';
645
+	//             // display each notice, however many that may be
646
+	//             foreach ($this->_notices[ $type ] as $message) {
647
+	//                 echo '<p>' . $message . '</p>';
648
+	//             }
649
+	//             // wrap it up
650
+	//             echo '</div>';
651
+	//         }
652
+	//     }
653
+	// }
654
+
655
+	/**
656
+	 * Gets the date format to use in teh csv. filterable
657
+	 *
658
+	 * @param string $current_format
659
+	 * @return string
660
+	 */
661
+	public function get_date_format_for_csv($current_format = null)
662
+	{
663
+		return apply_filters('FHEE__EE_CSV__get_date_format_for_csv__format', 'Y-m-d', $current_format);
664
+	}
665
+
666
+	/**
667
+	 * Gets the time format we want to use in CSV reports. Filterable
668
+	 *
669
+	 * @param string $current_format
670
+	 * @return string
671
+	 */
672
+	public function get_time_format_for_csv($current_format = null)
673
+	{
674
+		return apply_filters('FHEE__EE_CSV__get_time_format_for_csv__format', 'H:i:s', $current_format);
675
+	}
676 676
 }
Please login to merge, or discard this patch.
core/domain/entities/routing/data_nodes/EventEspressoData.php 1 patch
Indentation   +36 added lines, -36 removed lines patch added patch discarded remove patch
@@ -20,40 +20,40 @@
 block discarded – undo
20 20
  */
21 21
 class EventEspressoData extends PrimaryJsonDataNode
22 22
 {
23
-    const NODE_NAME = 'eventEspressoData';
24
-
25
-    private Api $api;
26
-
27
-    private Config $config;
28
-
29
-    private JedLocaleData $jed_locale;
30
-
31
-
32
-    /**
33
-     * @param Api $api
34
-     * @param Config $config
35
-     * @param JedLocaleData         $jed_locale
36
-     * @param JsonDataNodeValidator $validator
37
-     */
38
-    public function __construct(Api $api, Config $config, JedLocaleData $jed_locale, JsonDataNodeValidator $validator)
39
-    {
40
-        parent::__construct($validator);
41
-        $this->api = $api;
42
-        $this->config = $config;
43
-        $this->jed_locale = $jed_locale;
44
-        $this->setNodeName(EventEspressoData::NODE_NAME);
45
-    }
46
-
47
-
48
-    /**
49
-     * @throws DomainException
50
-     * @since 5.0.0.p
51
-     */
52
-    public function initialize()
53
-    {
54
-        $this->addDataNode($this->api);
55
-        $this->addDataNode($this->config);
56
-        $this->addData('i18n', $this->jed_locale->getData());
57
-        $this->setInitialized(true);
58
-    }
23
+	const NODE_NAME = 'eventEspressoData';
24
+
25
+	private Api $api;
26
+
27
+	private Config $config;
28
+
29
+	private JedLocaleData $jed_locale;
30
+
31
+
32
+	/**
33
+	 * @param Api $api
34
+	 * @param Config $config
35
+	 * @param JedLocaleData         $jed_locale
36
+	 * @param JsonDataNodeValidator $validator
37
+	 */
38
+	public function __construct(Api $api, Config $config, JedLocaleData $jed_locale, JsonDataNodeValidator $validator)
39
+	{
40
+		parent::__construct($validator);
41
+		$this->api = $api;
42
+		$this->config = $config;
43
+		$this->jed_locale = $jed_locale;
44
+		$this->setNodeName(EventEspressoData::NODE_NAME);
45
+	}
46
+
47
+
48
+	/**
49
+	 * @throws DomainException
50
+	 * @since 5.0.0.p
51
+	 */
52
+	public function initialize()
53
+	{
54
+		$this->addDataNode($this->api);
55
+		$this->addDataNode($this->config);
56
+		$this->addData('i18n', $this->jed_locale->getData());
57
+		$this->setInitialized(true);
58
+	}
59 59
 }
Please login to merge, or discard this patch.
core/domain/entities/routing/data_nodes/core/Capabilities.php 1 patch
Indentation   +25 added lines, -25 removed lines patch added patch discarded remove patch
@@ -7,33 +7,33 @@
 block discarded – undo
7 7
 
8 8
 class Capabilities extends JsonDataNode
9 9
 {
10
-    const NODE_NAME = 'capabilities';
10
+	const NODE_NAME = 'capabilities';
11 11
 
12 12
 
13
-    /**
14
-     * @param JsonDataNodeValidator $validator
15
-     */
16
-    public function __construct(JsonDataNodeValidator $validator)
17
-    {
18
-        parent::__construct($validator);
19
-        $this->setNodeName(Capabilities::NODE_NAME);
20
-    }
13
+	/**
14
+	 * @param JsonDataNodeValidator $validator
15
+	 */
16
+	public function __construct(JsonDataNodeValidator $validator)
17
+	{
18
+		parent::__construct($validator);
19
+		$this->setNodeName(Capabilities::NODE_NAME);
20
+	}
21 21
 
22 22
 
23
-    /**
24
-     * @inheritDoc
25
-     */
26
-    public function initialize()
27
-    {
28
-        $current_user      = wp_get_current_user();
29
-        $capabilities      = [];
30
-        $role_capabilities = $current_user->get_role_caps();
31
-        foreach ($role_capabilities as $capability => $you_can_do_it) {
32
-            if ($you_can_do_it) {
33
-                $capabilities[] = $capability;
34
-            }
35
-        }
36
-        sort($capabilities, SORT_NATURAL);
37
-        $this->setDataArray($capabilities);
38
-    }
23
+	/**
24
+	 * @inheritDoc
25
+	 */
26
+	public function initialize()
27
+	{
28
+		$current_user      = wp_get_current_user();
29
+		$capabilities      = [];
30
+		$role_capabilities = $current_user->get_role_caps();
31
+		foreach ($role_capabilities as $capability => $you_can_do_it) {
32
+			if ($you_can_do_it) {
33
+				$capabilities[] = $capability;
34
+			}
35
+		}
36
+		sort($capabilities, SORT_NATURAL);
37
+		$this->setDataArray($capabilities);
38
+	}
39 39
 }
Please login to merge, or discard this patch.
core/domain/entities/routing/handlers/admin/NonEspressoAdminAjax.php 1 patch
Indentation   +60 added lines, -60 removed lines patch added patch discarded remove patch
@@ -21,68 +21,68 @@
 block discarded – undo
21 21
  */
22 22
 class NonEspressoAdminAjax extends Route
23 23
 {
24
-    /**
25
-     * returns true if the current request matches this route
26
-     *
27
-     * @return bool
28
-     * @since   5.0.0.p
29
-     */
30
-    public function matchesCurrentRequest(): bool
31
-    {
32
-        return $this->request->isOtherAjax();
33
-    }
24
+	/**
25
+	 * returns true if the current request matches this route
26
+	 *
27
+	 * @return bool
28
+	 * @since   5.0.0.p
29
+	 */
30
+	public function matchesCurrentRequest(): bool
31
+	{
32
+		return $this->request->isOtherAjax();
33
+	}
34 34
 
35 35
 
36
-    /**
37
-     * @since 5.0.0.p
38
-     */
39
-    protected function registerDependencies()
40
-    {
41
-    }
36
+	/**
37
+	 * @since 5.0.0.p
38
+	 */
39
+	protected function registerDependencies()
40
+	{
41
+	}
42 42
 
43 43
 
44
-    /**
45
-     * implements logic required to run during request
46
-     *
47
-     * @return bool
48
-     * @since   5.0.0.p
49
-     */
50
-    protected function requestHandler(): bool
51
-    {
52
-        /** @var Domain $domain */
53
-        $domain  = $this->loader->getShared(Domain::class);
54
-        /** @var FeatureFlags $feature */
55
-        $feature = $this->loader->getShared(FeatureFlags::class);
56
-        if ($domain->isCaffeinated() && $feature->allowed(FeatureFlag::USE_ADVANCED_EVENT_EDITOR)) {
57
-            // Add duplicate button
58
-            add_filter(
59
-                'get_sample_permalink_html',
60
-                [DuplicateEventButton::class, 'addButton'],
61
-                8,
62
-                4
63
-            );
64
-        }
65
-        // Add shortlink button
66
-        add_filter(
67
-            'get_sample_permalink_html',
68
-            [EventShortlinkButton::class, 'addButton'],
69
-            10,
70
-            2
71
-        );
72
-        // Add ticket selector shortcode button
73
-        add_filter(
74
-            'get_sample_permalink_html',
75
-            [TicketSelectorShortcodeButton::class, 'addButton'],
76
-            12,
77
-            4
78
-        );
79
-        // Add preview button
80
-        add_filter(
81
-            'get_sample_permalink_html',
82
-            [PreviewButton::class, 'addButton'],
83
-            5,
84
-            2
85
-        );
86
-        return true;
87
-    }
44
+	/**
45
+	 * implements logic required to run during request
46
+	 *
47
+	 * @return bool
48
+	 * @since   5.0.0.p
49
+	 */
50
+	protected function requestHandler(): bool
51
+	{
52
+		/** @var Domain $domain */
53
+		$domain  = $this->loader->getShared(Domain::class);
54
+		/** @var FeatureFlags $feature */
55
+		$feature = $this->loader->getShared(FeatureFlags::class);
56
+		if ($domain->isCaffeinated() && $feature->allowed(FeatureFlag::USE_ADVANCED_EVENT_EDITOR)) {
57
+			// Add duplicate button
58
+			add_filter(
59
+				'get_sample_permalink_html',
60
+				[DuplicateEventButton::class, 'addButton'],
61
+				8,
62
+				4
63
+			);
64
+		}
65
+		// Add shortlink button
66
+		add_filter(
67
+			'get_sample_permalink_html',
68
+			[EventShortlinkButton::class, 'addButton'],
69
+			10,
70
+			2
71
+		);
72
+		// Add ticket selector shortcode button
73
+		add_filter(
74
+			'get_sample_permalink_html',
75
+			[TicketSelectorShortcodeButton::class, 'addButton'],
76
+			12,
77
+			4
78
+		);
79
+		// Add preview button
80
+		add_filter(
81
+			'get_sample_permalink_html',
82
+			[PreviewButton::class, 'addButton'],
83
+			5,
84
+			2
85
+		);
86
+		return true;
87
+	}
88 88
 }
Please login to merge, or discard this patch.
core/domain/services/capabilities/FeatureFlagsConfig.php 2 patches
Indentation   +185 added lines, -185 removed lines patch added patch discarded remove patch
@@ -17,189 +17,189 @@
 block discarded – undo
17 17
  */
18 18
 class FeatureFlagsConfig extends JsonDataWordpressOption
19 19
 {
20
-    /**
21
-     * WP option name for saving the Feature Flags configuration
22
-     */
23
-    private const OPTION_NAME = 'ee_feature_flags';
24
-
25
-    /**
26
-     * use FeatureFlag::USE_EVENT_EDITOR_BULK_EDIT instead
27
-     * this hasn't been deleted because it's used in the REM add-on
28
-     *
29
-     * @deprecated 5.0.18.p
30
-     */
31
-    public const  USE_EVENT_EDITOR_BULK_EDIT = FeatureFlag::USE_EVENT_EDITOR_BULK_EDIT;
32
-
33
-
34
-    private array $cap_checks;
35
-
36
-    protected Domain $domain;
37
-
38
-    private ?stdClass $feature_flags = null;
39
-
40
-    private array $removed;
41
-
42
-
43
-    public function __construct(Domain $domain, JsonDataHandler $json_data_handler)
44
-    {
45
-        $this->domain     = $domain;
46
-        $this->cap_checks = apply_filters(
47
-            'FHEE__EventEspresso_core_domain_services_capabilities_FeatureFlagsConfig_cap_checks',
48
-            []
49
-        );
50
-        $this->removed    = apply_filters(
51
-            'FHEE__EventEspresso_core_domain_services_capabilities_FeatureFlagsConfig_removed',
52
-            []
53
-        );
54
-        parent::__construct($json_data_handler, FeatureFlagsConfig::OPTION_NAME, $this->getDefaultFeatureFlagOptions());
55
-    }
56
-
57
-
58
-    /**
59
-     * see the FeatureFlag::USE_* constants for descriptions of each feature flag and their default values
60
-     *
61
-     * @return stdClass
62
-     */
63
-    public function getDefaultFeatureFlagOptions(): stdClass
64
-    {
65
-        return (object) [
66
-            FeatureFlag::USE_ADVANCED_EVENT_EDITOR     => true,
67
-            FeatureFlag::USE_DATETIME_STATUS_CONTROLS  => false,
68
-            FeatureFlag::USE_DEFAULT_TICKET_MANAGER    => true,
69
-            FeatureFlag::USE_EDD_PLUGIN_LICENSING      => true,
70
-            FeatureFlag::USE_EVENT_DESCRIPTION_RTE     => false,
71
-            FeatureFlag::USE_EVENT_EDITOR_BULK_EDIT    => $this->domain->isCaffeinated()
72
-                                                            && ! $this->domain->isMultiSite(),
73
-            FeatureFlag::USE_EXPERIMENTAL_RTE          => false,
74
-            FeatureFlag::USE_PAYMENT_PROCESSOR_FEES    => true,
75
-            FeatureFlag::USE_REG_FORM_BUILDER          => false,
76
-            FeatureFlag::USE_REG_FORM_TICKET_QUESTIONS => false,
77
-            FeatureFlag::USE_REG_OPTIONS_META_BOX      => false,
78
-            FeatureFlag::USE_SPCO_FORM_REFACTOR        => false,
79
-        ];
80
-    }
81
-
82
-
83
-    /**
84
-     * feature flags that absolutely must be enabled/disabled based on hard-coded conditions
85
-     *
86
-     * @return stdClass
87
-     * @since 5.0.20.p
88
-     */
89
-    public function getOverrides(): stdClass
90
-    {
91
-        // allow EDD to be disabled for testing.
92
-        $overrides[ FeatureFlag::USE_EDD_PLUGIN_LICENSING ] =
93
-            ! defined('EE_USE_EDD_PLUGIN_LICENSING') || EE_USE_EDD_PLUGIN_LICENSING;
94
-
95
-        return (object) $overrides;
96
-    }
97
-
98
-
99
-    /**
100
-     * @return stdClass
101
-     */
102
-    public function getFeatureFlags(): stdClass
103
-    {
104
-        if ($this->feature_flags) {
105
-            return $this->feature_flags;
106
-        }
107
-        $default_options     = $this->getDefaultFeatureFlagOptions();
108
-        $this->feature_flags = $this->getAll();
109
-        $overrides           = $this->getOverrides();
110
-        // ensure that all feature flags are set
111
-        foreach ($default_options as $key => $value) {
112
-            // unset any feature flags that have been removed
113
-            if (in_array($key, $this->removed, true)) {
114
-                unset($this->feature_flags->{$key});
115
-                continue;
116
-            }
117
-            // if the feature flag is not set, use the default value
118
-            if (! isset($this->feature_flags->$key)) {
119
-                $this->feature_flags->$key = $value;
120
-            }
121
-            // ensure that all overrides are set
122
-            if (isset($overrides->$key)) {
123
-                $this->feature_flags->$key = $overrides->$key;
124
-            }
125
-            // convert any feature flags that are CapChecks
126
-            if (in_array($key, $this->cap_checks, true)) {
127
-                if (! $this->feature_flags->{$key} instanceof CapCheck) {
128
-                    $this->feature_flags->{$key} = new CapCheck($key, "feature_flag_$key");
129
-                }
130
-            }
131
-        }
132
-        return $this->feature_flags;
133
-    }
134
-
135
-
136
-    public function saveFeatureFlagsConfig(?stdClass $feature_flags = null): int
137
-    {
138
-        $feature_flags = $feature_flags ?? $this->feature_flags;
139
-        foreach ($this->removed as $feature_flag) {
140
-            unset($feature_flags->{$feature_flag});
141
-        }
142
-        $this->feature_flags = $feature_flags;
143
-        return $this->updateOption($feature_flags);
144
-    }
145
-
146
-
147
-    /**
148
-     * enables a feature flag, ex:
149
-     * $this->enableFeatureFlag(FeatureFlag::USE_ADVANCED_EVENT_EDITOR);
150
-     *
151
-     * @param string $feature_flag the feature flag to enable. One of the FeatureFlag::USE_* constants
152
-     * @param bool   $add_if_missing
153
-     * @param bool   $save
154
-     * @return int
155
-     */
156
-    public function enableFeatureFlag(string $feature_flag, bool $add_if_missing = false, bool $save = true): int
157
-    {
158
-        if (! $this->feature_flags) {
159
-            $this->getFeatureFlags();
160
-        }
161
-        if (! property_exists($this->feature_flags, $feature_flag) && ! $add_if_missing) {
162
-            return WordPressOption::UPDATE_ERROR;
163
-        }
164
-        $this->feature_flags->{$feature_flag} = true;
165
-        // if feature flag is the advanced event editor bulk edit options
166
-        // then only enabled if the site is Caffeinated and not MultiSite
167
-        if ($feature_flag === FeatureFlag::USE_EVENT_EDITOR_BULK_EDIT) {
168
-            $this->feature_flags->{$feature_flag} = $this->domain->isCaffeinated() && ! $this->domain->isMultiSite();
169
-        }
170
-        if ($save) {
171
-            return $this->saveFeatureFlagsConfig($this->feature_flags);
172
-        }
173
-        return WordPressOption::UPDATE_NONE;
174
-    }
175
-
176
-
177
-    /**
178
-     * disables a feature flag, ex:
179
-     * $this->disableFeatureFlag(FeatureFlag::USE_ADVANCED_EVENT_EDITOR);
180
-     *
181
-     * @param string $feature_flag the feature flag to disable. One of the FeatureFlag::USE_* constants
182
-     * @param bool   $save
183
-     * @return int
184
-     */
185
-    public function disableFeatureFlag(string $feature_flag, bool $save = true): int
186
-    {
187
-        if (! $this->feature_flags) {
188
-            $this->getFeatureFlags();
189
-        }
190
-        if (! property_exists($this->feature_flags, $feature_flag)) {
191
-            return WordPressOption::UPDATE_ERROR;
192
-        }
193
-        $this->feature_flags->{$feature_flag} = false;
194
-        if ($save) {
195
-            return $this->saveFeatureFlagsConfig($this->feature_flags);
196
-        }
197
-        return WordPressOption::UPDATE_NONE;
198
-    }
199
-
200
-
201
-    public function getFeatureFlagsFormOptions(): ?array
202
-    {
203
-        return FeatureFlag::getFormOptions();
204
-    }
20
+	/**
21
+	 * WP option name for saving the Feature Flags configuration
22
+	 */
23
+	private const OPTION_NAME = 'ee_feature_flags';
24
+
25
+	/**
26
+	 * use FeatureFlag::USE_EVENT_EDITOR_BULK_EDIT instead
27
+	 * this hasn't been deleted because it's used in the REM add-on
28
+	 *
29
+	 * @deprecated 5.0.18.p
30
+	 */
31
+	public const  USE_EVENT_EDITOR_BULK_EDIT = FeatureFlag::USE_EVENT_EDITOR_BULK_EDIT;
32
+
33
+
34
+	private array $cap_checks;
35
+
36
+	protected Domain $domain;
37
+
38
+	private ?stdClass $feature_flags = null;
39
+
40
+	private array $removed;
41
+
42
+
43
+	public function __construct(Domain $domain, JsonDataHandler $json_data_handler)
44
+	{
45
+		$this->domain     = $domain;
46
+		$this->cap_checks = apply_filters(
47
+			'FHEE__EventEspresso_core_domain_services_capabilities_FeatureFlagsConfig_cap_checks',
48
+			[]
49
+		);
50
+		$this->removed    = apply_filters(
51
+			'FHEE__EventEspresso_core_domain_services_capabilities_FeatureFlagsConfig_removed',
52
+			[]
53
+		);
54
+		parent::__construct($json_data_handler, FeatureFlagsConfig::OPTION_NAME, $this->getDefaultFeatureFlagOptions());
55
+	}
56
+
57
+
58
+	/**
59
+	 * see the FeatureFlag::USE_* constants for descriptions of each feature flag and their default values
60
+	 *
61
+	 * @return stdClass
62
+	 */
63
+	public function getDefaultFeatureFlagOptions(): stdClass
64
+	{
65
+		return (object) [
66
+			FeatureFlag::USE_ADVANCED_EVENT_EDITOR     => true,
67
+			FeatureFlag::USE_DATETIME_STATUS_CONTROLS  => false,
68
+			FeatureFlag::USE_DEFAULT_TICKET_MANAGER    => true,
69
+			FeatureFlag::USE_EDD_PLUGIN_LICENSING      => true,
70
+			FeatureFlag::USE_EVENT_DESCRIPTION_RTE     => false,
71
+			FeatureFlag::USE_EVENT_EDITOR_BULK_EDIT    => $this->domain->isCaffeinated()
72
+															&& ! $this->domain->isMultiSite(),
73
+			FeatureFlag::USE_EXPERIMENTAL_RTE          => false,
74
+			FeatureFlag::USE_PAYMENT_PROCESSOR_FEES    => true,
75
+			FeatureFlag::USE_REG_FORM_BUILDER          => false,
76
+			FeatureFlag::USE_REG_FORM_TICKET_QUESTIONS => false,
77
+			FeatureFlag::USE_REG_OPTIONS_META_BOX      => false,
78
+			FeatureFlag::USE_SPCO_FORM_REFACTOR        => false,
79
+		];
80
+	}
81
+
82
+
83
+	/**
84
+	 * feature flags that absolutely must be enabled/disabled based on hard-coded conditions
85
+	 *
86
+	 * @return stdClass
87
+	 * @since 5.0.20.p
88
+	 */
89
+	public function getOverrides(): stdClass
90
+	{
91
+		// allow EDD to be disabled for testing.
92
+		$overrides[ FeatureFlag::USE_EDD_PLUGIN_LICENSING ] =
93
+			! defined('EE_USE_EDD_PLUGIN_LICENSING') || EE_USE_EDD_PLUGIN_LICENSING;
94
+
95
+		return (object) $overrides;
96
+	}
97
+
98
+
99
+	/**
100
+	 * @return stdClass
101
+	 */
102
+	public function getFeatureFlags(): stdClass
103
+	{
104
+		if ($this->feature_flags) {
105
+			return $this->feature_flags;
106
+		}
107
+		$default_options     = $this->getDefaultFeatureFlagOptions();
108
+		$this->feature_flags = $this->getAll();
109
+		$overrides           = $this->getOverrides();
110
+		// ensure that all feature flags are set
111
+		foreach ($default_options as $key => $value) {
112
+			// unset any feature flags that have been removed
113
+			if (in_array($key, $this->removed, true)) {
114
+				unset($this->feature_flags->{$key});
115
+				continue;
116
+			}
117
+			// if the feature flag is not set, use the default value
118
+			if (! isset($this->feature_flags->$key)) {
119
+				$this->feature_flags->$key = $value;
120
+			}
121
+			// ensure that all overrides are set
122
+			if (isset($overrides->$key)) {
123
+				$this->feature_flags->$key = $overrides->$key;
124
+			}
125
+			// convert any feature flags that are CapChecks
126
+			if (in_array($key, $this->cap_checks, true)) {
127
+				if (! $this->feature_flags->{$key} instanceof CapCheck) {
128
+					$this->feature_flags->{$key} = new CapCheck($key, "feature_flag_$key");
129
+				}
130
+			}
131
+		}
132
+		return $this->feature_flags;
133
+	}
134
+
135
+
136
+	public function saveFeatureFlagsConfig(?stdClass $feature_flags = null): int
137
+	{
138
+		$feature_flags = $feature_flags ?? $this->feature_flags;
139
+		foreach ($this->removed as $feature_flag) {
140
+			unset($feature_flags->{$feature_flag});
141
+		}
142
+		$this->feature_flags = $feature_flags;
143
+		return $this->updateOption($feature_flags);
144
+	}
145
+
146
+
147
+	/**
148
+	 * enables a feature flag, ex:
149
+	 * $this->enableFeatureFlag(FeatureFlag::USE_ADVANCED_EVENT_EDITOR);
150
+	 *
151
+	 * @param string $feature_flag the feature flag to enable. One of the FeatureFlag::USE_* constants
152
+	 * @param bool   $add_if_missing
153
+	 * @param bool   $save
154
+	 * @return int
155
+	 */
156
+	public function enableFeatureFlag(string $feature_flag, bool $add_if_missing = false, bool $save = true): int
157
+	{
158
+		if (! $this->feature_flags) {
159
+			$this->getFeatureFlags();
160
+		}
161
+		if (! property_exists($this->feature_flags, $feature_flag) && ! $add_if_missing) {
162
+			return WordPressOption::UPDATE_ERROR;
163
+		}
164
+		$this->feature_flags->{$feature_flag} = true;
165
+		// if feature flag is the advanced event editor bulk edit options
166
+		// then only enabled if the site is Caffeinated and not MultiSite
167
+		if ($feature_flag === FeatureFlag::USE_EVENT_EDITOR_BULK_EDIT) {
168
+			$this->feature_flags->{$feature_flag} = $this->domain->isCaffeinated() && ! $this->domain->isMultiSite();
169
+		}
170
+		if ($save) {
171
+			return $this->saveFeatureFlagsConfig($this->feature_flags);
172
+		}
173
+		return WordPressOption::UPDATE_NONE;
174
+	}
175
+
176
+
177
+	/**
178
+	 * disables a feature flag, ex:
179
+	 * $this->disableFeatureFlag(FeatureFlag::USE_ADVANCED_EVENT_EDITOR);
180
+	 *
181
+	 * @param string $feature_flag the feature flag to disable. One of the FeatureFlag::USE_* constants
182
+	 * @param bool   $save
183
+	 * @return int
184
+	 */
185
+	public function disableFeatureFlag(string $feature_flag, bool $save = true): int
186
+	{
187
+		if (! $this->feature_flags) {
188
+			$this->getFeatureFlags();
189
+		}
190
+		if (! property_exists($this->feature_flags, $feature_flag)) {
191
+			return WordPressOption::UPDATE_ERROR;
192
+		}
193
+		$this->feature_flags->{$feature_flag} = false;
194
+		if ($save) {
195
+			return $this->saveFeatureFlagsConfig($this->feature_flags);
196
+		}
197
+		return WordPressOption::UPDATE_NONE;
198
+	}
199
+
200
+
201
+	public function getFeatureFlagsFormOptions(): ?array
202
+	{
203
+		return FeatureFlag::getFormOptions();
204
+	}
205 205
 }
Please login to merge, or discard this patch.
Spacing   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -47,7 +47,7 @@  discard block
 block discarded – undo
47 47
             'FHEE__EventEspresso_core_domain_services_capabilities_FeatureFlagsConfig_cap_checks',
48 48
             []
49 49
         );
50
-        $this->removed    = apply_filters(
50
+        $this->removed = apply_filters(
51 51
             'FHEE__EventEspresso_core_domain_services_capabilities_FeatureFlagsConfig_removed',
52 52
             []
53 53
         );
@@ -89,7 +89,7 @@  discard block
 block discarded – undo
89 89
     public function getOverrides(): stdClass
90 90
     {
91 91
         // allow EDD to be disabled for testing.
92
-        $overrides[ FeatureFlag::USE_EDD_PLUGIN_LICENSING ] =
92
+        $overrides[FeatureFlag::USE_EDD_PLUGIN_LICENSING] =
93 93
             ! defined('EE_USE_EDD_PLUGIN_LICENSING') || EE_USE_EDD_PLUGIN_LICENSING;
94 94
 
95 95
         return (object) $overrides;
@@ -115,7 +115,7 @@  discard block
 block discarded – undo
115 115
                 continue;
116 116
             }
117 117
             // if the feature flag is not set, use the default value
118
-            if (! isset($this->feature_flags->$key)) {
118
+            if ( ! isset($this->feature_flags->$key)) {
119 119
                 $this->feature_flags->$key = $value;
120 120
             }
121 121
             // ensure that all overrides are set
@@ -124,7 +124,7 @@  discard block
 block discarded – undo
124 124
             }
125 125
             // convert any feature flags that are CapChecks
126 126
             if (in_array($key, $this->cap_checks, true)) {
127
-                if (! $this->feature_flags->{$key} instanceof CapCheck) {
127
+                if ( ! $this->feature_flags->{$key} instanceof CapCheck) {
128 128
                     $this->feature_flags->{$key} = new CapCheck($key, "feature_flag_$key");
129 129
                 }
130 130
             }
@@ -155,10 +155,10 @@  discard block
 block discarded – undo
155 155
      */
156 156
     public function enableFeatureFlag(string $feature_flag, bool $add_if_missing = false, bool $save = true): int
157 157
     {
158
-        if (! $this->feature_flags) {
158
+        if ( ! $this->feature_flags) {
159 159
             $this->getFeatureFlags();
160 160
         }
161
-        if (! property_exists($this->feature_flags, $feature_flag) && ! $add_if_missing) {
161
+        if ( ! property_exists($this->feature_flags, $feature_flag) && ! $add_if_missing) {
162 162
             return WordPressOption::UPDATE_ERROR;
163 163
         }
164 164
         $this->feature_flags->{$feature_flag} = true;
@@ -184,10 +184,10 @@  discard block
 block discarded – undo
184 184
      */
185 185
     public function disableFeatureFlag(string $feature_flag, bool $save = true): int
186 186
     {
187
-        if (! $this->feature_flags) {
187
+        if ( ! $this->feature_flags) {
188 188
             $this->getFeatureFlags();
189 189
         }
190
-        if (! property_exists($this->feature_flags, $feature_flag)) {
190
+        if ( ! property_exists($this->feature_flags, $feature_flag)) {
191 191
             return WordPressOption::UPDATE_ERROR;
192 192
         }
193 193
         $this->feature_flags->{$feature_flag} = false;
Please login to merge, or discard this patch.
core/domain/services/capabilities/FeatureFlags.php 1 patch
Indentation   +58 added lines, -58 removed lines patch added patch discarded remove patch
@@ -13,70 +13,70 @@
 block discarded – undo
13 13
  */
14 14
 class FeatureFlags
15 15
 {
16
-    private CapabilitiesChecker $capabilities_checker;
16
+	private CapabilitiesChecker $capabilities_checker;
17 17
 
18
-    protected FeatureFlagsConfig $option;
18
+	protected FeatureFlagsConfig $option;
19 19
 
20
-    /**
21
-     * array of key value pairs where the key is the feature flag in question
22
-     * and the value is either a boolean or a CapCheck object defining the required permissions
23
-     * example:
24
-     *       [
25
-     *          'use_bulk_edit' => true,
26
-     *          'use_death_ray' => new CapCheck( 'ee-death-ray-cap', 'context-desc' )
27
-     *      ]
28
-     * array is filterable via FHEE__EventEspresso_core_domain_services_capabilities_FeatureFlags
29
-     *
30
-     * @var boolean[]|CapCheck[]
31
-     */
32
-    private $feature_flags;
20
+	/**
21
+	 * array of key value pairs where the key is the feature flag in question
22
+	 * and the value is either a boolean or a CapCheck object defining the required permissions
23
+	 * example:
24
+	 *       [
25
+	 *          'use_bulk_edit' => true,
26
+	 *          'use_death_ray' => new CapCheck( 'ee-death-ray-cap', 'context-desc' )
27
+	 *      ]
28
+	 * array is filterable via FHEE__EventEspresso_core_domain_services_capabilities_FeatureFlags
29
+	 *
30
+	 * @var boolean[]|CapCheck[]
31
+	 */
32
+	private $feature_flags;
33 33
 
34 34
 
35
-    /**
36
-     * FeatureFlags constructor.
37
-     *
38
-     * @param CapabilitiesChecker $capabilities_checker
39
-     * @param FeatureFlagsConfig  $option
40
-     */
41
-    public function __construct(CapabilitiesChecker $capabilities_checker, FeatureFlagsConfig $option)
42
-    {
43
-        $this->capabilities_checker = $capabilities_checker;
44
-        $this->option = $option;
45
-        $this->feature_flags = apply_filters(
46
-            'FHEE__EventEspresso_core_domain_services_capabilities_FeatureFlags',
47
-            $this->option->getFeatureFlags()
48
-        );
49
-    }
35
+	/**
36
+	 * FeatureFlags constructor.
37
+	 *
38
+	 * @param CapabilitiesChecker $capabilities_checker
39
+	 * @param FeatureFlagsConfig  $option
40
+	 */
41
+	public function __construct(CapabilitiesChecker $capabilities_checker, FeatureFlagsConfig $option)
42
+	{
43
+		$this->capabilities_checker = $capabilities_checker;
44
+		$this->option = $option;
45
+		$this->feature_flags = apply_filters(
46
+			'FHEE__EventEspresso_core_domain_services_capabilities_FeatureFlags',
47
+			$this->option->getFeatureFlags()
48
+		);
49
+	}
50 50
 
51 51
 
52
-    /**
53
-     * @param string $feature
54
-     * @return bool
55
-     */
56
-    public function allowed(string $feature): bool
57
-    {
58
-        $flag = apply_filters(
59
-            'FHEE__EventEspresso_core_domain_services_capabilities_FeatureFlags__allowed__feature',
60
-            $this->feature_flags->{$feature} ?? false,
61
-            $feature
62
-        );
63
-        try {
64
-            return $flag instanceof CapCheck
65
-                ? $this->capabilities_checker->processCapCheck($flag)
66
-                : filter_var($flag, FILTER_VALIDATE_BOOLEAN);
67
-        } catch (InsufficientPermissionsException $e) {
68
-            // eat the exception
69
-        }
70
-        return false;
71
-    }
52
+	/**
53
+	 * @param string $feature
54
+	 * @return bool
55
+	 */
56
+	public function allowed(string $feature): bool
57
+	{
58
+		$flag = apply_filters(
59
+			'FHEE__EventEspresso_core_domain_services_capabilities_FeatureFlags__allowed__feature',
60
+			$this->feature_flags->{$feature} ?? false,
61
+			$feature
62
+		);
63
+		try {
64
+			return $flag instanceof CapCheck
65
+				? $this->capabilities_checker->processCapCheck($flag)
66
+				: filter_var($flag, FILTER_VALIDATE_BOOLEAN);
67
+		} catch (InsufficientPermissionsException $e) {
68
+			// eat the exception
69
+		}
70
+		return false;
71
+	}
72 72
 
73 73
 
74
-    /**
75
-     * @return array
76
-     */
77
-    public function getAllowedFeatures(): array
78
-    {
79
-        $allowed = array_filter((array) $this->feature_flags, [$this, 'allowed'], ARRAY_FILTER_USE_KEY);
80
-        return array_keys($allowed);
81
-    }
74
+	/**
75
+	 * @return array
76
+	 */
77
+	public function getAllowedFeatures(): array
78
+	{
79
+		$allowed = array_filter((array) $this->feature_flags, [$this, 'allowed'], ARRAY_FILTER_USE_KEY);
80
+		return array_keys($allowed);
81
+	}
82 82
 }
Please login to merge, or discard this patch.
core/services/json/JsonDataWordpressOption.php 1 patch
Indentation   +91 added lines, -91 removed lines patch added patch discarded remove patch
@@ -15,95 +15,95 @@
 block discarded – undo
15 15
  */
16 16
 abstract class JsonDataWordpressOption extends WordPressOption
17 17
 {
18
-    private JsonDataHandler $json_data_handler;
19
-
20
-    /**
21
-     * @var array|mixed|stdClass
22
-     */
23
-    private $options = [];
24
-
25
-
26
-    /**
27
-     * JsonDataWordpressOption constructor.
28
-     *
29
-     * @param JsonDataHandler $json_data_handler
30
-     * @param string          $option_name
31
-     * @param                 $default_value
32
-     * @param bool            $autoload
33
-     */
34
-    public function __construct(
35
-        JsonDataHandler $json_data_handler,
36
-        string $option_name,
37
-        $default_value,
38
-        bool $autoload = false
39
-    ) {
40
-        $this->json_data_handler = $json_data_handler;
41
-        if (! $this->json_data_handler->dataType()) {
42
-            $this->json_data_handler->configure(JsonDataHandler::DATA_TYPE_OBJECT);
43
-        }
44
-        parent::__construct($option_name, $default_value, $autoload);
45
-    }
46
-
47
-
48
-    /**
49
-     * @param      $value
50
-     * @param bool $force_update
51
-     * @return int
52
-     */
53
-    public function updateOption($value, bool $force_update = false): int
54
-    {
55
-        $update = parent::updateOption($this->json_data_handler->encodeData($value), $force_update);
56
-        if ($update === WordPressOption::UPDATE_SUCCESS) {
57
-            $this->options = $value;
58
-        }
59
-        return $update;
60
-    }
61
-
62
-
63
-    /**
64
-     * @param string $property
65
-     * @param mixed  $value
66
-     * @return void
67
-     */
68
-    public function addProperty(string $property, $value)
69
-    {
70
-        $options              = $this->getAll();
71
-        $options->{$property} = $value;
72
-        $this->updateOption($options);
73
-    }
74
-
75
-
76
-    /**
77
-     * @param string $property
78
-     * @return mixed
79
-     */
80
-    public function getProperty(string $property)
81
-    {
82
-        $options = $this->getAll();
83
-        return property_exists($options, $property) ? $options->{$property} : null;
84
-    }
85
-
86
-
87
-    /**
88
-     * @return array|mixed|stdClass
89
-     */
90
-    public function getAll()
91
-    {
92
-        if (empty($this->options)) {
93
-            $this->options = $this->json_data_handler->decodeJson($this->loadOption());
94
-        }
95
-        return $this->options;
96
-    }
97
-
98
-
99
-    /**
100
-     * @param string $property
101
-     * @return void
102
-     */
103
-    public function removeProperty(string $property)
104
-    {
105
-        $options = $this->getAll();
106
-        unset($options->{$property});
107
-        $this->updateOption($options);
108
-    }
18
+	private JsonDataHandler $json_data_handler;
19
+
20
+	/**
21
+	 * @var array|mixed|stdClass
22
+	 */
23
+	private $options = [];
24
+
25
+
26
+	/**
27
+	 * JsonDataWordpressOption constructor.
28
+	 *
29
+	 * @param JsonDataHandler $json_data_handler
30
+	 * @param string          $option_name
31
+	 * @param                 $default_value
32
+	 * @param bool            $autoload
33
+	 */
34
+	public function __construct(
35
+		JsonDataHandler $json_data_handler,
36
+		string $option_name,
37
+		$default_value,
38
+		bool $autoload = false
39
+	) {
40
+		$this->json_data_handler = $json_data_handler;
41
+		if (! $this->json_data_handler->dataType()) {
42
+			$this->json_data_handler->configure(JsonDataHandler::DATA_TYPE_OBJECT);
43
+		}
44
+		parent::__construct($option_name, $default_value, $autoload);
45
+	}
46
+
47
+
48
+	/**
49
+	 * @param      $value
50
+	 * @param bool $force_update
51
+	 * @return int
52
+	 */
53
+	public function updateOption($value, bool $force_update = false): int
54
+	{
55
+		$update = parent::updateOption($this->json_data_handler->encodeData($value), $force_update);
56
+		if ($update === WordPressOption::UPDATE_SUCCESS) {
57
+			$this->options = $value;
58
+		}
59
+		return $update;
60
+	}
61
+
62
+
63
+	/**
64
+	 * @param string $property
65
+	 * @param mixed  $value
66
+	 * @return void
67
+	 */
68
+	public function addProperty(string $property, $value)
69
+	{
70
+		$options              = $this->getAll();
71
+		$options->{$property} = $value;
72
+		$this->updateOption($options);
73
+	}
74
+
75
+
76
+	/**
77
+	 * @param string $property
78
+	 * @return mixed
79
+	 */
80
+	public function getProperty(string $property)
81
+	{
82
+		$options = $this->getAll();
83
+		return property_exists($options, $property) ? $options->{$property} : null;
84
+	}
85
+
86
+
87
+	/**
88
+	 * @return array|mixed|stdClass
89
+	 */
90
+	public function getAll()
91
+	{
92
+		if (empty($this->options)) {
93
+			$this->options = $this->json_data_handler->decodeJson($this->loadOption());
94
+		}
95
+		return $this->options;
96
+	}
97
+
98
+
99
+	/**
100
+	 * @param string $property
101
+	 * @return void
102
+	 */
103
+	public function removeProperty(string $property)
104
+	{
105
+		$options = $this->getAll();
106
+		unset($options->{$property});
107
+		$this->updateOption($options);
108
+	}
109 109
 }
Please login to merge, or discard this patch.
core/services/session/SessionStartHandler.php 2 patches
Indentation   +533 added lines, -533 removed lines patch added patch discarded remove patch
@@ -29,537 +29,537 @@
 block discarded – undo
29 29
  */
30 30
 class SessionStartHandler
31 31
 {
32
-    const OPTION_NAME_SESSION_SAVE_HANDLER_STATUS = 'ee_session_save_handler_status';
33
-
34
-    const REQUEST_PARAM_RETRY_SESSION             = 'ee_retry_session';
35
-
36
-    const SESSION_SAVE_HANDLER_STATUS_FAILED      = 'session_save_handler_failed';
37
-
38
-    const SESSION_SAVE_HANDLER_STATUS_SUCCESS     = 'session_save_handler_success';
39
-
40
-    const SESSION_SAVE_HANDLER_STATUS_UNKNOWN     = 'session_save_handler_untested';
41
-
42
-
43
-    protected RequestInterface $request;
44
-
45
-    /**
46
-     * @var string
47
-     * @since 5.0.47
48
-     */
49
-    private string $open_basedir = '';
50
-
51
-    /**
52
-     * @var string
53
-     * @since 5.0.47
54
-     */
55
-    private string $save_handler = '';
56
-
57
-    /**
58
-     * @var string
59
-     * @since 5.0.47
60
-     */
61
-    private string $save_path = '';
62
-
63
-    /**
64
-     * When true, verification of session save path filesystem checks was skipped
65
-     * because open_basedir prevented validation.
66
-     *
67
-     * @var bool
68
-     * @since 5.0.48
69
-     */
70
-    private bool $skip_save_path_checks = false;
71
-
72
-    /**
73
-     * @var bool
74
-     * @since 5.0.48
75
-     */
76
-    private bool $logging_enabled;
77
-
78
-
79
-    /**
80
-     * StartSession constructor.
81
-     *
82
-     * @param RequestInterface $request
83
-     */
84
-    public function __construct(RequestInterface $request)
85
-    {
86
-        $this->request = $request;
87
-        // only log session details in staging and production environments
88
-        $environment           = wp_get_environment_type();
89
-        $this->logging_enabled = WP_DEBUG && ($environment === 'staging' || $environment === 'production');
90
-    }
91
-
92
-
93
-    /**
94
-     * Check if a custom session save handler is in play
95
-     * and attempt to start the PHP session
96
-     *
97
-     * @since 4.9.68.p
98
-     */
99
-    public function startSession(): int
100
-    {
101
-        // check that session has started
102
-        if (session_id() === '') {
103
-            // clear any previous error
104
-            error_clear_last();
105
-
106
-            // convert warnings to ErrorException so we can catch them
107
-            $previous_handler = set_error_handler([$this, 'customErrorHandler'], E_WARNING);
108
-
109
-            try {
110
-                $this->initializeSessionVars();
111
-                // starts a new session if one doesn't already exist, or re-initiates an existing one
112
-                if ($this->hasCustomSessionSaveHandler()) {
113
-                    $this->checkCustomSessionSaveHandler();
114
-                } else {
115
-                    $this->verifySessionSavePath();
116
-                    $this->sessionStart();
117
-                }
118
-            } catch (Throwable $error) {
119
-                if ($this->logging_enabled) {
120
-                    error_log(
121
-                        sprintf(
122
-                            '[SessionStartHandler] session_start() warning: %s in %s:%s',
123
-                            $error->getMessage(),
124
-                            $error->getFile(),
125
-                            $error->getLine()
126
-                        )
127
-                    );
128
-                }
129
-                $this->displaySessionErrorNotice(
130
-                    $error->getMessage(),
131
-                    $error->getFile(),
132
-                    __FUNCTION__,
133
-                    $error->getLine()
134
-                );
135
-            } finally {
136
-                $this->restorePreviousErrorHandler($previous_handler);
137
-            }
138
-        }
139
-        return session_status();
140
-    }
141
-
142
-
143
-    /**
144
-     * @return void
145
-     * @since 5.0.47
146
-     */
147
-    private function initializeSessionVars(): void
148
-    {
149
-        $this->open_basedir = ini_get('open_basedir') ?: '';
150
-        $this->save_handler = strtolower((string) ini_get('session.save_handler'));
151
-        $this->save_path    = session_save_path() ?: '';
152
-    }
153
-
154
-
155
-    /**
156
-     * @return void
157
-     * @throws Throwable
158
-     * @since 5.0.46
159
-     */
160
-    private function sessionStart(): void
161
-    {
162
-        session_start();
163
-        session_write_close();
164
-    }
165
-
166
-
167
-    /**
168
-     * @return void
169
-     * @throws ErrorException
170
-     * @since 5.0.46
171
-     */
172
-    private function verifySessionSavePath(): void
173
-    {
174
-        if ($this->logging_enabled) {
175
-            error_log(
176
-                sprintf(
177
-                    "[SessionStartHandler] diagnostic: save_handler=%s save_path=%s open_basedir=%s",
178
-                    $this->save_handler,
179
-                    $this->save_path,
180
-                    $this->open_basedir ?: '(none)'
181
-                )
182
-            );
183
-        }
184
-        // Only validate the session save path as a filesystem directory when PHP is
185
-        // configured to use the 'files' session save handler. Other handlers
186
-        // (memcache, memcached, redis, user, etc.) use different transport
187
-        // formats (hosts, sockets, URIs) and are not filesystem directories.
188
-        if ($this->save_handler !== 'files') {
189
-            // assume the configured handler knows how to interpret the save path
190
-            // (e.g. "unix:///run/memcached/memcached.sock", "127.0.0.1:11211", etc.)
191
-            return;
192
-        }
193
-        $this->save_path = $this->normalizeSessionSavePath($this->save_path);
194
-        // If we were unable to safely validate the session save path because it
195
-        // appears to live outside PHP's open_basedir, skip filesystem checks to
196
-        // avoid emitting warnings or throwing exceptions on some hosts.
197
-        if ($this->skip_save_path_checks === true) {
198
-            if ($this->logging_enabled) {
199
-                error_log(
200
-                    sprintf(
201
-                        '[SessionStartHandler] skipped session_save_path filesystem validation due to open_basedir: %s',
202
-                        $this->save_path
203
-                    )
204
-                );
205
-            }
206
-            return;
207
-        }
208
-        // Use @-suppressed checks to avoid PHP warnings while validating.
209
-        if (! @is_dir($this->save_path) || ! @is_writable($this->save_path)) {
210
-            throw new ErrorException(
211
-                sprintf(
212
-                    esc_html__('Invalid or missing session save path: %s', 'event_espresso'),
213
-                    $this->save_path
214
-                ),
215
-                0,
216
-                E_WARNING,
217
-                __FILE__,
218
-                __LINE__
219
-            );
220
-        }
221
-    }
222
-
223
-
224
-    /**
225
-     * @param string $session_save_path
226
-     * @return string
227
-     * @since 5.0.47
228
-     */
229
-    private function normalizeSessionSavePath(string $session_save_path): string
230
-    {
231
-        // Normalize "N;/path" style values
232
-        if (strpos($session_save_path, ';') !== false) {
233
-            $parts             = explode(';', $session_save_path);
234
-            $session_save_path = end($parts);
235
-        }
236
-        $session_save_path = trim((string) $session_save_path);
237
-        if ($session_save_path === '') {
238
-            // fall back to a sane temp dir if PHP reports no explicit session_save_path
239
-            $session_save_path = sys_get_temp_dir();
240
-        }
241
-        // normalize trailing separators
242
-        $session_save_path = rtrim($session_save_path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
243
-        // For 'files' handler we expect a writable filesystem directory.
244
-        // However, calling realpath() or is_dir() on paths outside of PHP's
245
-        // configured open_basedir can trigger warnings.
246
-        // Handle that case explicitly by checking open_basedir first
247
-        // and avoiding functions that would emit warnings.
248
-        if ($this->open_basedir !== '') {
249
-            $session_save_path = $this->checkPathsOutsideBaseDir($session_save_path);
250
-        } else {
251
-            // No open_basedir restriction; realpath() is safe to use and gives
252
-            // us a canonical path for the directory checks.
253
-            $session_save_path = @realpath($session_save_path) ?: $session_save_path;
254
-        }
255
-        return $session_save_path;
256
-    }
257
-
258
-
259
-    /**
260
-     * @param string $session_save_path
261
-     * @return string
262
-     * @since 5.0.47
263
-     */
264
-    private function checkPathsOutsideBaseDir(string $session_save_path): string
265
-    {
266
-        // open_basedir is set; check whether the configured session path
267
-        // appears to be within one of the allowed paths.
268
-        $allowed_paths = array_filter(array_map('trim', explode(PATH_SEPARATOR, $this->open_basedir)));
269
-        $allowed       = false;
270
-        foreach ($allowed_paths as $allowed_path) {
271
-            if ($allowed_path === '') {
272
-                continue;
273
-            }
274
-            // normalize trailing separators for comparison
275
-            $allowed_path_norm = rtrim($allowed_path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
276
-            // compare start of paths for a match
277
-            if (strncmp($session_save_path, $allowed_path_norm, strlen($allowed_path_norm)) === 0) {
278
-                $allowed = true;
279
-                break;
280
-            }
281
-        }
282
-        if ($allowed) {
283
-            return $session_save_path;
284
-        }
285
-        // open_basedir is set and the session save path is outside it.
286
-        // Log for diagnostics and skip strict filesystem checks rather than
287
-        // throwing an exception which causes fatal behavior on some hosts.
288
-        if ($this->logging_enabled) {
289
-            error_log(
290
-                sprintf(
291
-                    "[SessionStartHandler] session_save_path outside open_basedir: save_path=%s open_basedir=%s - skipping validation",
292
-                    $session_save_path,
293
-                    $this->open_basedir
294
-                )
295
-            );
296
-        }
297
-        $this->skip_save_path_checks = true;
298
-        return $session_save_path;
299
-    }
300
-
301
-
302
-    /**
303
-     * Returns `true` if the 'session.save_handler' ini setting matches a known custom handler
304
-     *
305
-     * @return bool
306
-     * @since 4.9.68.p
307
-     */
308
-    private function hasCustomSessionSaveHandler(): bool
309
-    {
310
-        return $this->save_handler === 'user';
311
-    }
312
-
313
-
314
-    /**
315
-     * Attempt to start the PHP session when a custom Session Save Handler is known to be set.
316
-     *
317
-     * @throws ErrorException
318
-     * @throws Throwable
319
-     * @since 4.9.68.p
320
-     */
321
-    private function checkCustomSessionSaveHandler(): void
322
-    {
323
-        // If we've already successfully tested the session save handler
324
-        // on a previous request then just start the session
325
-        if ($this->sessionSaveHandlerIsValid()) {
326
-            $this->sessionStart();
327
-            return;
328
-        }
329
-        // If not, then attempt to deal with any errors,
330
-        // otherwise, try to hobble along without the session
331
-        if (! $this->handleSessionSaveHandlerErrors()) {
332
-            return;
333
-        }
334
-        // there is no record of a fatal error while trying to start the session
335
-        // so let's see if there's a custom session save handler. Proceed with caution
336
-        if ($this->initializeSessionSaveHandlerStatus() === false) {
337
-            throw new ErrorException(
338
-                esc_html__('Failed to initialize session save handler status', 'event_espresso'),
339
-                0,
340
-                E_WARNING,
341
-                __FILE__,
342
-                __LINE__
343
-            );
344
-        }
345
-        // hold your breath, the custom session save handler might cause a fatal here...
346
-        $this->sessionStart();
347
-        // phew! we made it! the custom session handler is a-ok
348
-        if ($this->setSessionSaveHandlerStatusToValid() === false) {
349
-            throw new ErrorException(
350
-                esc_html__('Failed to set session save handler status to valid', 'event_espresso'),
351
-                0,
352
-                E_WARNING,
353
-                __FILE__,
354
-                __LINE__
355
-            );
356
-        }
357
-    }
358
-
359
-
360
-    /**
361
-     * retrieves the value for the 'ee_session_save_handler_status' WP option.
362
-     * default value = 'session_save_handler_untested'
363
-     *
364
-     * @return string
365
-     * @since 4.9.68.p
366
-     */
367
-    private function getSessionSaveHandlerStatus(): string
368
-    {
369
-        return get_option(
370
-            SessionStartHandler::OPTION_NAME_SESSION_SAVE_HANDLER_STATUS,
371
-            SessionStartHandler::SESSION_SAVE_HANDLER_STATUS_UNKNOWN
372
-        );
373
-    }
374
-
375
-
376
-    /**
377
-     * Sets the 'ee_session_save_handler_status' WP option value to 'session_save_handler_failed'
378
-     * which can then be upgraded is everything works correctly
379
-     *
380
-     * @return bool
381
-     * @since 4.9.68.p
382
-     */
383
-    private function initializeSessionSaveHandlerStatus(): bool
384
-    {
385
-        return update_option(
386
-            SessionStartHandler::OPTION_NAME_SESSION_SAVE_HANDLER_STATUS,
387
-            SessionStartHandler::SESSION_SAVE_HANDLER_STATUS_FAILED
388
-        );
389
-    }
390
-
391
-
392
-    /**
393
-     * Sets the 'ee_session_save_handler_status' WP option value to 'session_save_handler_success'
394
-     *
395
-     * @return bool
396
-     * @since 4.9.68.p
397
-     */
398
-    private function setSessionSaveHandlerStatusToValid(): bool
399
-    {
400
-        return update_option(
401
-            SessionStartHandler::OPTION_NAME_SESSION_SAVE_HANDLER_STATUS,
402
-            SessionStartHandler::SESSION_SAVE_HANDLER_STATUS_SUCCESS
403
-        );
404
-    }
405
-
406
-
407
-    /**
408
-     * Sets the 'ee_session_save_handler_status' WP option value to 'session_save_handler_untested'
409
-     *
410
-     * @return bool
411
-     * @since 4.9.68.p
412
-     */
413
-    private function resetSessionSaveHandlerStatus(): bool
414
-    {
415
-        return update_option(
416
-            SessionStartHandler::OPTION_NAME_SESSION_SAVE_HANDLER_STATUS,
417
-            SessionStartHandler::SESSION_SAVE_HANDLER_STATUS_UNKNOWN
418
-        );
419
-    }
420
-
421
-
422
-    /**
423
-     * Returns `true` if the 'ee_session_save_handler_status' WP option value
424
-     * is equal to 'session_save_handler_success'
425
-     *
426
-     * @return bool
427
-     * @since 4.9.68.p
428
-     */
429
-    private function sessionSaveHandlerIsValid(): bool
430
-    {
431
-        return $this->getSessionSaveHandlerStatus() === SessionStartHandler::SESSION_SAVE_HANDLER_STATUS_SUCCESS;
432
-    }
433
-
434
-
435
-    /**
436
-     * Returns `true` if the 'ee_session_save_handler_status' WP option value
437
-     * is equal to 'session_save_handler_failed'
438
-     *
439
-     * @return bool
440
-     * @since 4.9.68.p
441
-     */
442
-    private function sessionSaveHandlerFailed(): bool
443
-    {
444
-        return $this->getSessionSaveHandlerStatus() === SessionStartHandler::SESSION_SAVE_HANDLER_STATUS_FAILED;
445
-    }
446
-
447
-
448
-    /**
449
-     * @param int    $severity
450
-     * @param string $message
451
-     * @param string $file
452
-     * @param int    $line
453
-     * @return bool
454
-     * @throws ErrorException
455
-     * @since 5.0.46
456
-     */
457
-    public function customErrorHandler(int $severity, string $message, string $file, int $line): bool
458
-    {
459
-        // Only convert warnings we care about
460
-        if (($severity & E_WARNING) === E_WARNING) {
461
-            throw new ErrorException($message, 0, $severity, $file, $line);
462
-        }
463
-        // fallback to PHP's normal handler for other severities
464
-        return false;
465
-    }
466
-
467
-
468
-    /**
469
-     * @param callable|null $previous_handler
470
-     * @return void
471
-     * @since 5.0.46
472
-     */
473
-    private function restorePreviousErrorHandler(?callable $previous_handler): void
474
-    {
475
-        if ($previous_handler !== null) {
476
-            set_error_handler($previous_handler);
477
-        } else {
478
-            restore_error_handler();
479
-        }
480
-    }
481
-
482
-
483
-    /**
484
-     * Returns `true` if no errors were detected with the session save handler,
485
-     * otherwise attempts to work notify the appropriate authorities
486
-     * with a suggestion for how to fix the issue, and returns `false`.
487
-     *
488
-     * @return bool
489
-     * @throws ErrorException
490
-     * @throws Throwable
491
-     * @since 4.9.68.p
492
-     */
493
-    private function handleSessionSaveHandlerErrors(): bool
494
-    {
495
-        // Check if we had a fatal error last time while trying to start the session
496
-        if ($this->sessionSaveHandlerFailed()) {
497
-            // apparently, last time we tried using the custom session save handler there was a fatal
498
-            if ($this->request->requestParamIsSet(SessionStartHandler::REQUEST_PARAM_RETRY_SESSION)) {
499
-                if ($this->resetSessionSaveHandlerStatus() === false) {
500
-                    throw new ErrorException(
501
-                        esc_html__('Failed to reset session save handler status', 'event_espresso'),
502
-                        0,
503
-                        E_WARNING,
504
-                        __FILE__,
505
-                        __LINE__
506
-                    );
507
-                }
508
-                // remove "ee_retry_session", otherwise if the problem still isn't fixed,
509
-                // we'll just keep getting the fatal error over and over.
510
-                // Better to remove it and redirect, and try on the next request
511
-                EEH_URL::safeRedirectAndExit(
512
-                    remove_query_arg(
513
-                        [SessionStartHandler::REQUEST_PARAM_RETRY_SESSION],
514
-                        EEH_URL::current_url()
515
-                    )
516
-                );
517
-            }
518
-            // so the session is broken, don't try it again,
519
-            // just show a message to users that can fix it
520
-            $this->displaySessionSaveHandlerErrorNotice();
521
-            return false;
522
-        }
523
-        return true;
524
-    }
525
-
526
-
527
-    /**
528
-     * @since 4.9.68.p
529
-     */
530
-    private function displaySessionSaveHandlerErrorNotice(): void
531
-    {
532
-        $retry_session_url = add_query_arg(
533
-            [SessionStartHandler::REQUEST_PARAM_RETRY_SESSION => true],
534
-            EEH_URL::current_url()
535
-        );
536
-        $this->displaySessionErrorNotice(
537
-            sprintf(
538
-                esc_html__(
539
-                    'It appears there was a fatal error while starting the session, so Event Espresso is not able to process registrations normally. Some hosting companies, like Pantheon, require an extra plugin for Event Espresso to work. Please install the %1$sWordPress Native PHP Sessions plugin%2$s, then %3$sclick here to check if the problem is resolved.%2$s',
540
-                    'event_espresso'
541
-                ),
542
-                '<a href="https://wordpress.org/plugins/wp-native-php-sessions/">',
543
-                '</a>',
544
-                '<a href="' . $retry_session_url . '">'
545
-            ),
546
-            __FILE__,
547
-            __FUNCTION__,
548
-            __LINE__
549
-        );
550
-    }
551
-
552
-
553
-    /**
554
-     * Generates an EE_Error notice regarding the current session woes
555
-     * but only if the current user is an admin with permission to 'install_plugins'.
556
-     *
557
-     * @since 5.0.46
558
-     */
559
-    private function displaySessionErrorNotice(string $message, string $file, string $function, int $line): void
560
-    {
561
-        if (current_user_can('install_plugins')) {
562
-            EE_Error::add_error($message, $file, $function, $line);
563
-        }
564
-    }
32
+	const OPTION_NAME_SESSION_SAVE_HANDLER_STATUS = 'ee_session_save_handler_status';
33
+
34
+	const REQUEST_PARAM_RETRY_SESSION             = 'ee_retry_session';
35
+
36
+	const SESSION_SAVE_HANDLER_STATUS_FAILED      = 'session_save_handler_failed';
37
+
38
+	const SESSION_SAVE_HANDLER_STATUS_SUCCESS     = 'session_save_handler_success';
39
+
40
+	const SESSION_SAVE_HANDLER_STATUS_UNKNOWN     = 'session_save_handler_untested';
41
+
42
+
43
+	protected RequestInterface $request;
44
+
45
+	/**
46
+	 * @var string
47
+	 * @since 5.0.47
48
+	 */
49
+	private string $open_basedir = '';
50
+
51
+	/**
52
+	 * @var string
53
+	 * @since 5.0.47
54
+	 */
55
+	private string $save_handler = '';
56
+
57
+	/**
58
+	 * @var string
59
+	 * @since 5.0.47
60
+	 */
61
+	private string $save_path = '';
62
+
63
+	/**
64
+	 * When true, verification of session save path filesystem checks was skipped
65
+	 * because open_basedir prevented validation.
66
+	 *
67
+	 * @var bool
68
+	 * @since 5.0.48
69
+	 */
70
+	private bool $skip_save_path_checks = false;
71
+
72
+	/**
73
+	 * @var bool
74
+	 * @since 5.0.48
75
+	 */
76
+	private bool $logging_enabled;
77
+
78
+
79
+	/**
80
+	 * StartSession constructor.
81
+	 *
82
+	 * @param RequestInterface $request
83
+	 */
84
+	public function __construct(RequestInterface $request)
85
+	{
86
+		$this->request = $request;
87
+		// only log session details in staging and production environments
88
+		$environment           = wp_get_environment_type();
89
+		$this->logging_enabled = WP_DEBUG && ($environment === 'staging' || $environment === 'production');
90
+	}
91
+
92
+
93
+	/**
94
+	 * Check if a custom session save handler is in play
95
+	 * and attempt to start the PHP session
96
+	 *
97
+	 * @since 4.9.68.p
98
+	 */
99
+	public function startSession(): int
100
+	{
101
+		// check that session has started
102
+		if (session_id() === '') {
103
+			// clear any previous error
104
+			error_clear_last();
105
+
106
+			// convert warnings to ErrorException so we can catch them
107
+			$previous_handler = set_error_handler([$this, 'customErrorHandler'], E_WARNING);
108
+
109
+			try {
110
+				$this->initializeSessionVars();
111
+				// starts a new session if one doesn't already exist, or re-initiates an existing one
112
+				if ($this->hasCustomSessionSaveHandler()) {
113
+					$this->checkCustomSessionSaveHandler();
114
+				} else {
115
+					$this->verifySessionSavePath();
116
+					$this->sessionStart();
117
+				}
118
+			} catch (Throwable $error) {
119
+				if ($this->logging_enabled) {
120
+					error_log(
121
+						sprintf(
122
+							'[SessionStartHandler] session_start() warning: %s in %s:%s',
123
+							$error->getMessage(),
124
+							$error->getFile(),
125
+							$error->getLine()
126
+						)
127
+					);
128
+				}
129
+				$this->displaySessionErrorNotice(
130
+					$error->getMessage(),
131
+					$error->getFile(),
132
+					__FUNCTION__,
133
+					$error->getLine()
134
+				);
135
+			} finally {
136
+				$this->restorePreviousErrorHandler($previous_handler);
137
+			}
138
+		}
139
+		return session_status();
140
+	}
141
+
142
+
143
+	/**
144
+	 * @return void
145
+	 * @since 5.0.47
146
+	 */
147
+	private function initializeSessionVars(): void
148
+	{
149
+		$this->open_basedir = ini_get('open_basedir') ?: '';
150
+		$this->save_handler = strtolower((string) ini_get('session.save_handler'));
151
+		$this->save_path    = session_save_path() ?: '';
152
+	}
153
+
154
+
155
+	/**
156
+	 * @return void
157
+	 * @throws Throwable
158
+	 * @since 5.0.46
159
+	 */
160
+	private function sessionStart(): void
161
+	{
162
+		session_start();
163
+		session_write_close();
164
+	}
165
+
166
+
167
+	/**
168
+	 * @return void
169
+	 * @throws ErrorException
170
+	 * @since 5.0.46
171
+	 */
172
+	private function verifySessionSavePath(): void
173
+	{
174
+		if ($this->logging_enabled) {
175
+			error_log(
176
+				sprintf(
177
+					"[SessionStartHandler] diagnostic: save_handler=%s save_path=%s open_basedir=%s",
178
+					$this->save_handler,
179
+					$this->save_path,
180
+					$this->open_basedir ?: '(none)'
181
+				)
182
+			);
183
+		}
184
+		// Only validate the session save path as a filesystem directory when PHP is
185
+		// configured to use the 'files' session save handler. Other handlers
186
+		// (memcache, memcached, redis, user, etc.) use different transport
187
+		// formats (hosts, sockets, URIs) and are not filesystem directories.
188
+		if ($this->save_handler !== 'files') {
189
+			// assume the configured handler knows how to interpret the save path
190
+			// (e.g. "unix:///run/memcached/memcached.sock", "127.0.0.1:11211", etc.)
191
+			return;
192
+		}
193
+		$this->save_path = $this->normalizeSessionSavePath($this->save_path);
194
+		// If we were unable to safely validate the session save path because it
195
+		// appears to live outside PHP's open_basedir, skip filesystem checks to
196
+		// avoid emitting warnings or throwing exceptions on some hosts.
197
+		if ($this->skip_save_path_checks === true) {
198
+			if ($this->logging_enabled) {
199
+				error_log(
200
+					sprintf(
201
+						'[SessionStartHandler] skipped session_save_path filesystem validation due to open_basedir: %s',
202
+						$this->save_path
203
+					)
204
+				);
205
+			}
206
+			return;
207
+		}
208
+		// Use @-suppressed checks to avoid PHP warnings while validating.
209
+		if (! @is_dir($this->save_path) || ! @is_writable($this->save_path)) {
210
+			throw new ErrorException(
211
+				sprintf(
212
+					esc_html__('Invalid or missing session save path: %s', 'event_espresso'),
213
+					$this->save_path
214
+				),
215
+				0,
216
+				E_WARNING,
217
+				__FILE__,
218
+				__LINE__
219
+			);
220
+		}
221
+	}
222
+
223
+
224
+	/**
225
+	 * @param string $session_save_path
226
+	 * @return string
227
+	 * @since 5.0.47
228
+	 */
229
+	private function normalizeSessionSavePath(string $session_save_path): string
230
+	{
231
+		// Normalize "N;/path" style values
232
+		if (strpos($session_save_path, ';') !== false) {
233
+			$parts             = explode(';', $session_save_path);
234
+			$session_save_path = end($parts);
235
+		}
236
+		$session_save_path = trim((string) $session_save_path);
237
+		if ($session_save_path === '') {
238
+			// fall back to a sane temp dir if PHP reports no explicit session_save_path
239
+			$session_save_path = sys_get_temp_dir();
240
+		}
241
+		// normalize trailing separators
242
+		$session_save_path = rtrim($session_save_path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
243
+		// For 'files' handler we expect a writable filesystem directory.
244
+		// However, calling realpath() or is_dir() on paths outside of PHP's
245
+		// configured open_basedir can trigger warnings.
246
+		// Handle that case explicitly by checking open_basedir first
247
+		// and avoiding functions that would emit warnings.
248
+		if ($this->open_basedir !== '') {
249
+			$session_save_path = $this->checkPathsOutsideBaseDir($session_save_path);
250
+		} else {
251
+			// No open_basedir restriction; realpath() is safe to use and gives
252
+			// us a canonical path for the directory checks.
253
+			$session_save_path = @realpath($session_save_path) ?: $session_save_path;
254
+		}
255
+		return $session_save_path;
256
+	}
257
+
258
+
259
+	/**
260
+	 * @param string $session_save_path
261
+	 * @return string
262
+	 * @since 5.0.47
263
+	 */
264
+	private function checkPathsOutsideBaseDir(string $session_save_path): string
265
+	{
266
+		// open_basedir is set; check whether the configured session path
267
+		// appears to be within one of the allowed paths.
268
+		$allowed_paths = array_filter(array_map('trim', explode(PATH_SEPARATOR, $this->open_basedir)));
269
+		$allowed       = false;
270
+		foreach ($allowed_paths as $allowed_path) {
271
+			if ($allowed_path === '') {
272
+				continue;
273
+			}
274
+			// normalize trailing separators for comparison
275
+			$allowed_path_norm = rtrim($allowed_path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
276
+			// compare start of paths for a match
277
+			if (strncmp($session_save_path, $allowed_path_norm, strlen($allowed_path_norm)) === 0) {
278
+				$allowed = true;
279
+				break;
280
+			}
281
+		}
282
+		if ($allowed) {
283
+			return $session_save_path;
284
+		}
285
+		// open_basedir is set and the session save path is outside it.
286
+		// Log for diagnostics and skip strict filesystem checks rather than
287
+		// throwing an exception which causes fatal behavior on some hosts.
288
+		if ($this->logging_enabled) {
289
+			error_log(
290
+				sprintf(
291
+					"[SessionStartHandler] session_save_path outside open_basedir: save_path=%s open_basedir=%s - skipping validation",
292
+					$session_save_path,
293
+					$this->open_basedir
294
+				)
295
+			);
296
+		}
297
+		$this->skip_save_path_checks = true;
298
+		return $session_save_path;
299
+	}
300
+
301
+
302
+	/**
303
+	 * Returns `true` if the 'session.save_handler' ini setting matches a known custom handler
304
+	 *
305
+	 * @return bool
306
+	 * @since 4.9.68.p
307
+	 */
308
+	private function hasCustomSessionSaveHandler(): bool
309
+	{
310
+		return $this->save_handler === 'user';
311
+	}
312
+
313
+
314
+	/**
315
+	 * Attempt to start the PHP session when a custom Session Save Handler is known to be set.
316
+	 *
317
+	 * @throws ErrorException
318
+	 * @throws Throwable
319
+	 * @since 4.9.68.p
320
+	 */
321
+	private function checkCustomSessionSaveHandler(): void
322
+	{
323
+		// If we've already successfully tested the session save handler
324
+		// on a previous request then just start the session
325
+		if ($this->sessionSaveHandlerIsValid()) {
326
+			$this->sessionStart();
327
+			return;
328
+		}
329
+		// If not, then attempt to deal with any errors,
330
+		// otherwise, try to hobble along without the session
331
+		if (! $this->handleSessionSaveHandlerErrors()) {
332
+			return;
333
+		}
334
+		// there is no record of a fatal error while trying to start the session
335
+		// so let's see if there's a custom session save handler. Proceed with caution
336
+		if ($this->initializeSessionSaveHandlerStatus() === false) {
337
+			throw new ErrorException(
338
+				esc_html__('Failed to initialize session save handler status', 'event_espresso'),
339
+				0,
340
+				E_WARNING,
341
+				__FILE__,
342
+				__LINE__
343
+			);
344
+		}
345
+		// hold your breath, the custom session save handler might cause a fatal here...
346
+		$this->sessionStart();
347
+		// phew! we made it! the custom session handler is a-ok
348
+		if ($this->setSessionSaveHandlerStatusToValid() === false) {
349
+			throw new ErrorException(
350
+				esc_html__('Failed to set session save handler status to valid', 'event_espresso'),
351
+				0,
352
+				E_WARNING,
353
+				__FILE__,
354
+				__LINE__
355
+			);
356
+		}
357
+	}
358
+
359
+
360
+	/**
361
+	 * retrieves the value for the 'ee_session_save_handler_status' WP option.
362
+	 * default value = 'session_save_handler_untested'
363
+	 *
364
+	 * @return string
365
+	 * @since 4.9.68.p
366
+	 */
367
+	private function getSessionSaveHandlerStatus(): string
368
+	{
369
+		return get_option(
370
+			SessionStartHandler::OPTION_NAME_SESSION_SAVE_HANDLER_STATUS,
371
+			SessionStartHandler::SESSION_SAVE_HANDLER_STATUS_UNKNOWN
372
+		);
373
+	}
374
+
375
+
376
+	/**
377
+	 * Sets the 'ee_session_save_handler_status' WP option value to 'session_save_handler_failed'
378
+	 * which can then be upgraded is everything works correctly
379
+	 *
380
+	 * @return bool
381
+	 * @since 4.9.68.p
382
+	 */
383
+	private function initializeSessionSaveHandlerStatus(): bool
384
+	{
385
+		return update_option(
386
+			SessionStartHandler::OPTION_NAME_SESSION_SAVE_HANDLER_STATUS,
387
+			SessionStartHandler::SESSION_SAVE_HANDLER_STATUS_FAILED
388
+		);
389
+	}
390
+
391
+
392
+	/**
393
+	 * Sets the 'ee_session_save_handler_status' WP option value to 'session_save_handler_success'
394
+	 *
395
+	 * @return bool
396
+	 * @since 4.9.68.p
397
+	 */
398
+	private function setSessionSaveHandlerStatusToValid(): bool
399
+	{
400
+		return update_option(
401
+			SessionStartHandler::OPTION_NAME_SESSION_SAVE_HANDLER_STATUS,
402
+			SessionStartHandler::SESSION_SAVE_HANDLER_STATUS_SUCCESS
403
+		);
404
+	}
405
+
406
+
407
+	/**
408
+	 * Sets the 'ee_session_save_handler_status' WP option value to 'session_save_handler_untested'
409
+	 *
410
+	 * @return bool
411
+	 * @since 4.9.68.p
412
+	 */
413
+	private function resetSessionSaveHandlerStatus(): bool
414
+	{
415
+		return update_option(
416
+			SessionStartHandler::OPTION_NAME_SESSION_SAVE_HANDLER_STATUS,
417
+			SessionStartHandler::SESSION_SAVE_HANDLER_STATUS_UNKNOWN
418
+		);
419
+	}
420
+
421
+
422
+	/**
423
+	 * Returns `true` if the 'ee_session_save_handler_status' WP option value
424
+	 * is equal to 'session_save_handler_success'
425
+	 *
426
+	 * @return bool
427
+	 * @since 4.9.68.p
428
+	 */
429
+	private function sessionSaveHandlerIsValid(): bool
430
+	{
431
+		return $this->getSessionSaveHandlerStatus() === SessionStartHandler::SESSION_SAVE_HANDLER_STATUS_SUCCESS;
432
+	}
433
+
434
+
435
+	/**
436
+	 * Returns `true` if the 'ee_session_save_handler_status' WP option value
437
+	 * is equal to 'session_save_handler_failed'
438
+	 *
439
+	 * @return bool
440
+	 * @since 4.9.68.p
441
+	 */
442
+	private function sessionSaveHandlerFailed(): bool
443
+	{
444
+		return $this->getSessionSaveHandlerStatus() === SessionStartHandler::SESSION_SAVE_HANDLER_STATUS_FAILED;
445
+	}
446
+
447
+
448
+	/**
449
+	 * @param int    $severity
450
+	 * @param string $message
451
+	 * @param string $file
452
+	 * @param int    $line
453
+	 * @return bool
454
+	 * @throws ErrorException
455
+	 * @since 5.0.46
456
+	 */
457
+	public function customErrorHandler(int $severity, string $message, string $file, int $line): bool
458
+	{
459
+		// Only convert warnings we care about
460
+		if (($severity & E_WARNING) === E_WARNING) {
461
+			throw new ErrorException($message, 0, $severity, $file, $line);
462
+		}
463
+		// fallback to PHP's normal handler for other severities
464
+		return false;
465
+	}
466
+
467
+
468
+	/**
469
+	 * @param callable|null $previous_handler
470
+	 * @return void
471
+	 * @since 5.0.46
472
+	 */
473
+	private function restorePreviousErrorHandler(?callable $previous_handler): void
474
+	{
475
+		if ($previous_handler !== null) {
476
+			set_error_handler($previous_handler);
477
+		} else {
478
+			restore_error_handler();
479
+		}
480
+	}
481
+
482
+
483
+	/**
484
+	 * Returns `true` if no errors were detected with the session save handler,
485
+	 * otherwise attempts to work notify the appropriate authorities
486
+	 * with a suggestion for how to fix the issue, and returns `false`.
487
+	 *
488
+	 * @return bool
489
+	 * @throws ErrorException
490
+	 * @throws Throwable
491
+	 * @since 4.9.68.p
492
+	 */
493
+	private function handleSessionSaveHandlerErrors(): bool
494
+	{
495
+		// Check if we had a fatal error last time while trying to start the session
496
+		if ($this->sessionSaveHandlerFailed()) {
497
+			// apparently, last time we tried using the custom session save handler there was a fatal
498
+			if ($this->request->requestParamIsSet(SessionStartHandler::REQUEST_PARAM_RETRY_SESSION)) {
499
+				if ($this->resetSessionSaveHandlerStatus() === false) {
500
+					throw new ErrorException(
501
+						esc_html__('Failed to reset session save handler status', 'event_espresso'),
502
+						0,
503
+						E_WARNING,
504
+						__FILE__,
505
+						__LINE__
506
+					);
507
+				}
508
+				// remove "ee_retry_session", otherwise if the problem still isn't fixed,
509
+				// we'll just keep getting the fatal error over and over.
510
+				// Better to remove it and redirect, and try on the next request
511
+				EEH_URL::safeRedirectAndExit(
512
+					remove_query_arg(
513
+						[SessionStartHandler::REQUEST_PARAM_RETRY_SESSION],
514
+						EEH_URL::current_url()
515
+					)
516
+				);
517
+			}
518
+			// so the session is broken, don't try it again,
519
+			// just show a message to users that can fix it
520
+			$this->displaySessionSaveHandlerErrorNotice();
521
+			return false;
522
+		}
523
+		return true;
524
+	}
525
+
526
+
527
+	/**
528
+	 * @since 4.9.68.p
529
+	 */
530
+	private function displaySessionSaveHandlerErrorNotice(): void
531
+	{
532
+		$retry_session_url = add_query_arg(
533
+			[SessionStartHandler::REQUEST_PARAM_RETRY_SESSION => true],
534
+			EEH_URL::current_url()
535
+		);
536
+		$this->displaySessionErrorNotice(
537
+			sprintf(
538
+				esc_html__(
539
+					'It appears there was a fatal error while starting the session, so Event Espresso is not able to process registrations normally. Some hosting companies, like Pantheon, require an extra plugin for Event Espresso to work. Please install the %1$sWordPress Native PHP Sessions plugin%2$s, then %3$sclick here to check if the problem is resolved.%2$s',
540
+					'event_espresso'
541
+				),
542
+				'<a href="https://wordpress.org/plugins/wp-native-php-sessions/">',
543
+				'</a>',
544
+				'<a href="' . $retry_session_url . '">'
545
+			),
546
+			__FILE__,
547
+			__FUNCTION__,
548
+			__LINE__
549
+		);
550
+	}
551
+
552
+
553
+	/**
554
+	 * Generates an EE_Error notice regarding the current session woes
555
+	 * but only if the current user is an admin with permission to 'install_plugins'.
556
+	 *
557
+	 * @since 5.0.46
558
+	 */
559
+	private function displaySessionErrorNotice(string $message, string $file, string $function, int $line): void
560
+	{
561
+		if (current_user_can('install_plugins')) {
562
+			EE_Error::add_error($message, $file, $function, $line);
563
+		}
564
+	}
565 565
 }
Please login to merge, or discard this patch.
Spacing   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -206,7 +206,7 @@  discard block
 block discarded – undo
206 206
             return;
207 207
         }
208 208
         // Use @-suppressed checks to avoid PHP warnings while validating.
209
-        if (! @is_dir($this->save_path) || ! @is_writable($this->save_path)) {
209
+        if ( ! @is_dir($this->save_path) || ! @is_writable($this->save_path)) {
210 210
             throw new ErrorException(
211 211
                 sprintf(
212 212
                     esc_html__('Invalid or missing session save path: %s', 'event_espresso'),
@@ -239,7 +239,7 @@  discard block
 block discarded – undo
239 239
             $session_save_path = sys_get_temp_dir();
240 240
         }
241 241
         // normalize trailing separators
242
-        $session_save_path = rtrim($session_save_path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
242
+        $session_save_path = rtrim($session_save_path, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
243 243
         // For 'files' handler we expect a writable filesystem directory.
244 244
         // However, calling realpath() or is_dir() on paths outside of PHP's
245 245
         // configured open_basedir can trigger warnings.
@@ -272,7 +272,7 @@  discard block
 block discarded – undo
272 272
                 continue;
273 273
             }
274 274
             // normalize trailing separators for comparison
275
-            $allowed_path_norm = rtrim($allowed_path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
275
+            $allowed_path_norm = rtrim($allowed_path, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
276 276
             // compare start of paths for a match
277 277
             if (strncmp($session_save_path, $allowed_path_norm, strlen($allowed_path_norm)) === 0) {
278 278
                 $allowed = true;
@@ -328,7 +328,7 @@  discard block
 block discarded – undo
328 328
         }
329 329
         // If not, then attempt to deal with any errors,
330 330
         // otherwise, try to hobble along without the session
331
-        if (! $this->handleSessionSaveHandlerErrors()) {
331
+        if ( ! $this->handleSessionSaveHandlerErrors()) {
332 332
             return;
333 333
         }
334 334
         // there is no record of a fatal error while trying to start the session
@@ -541,7 +541,7 @@  discard block
 block discarded – undo
541 541
                 ),
542 542
                 '<a href="https://wordpress.org/plugins/wp-native-php-sessions/">',
543 543
                 '</a>',
544
-                '<a href="' . $retry_session_url . '">'
544
+                '<a href="'.$retry_session_url.'">'
545 545
             ),
546 546
             __FILE__,
547 547
             __FUNCTION__,
Please login to merge, or discard this patch.
core/services/payments/PaymentProcessorFees.php 2 patches
Indentation   +207 added lines, -207 removed lines patch added patch discarded remove patch
@@ -30,211 +30,211 @@
 block discarded – undo
30 30
  */
31 31
 class PaymentProcessorFees
32 32
 {
33
-    public const  GATEWAY_PAYPAL  = 'PayPal Commerce';
34
-
35
-    public const  GATEWAY_SQUARE  = 'Square';
36
-
37
-    public const  GATEWAY_STRIPE  = 'Stripe';
38
-
39
-    private GracePeriod $grace_period;
40
-
41
-    private LicenseData $license_data;
42
-
43
-    /**
44
-     * @var float[][] $gateway_fees
45
-     */
46
-    private array $gateway_fees = [
47
-        LicenseStatus::ACTIVE  => [
48
-            PaymentProcessorFees::GATEWAY_PAYPAL => 0.00,
49
-            PaymentProcessorFees::GATEWAY_SQUARE => 0.00,
50
-            PaymentProcessorFees::GATEWAY_STRIPE => 0.00,
51
-        ],
52
-        LicenseStatus::EXPIRED => [
53
-            PaymentProcessorFees::GATEWAY_PAYPAL => 3.00,
54
-            PaymentProcessorFees::GATEWAY_SQUARE => 3.00,
55
-            PaymentProcessorFees::GATEWAY_STRIPE => 3.00,
56
-        ],
57
-        LicenseStatus::DECAF   => [
58
-            PaymentProcessorFees::GATEWAY_PAYPAL => 3.00,
59
-            PaymentProcessorFees::GATEWAY_SQUARE => 3.00,
60
-            PaymentProcessorFees::GATEWAY_STRIPE => 3.00,
61
-        ],
62
-    ];
63
-
64
-    private array $partner_gateways = [
65
-        PaymentProcessorFees::GATEWAY_PAYPAL,
66
-        PaymentProcessorFees::GATEWAY_SQUARE,
67
-        PaymentProcessorFees::GATEWAY_STRIPE,
68
-    ];
69
-
70
-
71
-    /**
72
-     * @param GracePeriod $grace_period
73
-     * @param LicenseData $license_data
74
-     */
75
-    public function __construct(GracePeriod $grace_period, LicenseData $license_data)
76
-    {
77
-        $this->grace_period = $grace_period;
78
-        $this->license_data = $license_data;
79
-    }
80
-
81
-
82
-    /**
83
-     * @param EE_Transaction $transaction
84
-     * @param string         $payment_method_name
85
-     * @param float          $amount
86
-     * @return float
87
-     * @throws EE_Error|ReflectionException|RuntimeException
88
-     * @throws Exception
89
-     */
90
-    public function applyGatewayPartnerFees(
91
-        EE_Transaction $transaction,
92
-        string $payment_method_name,
93
-        float $amount = 0.00
94
-    ): float {
95
-        $processing_fee = $this->forPaymentMethod($payment_method_name);
96
-        if ($processing_fee <= 0.00) {
97
-            return $amount;
98
-        }
99
-        $grand_total = $transaction->total_line_item(false);
100
-        if (
101
-            ! $grand_total instanceof EE_Line_Item
102
-            || ! $grand_total->is_total()
103
-            || $grand_total->TXN_ID() !== $transaction->ID()
104
-        ) {
105
-            // throw RuntimeException if total_line_item is not a total line item
106
-            throw new RuntimeException(
107
-                sprintf(
108
-                    esc_html__(
109
-                        'Invalid or missing grand total line item for transaction %1$d.',
110
-                        'event_espresso'
111
-                    ),
112
-                    $transaction->ID()
113
-                )
114
-            );
115
-        }
116
-        $line_item = EEH_Line_Item::add_percentage_based_item(
117
-            EEH_Line_Item::get_pre_tax_subtotal($grand_total),
118
-            esc_html__('Payment Processing Fee', 'event_espresso'),
119
-            $processing_fee,
120
-            '',
121
-            false,
122
-            sanitize_key(
123
-                sprintf(
124
-                    '%1$s-fee-%2$d',
125
-                    $payment_method_name,
126
-                    $transaction->ID()
127
-                )
128
-            ),
129
-            true
130
-        );
131
-        $line_item->save();
132
-        return $grand_total->recalculate_total_including_taxes();
133
-    }
134
-
135
-
136
-    /**
137
-     * Returns the fee for a specific payment method based on the license status.
138
-     *
139
-     * @param string $payment_method_name
140
-     * @return float
141
-     * @throws Exception|OutOfBoundsException|RuntimeException
142
-     */
143
-    public function forPaymentMethod(string $payment_method_name): float
144
-    {
145
-        if (! $this->isPartnerGateway($payment_method_name)) {
146
-            return 0.0;
147
-        }
148
-        $license_status = $this->license_data->licenseStatus();
149
-        $license_expires = $this->license_data->licenseExpiry();
150
-        // decaf, new activations, or expired licenses are allowed a grace period
151
-        if ($this->grace_period->withinGracePeriod($license_status, $license_expires)) {
152
-            return 0.0;
153
-        }
154
-        return $this->getGatewayFee($payment_method_name, $license_status);
155
-    }
156
-
157
-
158
-    /**
159
-     * Checks if a gateway is a partner gateway.
160
-     *
161
-     * @param string $payment_method_name
162
-     * @return bool
163
-     */
164
-    public function isPartnerGateway(string $payment_method_name): bool
165
-    {
166
-        return in_array($payment_method_name, $this->partner_gateways, true);
167
-    }
168
-
169
-
170
-    /**
171
-     * Returns the fee for a specific payment method based on the license status.
172
-     *
173
-     * @param string $payment_method_name
174
-     * @param string $license_status
175
-     * @return float
176
-     * @throws OutOfBoundsException
177
-     */
178
-    private function getGatewayFee(string $payment_method_name, string $license_status): float
179
-    {
180
-        if (isset($this->gateway_fees[ $license_status ][ $payment_method_name ])) {
181
-            return $this->gateway_fees[ $license_status ][ $payment_method_name ];
182
-        }
183
-        throw new OutOfBoundsException(
184
-            sprintf(
185
-                esc_html__('A partner fee for %1$s with %2$s license is not defined.', 'event_espresso'),
186
-                $payment_method_name,
187
-                $license_status
188
-            )
189
-        );
190
-    }
191
-
192
-
193
-    /**
194
-     * Get fees notice.
195
-     *
196
-     * @param $pm_name
197
-     * @return EE_Form_Section_HTML
198
-     */
199
-    public function getFeesNotice($pm_name): EE_Form_Section_HTML
200
-    {
201
-        $active  = $this->gateway_fees[ LicenseStatus::ACTIVE ][ $pm_name ] ?? 0;
202
-        $expired = $this->gateway_fees[ LicenseStatus::EXPIRED ][ $pm_name ] ?? 3;
203
-        return new EE_Form_Section_HTML(
204
-            EEH_HTML::tr(
205
-                EEH_HTML::th()
206
-                . EEH_HTML::thx()
207
-                . EEH_HTML::td(
208
-                    EEH_HTML::div(
209
-                        EEH_HTML::strong(
210
-                            esc_html__(
211
-                                "$pm_name Application Fees are based upon the status of your Support License:",
212
-                                'event_espresso'
213
-                            )
214
-                        )
215
-                        . EEH_HTML::ul()
216
-                        . EEH_HTML::li(
217
-                            sprintf(
218
-                                esc_html__('- Active licenses commission fees: %1$s', 'event_espresso'),
219
-                                "$active%"
220
-                            )
221
-                        )
222
-                        . EEH_HTML::li(
223
-                            sprintf(
224
-                                esc_html__('- Expired license commission fees: %1$s', 'event_espresso'),
225
-                                "$expired%"
226
-                            )
227
-                        )
228
-                        . EEH_HTML::ulx()
229
-                        . esc_html__(
230
-                            'Keep your support license active for: lower fees, up-to-date software and have access to our support team. By connecting and processing payments you agree to these terms.',
231
-                            'event_espresso'
232
-                        ),
233
-                        '',
234
-                        'ee-status-outline ee-status-bg--info'
235
-                    )
236
-                )
237
-            )
238
-        );
239
-    }
33
+	public const  GATEWAY_PAYPAL  = 'PayPal Commerce';
34
+
35
+	public const  GATEWAY_SQUARE  = 'Square';
36
+
37
+	public const  GATEWAY_STRIPE  = 'Stripe';
38
+
39
+	private GracePeriod $grace_period;
40
+
41
+	private LicenseData $license_data;
42
+
43
+	/**
44
+	 * @var float[][] $gateway_fees
45
+	 */
46
+	private array $gateway_fees = [
47
+		LicenseStatus::ACTIVE  => [
48
+			PaymentProcessorFees::GATEWAY_PAYPAL => 0.00,
49
+			PaymentProcessorFees::GATEWAY_SQUARE => 0.00,
50
+			PaymentProcessorFees::GATEWAY_STRIPE => 0.00,
51
+		],
52
+		LicenseStatus::EXPIRED => [
53
+			PaymentProcessorFees::GATEWAY_PAYPAL => 3.00,
54
+			PaymentProcessorFees::GATEWAY_SQUARE => 3.00,
55
+			PaymentProcessorFees::GATEWAY_STRIPE => 3.00,
56
+		],
57
+		LicenseStatus::DECAF   => [
58
+			PaymentProcessorFees::GATEWAY_PAYPAL => 3.00,
59
+			PaymentProcessorFees::GATEWAY_SQUARE => 3.00,
60
+			PaymentProcessorFees::GATEWAY_STRIPE => 3.00,
61
+		],
62
+	];
63
+
64
+	private array $partner_gateways = [
65
+		PaymentProcessorFees::GATEWAY_PAYPAL,
66
+		PaymentProcessorFees::GATEWAY_SQUARE,
67
+		PaymentProcessorFees::GATEWAY_STRIPE,
68
+	];
69
+
70
+
71
+	/**
72
+	 * @param GracePeriod $grace_period
73
+	 * @param LicenseData $license_data
74
+	 */
75
+	public function __construct(GracePeriod $grace_period, LicenseData $license_data)
76
+	{
77
+		$this->grace_period = $grace_period;
78
+		$this->license_data = $license_data;
79
+	}
80
+
81
+
82
+	/**
83
+	 * @param EE_Transaction $transaction
84
+	 * @param string         $payment_method_name
85
+	 * @param float          $amount
86
+	 * @return float
87
+	 * @throws EE_Error|ReflectionException|RuntimeException
88
+	 * @throws Exception
89
+	 */
90
+	public function applyGatewayPartnerFees(
91
+		EE_Transaction $transaction,
92
+		string $payment_method_name,
93
+		float $amount = 0.00
94
+	): float {
95
+		$processing_fee = $this->forPaymentMethod($payment_method_name);
96
+		if ($processing_fee <= 0.00) {
97
+			return $amount;
98
+		}
99
+		$grand_total = $transaction->total_line_item(false);
100
+		if (
101
+			! $grand_total instanceof EE_Line_Item
102
+			|| ! $grand_total->is_total()
103
+			|| $grand_total->TXN_ID() !== $transaction->ID()
104
+		) {
105
+			// throw RuntimeException if total_line_item is not a total line item
106
+			throw new RuntimeException(
107
+				sprintf(
108
+					esc_html__(
109
+						'Invalid or missing grand total line item for transaction %1$d.',
110
+						'event_espresso'
111
+					),
112
+					$transaction->ID()
113
+				)
114
+			);
115
+		}
116
+		$line_item = EEH_Line_Item::add_percentage_based_item(
117
+			EEH_Line_Item::get_pre_tax_subtotal($grand_total),
118
+			esc_html__('Payment Processing Fee', 'event_espresso'),
119
+			$processing_fee,
120
+			'',
121
+			false,
122
+			sanitize_key(
123
+				sprintf(
124
+					'%1$s-fee-%2$d',
125
+					$payment_method_name,
126
+					$transaction->ID()
127
+				)
128
+			),
129
+			true
130
+		);
131
+		$line_item->save();
132
+		return $grand_total->recalculate_total_including_taxes();
133
+	}
134
+
135
+
136
+	/**
137
+	 * Returns the fee for a specific payment method based on the license status.
138
+	 *
139
+	 * @param string $payment_method_name
140
+	 * @return float
141
+	 * @throws Exception|OutOfBoundsException|RuntimeException
142
+	 */
143
+	public function forPaymentMethod(string $payment_method_name): float
144
+	{
145
+		if (! $this->isPartnerGateway($payment_method_name)) {
146
+			return 0.0;
147
+		}
148
+		$license_status = $this->license_data->licenseStatus();
149
+		$license_expires = $this->license_data->licenseExpiry();
150
+		// decaf, new activations, or expired licenses are allowed a grace period
151
+		if ($this->grace_period->withinGracePeriod($license_status, $license_expires)) {
152
+			return 0.0;
153
+		}
154
+		return $this->getGatewayFee($payment_method_name, $license_status);
155
+	}
156
+
157
+
158
+	/**
159
+	 * Checks if a gateway is a partner gateway.
160
+	 *
161
+	 * @param string $payment_method_name
162
+	 * @return bool
163
+	 */
164
+	public function isPartnerGateway(string $payment_method_name): bool
165
+	{
166
+		return in_array($payment_method_name, $this->partner_gateways, true);
167
+	}
168
+
169
+
170
+	/**
171
+	 * Returns the fee for a specific payment method based on the license status.
172
+	 *
173
+	 * @param string $payment_method_name
174
+	 * @param string $license_status
175
+	 * @return float
176
+	 * @throws OutOfBoundsException
177
+	 */
178
+	private function getGatewayFee(string $payment_method_name, string $license_status): float
179
+	{
180
+		if (isset($this->gateway_fees[ $license_status ][ $payment_method_name ])) {
181
+			return $this->gateway_fees[ $license_status ][ $payment_method_name ];
182
+		}
183
+		throw new OutOfBoundsException(
184
+			sprintf(
185
+				esc_html__('A partner fee for %1$s with %2$s license is not defined.', 'event_espresso'),
186
+				$payment_method_name,
187
+				$license_status
188
+			)
189
+		);
190
+	}
191
+
192
+
193
+	/**
194
+	 * Get fees notice.
195
+	 *
196
+	 * @param $pm_name
197
+	 * @return EE_Form_Section_HTML
198
+	 */
199
+	public function getFeesNotice($pm_name): EE_Form_Section_HTML
200
+	{
201
+		$active  = $this->gateway_fees[ LicenseStatus::ACTIVE ][ $pm_name ] ?? 0;
202
+		$expired = $this->gateway_fees[ LicenseStatus::EXPIRED ][ $pm_name ] ?? 3;
203
+		return new EE_Form_Section_HTML(
204
+			EEH_HTML::tr(
205
+				EEH_HTML::th()
206
+				. EEH_HTML::thx()
207
+				. EEH_HTML::td(
208
+					EEH_HTML::div(
209
+						EEH_HTML::strong(
210
+							esc_html__(
211
+								"$pm_name Application Fees are based upon the status of your Support License:",
212
+								'event_espresso'
213
+							)
214
+						)
215
+						. EEH_HTML::ul()
216
+						. EEH_HTML::li(
217
+							sprintf(
218
+								esc_html__('- Active licenses commission fees: %1$s', 'event_espresso'),
219
+								"$active%"
220
+							)
221
+						)
222
+						. EEH_HTML::li(
223
+							sprintf(
224
+								esc_html__('- Expired license commission fees: %1$s', 'event_espresso'),
225
+								"$expired%"
226
+							)
227
+						)
228
+						. EEH_HTML::ulx()
229
+						. esc_html__(
230
+							'Keep your support license active for: lower fees, up-to-date software and have access to our support team. By connecting and processing payments you agree to these terms.',
231
+							'event_espresso'
232
+						),
233
+						'',
234
+						'ee-status-outline ee-status-bg--info'
235
+					)
236
+				)
237
+			)
238
+		);
239
+	}
240 240
 }
Please login to merge, or discard this patch.
Spacing   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -142,7 +142,7 @@  discard block
 block discarded – undo
142 142
      */
143 143
     public function forPaymentMethod(string $payment_method_name): float
144 144
     {
145
-        if (! $this->isPartnerGateway($payment_method_name)) {
145
+        if ( ! $this->isPartnerGateway($payment_method_name)) {
146 146
             return 0.0;
147 147
         }
148 148
         $license_status = $this->license_data->licenseStatus();
@@ -177,8 +177,8 @@  discard block
 block discarded – undo
177 177
      */
178 178
     private function getGatewayFee(string $payment_method_name, string $license_status): float
179 179
     {
180
-        if (isset($this->gateway_fees[ $license_status ][ $payment_method_name ])) {
181
-            return $this->gateway_fees[ $license_status ][ $payment_method_name ];
180
+        if (isset($this->gateway_fees[$license_status][$payment_method_name])) {
181
+            return $this->gateway_fees[$license_status][$payment_method_name];
182 182
         }
183 183
         throw new OutOfBoundsException(
184 184
             sprintf(
@@ -198,8 +198,8 @@  discard block
 block discarded – undo
198 198
      */
199 199
     public function getFeesNotice($pm_name): EE_Form_Section_HTML
200 200
     {
201
-        $active  = $this->gateway_fees[ LicenseStatus::ACTIVE ][ $pm_name ] ?? 0;
202
-        $expired = $this->gateway_fees[ LicenseStatus::EXPIRED ][ $pm_name ] ?? 3;
201
+        $active  = $this->gateway_fees[LicenseStatus::ACTIVE][$pm_name] ?? 0;
202
+        $expired = $this->gateway_fees[LicenseStatus::EXPIRED][$pm_name] ?? 3;
203 203
         return new EE_Form_Section_HTML(
204 204
             EEH_HTML::tr(
205 205
                 EEH_HTML::th()
Please login to merge, or discard this patch.