value lines (a list without labels for plugins). * * @return array[] */ public static function sections(): array { $sections = array( 'ProdTabs' => self::prodtabs(), 'Site' => self::site(), 'Active plugins' => self::plugins(), ); /** * Sections of the support report of the overview, for ProdTabs Premium and other extensions. * * @param array[] $sections Title => array of label => value lines. */ return (array) apply_filters( 'prodtabs_system_info', $sections ); } /** The report as plain text. */ public static function report(): string { $lines = array(); foreach ( self::sections() as $title => $rows ) { $lines[] = '### ' . $title; foreach ( (array) $rows as $label => $value ) { $lines[] = is_int( $label ) ? '- ' . $value : $label . ': ' . $value; } $lines[] = ''; } return trim( implode( "\n", $lines ) ); } /** * The report packed for the address of the support form (si, after the #: see Admin_Hub::support_url()). The * browser never sends that part of an address, so the report reaches refficience.com only when the admin sends the * form with it. Values in a fixed order (schema 1, decoded by the form on refficience.com) joined by ~, yes/no * settings as bits of one number, plugins as folder:version joined by commas. Plain readable text, not encoded: * the admin can read it in the address. */ public static function code(): string { $clean = static function ( $value ): string { return str_replace( array( '~', ',' ), ' ', (string) $value ); }; $fields = self::payload(); $last = array_pop( $fields ); return implode( '~', array_map( $clean, $fields ) ) . '~' . implode( ',', array_map( $clean, $last ) ); } /** * Values of the packed report, in the order of schema 1. * * @return array */ public static function payload(): array { global $wp_version; $settings = Settings::all(); $natives = array(); foreach ( $settings['native_order'] as $key ) { $native = $settings['natives'][ $key ] ?? array(); $natives[] = substr( $key, 0, 1 ) . ( empty( $native['visible'] ) ? '-' : '' ) . ( '' !== ( $native['title'] ?? '' ) ? '*' : '' ); } $theme = wp_get_theme(); $parent = $theme->parent(); $hpos = class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' ) && \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled(); $bits = array( $settings['heading'], $settings['hide_untranslated'], $settings['the_content'], $settings['yikes_compat'], $settings['menu'], $settings['delete_data'], defined( 'WP_DEBUG' ) && WP_DEBUG, wp_using_ext_object_cache(), is_multisite(), $hpos, function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ); $flags = 0; foreach ( $bits as $i => $on ) { $flags |= $on ? 1 << $i : 0; } $shared = wp_count_posts( Library::POST_TYPE ); $yikes = Import_Yikes::detect(); $state = Import_Yikes::state(); $import = $state ? ( empty( $state['finished'] ) ? 'p' : gmdate( 'ymd', (int) $state['finished'] ) ) . '/' . (int) ( $state['done'] ?? 0 ) . '/' . (int) ( $state['total'] ?? 0 ) : ''; $plugin = Multilingual::plugin(); $version = array( 'polylang' => 'POLYLANG_VERSION', 'wpml' => 'ICL_SITEPRESS_VERSION', 'translatepress' => 'TRP_PLUGIN_VERSION', 'weglot' => 'WEGLOT_VERSION', )[ $plugin ] ?? ''; $plugins = array(); foreach ( self::active_files() as $file => $data ) { $plugins[] = ( false !== strpos( $file, '/' ) ? dirname( $file ) : basename( $file, '.php' ) ) . ':' . $data['Version']; } sort( $plugins ); return array( 1, PRODTABS_VERSION, defined( 'PRODTABS_PRO_VERSION' ) ? PRODTABS_PRO_VERSION : '', implode( ' ', $natives ), $flags, $settings['accordion'] ? (int) $settings['accordion_width'] : 0, (int) ( $shared->publish ?? 0 ), (int) ( $shared->draft ?? 0 ), Admin_Hub::products_with_tabs(), (int) $yikes['products'], (int) $yikes['reusable'], $import, $wp_version, defined( 'WC_VERSION' ) ? WC_VERSION : '', PHP_VERSION, (string) ini_get( 'memory_limit' ), (int) ini_get( 'max_input_vars' ), get_locale(), get_user_locale(), array( 'polylang' => 'p', 'wpml' => 'w', 'translatepress' => 't', 'weglot' => 'g', )[ $plugin ] ?? '', '' !== $version && defined( $version ) ? (string) constant( $version ) : '', '' !== $plugin ? implode( ' ', Multilingual::languages() ) . '/' . Multilingual::default_language() : '', $theme->get( 'Name' ) . ':' . $theme->get( 'Version' ), $parent ? $parent->get( 'Name' ) . ':' . $parent->get( 'Version' ) : '', home_url( '/' ), $plugins, ); } /** ProdTabs: versions, settings, tabs and the import from Custom Product Tabs. */ private static function prodtabs(): array { $settings = Settings::all(); $natives = array(); foreach ( $settings['native_order'] as $key ) { $native = $settings['natives'][ $key ] ?? array(); $natives[] = $key . ( empty( $native['visible'] ) ? ' (hidden)' : '' ) . ( '' !== ( $native['title'] ?? '' ) ? ' (renamed)' : '' ); } $shared = wp_count_posts( Library::POST_TYPE ); $rows = array( 'Version' => PRODTABS_VERSION, 'Premium' => defined( 'PRODTABS_PRO_VERSION' ) ? PRODTABS_PRO_VERSION : 'not active', 'WooCommerce tabs' => implode( ', ', $natives ), 'Heading in panels' => self::yes( $settings['heading'] ), 'Accordion on phones' => $settings['accordion'] ? 'yes, under ' . (int) $settings['accordion_width'] . ' px' : 'no', 'Hide untranslated tabs' => self::yes( $settings['hide_untranslated'] ), 'Full content filter' => self::yes( $settings['the_content'] ), 'YIKES compatibility' => self::yes( $settings['yikes_compat'] ), 'ProdTabs menu' => self::yes( $settings['menu'] ), 'Delete data on uninstall' => self::yes( $settings['delete_data'] ), 'Shared tabs' => (int) ( $shared->publish ?? 0 ) . ' published, ' . (int) ( $shared->draft ?? 0 ) . ' draft', 'Products with tabs' => Admin_Hub::products_with_tabs(), ); $yikes = Import_Yikes::detect(); if ( $yikes['products'] || $yikes['reusable'] ) { $rows['Custom Product Tabs data'] = (int) $yikes['products'] . ' products, ' . (int) $yikes['reusable'] . ' saved tabs'; } $state = Import_Yikes::state(); if ( $state ) { $rows['Import from YIKES'] = ( empty( $state['finished'] ) ? 'in progress' : 'finished ' . gmdate( 'Y-m-d', (int) $state['finished'] ) ) . ', ' . (int) ( $state['done'] ?? 0 ) . ' of ' . (int) ( $state['total'] ?? 0 ) . ' products'; } return $rows; } /** WordPress, WooCommerce, PHP, languages and theme. */ private static function site(): array { global $wp_version; $theme = wp_get_theme(); $hpos = class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' ) && \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled(); $rows = array( 'Address' => home_url( '/' ), 'WordPress' => $wp_version . ( is_multisite() ? ', multisite' : '' ), 'WooCommerce' => ( defined( 'WC_VERSION' ) ? WC_VERSION : 'unknown' ) . ( $hpos ? ', HPOS' : '' ), 'PHP' => PHP_VERSION . ', memory_limit ' . ini_get( 'memory_limit' ) . ', max_input_vars ' . ini_get( 'max_input_vars' ), 'Debug' => self::yes( defined( 'WP_DEBUG' ) && WP_DEBUG ), 'Object cache' => self::yes( wp_using_ext_object_cache() ), 'Language' => 'site ' . get_locale() . ', admin ' . get_user_locale(), ); $plugin = Multilingual::plugin(); if ( '' !== $plugin ) { $known = array( 'polylang' => array( 'Polylang', 'POLYLANG_VERSION' ), 'wpml' => array( 'WPML', 'ICL_SITEPRESS_VERSION' ), 'translatepress' => array( 'TranslatePress', 'TRP_PLUGIN_VERSION' ), 'weglot' => array( 'Weglot', 'WEGLOT_VERSION' ), ); list( $name, $constant ) = $known[ $plugin ]; $rows['Translation plugin'] = $name . ( defined( $constant ) ? ' ' . constant( $constant ) : '' ) . ', languages ' . implode( ' ', Multilingual::languages() ) . ', default ' . Multilingual::default_language(); } $rows['Theme'] = $theme->get( 'Name' ) . ' ' . $theme->get( 'Version' ) . ( $theme->parent() ? ', child of ' . $theme->parent()->get( 'Name' ) . ' ' . $theme->parent()->get( 'Version' ) : '' ) . ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ? ', block theme' : ', classic theme' ); return $rows; } /** Active plugins, network-wide ones included, with their versions. */ private static function plugins(): array { $rows = array(); foreach ( self::active_files() as $data ) { $rows[] = $data['Name'] . ' ' . $data['Version']; } natcasesort( $rows ); return array_values( $rows ); } /** * Header data of the active plugins, network-wide ones included. * * @return array[] Plugin file => header data. */ private static function active_files(): array { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $all = get_plugins(); $active = (array) get_option( 'active_plugins', array() ); if ( is_multisite() ) { $active = array_merge( $active, array_keys( (array) get_site_option( 'active_sitewide_plugins', array() ) ) ); } return array_intersect_key( $all, array_flip( array_unique( $active ) ) ); } /** * Yes or no. * * @param mixed $value Value read as a boolean. */ private static function yes( $value ): string { return $value ? 'yes' : 'no'; } }