abspath = rtrim( ABSPATH, '/\\' ); parent::__construct( array( 'singular' => esc_html__( 'profile', 'code-profiler' ), 'plural' => esc_html__( 'profiles', 'code-profiler' ), 'ajax' => false )); } /* * Empty list */ function no_items() { esc_html_e( 'No profile found.', 'code-profiler' ); } /* * Default */ function column_default( $item, $column_name ) { if ( $item[ $column_name ] == '-' ) { // Old profiles (CP <=1.2) must not call number_format() return $item[ $column_name ]; } switch( $column_name ) { case 'profile': return $item[ $column_name ]; break; case 'date': return date('Y/m/d \@ H:i', $item[ $column_name ] ); break; case 'time': return $item[ $column_name ] .' s'; break; case 'mem': return number_format( $item[ $column_name ], 2 ) .' MB'; break; case 'io': case 'queries': case 'items': return number_format( $item[ $column_name ] ); break; default: return ''; } } /* * Sortable columns */ function get_sortable_columns() { return array( 'profile' => array( 'profile', true ), 'date' => array( 'date', true ), 'items' => array( 'items', true ), 'time' => array( 'time', true ), 'mem' => array( 'mem', true ), 'io' => array( 'io', true ), 'queries' => array( 'queries', true ) ); } /* * Columns */ function get_columns(){ return array( 'cb' => '', 'profile' => esc_html__( 'Profile', 'code-profiler' ), 'date' => esc_html__( 'Date', 'code-profiler' ), 'items' => esc_html__( 'Items', 'code-profiler' ), 'time' => esc_html__( 'Time', 'code-profiler' ), 'mem' => esc_html__( 'Memory', 'code-profiler' ), 'io' => esc_html__( 'File I/O', 'code-profiler' ), 'queries' => esc_html__( 'SQL', 'code-profiler' ) ); } /* * Sorting */ function usort_reorder( $a, $b ) { // Sort by date by default $orderby = (! empty( $_GET['orderby'] ) ) ? sanitize_key( $_GET['orderby'] ) : 'ID'; $order = (! empty( $_GET['order'] ) ) ? sanitize_key( $_GET['order'] ) : 'asc'; $result = $this->cmp_num_or_string( $a[$orderby], $b[$orderby] ); return ( $order === 'asc' ) ? $result : -$result; } /* * Sort string and numeric values differently */ function cmp_num_or_string( $a, $b ) { if ( is_numeric( $a ) && is_numeric( $b ) ) { return ($a-$b) ? ($a-$b)/abs($a-$b) : 0; } else { return strcmp( $a, $b ); } } /* * Row action links */ function column_profile( $item ) { // Keep sorting order for the "delete" action link $orderby = (! empty( $_GET['orderby'] ) ) ? sanitize_key( $_GET['orderby'] ) : 'ID'; $order = (! empty( $_GET['order'] ) ) ? sanitize_key( $_GET['order'] ) : 'asc'; $actions = array( 'view' => sprintf( '%s', 'view_profile', esc_attr( $item['ID'] ), esc_attr__('View') ), 'delete' => sprintf( '%s', 'delete_profiles', esc_attr( $item['ID'] ), wp_create_nonce( 'bulk-'. $this->_args['plural'] ), $orderby, $order, esc_attr__('Delete') ) ); return sprintf( '%1$s %2$s', $item['profile'], $this->row_actions( $actions ) ); } /* * Bulk action menu (delete) */ function get_bulk_actions() { return array( 'delete_profiles' => esc_html__('Delete') ); } /* * Checkboxes */ function column_cb($item) { return sprintf( '', esc_attr( $item['ID'] ) ); } /* * Prepare to display profiles */ function prepare_items() { $columns = $this->get_columns(); $hidden = array(); $sortable = $this->get_sortable_columns(); $this->_column_headers = array( $columns, $hidden, $sortable ); // Fetch our data $profile = $this->fetch_profiles(); usort( $profile, array( &$this, 'usort_reorder' ) ); $per_page = 30; // If we just delete some profiles, we go back to page #1: if ( isset( $_REQUEST['action'] ) && $_REQUEST['action'] == 'delete_profiles' ) { $current_page = 1; } else { $current_page = $this->get_pagenum(); } $total_items = count( $profile ); $this->items = array_slice( $profile,( ( $current_page-1 )* $per_page ), $per_page ); $this->set_pagination_args( array( 'total_items' => $total_items, 'per_page' => $per_page )); } /* * Retrieve all profiles */ function fetch_profiles() { $profiles = []; $glob = glob( CODE_PROFILER_UPLOAD_DIR .'/*.slugs.profile' ); if ( is_array( $glob ) ) { $count = 0; foreach( $glob as $path ) { $file = basename( $path ); if ( preg_match( '`^(\d{10}\.\d+)\.(.+?)\.(?:slugs)\.profile$`', $file, $match ) ) { $error = 0; $fsize = 0; // Make sure we have all profile files foreach( $this->default_files as $pname ) { // Ignore these ones, there aren't mandatory if ( in_array( $pname, [ 'composer', 'summary' ] ) ) { continue; } if ( file_exists( CODE_PROFILER_UPLOAD_DIR ."/{$match[1]}.{$match[2]}.$pname.profile" ) ) { $fsize += filesize( CODE_PROFILER_UPLOAD_DIR ."/{$match[1]}.{$match[2]}.$pname.profile" ); } else { $error = 1; break; } } // Delete if incomplete if ( $error ) { foreach( $this->default_files as $pname ) { if ( file_exists( CODE_PROFILER_UPLOAD_DIR ."/{$match[1]}.{$match[2]}.$pname.profile" ) ) { unlink( CODE_PROFILER_UPLOAD_DIR ."/{$match[1]}.{$match[2]}.$pname.profile" ); } } continue; } // Search query $search = false; if (! empty( $_REQUEST['s'] ) ) { foreach( $this->default_files as $pname ) { // Ignore these ones, there aren't mandatory if ( in_array( $pname, [ 'composer', 'summary' ] ) ) { continue; } $search = $this->search_profile_file( CODE_PROFILER_UPLOAD_DIR ."/{$match[1]}.{$match[2]}.$pname.profile", sanitize_text_field( $_REQUEST['s'] ) ); if ( $search === true ) { break; } } } if (! empty( $_REQUEST['s'] ) && $search === false ) { continue; } // Fetch the summary file (CP >1.2) if ( file_exists( CODE_PROFILER_UPLOAD_DIR ."/{$match[1]}.{$match[2]}.summary.profile" ) ) { $summary = json_decode( file_get_contents( CODE_PROFILER_UPLOAD_DIR ."/{$match[1]}.{$match[2]}.summary.profile" ), true ); } if ( empty( $summary['memory'] ) ) { $summary['memory'] = '-'; } if ( empty( $summary['queries'] ) ) { $summary['queries'] = '-'; } if ( empty( $summary['time'] ) ) { $summary['time'] = '-'; } if ( empty( $summary['items'] ) ) { $summary['items'] = '-'; } if ( empty( $summary['io'] ) ) { $summary['io'] = '-'; } $fsize += filesize( $path ); $profiles[$count]['ID'] = $match[1]; $profiles[$count]['profile'] = esc_html( $match[2] ); $profiles[$count]['date'] = filemtime( $path ); $profiles[$count]['mem'] = $summary['memory']; $profiles[$count]['time'] = $summary['time']; $profiles[$count]['queries'] = $summary['queries']; $profiles[$count]['items'] = $summary['items']; $profiles[$count]['io'] = $summary['io']; $count++; } } } return $profiles; } /* * Search a profile file for a string. */ private function search_profile_file( $file, $string ) { $fh = fopen( $file, 'rb' ); if ( $fh === false ) { return false; } while(! feof( $fh ) ) { $line = fgets( $fh ); // Remove the ABSPATH, we don't include it in the search $line = ltrim( str_replace( $this->abspath, '', $line ), '\\/' ); if ( stripos( $line, $string ) !== false ) { fclose( $fh ); return true; } } fclose( $fh ); return false; } /* * Delete one or more profiles. */ public function delete_profiles( $profiles ) { $error = false; foreach( $profiles as $name ) { $profile_name = code_profiler_get_profile_path( $name ); if ( $profile_name === false ) { $error = true; continue; } foreach( $this->default_files as $pname ) { if ( file_exists( "$profile_name.$pname.profile" ) ) { unlink( "$profile_name.$pname.profile" ); } } } return $error; } } // ===================================================================== // EOF