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

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

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