counts(); return null === $counts ? array() : self::evaluate( $counts['out'], $counts['low'] ); } /** * Pure evaluation. * * @param int $out Published products out of stock. * @param int $low Published products in stock at or below the low-stock threshold. * @return Result[] */ public static function evaluate( int $out, int $low ): array { if ( 0 === $out && 0 === $low ) { return array( new Result( 'woocommerce.stock', Result::GOOD, __( 'No products are out of stock or running low', 'keepwatch' ) ) ); } if ( $out > 0 ) { $label = 0 < $low /* translators: 1: products out of stock, 2: products running low */ ? sprintf( __( '%1$d products out of stock, %2$d running low', 'keepwatch' ), $out, $low ) /* translators: %d: number of products */ : sprintf( _n( '%d product is out of stock', '%d products are out of stock', $out, 'keepwatch' ), $out ); $link = admin_url( 'edit.php?post_type=product&stock_status=outofstock' ); } else { /* translators: %d: number of products */ $label = sprintf( _n( '%d product is running low on stock', '%d products are running low on stock', $low, 'keepwatch' ), $low ); $link = admin_url( 'admin.php?page=wc-reports&tab=stock&report=low_in_stock' ); } return array( new Result( 'woocommerce.stock', Result::RECOMMENDED, $label, __( 'Customers cannot buy products that are out of stock. Restock them or hide them from the shop.', 'keepwatch' ), $link, __( 'View products', 'keepwatch' ) ), ); } /** * Out-of-stock and low-stock counts for published products (WooCommerce product lookup table). * * @return array{out: int, low: int}|null */ private function counts(): ?array { global $wpdb; $table = $wpdb->prefix . 'wc_product_meta_lookup'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- read-only counts, cached by the check schedule. if ( $table !== $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table ) ) ) ) { return null; } $threshold = max( 0, (int) get_option( 'woocommerce_notify_low_stock_amount', 2 ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- see above. $row = $wpdb->get_row( $wpdb->prepare( 'SELECT SUM(l.stock_status = %s) AS out_count, SUM(l.stock_status = %s AND l.stock_quantity IS NOT NULL AND l.stock_quantity <= %d) AS low_count FROM %i l INNER JOIN %i p ON p.ID = l.product_id WHERE p.post_status = %s AND p.post_type IN (%s, %s)', 'outofstock', 'instock', $threshold, $table, $wpdb->posts, 'publish', 'product', 'product_variation' ), ARRAY_A ); return is_array( $row ) ? array( 'out' => (int) $row['out_count'], 'low' => (int) $row['low_count'], ) : null; } }