@@ -13,664 +13,664 @@ |
||
| 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 | } |
@@ -20,40 +20,40 @@ |
||
| 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 | } |
@@ -7,33 +7,33 @@ |
||
| 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 | } |
@@ -21,68 +21,68 @@ |
||
| 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 | } |
@@ -17,189 +17,189 @@ |
||
| 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 | } |
@@ -47,7 +47,7 @@ discard block |
||
| 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 |
||
| 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 |
||
| 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 |
||
| 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 |
||
| 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 |
||
| 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; |
@@ -13,70 +13,70 @@ |
||
| 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 | } |
@@ -15,95 +15,95 @@ |
||
| 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 | } |
@@ -29,537 +29,537 @@ |
||
| 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 | } |
@@ -206,7 +206,7 @@ discard block |
||
| 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 |
||
| 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 |
||
| 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 |
||
| 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 |
||
| 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__, |
@@ -30,211 +30,211 @@ |
||
| 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 | } |
@@ -142,7 +142,7 @@ discard block |
||
| 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 |
||
| 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 |
||
| 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() |