|null */ private $core = null; /** * Every valid check by ID (after the filter). * * @var array|null */ private $all = null; /** * Constructor. * * @since 1.0.0 * @param Module $module Power-up. */ public function __construct( Module $module ) { $this->module = $module; } /** * Tables the generic orphan checker may read and delete from: this site's WordPress, * WooCommerce and Action Scheduler tables it knows. Not filterable. * * @since 1.0.0 * @return string[] */ public static function allowed_tables(): array { return array_values( array_unique( array_merge( self::core_tables(), self::prefixed_tables() ) ) ); } /** * WordPress core tables of the whitelist (they always exist). * * @since 1.0.0 * @return string[] */ public static function core_tables(): array { global $wpdb; return array( $wpdb->posts, $wpdb->postmeta, $wpdb->comments, $wpdb->commentmeta, $wpdb->terms, $wpdb->termmeta, $wpdb->term_taxonomy, $wpdb->term_relationships, $wpdb->options, $wpdb->users, $wpdb->usermeta ); } /** * WooCommerce and Action Scheduler tables of the whitelist (this site's prefix). * * @since 1.0.0 * @return string[] */ public static function prefixed_tables(): array { global $wpdb; $names = array( 'woocommerce_sessions', 'woocommerce_order_items', 'woocommerce_order_itemmeta', 'woocommerce_log', 'wc_orders', 'wc_orders_meta', 'wc_order_addresses', 'wc_order_operational_data', 'wc_order_stats', 'wc_order_product_lookup', 'wc_order_tax_lookup', 'wc_order_coupon_lookup', 'wc_product_meta_lookup', 'wc_product_attributes_lookup', 'wc_customer_lookup', 'wc_download_log', 'actionscheduler_actions', 'actionscheduler_logs' ); return array_map( static fn( $name ) => $wpdb->prefix . $name, $names ); } /** * Group labels. * * @since 1.0.0 * @return array */ public static function group_labels(): array { return array_combine( self::GROUPS, array( __( 'WooCommerce leftovers', 'admin-powerups-for-woocommerce' ), __( 'Scheduled action history', 'admin-powerups-for-woocommerce' ), __( 'WordPress clutter', 'admin-powerups-for-woocommerce' ), ) ); } /** * The core checks. * * @since 1.0.0 * @return array */ public function core(): array { if ( null === $this->core ) { $this->core = array(); foreach ( array_merge( WooCommerceChecks::build( $this->module ), ActionChecks::build(), WordPressChecks::build( $this->module ) ) as $check ) { $this->core[ $check->id() ] = $check; } } return $this->core; } /** * Every valid check by ID, core first, after `apwc_database_cleanup_checks`. * * @since 1.0.0 * @return array */ public function all(): array { if ( null !== $this->all ) { return $this->all; } /** * Filter the Database clean-up checks. Add an object implementing * `SmartWP\AdminPowerups\Modules\DatabaseCleanup\Check` (extend `BaseCheck`), or remove * one. Entries that aren't checks, or whose ID is invalid (1–64 of a-z, 0-9, -) or already * taken, are dropped. An unknown risk counts as Advanced, and Advanced checks always need * the backup confirmation. Only core Safe checks ever run on the weekly schedule. * * @since 1.0.0 * @param array $checks Checks by ID. */ $filtered = apply_filters( 'apwc_database_cleanup_checks', $this->core() ); $this->all = array(); foreach ( is_array( $filtered ) ? $filtered : array() as $check ) { if ( ! $check instanceof Check ) { continue; } $id = $check->id(); if ( ! preg_match( '/^' . self::ID_PATTERN . '$/', $id ) || isset( $this->all[ $id ] ) ) { continue; } $this->all[ $id ] = $check; } return $this->all; } /** * A check by ID. * * @since 1.0.0 * @param string $id Check ID. * @return Check|null */ public function get( string $id ): ?Check { return $this->all()[ $id ] ?? null; } /** * Checks by group; unknown groups show under WordPress clutter. * * @since 1.0.0 * @return array> */ public function grouped(): array { $out = array_fill_keys( self::GROUPS, array() ); foreach ( $this->all() as $id => $check ) { $group = in_array( $check->group(), self::GROUPS, true ) ? $check->group() : self::FALLBACK_GROUP; $out[ $group ][ $id ] = $check; } return $out; } /** * The check's level: a known DevGuard level, anything else Advanced. * * @since 1.0.0 * @param Check $check Check. * @return string */ public static function risk( Check $check ): string { return DevGuard::level( (string) $check->risk() ); } /** * Whether a clean needs the backup confirmation (always for Advanced). * * @since 1.0.0 * @param Check $check Check. * @return bool */ public static function needs_backup( Check $check ): bool { return $check->requires_backup() || DevGuard::ADVANCED === self::risk( $check ); } /** * Whether a check is one of ours (the same object the core list built), not added or * replaced by a filter. * * @since 1.0.0 * @param Check $check Check. * @return bool */ public function is_core( Check $check ): bool { $core = $this->core(); return isset( $core[ $check->id() ] ) && $core[ $check->id() ] === $check; } /** * Core checks that may run on the weekly schedule: Safe ones, by ID => label. * * @since 1.0.0 * @return array */ public function schedulable(): array { $out = array(); foreach ( $this->core() as $id => $check ) { if ( DevGuard::SAFE === self::risk( $check ) ) { $out[ $id ] = $check->label(); } } return $out; } /** * The schedule's default selection: Safe checks that aren't slow to walk (big tables are * left to the admin's choice) and need no object capability (so a default can never go past * what the saving user may clean). * * @since 1.0.0 * @return string[] */ public function schedule_defaults(): array { $core = $this->core(); return array_values( array_filter( array_keys( $this->schedulable() ), static fn( $id ) => ! $core[ $id ]->slow() && '' === $core[ $id ]->object_cap() ) ); } /** * IDs of checks whose threshold comes from a power-up setting (the row links to Settings). * * @since 1.0.0 * @return string[] */ public function configurable(): array { $out = array(); foreach ( $this->all() as $id => $check ) { if ( $check instanceof BaseCheck && '' !== $check->setting() ) { $out[] = $id; } } return $out; } /* ------------------------------------------------------------------ shared by definitions */ /** * Post types never touched by post checks: WooCommerce order types (and HPOS placeholders). * * @since 1.0.0 * @return string[] */ public static function order_types(): array { $types = function_exists( 'wc_get_order_types' ) ? wc_get_order_types() : array(); return array_values( array_unique( array_merge( array( 'shop_order', 'shop_order_refund', 'shop_order_placehold', 'shop_subscription' ), array_filter( (array) $types, 'is_string' ) ) ) ); } /** * Parent types whose revisions are kept (Site Editor history), plus filter additions. * * @since 1.0.0 * @return string[] */ public static function revision_excluded_types(): array { $core = array( 'wp_global_styles', 'wp_template', 'wp_template_part', 'wp_navigation', 'wp_block' ); /** * Filter the post types whose revisions the Database clean-up keeps. Types can only be * added: the Site Editor types are always kept. * * @since 1.0.0 * @param string[] $types Post types. */ $extra = apply_filters( 'apwc_database_cleanup_revision_excluded_types', $core ); return array_values( array_unique( array_merge( $core, array_filter( (array) $extra, 'is_string' ) ) ) ); } /** * Post types the trash check keeps, plus filter additions (order types always). * * @since 1.0.0 * @return string[] */ public static function trash_excluded_types(): array { $core = self::order_types(); /** * Filter the post types whose trashed posts the Database clean-up keeps. Types can only be * added: WooCommerce order types are always kept. * * @since 1.0.0 * @param string[] $types Post types. */ $extra = apply_filters( 'apwc_database_cleanup_trash_excluded_types', $core ); return array_values( array_unique( array_merge( $core, array_filter( (array) $extra, 'is_string' ) ) ) ); } }