PluginProbe ʕ •ᴥ•ʔ
Royal Addons for Elementor – Addons and Templates Kit for Elementor / 1.7.1064
Royal Addons for Elementor – Addons and Templates Kit for Elementor v1.7.1064
1.7.1064 1.7.1063 1.7.1062 1.7.1061 1.7.1060 1.7.1059 1.7.1058 trunk 1.0.0 1.1.0 1.2 1.3 1.3.1 1.3.2 1.3.21 1.3.22 1.3.23 1.3.24 1.3.25 1.3.26 1.3.27 1.3.28 1.3.29 1.3.30 1.3.31 1.3.32 1.3.33 1.3.34 1.3.35 1.3.36 1.3.37 1.3.38 1.3.39 1.3.40 1.3.41 1.3.42 1.3.43 1.3.44 1.3.45 1.3.46 1.3.47 1.3.48 1.3.49 1.3.50 1.3.51 1.3.52 1.3.53 1.3.54 1.3.55 1.3.56 1.3.57 1.3.58 1.3.59 1.3.60 1.3.61 1.3.62 1.3.63 1.3.64 1.3.65 1.3.66 1.3.67 1.3.68 1.3.69 1.3.70 1.3.71 1.3.72 1.3.73 1.3.74 1.3.75 1.3.76 1.3.77 1.3.78 1.3.79 1.3.80 1.3.81 1.3.82 1.3.83 1.3.84 1.3.85 1.3.86 1.3.87 1.3.88 1.3.89 1.3.90 1.3.91 1.3.92 1.3.93 1.3.94 1.3.95 1.3.96 1.3.97 1.3.971 1.3.972 1.3.973 1.3.974 1.3.975 1.3.976 1.3.977 1.3.978 1.3.979 1.3.980 1.3.981 1.3.982 1.3.983 1.3.984 1.3.985 1.3.986 1.3.987 1.7.1 1.7.1001 1.7.1002 1.7.1003 1.7.1004 1.7.1005 1.7.1006 1.7.1007 1.7.1008 1.7.1009 1.7.1010 1.7.1011 1.7.1012 1.7.1013 1.7.1014 1.7.1015 1.7.1016 1.7.1017 1.7.1018 1.7.1019 1.7.1020 1.7.1021 1.7.1022 1.7.1023 1.7.1024 1.7.1025 1.7.1026 1.7.1027 1.7.1028 1.7.1029 1.7.1030 1.7.1031 1.7.1032 1.7.1033 1.7.1034 1.7.1035 1.7.1036 1.7.1037 1.7.1038 1.7.1039 1.7.1040 1.7.1041 1.7.1042 1.7.1043 1.7.1044 1.7.1045 1.7.1046 1.7.1047 1.7.1048 1.7.1049 1.7.1050 1.7.1051 1.7.1052 1.7.1053 1.7.1054 1.7.1055 1.7.1056 1.7.1057
royal-elementor-addons / freemius / includes / class-fs-logger.php
royal-elementor-addons / freemius / includes Last commit date
customizer 5 days ago debug 5 days ago entities 5 days ago managers 5 days ago sdk 5 days ago supplements 5 days ago class-freemius-abstract.php 5 days ago class-freemius.php 5 days ago class-fs-admin-notices.php 5 days ago class-fs-api.php 5 days ago class-fs-garbage-collector.php 5 days ago class-fs-lock.php 5 days ago class-fs-logger.php 5 days ago class-fs-options.php 5 days ago class-fs-plugin-updater.php 5 days ago class-fs-security.php 5 days ago class-fs-storage.php 5 days ago class-fs-user-lock.php 5 days ago fs-core-functions.php 5 days ago fs-essential-functions.php 5 days ago fs-html-escaping-functions.php 5 days ago fs-plugin-info-dialog.php 5 days ago index.php 5 days ago l10n.php 5 days ago
class-fs-logger.php
729 lines
1 <?php
2 /**
3 * @package Freemius
4 * @copyright Copyright (c) 2015, Freemius, Inc.
5 * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6 * @since 1.0.3
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 class FS_Logger {
14 private $_id;
15 private $_on = false;
16 private $_echo = false;
17 private $_file_start = 0;
18 /**
19 * @var int PHP Process ID.
20 */
21 private static $_processID;
22 /**
23 * @var string PHP Script user name.
24 */
25 private static $_ownerName;
26 /**
27 * @var bool Is storage logging turned on.
28 */
29 private static $_isStorageLoggingOn;
30 /**
31 * @var int ABSPATH length.
32 */
33 private static $_abspathLength;
34
35 /**
36 * @var FS_Logger[] $LOGGERS
37 */
38 private static $LOGGERS = array();
39 private static $LOG = array();
40 private static $CNT = 0;
41 private static $_HOOKED_FOOTER = false;
42
43 private function __construct( $id, $on = false, $echo = false ) {
44 $bt = debug_backtrace();
45
46 $this->_id = $id;
47
48 $caller = $bt[2];
49
50 if ( false !== strpos( $caller['file'], 'plugins' ) ) {
51 $this->_file_start = strpos( $caller['file'], 'plugins' ) + strlen( 'plugins/' );
52 } else {
53 $this->_file_start = strpos( $caller['file'], 'themes' ) + strlen( 'themes/' );
54 }
55
56 if ( $on ) {
57 $this->on();
58 }
59 if ( $echo ) {
60 $this->echo_on();
61 }
62 }
63
64 /**
65 * @param string $id
66 * @param bool $on
67 * @param bool $echo
68 *
69 * @return FS_Logger
70 */
71 public static function get_logger( $id, $on = false, $echo = false ) {
72 $id = strtolower( $id );
73
74 if ( ! isset( self::$_processID ) ) {
75 self::init();
76 }
77
78 if ( ! isset( self::$LOGGERS[ $id ] ) ) {
79 self::$LOGGERS[ $id ] = new FS_Logger( $id, $on, $echo );
80 }
81
82 return self::$LOGGERS[ $id ];
83 }
84
85 /**
86 * Initialize logging global info.
87 *
88 * @author Vova Feldman (@svovaf)
89 * @since 1.2.1.6
90 */
91 private static function init() {
92 self::$_ownerName = function_exists( 'get_current_user' ) ?
93 get_current_user() :
94 'unknown';
95 self::$_isStorageLoggingOn = ( 1 == get_option( 'fs_storage_logger', 0 ) );
96 self::$_abspathLength = strlen( ABSPATH );
97 self::$_processID = mt_rand( 0, 32000 );
98
99 // Process ID may be `false` on errors.
100 if ( ! is_numeric( self::$_processID ) ) {
101 self::$_processID = 0;
102 }
103 }
104
105 private static function hook_footer() {
106 if ( self::$_HOOKED_FOOTER ) {
107 return;
108 }
109
110 if ( is_admin() ) {
111 add_action( 'admin_footer', 'FS_Logger::dump', 100 );
112 } else {
113 add_action( 'wp_footer', 'FS_Logger::dump', 100 );
114 }
115 }
116
117 function is_on() {
118 return $this->_on;
119 }
120
121 function on() {
122 $this->_on = true;
123
124 if ( ! function_exists( 'dbDelta' ) ) {
125 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
126 }
127
128 self::hook_footer();
129 }
130 function echo_on() {
131 $this->on();
132
133 $this->_echo = true;
134 }
135
136 function is_echo_on() {
137 return $this->_echo;
138 }
139
140 function get_id() {
141 return $this->_id;
142 }
143
144 function get_file() {
145 return $this->_file_start;
146 }
147
148 private function _log( &$message, $type, $wrapper = false ) {
149 if ( ! $this->is_on() ) {
150 return;
151 }
152
153 $bt = debug_backtrace();
154 $depth = $wrapper ? 3 : 2;
155 while ( $depth < count( $bt ) - 1 && 'eval' === $bt[ $depth ]['function'] ) {
156 $depth ++;
157 }
158
159 $caller = $bt[ $depth ];
160
161 /**
162 * Retrieve the correct call file & line number from backtrace
163 * when logging from a wrapper method.
164 *
165 * @author Vova Feldman
166 * @since 1.2.1.6
167 */
168 if ( empty( $caller['line'] ) ) {
169 $depth --;
170
171 while ( $depth >= 0 ) {
172 if ( ! empty( $bt[ $depth ]['line'] ) ) {
173 $caller['line'] = $bt[ $depth ]['line'];
174 $caller['file'] = $bt[ $depth ]['file'];
175 break;
176 }
177 }
178 }
179
180 $log = array_merge( $caller, array(
181 'cnt' => self::$CNT ++,
182 'logger' => $this,
183 'timestamp' => microtime( true ),
184 'log_type' => $type,
185 'msg' => $message,
186 ) );
187
188 if ( self::$_isStorageLoggingOn ) {
189 $this->db_log( $type, $message, self::$CNT, $caller );
190 }
191
192 self::$LOG[] = $log;
193
194 if ( $this->is_echo_on() && ! Freemius::is_ajax() ) {
195 echo self::format_html( $log ) . "\n";
196 }
197 }
198
199 function log( $message, $wrapper = false ) {
200 $this->_log( $message, 'log', $wrapper );
201 }
202
203 function info( $message, $wrapper = false ) {
204 $this->_log( $message, 'info', $wrapper );
205 }
206
207 function warn( $message, $wrapper = false ) {
208 $this->_log( $message, 'warn', $wrapper );
209 }
210
211 function error( $message, $wrapper = false ) {
212 $this->_log( $message, 'error', $wrapper );
213 }
214
215 /**
216 * Log API error.
217 *
218 * @author Vova Feldman (@svovaf)
219 * @since 1.2.1.5
220 *
221 * @param mixed $api_result
222 * @param bool $wrapper
223 */
224 function api_error( $api_result, $wrapper = false ) {
225 $message = '';
226 if ( is_object( $api_result ) &&
227 ! empty( $api_result->error ) &&
228 ! empty( $api_result->error->message )
229 ) {
230 $message = $api_result->error->message;
231 } else if ( is_object( $api_result ) ) {
232 $message = var_export( $api_result, true );
233 } else if ( is_string( $api_result ) ) {
234 $message = $api_result;
235 } else if ( empty( $api_result ) ) {
236 $message = 'Empty API result.';
237 }
238
239 $message = 'API Error: ' . $message;
240
241 $this->_log( $message, 'error', $wrapper );
242 }
243
244 function entrance( $message = '', $wrapper = false ) {
245 $msg = 'Entrance' . ( empty( $message ) ? '' : ' > ' ) . $message;
246
247 $this->_log( $msg, 'log', $wrapper );
248 }
249
250 function departure( $message = '', $wrapper = false ) {
251 $msg = 'Departure' . ( empty( $message ) ? '' : ' > ' ) . $message;
252
253 $this->_log( $msg, 'log', $wrapper );
254 }
255
256 #--------------------------------------------------------------------------------
257 #region Log Formatting
258 #--------------------------------------------------------------------------------
259
260 private static function format( $log, $show_type = true ) {
261 return '[' . str_pad( $log['cnt'], strlen( self::$CNT ), '0', STR_PAD_LEFT ) . '] [' . $log['logger']->_id . '] ' . ( $show_type ? '[' . $log['log_type'] . ']' : '' ) . ( ! empty( $log['class'] ) ? $log['class'] . $log['type'] : '' ) . $log['function'] . ' >> ' . $log['msg'] . ( isset( $log['file'] ) ? ' (' . substr( $log['file'], $log['logger']->_file_start ) . ' ' . $log['line'] . ') ' : '' ) . ' [' . $log['timestamp'] . ']';
262 }
263
264 private static function format_html( $log ) {
265 return '<div style="font-size: 13px; font-family: monospace; color: #7da767; padding: 8px 3px; background: #000; border-bottom: 1px solid #555;">[' . $log['cnt'] . '] [' . $log['logger']->_id . '] [' . $log['log_type'] . '] <b><code style="color: #c4b1e0;">' . ( ! empty( $log['class'] ) ? $log['class'] . $log['type'] : '' ) . $log['function'] . '</code> >> <b style="color: #f59330;">' . esc_html( $log['msg'] ) . '</b></b>' . ( isset( $log['file'] ) ? ' (' . substr( $log['file'], $log['logger']->_file_start ) . ' ' . $log['line'] . ')' : '' ) . ' [' . $log['timestamp'] . ']</div>';
266 }
267
268 #endregion
269
270 static function dump() {
271 ?>
272 <!-- BEGIN: Freemius PHP Console Log -->
273 <script type="text/javascript">
274 <?php
275 foreach ( self::$LOG as $log ) {
276 echo 'console.' . $log['log_type'] . '(' . json_encode( self::format( $log, false ) ) . ')' . "\n";
277 }
278 ?>
279 </script>
280 <!-- END: Freemius PHP Console Log -->
281 <?php
282 }
283
284 static function get_log() {
285 return self::$LOG;
286 }
287
288 #--------------------------------------------------------------------------------
289 #region Database Logging
290 #--------------------------------------------------------------------------------
291
292 /**
293 * @author Vova Feldman (@svovaf)
294 * @since 1.2.1.6
295 *
296 * @return bool
297 */
298 public static function is_storage_logging_on() {
299 if ( ! isset( self::$_isStorageLoggingOn ) ) {
300 self::$_isStorageLoggingOn = ( 1 == get_option( 'fs_storage_logger', 0 ) );
301 }
302
303 return self::$_isStorageLoggingOn;
304 }
305
306 /**
307 * Turns on/off database persistent debugging to capture
308 * multi-session logs to debug complex flows like
309 * plugin auto-deactivate on premium version activation.
310 *
311 * @todo Check if Theme Check has issues with DB tables for themes.
312 *
313 * @author Vova Feldman (@svovaf)
314 * @since 1.2.1.6
315 *
316 * @param bool $is_on
317 *
318 * @return bool
319 */
320 public static function _set_storage_logging( $is_on = true ) {
321 global $wpdb;
322
323 $table = "{$wpdb->prefix}fs_logger";
324
325 /**
326 * Drop logging table in any case.
327 */
328 $result = $wpdb->query( "DROP TABLE IF EXISTS $table;" );
329
330 if ( $is_on ) {
331 /**
332 * Create logging table.
333 *
334 * NOTE:
335 * dbDelta must use KEY and not INDEX for indexes.
336 *
337 * @link https://core.trac.wordpress.org/ticket/2695
338 */
339 $result = $wpdb->query( "CREATE TABLE IF NOT EXISTS {$table} (
340 `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
341 `process_id` INT UNSIGNED NOT NULL,
342 `user_name` VARCHAR(64) NOT NULL,
343 `logger` VARCHAR(128) NOT NULL,
344 `log_order` INT UNSIGNED NOT NULL,
345 `type` ENUM('log','info','warn','error') NOT NULL DEFAULT 'log',
346 `message` TEXT NOT NULL,
347 `file` VARCHAR(256) NOT NULL,
348 `line` INT UNSIGNED NOT NULL,
349 `function` VARCHAR(256) NOT NULL,
350 `request_type` ENUM('call','ajax','cron') NOT NULL DEFAULT 'call',
351 `request_url` VARCHAR(1024) NOT NULL,
352 `created` DECIMAL(16, 6) NOT NULL,
353 PRIMARY KEY (`id`),
354 KEY `process_id` (`process_id` ASC),
355 KEY `process_logger` (`process_id` ASC, `logger` ASC),
356 KEY `function` (`function` ASC),
357 KEY `type` (`type` ASC))" );
358 }
359
360 if ( false !== $result ) {
361 update_option( 'fs_storage_logger', ( $is_on ? 1 : 0 ) );
362 self::$_isStorageLoggingOn = $is_on;
363 }
364
365 return ( false !== $result );
366 }
367
368 /**
369 * @author Vova Feldman (@svovaf)
370 * @since 1.2.1.6
371 *
372 * @param string $type
373 * @param string $message
374 * @param int $log_order
375 * @param array $caller
376 *
377 * @return false|int
378 */
379 private function db_log(
380 &$type,
381 &$message,
382 &$log_order,
383 &$caller
384 ) {
385 global $wpdb;
386
387 $request_type = 'call';
388 if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
389 $request_type = 'cron';
390 } else if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
391 $request_type = 'ajax';
392 }
393
394 $request_url = WP_FS__IS_HTTP_REQUEST ?
395 $_SERVER['REQUEST_URI'] :
396 '';
397
398 return $wpdb->insert(
399 "{$wpdb->prefix}fs_logger",
400 array(
401 'process_id' => self::$_processID,
402 'user_name' => self::$_ownerName,
403 'logger' => $this->_id,
404 'log_order' => $log_order,
405 'type' => $type,
406 'request_type' => $request_type,
407 'request_url' => $request_url,
408 'message' => $message,
409 'file' => isset( $caller['file'] ) ?
410 substr( $caller['file'], self::$_abspathLength ) :
411 '',
412 'line' => $caller['line'],
413 'function' => ( ! empty( $caller['class'] ) ? $caller['class'] . $caller['type'] : '' ) . $caller['function'],
414 'created' => microtime( true ),
415 )
416 );
417 }
418
419 /**
420 * Persistent DB logger columns.
421 *
422 * @var array
423 */
424 private static $_log_columns = array(
425 'id',
426 'process_id',
427 'user_name',
428 'logger',
429 'log_order',
430 'type',
431 'message',
432 'file',
433 'line',
434 'function',
435 'request_type',
436 'request_url',
437 'created',
438 );
439
440 /**
441 * Create DB logs query.
442 *
443 * @author Vova Feldman (@svovaf)
444 * @since 1.2.1.6
445 *
446 * @param bool $filters
447 * @param int $limit
448 * @param int $offset
449 * @param bool $order
450 * @param bool $escape_eol
451 *
452 * @return string
453 */
454 private static function build_db_logs_query(
455 $filters = false,
456 $limit = 200,
457 $offset = 0,
458 $order = false,
459 $escape_eol = false
460 ) {
461 global $wpdb;
462
463 $select = '*';
464
465 if ( $escape_eol ) {
466 $select = '';
467 for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) {
468 if ( $i > 0 ) {
469 $select .= ', ';
470 }
471
472 if ( 'message' !== self::$_log_columns[ $i ] ) {
473 $select .= self::$_log_columns[ $i ];
474 } else {
475 $select .= 'REPLACE(message , \'\n\', \' \') AS message';
476 }
477 }
478 }
479
480 $query = "SELECT {$select} FROM {$wpdb->prefix}fs_logger";
481 if ( is_array( $filters ) ) {
482 $criteria = array();
483
484 if ( ! empty( $filters['type'] ) && 'all' !== $filters['type'] ) {
485 $filters['type'] = strtolower( $filters['type'] );
486
487 switch ( $filters['type'] ) {
488 case 'warn_error':
489 $criteria[] = array( 'col' => 'type', 'val' => array( 'warn', 'error' ) );
490 break;
491 case 'error':
492 case 'warn':
493 $criteria[] = array( 'col' => 'type', 'val' => $filters['type'] );
494 break;
495 case 'info':
496 default:
497 $criteria[] = array( 'col' => 'type', 'val' => array( 'info', 'log' ) );
498 break;
499 }
500 }
501
502 if ( ! empty( $filters['request_type'] ) ) {
503 $filters['request_type'] = strtolower( $filters['request_type'] );
504
505 if ( in_array( $filters['request_type'], array( 'call', 'ajax', 'cron' ) ) ) {
506 $criteria[] = array( 'col' => 'request_type', 'val' => $filters['request_type'] );
507 }
508 }
509
510 if ( ! empty( $filters['file'] ) ) {
511 $criteria[] = array(
512 'col' => 'file',
513 'op' => 'LIKE',
514 'val' => '%' . esc_sql( $filters['file'] ),
515 );
516 }
517
518 if ( ! empty( $filters['function'] ) ) {
519 $criteria[] = array(
520 'col' => 'function',
521 'op' => 'LIKE',
522 'val' => '%' . esc_sql( $filters['function'] ),
523 );
524 }
525
526 if ( ! empty( $filters['process_id'] ) && is_numeric( $filters['process_id'] ) ) {
527 $criteria[] = array( 'col' => 'process_id', 'val' => $filters['process_id'] );
528 }
529
530 if ( ! empty( $filters['logger'] ) ) {
531 $criteria[] = array(
532 'col' => 'logger',
533 'op' => 'LIKE',
534 'val' => '%' . esc_sql( $filters['logger'] ) . '%',
535 );
536 }
537
538 if ( ! empty( $filters['message'] ) ) {
539 $criteria[] = array(
540 'col' => 'message',
541 'op' => 'LIKE',
542 'val' => '%' . esc_sql( $filters['message'] ) . '%',
543 );
544 }
545
546 if ( 0 < count( $criteria ) ) {
547 $query .= "\nWHERE\n";
548
549 $first = true;
550 foreach ( $criteria as $c ) {
551 if ( ! $first ) {
552 $query .= "AND\n";
553 }
554
555 if ( is_array( $c['val'] ) ) {
556 $operator = 'IN';
557
558 for ( $i = 0, $len = count( $c['val'] ); $i < $len; $i ++ ) {
559 $c['val'][ $i ] = "'" . esc_sql( $c['val'][ $i ] ) . "'";
560 }
561
562 $val = '(' . implode( ',', $c['val'] ) . ')';
563 } else {
564 $operator = ! empty( $c['op'] ) ? $c['op'] : '=';
565 $val = "'" . esc_sql( $c['val'] ) . "'";
566 }
567
568 $query .= "`{$c['col']}` {$operator} {$val}\n";
569
570 $first = false;
571 }
572 }
573 }
574
575 if ( ! is_array( $order ) ) {
576 $order = array(
577 'col' => 'id',
578 'order' => 'desc'
579 );
580 }
581
582 $query .= " ORDER BY {$order['col']} {$order['order']} LIMIT {$offset},{$limit}";
583
584 return $query;
585 }
586
587 /**
588 * Load logs from DB.
589 *
590 * @author Vova Feldman (@svovaf)
591 * @since 1.2.1.6
592 *
593 * @param bool $filters
594 * @param int $limit
595 * @param int $offset
596 * @param bool $order
597 *
598 * @return object[]|null
599 */
600 public static function load_db_logs(
601 $filters = false,
602 $limit = 200,
603 $offset = 0,
604 $order = false
605 ) {
606 global $wpdb;
607
608 $query = self::build_db_logs_query(
609 $filters,
610 $limit,
611 $offset,
612 $order
613 );
614
615 return $wpdb->get_results( $query );
616 }
617
618 /**
619 * Load logs from DB.
620 *
621 * @author Vova Feldman (@svovaf)
622 * @since 1.2.1.6
623 *
624 * @param bool $filters
625 * @param string $filename
626 * @param int $limit
627 * @param int $offset
628 * @param bool $order
629 *
630 * @return false|string File download URL or false on failure.
631 */
632 public static function download_db_logs(
633 $filters = false,
634 $filename = '',
635 $limit = 10000,
636 $offset = 0,
637 $order = false
638 ) {
639 if ( empty( $filename ) ) {
640 $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv';
641 }
642
643 $upload_dir = wp_upload_dir();
644 $filepath = rtrim( $upload_dir['path'], '/' ) . "/{$filename}";
645
646 WP_Filesystem();
647 if ( ! $GLOBALS['wp_filesystem']->is_writable( dirname( $filepath ) ) ) {
648 return false;
649 }
650
651 $query = self::build_db_logs_query(
652 $filters,
653 $limit,
654 $offset,
655 $order,
656 true
657 );
658
659 $columns = '';
660 for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) {
661 if ( $i > 0 ) {
662 $columns .= ', ';
663 }
664
665 $columns .= "'" . self::$_log_columns[ $i ] . "'";
666 }
667
668 $query = "SELECT {$columns} UNION ALL " . $query;
669
670 $result = $GLOBALS['wpdb']->get_results( $query );
671
672 if ( false === $result ) {
673 return false;
674 }
675
676 if ( ! self::write_csv_to_filesystem( $filepath, $result ) ) {
677 return false;
678 }
679
680 return rtrim( $upload_dir['url'], '/' ) . '/' . $filename;
681 }
682
683 /**
684 * @author Vova Feldman (@svovaf)
685 * @since 1.2.1.6
686 *
687 * @param string $filename
688 *
689 * @return string
690 */
691 public static function get_logs_download_url( $filename = '' ) {
692 $upload_dir = wp_upload_dir();
693 if ( empty( $filename ) ) {
694 $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv';
695 }
696
697 return rtrim( $upload_dir['url'], '/' ) . $filename;
698 }
699
700 /**
701 * @param string $file_path
702 * @param array $query_results
703 *
704 * @return bool
705 */
706 private static function write_csv_to_filesystem( $file_path, $query_results ) {
707 if ( empty( $query_results ) ) {
708 return false;
709 }
710
711 $content = '';
712
713 foreach ( $query_results as $row ) {
714 $row_data = array_map( function ( $value ) {
715 return str_replace( "\n", ' ', $value );
716 }, (array) $row );
717 $content .= implode( "\t", $row_data ) . "\n";
718 }
719
720 if ( ! $GLOBALS['wp_filesystem']->put_contents( $file_path, $content, FS_CHMOD_FILE ) ) {
721 return false;
722 }
723
724 return true;
725 }
726
727 #endregion
728 }
729