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

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