PluginProbe
Code Profiler – WordPress Performance Profiling and Debugging Made Easy / trunk
Code Profiler – WordPress Performance Profiling and Debugging Made Easy vtrunk
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 / class-table-profiles.php

class-table-profiles.php in Code Profiler – WordPress Performance Profiling and Debugging Made Easy trunk, at lib/class-table-profiles.php

435 lines 11.5 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
20 class CodeProfiler_Table_Profiles extends WP_List_Table {
21
22
23 private $default_files = [
24 'diskio',
25 'iostats',
26 'slugs',
27 'summary',
28 'composer'
29 ];
30 private $optional_files = [
31 'summary',
32 'composer'
33 ];
34 private $abspath;
35 private $row_count = 0;
36
37
38 /**
39 * Initialize
40 */
41 function __construct() {
42
43 $this->abspath = rtrim( ABSPATH, '/\\');
44
45 parent::__construct( [
46 'singular' => esc_html__('profile', 'code-profiler'),
47 'plural' => esc_html__('profiles', 'code-profiler'),
48 'ajax' => false
49 ] );
50 }
51
52
53 /**
54 * Empty list
55 */
56 function no_items() {
57 esc_html_e('No profile found.', 'code-profiler');
58 }
59
60
61 /**
62 * Default
63 */
64 function column_default( $item, $column_name ) {
65
66 if ( $item[ $column_name ] == '-') {
67 // Old profiles (CP <=1.2) must not call number_format()
68 return $item[ $column_name ];
69 }
70 switch( $column_name ) {
71 case 'date':
72 return sprintf(
73 /* translators: This is already translated in WordPress core */
74 __('%1$s at %2$s'),
75 date('Y/m/d', $item[ $column_name ] ) .'<br />',
76 date('g:i a', $item[ $column_name ] )
77 );
78 break;
79 case 'time':
80 return $item[ $column_name ] .' s';
81 break;
82 case 'mem':
83 return number_format( $item[ $column_name ], 2 ) .' MB';
84 break;
85 case 'io':
86 case 'queries':
87 case 'items':
88 return number_format( $item[ $column_name ] );
89 break;
90 default:
91 return '';
92 }
93 }
94
95
96 /**
97 * Sortable columns
98 */
99 function get_sortable_columns() {
100 return [
101 'profile' => ['profile', true ],
102 'date' => ['date', true ],
103 'items' => ['items', true ],
104 'time' => ['time', true ],
105 'mem' => ['mem', true ],
106 'io' => ['io', true ],
107 'queries' => ['queries', true ]
108 ];
109 }
110
111
112 /**
113 * Columns
114 */
115 function get_columns(){
116 return [
117 'cb' => '<input type="checkbox" />',
118 'profile' => esc_html__('Profile', 'code-profiler'),
119 'date' => esc_html__('Date', 'code-profiler'),
120 'items' => esc_html__('Items', 'code-profiler'),
121 'time' => esc_html__('Time', 'code-profiler'),
122 'mem' => esc_html__('Memory', 'code-profiler'),
123 'io' => esc_html__('File I/O', 'code-profiler'),
124 'queries' => esc_html__('SQL', 'code-profiler')
125 ];
126 }
127
128
129 /**
130 * Sorting
131 */
132 function usort_reorder( $a, $b ) {
133 // Sort by date by default
134 $orderby = (! empty( $_GET['orderby'] ) ) ? sanitize_key( $_GET['orderby'] ) : 'ID';
135 $order = (! empty( $_GET['order'] ) ) ? sanitize_key( $_GET['order'] ) : 'asc';
136 $result = $this->cmp_num_or_string( $a[$orderby], $b[$orderby] );
137 return ( $order === 'asc') ? $result : -$result;
138 }
139
140
141 /**
142 * Sort string and numeric values differently
143 */
144 function cmp_num_or_string( $a, $b ) {
145 if ( is_numeric( $a ) && is_numeric( $b ) ) {
146 return ($a-$b) ? ($a-$b)/abs($a-$b) : 0;
147 } else {
148 return strcmp( $a, $b );
149 }
150 }
151
152
153 /**
154 * Row action links
155 */
156 function column_profile( $item ) {
157
158 $this->row_count++;
159
160 // Keep sorting order for the "delete" action link
161 $orderby = (! empty( $_GET['orderby'] ) ) ? sanitize_key( $_GET['orderby'] ) : 'ID';
162 $order = (! empty( $_GET['order'] ) ) ? sanitize_key( $_GET['order'] ) : 'asc';
163
164 /**
165 * The option to re-run a profile is only available with profiles created with v1.8.
166 */
167 if (! empty( $item['rerun'] ) ) {
168 $rerun = sprintf(
169 '<a href="?page=code-profiler&cptab=profiler&action=rerun&profiles[]=%1$s" title="'.
170 esc_attr__('Re-run the profile with the same options and parameters.', 'code-profiler') .
171 '">%2$s</a>',
172 esc_attr( $item['ID'] ),
173 esc_html__('Re-run', 'code-profiler')
174 );
175 } else {
176 $rerun = '<font style="cursor:not-allowed" title="'.
177 esc_attr__('This feature is only available with profiles created with Code Profiler v1.8+',
178 'code-profiler') .'">'. esc_html__('Re-run', 'code-profiler') .'</font>';
179 }
180
181 $actions = array(
182 'view' => sprintf(
183 '<a href="?page=code-profiler&cptab=profiles_list&action=%s&id=%s&section=1">%s</a>',
184 'view_profile',
185 esc_attr( $item['ID'] ),
186 esc_html__('View')
187 ),
188 'edit' => sprintf(
189 '<a style="cursor:pointer" onClick="cpjs_toggle_name(\'%s\')">%s</a>',
190 esc_attr( $this->row_count ),
191 esc_html__('Quick Edit')
192 ),
193 'rerun' => $rerun,
194 'delete' => sprintf(
195 '<a href="?page=code-profiler&cptab=profiles_list&action=%s&profiles[]=%s'.
196 '&_wpnonce=%s&orderby=%s&order=%s" onclick="return cpjs_delete_profile();">%s</a>',
197 'delete_profiles',
198 esc_attr( $item['ID'] ),
199 wp_create_nonce('bulk-'. $this->_args['plural'] ),
200 $orderby,
201 $order,
202 esc_html__('Delete')
203 )
204 );
205
206 $profile_name = sprintf(
207 '<div id="profile_name_%1$s">%2$s</div>'.
208 '<div id="profile_div_%1$s" style="display:none">'.
209 '<input type="text" id="edit-%1$s" name="edit_%3$s" value="" maxlength="100" />'.
210 '<p>'.
211 '<input type="button" class="button button-primary button-small" value="%4$s" onClick="cpjs_edit_name(\'%3$s\', \'%1$s\', \'%6$s\', \'%1$s\')"/>'.
212 '&nbsp;'.
213 '<input type="button" class="button button-secondary button-small" value="%5$s" onClick="cpjs_toggle_name(\'%1$s\')"/>'.
214 '&nbsp;'.
215 '<span id="profile_spinner_%1$s" class="spinner" style="float:none"></span>'.
216 '</p>'.
217 '</div>',
218 esc_attr( $this->row_count ),
219 esc_html( $item['profile'] ),
220 esc_attr( $item['ID'] ),
221 esc_attr__('Update'),
222 esc_attr__('Cancel'),
223 wp_create_nonce('rename-profile')
224 );
225
226 return sprintf('%1$s %2$s', $profile_name, $this->row_actions( $actions ) );
227 }
228
229
230 /**
231 * Bulk action menu (delete)
232 */
233 function get_bulk_actions() {
234 return [
235 'delete_profiles' => esc_html__('Delete')
236 ];
237 }
238
239
240 /**
241 * Checkboxes
242 */
243 function column_cb($item) {
244 return sprintf(
245 '<input type="checkbox" name="profiles[]" value="%s" />',
246 esc_attr( $item['ID'] )
247 );
248 }
249
250
251 /**
252 * Prepare to display profiles
253 */
254 function prepare_items() {
255 $columns = $this->get_columns();
256 $hidden = array();
257 $sortable = $this->get_sortable_columns();
258 $this->_column_headers = array( $columns, $hidden, $sortable );
259
260 // Fetch our data
261 $profile = $this->fetch_profiles();
262 usort( $profile, array( &$this, 'usort_reorder') );
263
264 $per_page = 30;
265
266 // If we just delete some profiles, we go back to page #1:
267 if ( isset( $_REQUEST['action'] ) && $_REQUEST['action'] == 'delete_profiles') {
268 $current_page = 1;
269 } else {
270 $current_page = $this->get_pagenum();
271 }
272
273 $total_items = count( $profile );
274 $this->items = array_slice( $profile,( ( $current_page-1 )* $per_page ), $per_page );
275
276 $this->set_pagination_args( array(
277 'total_items' => $total_items,
278 'per_page' => $per_page
279 ));
280 }
281
282
283 /**
284 * Retrieve all profiles
285 */
286 function fetch_profiles() {
287
288 $profiles = [];
289 $glob = code_profiler_glob( CODE_PROFILER_UPLOAD_DIR, '\.slugs\.profile$', true );
290
291 if ( is_array( $glob ) ) {
292 $count = 0;
293 foreach( $glob as $path ) {
294 $file = basename( $path );
295 if ( preg_match('`^(\d{10}\.\d+)\.(.+?)\.(?:slugs)\.profile$`', $file, $match ) ) {
296
297 $error = 0; $fsize = 0;
298 // Make sure we have all profile files
299 foreach( $this->default_files as $pname ) {
300 /**
301 * Ignore these ones, there aren't mandatory.
302 */
303 if ( in_array( $pname, $this->optional_files ) ) {
304 continue;
305 }
306 if ( is_file( CODE_PROFILER_UPLOAD_DIR ."/{$match[1]}.{$match[2]}.$pname.profile" ) ) {
307 $fsize += filesize( CODE_PROFILER_UPLOAD_DIR ."/{$match[1]}.{$match[2]}.$pname.profile" );
308 } else {
309 $error = 1;
310 break;
311 }
312 }
313 // Delete if incomplete
314 if ( $error ) {
315 foreach( $this->default_files as $pname ) {
316 if ( is_file( CODE_PROFILER_UPLOAD_DIR ."/{$match[1]}.{$match[2]}.$pname.profile" ) ) {
317 unlink( CODE_PROFILER_UPLOAD_DIR ."/{$match[1]}.{$match[2]}.$pname.profile" );
318 }
319 }
320 continue;
321 }
322
323 // Search query
324 $search = false;
325 if (! empty( $_REQUEST['s'] ) ) {
326 foreach( $this->default_files as $pname ) {
327 /**
328 * Ignore these ones, there aren't mandatory.
329 */
330 if ( in_array( $pname, $this->optional_files ) ) {
331 continue;
332 }
333 $search = $this->search_profile_file(
334 CODE_PROFILER_UPLOAD_DIR ."/{$match[1]}.{$match[2]}.$pname.profile",
335 sanitize_text_field( $_REQUEST['s'] )
336 );
337 if ( $search === true ) {
338 break;
339 }
340 }
341 }
342 if (! empty( $_REQUEST['s'] ) && $search === false ) {
343 continue;
344 }
345
346 // Fetch the summary file (CP >1.2)
347 if ( file_exists( CODE_PROFILER_UPLOAD_DIR ."/{$match[1]}.{$match[2]}.summary.profile" ) ) {
348 $summary = json_decode(
349 file_get_contents( CODE_PROFILER_UPLOAD_DIR ."/{$match[1]}.{$match[2]}.summary.profile" ), true
350 );
351 }
352 if ( empty( $summary['memory'] ) ) { $summary['memory'] = '-'; }
353 if ( empty( $summary['queries'] ) ) { $summary['queries'] = '-'; }
354 if ( empty( $summary['time'] ) ) { $summary['time'] = '-'; }
355 if ( empty( $summary['items'] ) ) { $summary['items'] = '-'; }
356 if ( empty( $summary['io'] ) ) { $summary['io'] = '-'; }
357
358 /**
359 * Check if we can re-run the profile (v1.8+).
360 */
361 if (! empty( $summary['rerun'] ) ) {
362 $profiles[$count]['rerun'] = true;
363 }
364 $fsize += filesize( $path );
365 $profiles[$count]['ID'] = $match[1];
366 $profiles[$count]['profile'] = esc_html( $match[2] );
367 $profiles[$count]['date'] = filemtime( $path );
368 $profiles[$count]['mem'] = $summary['memory'];
369 $profiles[$count]['time'] = $summary['time'];
370 $profiles[$count]['queries'] = $summary['queries'];
371 $profiles[$count]['items'] = $summary['items'];
372 $profiles[$count]['io'] = $summary['io'];
373 $count++;
374 }
375 }
376 }
377 return $profiles;
378 }
379
380
381 /**
382 * Search a profile file for a string.
383 */
384 private function search_profile_file( $file, $string ) {
385
386 // Search in the filename too
387 if ( stripos( $file, $string ) !== false ) {
388 return true;
389 }
390
391 $fh = fopen( $file, 'rb');
392 if ( $fh === false ) {
393 return false;
394 }
395 while(! feof( $fh ) ) {
396 $line = fgets( $fh );
397 // Remove the ABSPATH, we don't include it in the search
398 $line = ltrim( str_replace( $this->abspath, '', $line ), '\\/');
399 if ( stripos( $line, $string ) !== false ) {
400 fclose( $fh );
401 return true;
402 }
403 }
404
405 fclose( $fh );
406 return false;
407 }
408
409
410 /**
411 * Delete one or more profiles.
412 */
413 public function delete_profiles( $profiles ) {
414
415 $error = false;
416 foreach( $profiles as $name ) {
417 $profile_name = code_profiler_get_profile_path( $name );
418 if ( $profile_name === false ) {
419 $error = true;
420 continue;
421 }
422 foreach( $this->default_files as $pname ) {
423 if ( is_file( "$profile_name.$pname.profile") ) {
424 unlink( "$profile_name.$pname.profile");
425 }
426 }
427 }
428 return $error;
429 }
430
431 }
432
433 // =====================================================================
434 // EOF
435