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

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