PluginProbe
Code Profiler – WordPress Performance Profiling and Debugging Made Easy / 1.7.3
Code Profiler – WordPress Performance Profiling and Debugging Made Easy v1.7.3
1.9.5 1.9.4 1.9.3 trunk 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.5 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.6 1.6.1 1.6.10 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 All 40 releases
code-profiler / lib / helper.php

helper.php in Code Profiler – WordPress Performance Profiling and Debugging Made Easy 1.7.3, at lib/helper.php

697 lines 19.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 +=====================================================================+
4 | ____ _ ____ __ _ _ |
5 | / ___|___ __| | ___ | _ \ _ __ ___ / _(_) | ___ _ __ |
6 | | | / _ \ / _` |/ _ \ | |_) | '__/ _ \| |_| | |/ _ \ '__| |
7 | | |__| (_) | (_| | __/ | __/| | | (_) | _| | | __/ | |
8 | \____\___/ \__,_|\___| |_| |_| \___/|_| |_|_|\___|_| |
9 | |
10 | (c) Jerome Bruandet ~ https://nintechnet.com/codeprofiler/ |
11 +=====================================================================+
12 */
13
14 if (! defined('ABSPATH') ) {
15 die('Forbidden');
16 }
17
18 // =====================================================================
19 // Various constants
20
21 // The profiles directory can be defined in the wp-config.php script
22 if (! defined('CODE_PROFILER_UPLOAD_DIR') ) {
23 // When running Code Profiler via WP CLI on a child site of a multisite installation,
24 // we need to get the main site upload folder otherwise wp_upload_dir will return
25 // the child site's
26 if ( is_multisite() && ! ( is_main_network() && is_main_site() && defined('MULTISITE') ) ) {
27 $upload_dir = code_profiler_upload_dir();
28 } else {
29 $upload_dir = wp_upload_dir();
30 }
31 define('CODE_PROFILER_UPLOAD_DIR', $upload_dir['basedir'] .'/code-profiler');
32 }
33 define('CODE_PROFILER_LOG', CODE_PROFILER_UPLOAD_DIR .'/log.php');
34 define('CODE_PROFILER_TMP_IOSTATS_LOG', 'iostats.tmp');
35 define('CODE_PROFILER_TMP_SUMMARY_LOG', 'summary.tmp');
36 define('CODE_PROFILER_TMP_CALLS_LOG', 'calls.tmp');
37 define('CODE_PROFILER_TMP_DISKIO_LOG', 'diskio.tmp');
38 define('CODE_PROFILER_TMP_CONNECTIONS_LOG', 'connections.tmp');
39 define('CODE_PROFILER_UPDATE_NOTICE', '<div class="updated notice is-dismissible"><p>%s</p></div>');
40 define('CODE_PROFILER_ERROR_NOTICE', '<div class="error notice is-dismissible"><p>%s</p></div>');
41 if (! defined('CODE_PROFILER_MUPLUGIN') ) {
42 // MU plugin's name can be defined in the wp-config.php
43 define('CODE_PROFILER_MUPLUGIN', '0----code-profiler.php');
44 }
45
46 /**
47 * Since WP 6.7, translation loading must not be triggered too early.
48 */
49 add_action('init', 'code_profiler_i18n_constants');
50 function code_profiler_i18n_constants() {
51
52 global $wp_version;
53 if (! defined('CODE_PROFILER_UA') ) { // UA signatures can be user-defined in the wp-config.php
54 define ('CODE_PROFILER_UA', [
55 esc_html__('Desktop', 'code-profiler') => [
56 'Firefox' => 'Mozilla/5.0 (Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0',
57 'Chrome' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML,'.
58 ' like Gecko) Chrome/133.0.0.0 Safari/537.36',
59 'Edge' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,'.
60 ' like Gecko) Chrome/133.0.0.0 Safari/537.36 Edg/131.0.2903.86'
61 ],
62 esc_html__('Mobile', 'code-profiler') => [
63 'Android Phone' => 'Mozilla/5.0 (Android 15; Mobile; rv:68.0) Gecko/68.0 Firefox/135.0',
64 'Android Tablet' => 'Mozilla/5.0 (Linux; Android 15.0; SAMSUNG-SM-T377A Build/NMF26X)'.
65 ' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Mobile Safari/537.36',
66 'iPhone' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_7_2 like Mac OS X) AppleWebKit/605.1.15'.
67 ' (KHTML, like Gecko) Version/17_7_2 Mobile/15E148 Safari/604.1',
68 'iPad' => 'Mozilla/5.0 (iPad; CPU OS 17_7_2 like Mac OS X) AppleWebKit/605.1.15'.
69 ' (KHTML, like Gecko) GSA/213.0.449417121 Mobile/15E148 Safari/605.1.15'
70 ],
71 esc_html__('Bot', 'code-profiler') => [
72 'Google Bot' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
73 'WordPress' => 'Mozilla/5.0 (compatible; CodeProfiler for WordPress/'. $wp_version .
74 '; https://nintechnet.com/codeprofiler/)'
75 ]
76
77 ] );
78 }
79 define('CODE_PROFILER_ACCURACY', [
80 1 => __('Highest', 'code-profiler'),
81 5 => __('High', 'code-profiler'),
82 10 => __('Moderate', 'code-profiler'),
83 15 => __('Low', 'code-profiler'),
84 20 => __('Lowest', 'code-profiler')
85 ]);
86 }
87
88 // =====================================================================
89 // Find the main site upload dir on a multisite installation.
90 // Used when running the profiler via WP CLI.
91 // This code is based on the WP private _wp_upload_dir function.
92
93 function code_profiler_upload_dir() {
94
95 $upload_path = trim( get_option('upload_path') );
96 if ( empty( $upload_path ) || 'wp-content/uploads' === $upload_path ) {
97 $basedir = WP_CONTENT_DIR . '/uploads';
98 } elseif ( strpos( $upload_path, ABSPATH ) !== 0 ) {
99 // $basedir is absolute, $upload_path is (maybe) relative to ABSPATH.
100 $basedir = path_join( ABSPATH, $upload_path );
101 } else {
102 $basedir = $upload_path;
103 }
104
105 if ( defined('UPLOADS') && ! ( is_multisite() && get_site_option('ms_files_rewriting') ) ) {
106 $basedir = ABSPATH . UPLOADS;
107 }
108
109 return ['basedir' => $basedir];
110 }
111
112 // =====================================================================
113 // Prevent cURL timeout if a plugin changes its timeout options.
114
115 function code_profiler_curl_timeout( $handle, $parsed_args, $url ) {
116
117 if ( isset( $_REQUEST['action'] ) &&
118 $_REQUEST['action'] == 'codeprofiler_start_profiler') {
119
120 curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, 300 );
121 curl_setopt( $handle, CURLOPT_TIMEOUT, 300 );
122 }
123 }
124 add_action('http_api_curl', 'code_profiler_curl_timeout', 1000, 3 );
125
126 // =====================================================================
127 // Update version in the DB and check if options must be updated.
128
129 function code_profiler_init_update() {
130
131 $cp_options = get_option('code-profiler');
132 if ( empty( $cp_options['version'] ) ||
133 version_compare( $cp_options['version'], CODE_PROFILER_VERSION, '<') ) {
134
135 $cp_options['version'] = CODE_PROFILER_VERSION;
136
137 // Version 1.1
138 if (! isset( $cp_options['enable_wpcli'] ) ) {
139 $cp_options['enable_wpcli'] = 1;
140 }
141
142 // Version 1.2
143 if (! isset( $cp_options['disable_wpcron'] ) ) {
144 $cp_options['disable_wpcron'] = 1;
145 }
146
147 // Version 1.3.1
148 if (! isset( $cp_options['http_response'] ) ) {
149 $cp_options['http_response'] = '^(?:3|4|5)\d{2}$';
150 }
151
152 // Version 1.5
153 if (! isset( $cp_options['accuracy'] ) ) {
154 $cp_options['accuracy'] = 1;
155 }
156
157 // Update version in the DB
158 update_option('code-profiler', $cp_options );
159 }
160
161 }
162 add_action('admin_init', 'code_profiler_init_update');
163
164 // =====================================================================
165 // Verify or create our storage folder in the uploads directory.
166 // Call during activation and access to the plugin.
167
168 function code_profiler_check_uploadsdir() {
169
170 if (! file_exists( CODE_PROFILER_UPLOAD_DIR ) ) {
171 mkdir( CODE_PROFILER_UPLOAD_DIR, 0755 );
172 }
173 if (! is_writable( CODE_PROFILER_UPLOAD_DIR ) ) {
174 // PHP running as an Apache module?
175 chmod( CODE_PROFILER_UPLOAD_DIR, 0777 );
176 }
177 if (! file_exists( CODE_PROFILER_UPLOAD_DIR .'/index.html') ) {
178 touch( CODE_PROFILER_UPLOAD_DIR .'/index.html');
179 }
180 // For Apache & Litespeed
181 if (! file_exists( CODE_PROFILER_UPLOAD_DIR .'/.htaccess') ) {
182 file_put_contents(
183 CODE_PROFILER_UPLOAD_DIR .'/.htaccess',
184 'Require all denied'
185 );
186 }
187 // Make sure there's a MU plugin directory
188 if (! is_dir( WPMU_PLUGIN_DIR ) ) {
189 wp_mkdir_p( WPMU_PLUGIN_DIR );
190 }
191
192
193 if (! file_exists( WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN ) ) {
194
195 if (! is_writable( WPMU_PLUGIN_DIR ) ) {
196 wp_die(
197 sprintf(
198 __('Error: The MU folder is read only, please make it writable: %s', 'code-profiler'),
199 esc_html( WPMU_PLUGIN_DIR ) .'/'
200 )
201 );
202 }
203
204 if (! file_exists( __DIR__ .'/mu.plugin') ) {
205 code_profiler_log_error( sprintf(
206 esc_html__('Cannot find the MU plugin: %s', 'code-profiler'),
207 __DIR__ .'/mu.plugin'
208 ));
209 } else {
210 $res = copy( __DIR__ .'/mu.plugin', WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN );
211 if ( $res === false ) {
212 code_profiler_log_error( sprintf(
213 esc_html__('Cannot copy the MU plugin to %s', 'code-profiler'),
214 WPMU_PLUGIN_DIR
215 ));
216 }
217 }
218 } else {
219 // Update MU plugin it if needed
220 if ( md5_file( WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN ) !== md5_file( __DIR__ .'/mu.plugin') ) {
221 copy( __DIR__ .'/mu.plugin', WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN );
222 }
223 }
224
225 // Log file
226 if (! file_exists( CODE_PROFILER_LOG ) ) {
227 file_put_contents( CODE_PROFILER_LOG, "<?php exit; ?>\n" );
228 }
229
230 }
231
232 // =====================================================================
233 // Check the PHP memory limit and return a suggested size
234 // for the profiler's buffer.
235
236 function code_profiler_suggested_memory() {
237
238 $memory_limit = ini_get('memory_limit');
239
240 if ('-1' == $memory_limit ) {
241 // Return max size
242 return 10;
243 }
244
245 if ( preg_match('/^(\d+)([PTGMK])$/i', $memory_limit, $match ) ) {
246 $bytes = (int) $match[1];
247 switch ( strtoupper( $match[2] ) ) {
248 case 'P':
249 $bytes *= 1024;
250 case 'T':
251 $bytes *= 1024;
252 case 'G':
253 $bytes *= 1024;
254 case 'M':
255 $bytes *= 1024;
256 case 'K':
257 $bytes *= 1024;
258 }
259 // 256 MB
260 if ( $bytes >= 268435456 ) {
261 return 10;
262 // 128 MB
263 } elseif ( $bytes >= 134217728 ) {
264 return 7;
265 // 64 MB
266 } elseif ( $bytes >= 67108864 ) {
267 return 4;
268 // <64 MB
269 } else {
270 return 1;
271 }
272 }
273 // Don't know :/
274 return 5;
275 }
276
277 // =====================================================================
278 // Create the default options.
279
280 function code_profiler_default_options() {
281
282 $buffer = code_profiler_suggested_memory();
283
284 $cp_options = [
285 'show_paths' => 'relative',
286 'display_name' => 'full',
287 'truncate_name' => 30,
288 'chart_type' => 'x',
289 'chart_max_plugins' => 25,
290 'hide_empty_value' => 1,
291 'table_max_rows' => 30,
292 'warn_composer' => 1,
293 'enable_wpcli' => 1,
294 'disable_wpcron' => 1,
295 'http_response' => '^(?:3|4|5)\d{2}$',
296 'accuracy' => 1,
297 'buffer' => (int) $buffer
298 ];
299
300 update_option('code-profiler', $cp_options );
301
302 }
303
304 // ===================================================================== 2023-06-14
305 // Create a profile name.
306
307 function code_profiler_profile_name() {
308
309 return date('Y-m-d_') . substr( time(), 4 );
310 }
311
312 // ===================================================================== 2023-06-14
313 // Disable PHP display_errors so that notice, warning and error messages
314 // don't show up in the AJAX response.
315
316 function code_profiler_hide_errors() {
317
318 ini_set('display_errors', 0 );
319 }
320
321 // =====================================================================
322 // Disable opcode cache.
323
324 function code_profiler_disable_opcode() {
325
326 try {
327 if ( extension_loaded('Zend OPcache') ) {
328 ini_set('opcache.enable', 0);
329 } elseif ( extension_loaded('wincache') ) {
330 ini_set('wincache.fcenabled', 0);
331 }
332 set_time_limit(0);
333 ini_set('memory_limit', -1);
334
335 } catch ( Exception $e ) { }
336
337 $cp_options = get_option('code-profiler');
338 if (! empty( $cp_options['disable_wpcron'] ) ) {
339 if (! defined('DISABLE_WP_CRON') ) {
340 define('DISABLE_WP_CRON', true );
341 }
342 }
343 }
344
345 // =====================================================================
346 // Verify the security key when running the profiler.
347
348 function code_profiler_verify_key() {
349
350 $response = [
351 'status' => 'error',
352 'message' => __('Security keys do not match. Reload the page and try again (%s)',
353 'code-profiler')
354 ];
355
356 if ( empty( $_REQUEST['profiler_key'] ) ) {
357 $response['message'] = sprintf( $response['message'], '001');
358
359 } else {
360 $file = CODE_PROFILER_UPLOAD_DIR .'/key_'. sha1( $_REQUEST['profiler_key'] ) .'.tmp';
361 if ( file_exists( $file ) ) {
362 // Delete it and accept the request
363 unlink( $file );
364 return;
365 }
366 $response['message'] = sprintf( $response['message'], '002');
367 }
368 wp_send_json( $response );
369
370 }
371
372 // =====================================================================
373 // Write message to the log.
374 // Log level can be a combination of INFO (1), WARN (2), ERROR (4)
375 // and DEBUG (8).
376
377 function code_profiler_write2log( $message, $level, $create ) {
378
379 if ( empty( $create ) ) {
380 file_put_contents(
381 CODE_PROFILER_LOG,
382 time() ."~~$level~~$message\n",
383 FILE_APPEND
384 );
385 } else {
386 file_put_contents(
387 CODE_PROFILER_LOG,
388 time() ."~~$level~~$message\n"
389 );
390 }
391 }
392
393 function code_profiler_log_info( $string, $create = 0 ) {
394 code_profiler_write2log( $string, 1, $create );
395 }
396 function code_profiler_log_warn( $string, $create = 0 ) {
397 code_profiler_write2log( $string, 2, $create );
398 }
399 function code_profiler_log_error( $string, $create = 0 ) {
400 code_profiler_write2log( $string, 4, $create );
401 }
402 function code_profiler_log_debug( $string, $create = 0 ) {
403 code_profiler_write2log( $string, 8, $create );
404 }
405
406 // =====================================================================
407 // Retrieve and return matching files from a directory.
408
409 function code_profiler_glob( $directory, $regex, $pathname = false ) {
410
411 $list = [];
412
413 foreach ( new DirectoryIterator( $directory ) as $finfo ) {
414 if (! $finfo->isDot() && preg_match("`$regex`", $finfo->getFilename() ) ) {
415 if ( $pathname ) {
416 $list[] = $finfo->getPathname();
417 } else {
418 $list[] = $finfo->getFilename();
419 }
420 }
421 }
422 return $list;
423 }
424
425 // =====================================================================
426 // Verify if a profile exists and return its full path.
427
428 function code_profiler_get_profile_path( $id, $type = 'slugs') {
429
430 if (! empty( $id ) && preg_match('/^\d{10}\.\d+$/', $id ) ) {
431
432 $glob = code_profiler_glob(CODE_PROFILER_UPLOAD_DIR, "$id\..+\.$type\.profile$", true);
433
434 if ( is_array( $glob ) && ! empty( $glob[0] ) ) {
435 if ( preg_match( "`$id\.(.+?).$type.profile$`", $glob[0], $match ) ) {
436
437 return CODE_PROFILER_UPLOAD_DIR. "/$id.{$match[1]}";
438 }
439 }
440 }
441 return false;
442 }
443
444 // =====================================================================
445 // Open, parse and return the content of a profile file.
446
447 function code_profiler_get_profile_data( $file, $type = 'slugs') {
448
449 $buffer = [];
450
451 $fh = fopen( "$file.$type.profile", 'rb');
452
453 if ( $fh === false ) {
454 $err = sprintf(
455 CODE_PROFILER_ERROR_NOTICE,
456 sprintf(
457 esc_html__('Unable to open profile file: %s.', 'code-profiler'),
458 esc_html( "$file.$type.profile" )
459 )
460 );
461 $buffer['error'] = $err;
462 return $buffer;
463 }
464
465 while (! feof( $fh ) ) {
466 $buffer[] = fgetcsv( $fh, 1000, "\t" );
467 }
468
469 fclose( $fh );
470
471 if ( empty( $buffer ) ) {
472 $err = sprintf(
473 CODE_PROFILER_ERROR_NOTICE,
474 sprintf(
475 esc_html__('Profile file is empty or corrupted: %s.', 'code-profiler'),
476 esc_html( "$file.$type.profile" )
477 )
478 );
479 $buffer['error'] = $err;
480 return $buffer;
481 }
482
483 // Get rid of the empty field created by fgetcsv
484 return array_filter( $buffer );
485 }
486
487 // =====================================================================
488 // Exit with a json-encoded error message except if we're running
489 // from WP CLI
490
491 function code_profiler_wp_send_json( $response ) {
492
493 if ( defined('WP_CLI') && WP_CLI ) {
494 WP_CLI::error( $response['message'] );
495 }
496 wp_send_json( $response );
497 }
498
499 // =====================================================================
500 // Retrieve the summary stats for a given profile.
501
502 function code_profiler_getsummarystats( $profile_path, $type = 'html') {
503
504 if ( $type == 'html') {
505 $open = '<ul><li>&#10148; ';
506 $div = '</li><li>&#10148; ';
507 $close = '</li></ul>';
508 } else {
509 // WP-CLI
510 $open = " \u{27A4} ";
511 $div = "\n \u{27A4} ";
512 $close = "\n\n";
513 }
514
515 if (! file_exists( "$profile_path.summary.profile" ) ) {
516 return false;
517 }
518 $decode = json_decode( file_get_contents( "$profile_path.summary.profile" ) );
519
520 $string = $open;
521
522 if ( empty( $decode->items ) ) {
523 $tmp = __('N/A', 'code-profiler');
524 } else {
525 $tmp = (int) --$decode->items;
526 }
527 $string .= sprintf(
528 __('%s plugins and 1 theme', 'code-profiler'),
529 $tmp
530 );
531
532 $string .= $div;
533
534 if ( empty( $decode->time ) ) {
535 $tmp = __('N/A', 'code-profiler');
536 } else {
537 $tmp = number_format( $decode->time, 4 );
538 }
539 $string .= sprintf(
540 __('Execution time: %ss', 'code-profiler'),
541 $tmp
542 );
543
544 $string .= $div;
545
546 if ( empty( $decode->memory ) ) {
547 $tmp = __('N/A', 'code-profiler');
548 } else {
549 $tmp = number_format( $decode->memory, 2);
550 }
551 $string .= sprintf(
552 __('Peak memory: %s MB', 'code-profiler'),
553 $tmp
554 );
555
556 $string .= $div;
557
558 if ( empty( $decode->io ) ) {
559 $tmp = __('N/A', 'code-profiler');
560 } else {
561 $tmp = number_format( (int) $decode->io );
562 }
563 $string .= sprintf(
564 __('File I/O operations: %s', 'code-profiler'),
565 $tmp
566 );
567
568 $string .= $div;
569
570 if ( empty( $decode->queries ) ) {
571 $tmp = __('N/A', 'code-profiler');
572 } else {
573 $tmp = number_format( (int) $decode->queries );
574 }
575 $string .= sprintf(
576 __('SQL queries: %s', 'code-profiler'),
577 $tmp
578 );
579
580 $string .= $div;
581
582 if ( empty( CODE_PROFILER_ACCURACY[ $decode->precision ] ) ) {
583 $tmp = __('N/A', 'code-profiler');
584 } else {
585 $tmp = CODE_PROFILER_ACCURACY[ $decode->precision ];
586 }
587 $string .= sprintf(
588 __('Accuracy: %s', 'code-profiler'),
589 $tmp
590 );
591
592 $string .= $close;
593
594 return $string;
595 }
596
597 // =====================================================================
598 // Remove *tmp files left in the profiles folder.
599
600 function code_profiler_cleantmpfiles() {
601
602 $glob = code_profiler_glob( CODE_PROFILER_UPLOAD_DIR, '\.tmp$', true );
603
604 if ( is_array( $glob ) ) {
605 foreach( $glob as $file ) {
606 unlink( $file );
607 }
608 }
609 }
610
611 // =====================================================================
612 // Remove non-ASCII characters from a string.
613
614 function code_profiler_ASCII_filter( $string ) {
615
616 return preg_replace('/[\x00-\x1f\x7f-\xff]/', '', $string );
617 }
618
619 // =====================================================================
620 // Clear the log if it's bigger than 100KB.
621
622 function code_profiler_clearlog() {
623
624 if ( file_exists( CODE_PROFILER_LOG ) && filesize( CODE_PROFILER_LOG ) > 100000 ) {
625 file_put_contents( CODE_PROFILER_LOG, "<?php exit; ?>\n" );
626 }
627 }
628
629 // ===================================================================== 2023-06-15
630 // We don't want to be bothered by other themes/plugins' admin notices.
631
632 add_action('admin_head', 'code_profiler_hide_admin_notices');
633
634 function code_profiler_hide_admin_notices() {
635 if ( isset( $_GET['page'] ) && $_GET['page'] == 'code-profiler') {
636 remove_all_actions('admin_notices');
637 remove_all_actions('all_admin_notices');
638 add_filter('admin_footer_text', 'code_profiler_admin_footer');
639 }
640 }
641
642 function code_profiler_admin_footer () {
643 echo '<span id="footer-thankyou">'.
644 sprintf(
645 /* Translators: %s are the '<a href="">' and '</a>' anchors */
646 esc_html(
647 'Thank you for using %sCode Profiler%s.',
648 'code-profiler'
649 ),
650 '<a href="https://nintechnet.com/codeprofiler/" target="_blank">',
651 '</a>'
652 ).
653 '</span>';
654 }
655
656 // =====================================================================
657 // Display/hide an element depending on some value
658
659 function code_profiler_hide( $var, $val ) {
660
661 if ( $var == $val ) {
662 echo " style='display:none'";
663 }
664 }
665
666 // =====================================================================
667 // Retrieve all themes.
668
669 function code_profiler_get_themes() {
670
671 $list = [];
672
673 // Make sure the function is loaded
674 if (! function_exists('wp_get_themes') ) {
675 require_once ABSPATH . 'wp-includes/theme.php';
676 }
677 $themes = wp_get_themes();
678 foreach( $themes as $k => $v ) {
679 if ( $v->Name ) {
680 $list[ $k ] = [
681 'n' => $v->Name,
682
683 't' => $v->Template
684 ];
685 } else {
686 $list[ $k ] = [
687 'n' => $k,
688 't' => $v->Template
689 ];
690 }
691 }
692 return $list;
693 }
694
695 // =====================================================================
696 // EOF
697