Create New Item
×
Item Type
File
Folder
Item Name
×
Search file in folder and subfolders...
File Manager
/
wp-content
/
plugins
/
vibes
/
includes
/
features
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?php /** * WP-CLI for Vibes. * * Adds WP-CLI commands to Vibes * * @package Features * @author Pierre Lannoy <https://pierre.lannoy.fr/>. * @since 1.0.0 */ namespace Vibes\Plugin\Feature; use Vibes\Plugin\Feature\Memory; use Vibes\System\BrowserPerformance; use Vibes\System\Cache; use Vibes\System\Conversion; use Vibes\System\WebVitals; use Vibes\System\Date; use Vibes\System\Device; use Vibes\System\EmojiFlag; use Vibes\System\Environment; use Vibes\System\Markdown; use Vibes\System\Option; use Vibes\System\GeoIP; use Vibes\System\Timezone; use Vibes\System\UUID; use Vibes\System\Http; use Vibes\System\SharedMemory; use Vibes\System\Mime; /** * Manages Vibes and view current and past performance signals. * * @package Features * @author Pierre Lannoy <https://pierre.lannoy.fr/>. * @since 1.0.0 */ class Wpcli { /** * List of color format per bound. * * @since 1.0.0 * @var array $level_color Level colors. */ private $level_color = [ 'standard' => [ 'webvital' => '%4%c', 'navigation' => '%2%k', 'resource' => '%5%w', ], 'soft' => [ 'webvital' => '%0%c', 'navigation' => '%0%g', 'resource' => '%0%m', ], ]; /** * List of exit codes. * * @since 1.0.0 * @var array $exit_codes Exit codes. */ private $exit_codes = [ 0 => 'operation successful.', 1 => 'unrecognized setting.', 2 => 'unrecognized action.', 255 => 'unknown error.', ]; /** * Flush output without warnings. * * @since 1.0.0 */ private function flush() { // phpcs:ignore set_error_handler( null ); // phpcs:ignore @ob_flush(); // phpcs:ignore restore_error_handler(); } /** * Write ids as clean stdout. * * @param array $ids The ids. * @param string $field Optional. The field to output. * @since 1.0.0 */ private function write_ids( $ids, $field = '' ) { $result = ''; $last = end( $ids ); foreach ( $ids as $key => $id ) { if ( '' === $field ) { $result .= $key; } else { $result .= $id[ $field ]; } if ( $id !== $last ) { $result .= ' '; } } // phpcs:ignore fwrite( STDOUT, $result ); } /** * Write an error. * * @param integer $code Optional. The error code. * @param boolean $stdout Optional. Clean stdout output. * @since 1.0.0 */ private function error( $code = 255, $stdout = false ) { $msg = '[' . VIBES_PRODUCT_NAME . '] ' . ucfirst( $this->exit_codes[ $code ] ); if ( \WP_CLI\Utils\isPiped() ) { // phpcs:ignore fwrite( STDOUT, '' ); // phpcs:ignore exit( $code ); } elseif ( $stdout ) { // phpcs:ignore fwrite( STDERR, $msg ); // phpcs:ignore exit( $code ); } else { \WP_CLI::error( $msg ); } } /** * Write a warning. * * @param string $msg The message. * @param string $result Optional. The result. * @param boolean $stdout Optional. Clean stdout output. * @since 1.0.0 */ private function warning( $msg, $result = '', $stdout = false ) { $msg = '[' . VIBES_PRODUCT_NAME . '] ' . ucfirst( $msg ); if ( \WP_CLI\Utils\isPiped() || $stdout ) { // phpcs:ignore fwrite( STDOUT, $result ); } else { \WP_CLI::warning( $msg ); } } /** * Write a success. * * @param string $msg The message. * @param string $result Optional. The result. * @param boolean $stdout Optional. Clean stdout output. * @since 1.0.0 */ private function success( $msg, $result = '', $stdout = false ) { $msg = '[' . VIBES_PRODUCT_NAME . '] ' . ucfirst( $msg ); if ( \WP_CLI\Utils\isPiped() || $stdout ) { // phpcs:ignore fwrite( STDOUT, $result ); } else { \WP_CLI::success( $msg ); } } /** * Write a wimple line. * * @param string $msg The message. * @param string $result Optional. The result. * @param boolean $stdout Optional. Clean stdout output. * @since 1.0.0 */ private function line( $msg, $result = '', $stdout = false ) { if ( \WP_CLI\Utils\isPiped() || $stdout ) { // phpcs:ignore fwrite( STDOUT, $result ); } else { \WP_CLI::line( $msg ); } } /** * Write a wimple log line. * * @param string $msg The message. * @param boolean $stdout Optional. Clean stdout output. * @since 1.0.0 */ private function log( $msg, $stdout = false ) { if ( ! \WP_CLI\Utils\isPiped() && ! $stdout ) { \WP_CLI::log( $msg ); } } /** * Get params from command line. * * @param array $args The command line parameters. * @return array The true parameters. * @since 1.0.0 */ private function get_params( $args ) { $result = ''; if ( array_key_exists( 'settings', $args ) ) { $result = \json_decode( $args['settings'], true ); } if ( ! $result || ! is_array( $result ) ) { $result = []; } return $result; } /** * Filters records. * * @param array $records The records to filter. * @param array $filters Optional. The filter to apply. * @param string $index Optional. The starting index. * * @return array The filtered records. * @since 1.0.0 */ public static function records_filter( $records, $filters = [], $index = '' ) { $result = []; foreach ( $records as $idx => $record ) { foreach ( $filters as $key => $filter ) { if ( ! preg_match( $filter, $record[ $key ] ) ) { continue 2; } } $result[ $idx ] = $record; } if ( '' !== $index ) { $tmp = []; foreach ( $result as $key => $record ) { if ( 0 < strcmp( $key, $index ) ) { $tmp[ $key ] = $record; } } $result = $tmp; } uksort( $result, 'strcmp' ); return $result; } /** * Format records records. * * @param array $records The records to display. * @param integer $pad Optional. Line padding. * * @return array The ready to print records. * @since 1.0.0 */ public static function records_format( $records, $pad = 160 ) { $result = []; $geoip = new GeoIP(); foreach ( $records as $idx => $record ) { if ( $geoip->is_installed() ) { $country = EmojiFlag::get( $record['country'] ); } else { $country = ' '; } $line = '[' . $record['timestamp'] . '] '; $line .= strtoupper( str_pad( $record['type'], 10 ) ) . ' '; $line .= $country . ' '; $line .= vibes_mb_str_pad( Device::get_icon( $record['class'] ) . ' ', ( 'Desktop' === Device::get_id_name( $record['class'] ) && 1 === Environment::exec_mode() ) ? 4 : 2 ) . strtoupper( str_pad( Device::get_id_name( $record['class'] ), 7 ) . ' ' ); if ( 'webvital' === $record['type'] ) { $line .= WebVitals::get_info_line( $record ) . ' '; $line .= $record['endpoint']; } elseif ( 'navigation' === $record['type'] || 'resource' === $record['type'] ) { $line .= BrowserPerformance::get_info_line( $record ) . ' '; } $line = preg_replace( '/[\x00-\x1F\x7F\xA0]/u', '', $line ); if ( $pad - 1 < strlen( $line ) ) { $line = substr( $line, 0, $pad - 1 ) . '…'; } $result[ $idx ] = [ 'type' => strtolower( $record['type'] ), 'line' => vibes_mb_str_pad( $line, $pad ), ]; } return $result; } /** * Displays records. * * @param array $records The records to display. * @param string $theme Optional. Colors scheme. * @param integer $pad Optional. Line padding. * @since 1.0.0 */ private function records_display( $records, $theme = 'standard', $pad = 160 ) { if ( ! array_key_exists( $theme, $this->level_color ) ) { $theme = 'standard'; } foreach ( self::records_format( $records, $pad ) as $record ) { \WP_CLI::line( \WP_CLI::colorize( $this->level_color[ $theme ][ strtolower( $record['type'] ) ] ) . $record['line'] . \WP_CLI::colorize( '%n' ) ); } } /** * Get Vibes details and operation modes. * * ## EXAMPLES * * wp vibes status * * * === For other examples and recipes, visit https://github.com/Pierre-Lannoy/wp-vibes/blob/master/WP-CLI.md === * */ public function status( $args, $assoc_args ) { \WP_CLI::line( sprintf( '%s is running.', Environment::plugin_version_text() ) ); if ( Option::network_get( 'capture' ) ) { \WP_CLI::line( 'Navigation analytics: enabled.' ); } else { \WP_CLI::line( 'Navigation analytics: disabled.' ); } if ( Option::network_get( 'rcapture' ) ) { \WP_CLI::line( 'Resources analytics: enabled.' ); } else { \WP_CLI::line( 'Resources analytics: disabled.' ); } if ( Option::network_get( 'livelog' ) ) { \WP_CLI::line( 'Auto-Monitoring: enabled.' ); } else { \WP_CLI::line( 'Auto-Monitoring: disabled.' ); } if ( Option::network_get( 'metrics' ) ) { \WP_CLI::line( 'Metrics collation: enabled.' ); } else { \WP_CLI::line( 'Metrics collation: disabled.' ); } if ( \DecaLog\Engine::isDecalogActivated() ) { \WP_CLI::line( 'Logging support: ' . \DecaLog\Engine::getVersionString() . '.' ); } else { \WP_CLI::line( 'Logging support: no.' ); } $geo = new GeoIP(); if ( $geo->is_installed() ) { \WP_CLI::line( 'IP information support: yes (' . $geo->get_full_name() . ').' ); } else { \WP_CLI::line( 'IP information support: no.' ); } if ( defined( 'PODD_VERSION' ) ) { \WP_CLI::line( 'Device detection support: yes (Device Detector v' . PODD_VERSION . ').'); } else { \WP_CLI::line( 'Device detection support: no.' ); } if ( SharedMemory::$available ) { \WP_CLI::line( 'Shared memory support: yes (shmop v' . phpversion( 'shmop' ) . ').' ); } else { \WP_CLI::line( 'Shared memory support: no.' ); } \WP_CLI::line( 'MIME types support: yes (pooMT v' . VIBES_MIME_VERSION . ').' ); } /** * Modify Vibes main settings. * * ## OPTIONS * * <enable|disable> * : The action to take. * * <navigation-analytics|resource-analytics|auto-monitoring|smart-filter|metrics> * : The setting to change. * * [--yes] * : Answer yes to the confirmation message, if any. * * [--stdout] * : Use clean STDOUT output to use results in scripts. Unnecessary when piping commands because piping is detected by Vibes. * * ## EXAMPLES * * wp vibes settings enable auto-monitoring * wp vibes settings disable early-monitoring --yes * * * === For other examples and recipes, visit https://github.com/Pierre-Lannoy/wp-vibes/blob/master/WP-CLI.md === * */ public function settings( $args, $assoc_args ) { $stdout = \WP_CLI\Utils\get_flag_value( $assoc_args, 'stdout', false ); $action = isset( $args[0] ) ? (string) $args[0] : ''; $setting = isset( $args[1] ) ? (string) $args[1] : ''; switch ( $action ) { case 'enable': switch ( $setting ) { case 'navigation-analytics': Option::network_set( 'capture', true ); $this->success( 'navigation analytics are now activated.', '', $stdout ); break; case 'resource-analytics': Option::network_set( 'rcapture', true ); $this->success( 'resources analytics are now activated.', '', $stdout ); break; case 'auto-monitoring': Option::network_set( 'livelog', true ); $this->success( 'auto-monitoring is now activated.', '', $stdout ); break; case 'smart-filter': Option::network_set( 'smart_filter', true ); $this->success( 'smart filter is now activated.', '', $stdout ); break; case 'metrics': Option::network_set( 'metrics', true ); $this->success( 'metrics collation is now activated.', '', $stdout ); break; default: $this->error( 1, $stdout ); } break; case 'disable': switch ( $setting ) { case 'navigation-analytics': \WP_CLI::confirm( 'Are you sure you want to deactivate navigation analytic?', $assoc_args ); Option::network_set( 'capture', false ); $this->success( 'navigation analytics are now deactivated.', '', $stdout ); break; case 'resource-analytics': \WP_CLI::confirm( 'Are you sure you want to deactivate resources analytic?', $assoc_args ); Option::network_set( 'rcapture', false ); $this->success( 'resources analytics are now deactivated.', '', $stdout ); break; case 'auto-monitoring': \WP_CLI::confirm( 'Are you sure you want to deactivate auto-monitoring?', $assoc_args ); Option::network_set( 'livelog', false ); $this->success( 'auto-monitoring is now deactivated.', '', $stdout ); break; case 'smart-filter': \WP_CLI::confirm( 'Are you sure you want to deactivate smart filter?', $assoc_args ); Option::network_set( 'smart_filter', false ); $this->success( 'smart filter is now deactivated.', '', $stdout ); break; case 'metrics': \WP_CLI::confirm( 'Are you sure you want to deactivate metrics collation?', $assoc_args ); Option::network_set( 'metrics', false ); $this->success( 'metrics collation is now deactivated.', '', $stdout ); break; default: $this->error( 1, $stdout ); } break; default: $this->error( 2, $stdout ); } } /** * Get information on exit codes. * * ## OPTIONS * * <list> * : The action to take. * --- * options: * - list * --- * * [--format=<format>] * : Allows overriding the output of the command when listing exit codes. * --- * default: table * options: * - table * - json * - csv * - yaml * - ids * - count * --- * * ## EXAMPLES * * Lists available exit codes: * + wp vibes exitcode list * + wp vibes exitcode list --format=json * * * === For other examples and recipes, visit https://github.com/Pierre-Lannoy/wp-vibes/blob/master/WP-CLI.md === * */ public function exitcode( $args, $assoc_args ) { $stdout = \WP_CLI\Utils\get_flag_value( $assoc_args, 'stdout', false ); $format = \WP_CLI\Utils\get_flag_value( $assoc_args, 'format', 'table' ); $action = isset( $args[0] ) ? $args[0] : 'list'; $codes = []; foreach ( $this->exit_codes as $key => $msg ) { $codes[ $key ] = [ 'code' => $key, 'meaning' => ucfirst( $msg ), ]; } switch ( $action ) { case 'list': if ( 'ids' === $format ) { $this->write_ids( $codes ); } else { \WP_CLI\Utils\format_items( $format, $codes, [ 'code', 'meaning' ] ); } break; } } /** * Display past or current performance signals. * * ## OPTIONS * * [<count>] * : An integer value [1-60] indicating how many most recent signals to display. If 0 or nothing is supplied as value, a live session is launched, displaying signals as soon as they occur. * * [--signal=<signal_type>] * : The signal type to display. * --- * default: all * options: * - all * - navigation * - webvital * - resource * --- * *[--filter=<filter>] * : The misc. filters to apply. Show only signals matching the specified pattern. * MUST be a json string containing pairs "field":"regexp". * --- * default: '{}' * available fields: 'site' and 'endpoint' * example: '{"endpoint":"/\/blog\/(.*)/"}' * --- * * [--col=<columns>] * : The Number of columns (char in a row) to display. Default is 160. Min is 80 and max is 400. * * [--theme=<theme>] * : Modifies the colors scheme. * --- * default: standard * options: * - standard * - soft * --- * * [--yes] * : Answer yes to the confirmation message, if any. * * ## NOTES * * + This command needs shared memory support for PHP: the PHP module "shmop" must be activated in your PHP web configuration AND in your PHP command-line configuration. * + This command relies on an internal monitor. If this monitor is not started at launch time, you will be prompted to starting it. * + If the monitor has just been started there will not be much to display if <count> is different from 0... * + In a live session, just use CTRL-C to terminate it. * * ## EXAMPLES * * wp vibes tail * wp vibes tail 20 * wp vibes tail 20 --signal=navigation * wp vibes tail --filter='{"endpoint":"/\/blog\/(.*)/"}' * * * === For other examples and recipes, visit https://github.com/Pierre-Lannoy/wp-vibes/blob/master/WP-CLI.md === * */ public function tail( $args, $assoc_args ) { if ( ! function_exists( 'shmop_open' ) || ! function_exists( 'shmop_read' ) || ! function_exists( 'shmop_write' ) || ! function_exists( 'shmop_delete' ) ) { \WP_CLI::error( 'unable to launch tail command, no shared memory manager found.' ); } if ( ! Option::network_get( 'livelog' ) ) { \WP_CLI::warning( 'monitoring is currently disabled. The tail command needs monitoring...' ); \WP_CLI::confirm( 'Would you like to enable monitoring and to resume command?', $assoc_args ); Option::network_set( 'livelog', true ); } $filters = []; $count = isset( $args[0] ) ? (int) $args[0] : 0; if ( 0 > $count || 60 < $count ) { $count = 0; } $col = isset( $assoc_args['col'] ) ? (int) $assoc_args['col'] : 160; if ( 80 > $col ) { $col = 80; } if ( 400 < $col ) { $col = 400; } $filter = \json_decode( isset( $assoc_args['filter'] ) ? (string) $assoc_args['filter'] : '{}', true ); if ( is_array( $filter ) ) { foreach ( [ 'site', 'endpoint' ] as $field ) { if ( array_key_exists( $field, $filter ) ) { $value = (string) $filter[ $field ]; if ( '' === $value ) { continue; } $filters[ $field ] = $value; } } } $signal = isset( $assoc_args['signal'] ) ? (string) $assoc_args['signal'] : 'all'; switch ( $signal ) { case 'navigation': case 'webvital': case 'resource': $filters['type'] = '/' . $signal . '/'; break; default: if ( isset( $filters['type'] ) ) { unset( $filters['type'] ); } } $records = Memory::read(); if ( 0 === $count ) { \DecaLog\Engine::eventsLogger( VIBES_SLUG )->notice( 'Live console launched.' ); while ( true ) { $this->records_display( self::records_filter( Memory::read(), $filters ), $assoc_args['theme'] ?? 'standard', $col ); $this->flush(); } } else { $this->records_display( array_slice( self::records_filter( $records, $filters ), -$count ), $assoc_args['theme'] ?? 'standard', $col ); } } /** * Get the WP-CLI help file. * * @param array $attributes 'style' => 'markdown', 'html'. * 'mode' => 'raw', 'clean'. * @return string The output of the shortcode, ready to print. * @since 1.0.0 */ public static function sc_get_helpfile( $attributes ) { $md = new Markdown(); return $md->get_shortcode( 'WP-CLI.md', $attributes ); } } add_shortcode( 'vibes-wpcli', [ 'Vibes\Plugin\Feature\Wpcli', 'sc_get_helpfile' ] ); if ( defined( 'WP_CLI' ) && WP_CLI ) { require_once VIBES_ASSETS_DIR . 'mime-types.php'; \WP_CLI::add_command( 'vibes', 'Vibes\Plugin\Feature\Wpcli' ); }