PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.4.2
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.4.2
1.4.2 1.4.1 1.4.0 1.3.7 1.3.6 1.3.5 1.3.4 1.3.3 1.3.2 1.3.1 1.3.0 1.2.4 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 All 36 releases
← All changes | includes/modules/Cache/CacheModule.php +107 -24 1.4.0 → 1.4.2 View file →
@@ -544,20 +544,21 @@
544 544 ),
545 545 array(
546 546 'name' => 'xspeed cache',
547 547 'callback' => array( $this, 'cli_handler' ),
548 - 'shortdesc' => 'Inspect the Cache module: `status` (settings), `inventory` (which pages are cached, and how old), `size` (where the disk usage goes), `purge-log` (what cleared the cache, when and why), `purge-url <url>` to clear one page, `recheck-rewrite` to re-run the static-rewrite probe, or `nginx-config` to print the unified nginx server-block for pasting into a vhost, or `edge` to show which cache is in front of the site and what xSpeed tells it, or `listings` to show which pages were recorded running a post list of their own (cleared by a narrow purge when a save may change that list). To clear the whole site use `wp xspeed purge`.',
548 + 'shortdesc' => 'Inspect the Cache module: `status` (settings), `inventory` (which pages are cached, and how old), `size` (where the disk usage goes), `purge-log` (what cleared the cache, when and why), `purge` to clear everything (same as `wp xspeed purge`), `purge <url>` or `purge-url <url>` to clear one page, `recheck-rewrite` to re-run the static-rewrite probe, or `nginx-config` to print the unified nginx server-block for pasting into a vhost, or `edge` to show which cache is in front of the site and what xSpeed tells it, or `listings` to show which pages were recorded running a post list of their own (cleared by a narrow purge when a save may change that list). To clear the whole site use `wp xspeed purge`.',
549 549 'synopsis' => array(
550 550 array(
551 551 'type' => 'positional',
552 552 'name' => 'action',
553 - 'options' => array( 'status', 'inventory', 'size', 'purge-log', 'purge-url', 'recheck-rewrite', 'nginx-config', 'edge', 'listings' ),
553 + 'options' => array( 'status', 'inventory', 'size', 'purge', 'purge-log', 'purge-url', 'recheck-rewrite', 'nginx-config', 'edge', 'listings' ),
554 554 'optional' => true,
555 555 ),
556 556 array(
557 - 'type' => 'positional',
558 - 'name' => 'url',
559 - 'optional' => true,
557 + 'type' => 'positional',
558 + 'name' => 'url',
559 + 'description' => 'For purge and purge-url: the page to clear, as a URL on this site or a path such as /about/. Without it, purge clears everything.',
560 + 'optional' => true,
560 561 ),
561 562 array(
562 563 'type' => 'assoc',
563 564 'name' => 'limit',
@@ -566,13 +567,26 @@
566 567 ),
567 568 array(
568 569 'type' => 'assoc',
569 570 'name' => 'cause',
570 - 'description' => 'Label recorded in the purge log for purge-url. Default "CLI".',
571 + 'description' => 'Label recorded in the purge log for purge and purge-url. Default "CLI".',
571 572 'optional' => true,
572 573 ),
573 574 array(
574 575 'type' => 'assoc',
576 + 'name' => 'type',
577 + 'description' => 'For purge without a URL: what to clear, as in `wp xspeed purge --type`. Default all.',
578 + 'optional' => true,
579 + ),
580 + array(
581 + 'type' => 'assoc',
582 + 'name' => 'format',
583 + 'description' => 'For purge without a URL: table (default) or json.',
584 + 'optional' => true,
585 + 'options' => array( 'table', 'json' ),
586 + ),
587 + array(
588 + 'type' => 'assoc',
575 589 'name' => 'server',
576 590 'description' => 'Server type to assume for nginx-config, skipping detection. Detection needs SERVER_SOFTWARE, which the command line does not have; an undetectable host is assumed to be nginx anyway, so this is for stating it outright — or for the case detection is positively wrong, such as nginx in front of Apache.',
577 591 'options' => array( 'nginx', 'apache', 'litespeed' ),
578 592 'optional' => true,
@@ -724,9 +738,16 @@
724 738 foreach ( $report['types'] as $row ) {
725 739 switch ( $row['status'] ) {
726 740 case \XSpeed\Purge_Runner::CLEARED:
727 741 ++$cleared;
728 - \WP_CLI::log( sprintf( ' cleared %s%s', $row['label'], self::purge_amount( $row ) ) );
742 + \WP_CLI::log(
743 + sprintf(
744 + ' cleared %s%s%s',
745 + $row['label'],
746 + self::purge_amount( $row ),
747 + '' !== $row['reason'] ? '; ' . $row['reason'] : ''
748 + )
749 + );
729 750 break;
730 751 case \XSpeed\Purge_Runner::FAILED:
731 752 ++$failed;
732 753 \WP_CLI::log( sprintf( ' FAILED %s — %s', $row['label'], $row['reason'] ) );
@@ -760,8 +781,65 @@
760 781 \WP_CLI::success( sprintf( 'Purged %d cache store(s); %d skipped.', $cleared, $skipped ) );
761 782 }
762 783
763 784 /**
785 + * Clear one page: `wp xspeed cache purge-url <url>` and
786 + * `wp xspeed cache purge <url>`.
787 + *
788 + * A string purge_url() cannot act on (another scheme, a scheme with no
789 + * host, a bare word) is refused with an error. purge_url() returns 0 for
790 + * it, which read as "already cold" and exited 0.
791 + *
792 + * @param string $url Absolute URL or site-relative path.
793 + * @param array<string,string> $assoc Flags; only --cause is read.
794 + */
795 + private function cli_purge_url( string $url, array $assoc ): void {
796 + if ( ! self::is_purgeable_url( $url ) ) {
797 + \WP_CLI::error( sprintf( 'Not a page URL: %s. Give an http(s) URL on this site or a path starting with /, such as /about/.', $url ) );
798 + return;
799 + }
800 + $cause = isset( $assoc['cause'] ) && '' !== trim( (string) $assoc['cause'] ) ? trim( (string) $assoc['cause'] ) : 'CLI';
801 + $result = \XSpeed\Cache::purge_url_reported( $url, $cause );
802 + $removed = $result['removed'];
803 + $forwarded = implode( ', ', $result['forwarded'] );
804 + if ( $removed > 0 ) {
805 + \WP_CLI::success(
806 + '' === $forwarded
807 + ? sprintf( 'Purged %d cache file(s) for %s', $removed, $url )
808 + : sprintf( 'Purged %d cache file(s) for %s, and sent the purge to %s', $removed, $url, $forwarded )
809 + );
810 + } elseif ( '' !== $forwarded ) {
811 + // xSpeed's own cache held nothing, but a cache in front of
812 + // PHP took the purge: that copy is the one visitors get.
813 + \WP_CLI::success( sprintf( 'Sent the purge for %s to %s. xSpeed\'s own cache held no copy.', $url, $forwarded ) );
814 + } else {
815 + \WP_CLI::log( sprintf( 'No cache entries found for %s (already cold, or the URL never cached).', $url ) );
816 + }
817 + }
818 +
819 + /**
820 + * Whether purge_url() can act on this string: an http(s) URL with a
821 + * host, a host without a scheme (`site.test/page/`), or a path starting
822 + * with `/`. The same rules purge_url() applies before it returns 0.
823 + *
824 + * @param string $url What the operator typed.
825 + */
826 + private static function is_purgeable_url( string $url ): bool {
827 + $parts = wp_parse_url( $url );
828 + if ( ! is_array( $parts ) ) {
829 + return false;
830 + }
831 + if ( isset( $parts['scheme'] ) ) {
832 + return in_array( strtolower( (string) $parts['scheme'] ), array( 'http', 'https' ), true ) && ! empty( $parts['host'] );
833 + }
834 + if ( isset( $parts['host'] ) ) {
835 + return true;
836 + }
837 + $path = isset( $parts['path'] ) ? (string) $parts['path'] : '';
838 + return '' !== $path && '/' === $path[0];
839 + }
840 +
841 + /**
764 842 * The " — 42 entries (1.3 MB)" tail on a cleared line.
765 843 *
766 844 * Entries and bytes are both optional: a Redis FLUSHALL reports neither,
767 845 * and printing "0 entries" for it would read as an empty cache rather
@@ -784,8 +862,29 @@
784 862 public function cli_handler( array $args, array $assoc ): void {
785 863 $action = isset( $args[0] ) ? (string) $args[0] : 'status';
786 864 $limit = isset( $assoc['limit'] ) ? max( 1, (int) $assoc['limit'] ) : 20;
787 865
866 + // `wp xspeed cache purge` is what people type. It runs the same
867 + // purge as `wp xspeed purge`; before it was listed, WP-CLI rejected
868 + // it with "Invalid value specified for positional arg". Given a URL
869 + // it clears that one page, as purge-url does: it used to drop the
870 + // URL and clear the whole site.
871 + if ( 'purge' === $action ) {
872 + $url = isset( $args[1] ) ? trim( (string) $args[1] ) : '';
873 + if ( '' === $url ) {
874 + $this->cli_purge( array(), $assoc );
875 + return;
876 + }
877 + foreach ( array( 'type', 'format' ) as $flag ) {
878 + if ( isset( $assoc[ $flag ] ) ) {
879 + \WP_CLI::error( sprintf( '--%s applies to a full purge, not to one URL. Run `wp xspeed cache purge %s` without it, or drop the URL to purge everything.', $flag, $url ) );
880 + return;
881 + }
882 + }
883 + $this->cli_purge_url( $url, $assoc );
884 + return;
885 + }
886 +
788 887 /*
789 888 * Print the unified nginx server-block so an installer, provisioning
790 889 * script, or another plugin can fetch it non-interactively and write
791 890 * it into a vhost. Previously this was only reachable via
@@ -942,25 +1041,9 @@
942 1041 if ( '' === $url ) {
943 1042 \WP_CLI::error( 'Usage: wp xspeed cache purge-url <url-or-path>' );
944 1043 return;
945 1044 }
946 - $cause = isset( $assoc['cause'] ) && '' !== trim( (string) $assoc['cause'] ) ? trim( (string) $assoc['cause'] ) : 'CLI';
947 - $result = \XSpeed\Cache::purge_url_reported( $url, $cause );
948 - $removed = $result['removed'];
949 - $forwarded = implode( ', ', $result['forwarded'] );
950 - if ( $removed > 0 ) {
951 - \WP_CLI::success(
952 - '' === $forwarded
953 - ? sprintf( 'Purged %d cache file(s) for %s', $removed, $url )
954 - : sprintf( 'Purged %d cache file(s) for %s, and sent the purge to %s', $removed, $url, $forwarded )
955 - );
956 - } elseif ( '' !== $forwarded ) {
957 - // xSpeed's own cache held nothing, but a cache in front of
958 - // PHP took the purge: that copy is the one visitors get.
959 - \WP_CLI::success( sprintf( 'Sent the purge for %s to %s. xSpeed\'s own cache held no copy.', $url, $forwarded ) );
960 - } else {
961 - \WP_CLI::log( sprintf( 'No cache entries found for %s (already cold, or the URL never cached).', $url ) );
962 - }
1045 + $this->cli_purge_url( $url, $assoc );
963 1046 return;
964 1047 }
965 1048
966 1049 if ( 'inventory' === $action ) {