PluginProbe
Code Profiler – WordPress Performance Profiling and Debugging Made Easy / 1.8.2
Code Profiler – WordPress Performance Profiling and Debugging Made Easy v1.8.2
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 1.8.2, at lib/class-table-profiles.php

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