| @@ -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 ) { |