PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.83
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.83
51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 All 39 releases
← All changes | freemius/includes/class-fs-logger.php +728 -0 51.1.251.1.83 View file →
@@ -1,0 +1,728 @@
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 + }