ajax
5 years ago
capabilities
4 years ago
endpoints
5 years ago
exceptions
7 years ago
filters
4 years ago
formatter
4 years ago
google_search_console
5 years ago
import
4 years ago
listeners
8 years ago
menu
4 years ago
metabox
4 years ago
notifiers
4 years ago
pages
4 years ago
roles
5 years ago
ryte
4 years ago
services
5 years ago
statistics
5 years ago
taxonomy
4 years ago
tracking
4 years ago
views
4 years ago
watchers
5 years ago
admin-settings-changed-listener.php
5 years ago
ajax.php
4 years ago
class-admin-asset-analysis-worker-location.php
5 years ago
class-admin-asset-dev-server-location.php
5 years ago
class-admin-asset-location.php
8 years ago
class-admin-asset-manager.php
4 years ago
class-admin-asset-seo-location.php
4 years ago
class-admin-asset-yoast-components-l10n.php
4 years ago
class-admin-editor-specific-replace-vars.php
5 years ago
class-admin-gutenberg-compatibility-notification.php
5 years ago
class-admin-help-panel.php
5 years ago
class-admin-init.php
4 years ago
class-admin-recommended-replace-vars.php
6 years ago
class-admin-user-profile.php
6 years ago
class-admin-utils.php
5 years ago
class-admin.php
4 years ago
class-asset.php
5 years ago
class-bulk-description-editor-list-table.php
5 years ago
class-bulk-editor-list-table.php
4 years ago
class-bulk-title-editor-list-table.php
6 years ago
class-collector.php
6 years ago
class-config.php
4 years ago
class-customizer.php
5 years ago
class-database-proxy.php
5 years ago
class-export.php
5 years ago
class-expose-shortlinks.php
4 years ago
class-gutenberg-compatibility.php
4 years ago
class-helpscout.php
5 years ago
class-meta-columns.php
4 years ago
class-my-yoast-proxy.php
5 years ago
class-option-tab.php
4 years ago
class-option-tabs-formatter.php
5 years ago
class-option-tabs.php
5 years ago
class-paper-presenter.php
5 years ago
class-plugin-availability.php
5 years ago
class-plugin-conflict.php
4 years ago
class-premium-popup.php
5 years ago
class-premium-upsell-admin-block.php
4 years ago
class-primary-term-admin.php
5 years ago
class-product-upsell-notice.php
5 years ago
class-remote-request.php
5 years ago
class-schema-person-upgrade-notification.php
4 years ago
class-suggested-plugins.php
4 years ago
class-yoast-columns.php
5 years ago
class-yoast-dashboard-widget.php
4 years ago
class-yoast-form.php
4 years ago
class-yoast-input-validation.php
5 years ago
class-yoast-network-admin.php
5 years ago
class-yoast-network-settings-api.php
4 years ago
class-yoast-notification-center.php
4 years ago
class-yoast-notification.php
5 years ago
class-yoast-notifications.php
5 years ago
class-yoast-plugin-conflict.php
4 years ago
index.php
10 years ago
interface-collection.php
7 years ago
interface-installable.php
8 years ago
class-my-yoast-proxy.php
210 lines
| 1 | <?php |
| 2 | /** |
| 3 | * WPSEO plugin file. |
| 4 | * |
| 5 | * @package WPSEO\Admin |
| 6 | */ |
| 7 | |
| 8 | /** |
| 9 | * Loads the MyYoast proxy. |
| 10 | * |
| 11 | * This class registers a proxy page on `admin.php`. Which is reached with the `page=PAGE_IDENTIFIER` parameter. |
| 12 | * It will read external files and serves them like they are located locally. |
| 13 | */ |
| 14 | class WPSEO_MyYoast_Proxy implements WPSEO_WordPress_Integration { |
| 15 | |
| 16 | /** |
| 17 | * The page identifier used in WordPress to register the MyYoast proxy page. |
| 18 | * |
| 19 | * @var string |
| 20 | */ |
| 21 | const PAGE_IDENTIFIER = 'wpseo_myyoast_proxy'; |
| 22 | |
| 23 | /** |
| 24 | * The cache control's max age. Used in the header of a successful proxy response. |
| 25 | * |
| 26 | * @var int |
| 27 | */ |
| 28 | const CACHE_CONTROL_MAX_AGE = DAY_IN_SECONDS; |
| 29 | |
| 30 | /** |
| 31 | * Registers the hooks when the user is on the right page. |
| 32 | * |
| 33 | * @codeCoverageIgnore |
| 34 | * |
| 35 | * @return void |
| 36 | */ |
| 37 | public function register_hooks() { |
| 38 | if ( ! $this->is_proxy_page() ) { |
| 39 | return; |
| 40 | } |
| 41 | |
| 42 | // Register the page for the proxy. |
| 43 | add_action( 'admin_menu', [ $this, 'add_proxy_page' ] ); |
| 44 | add_action( 'admin_init', [ $this, 'handle_proxy_page' ] ); |
| 45 | } |
| 46 | |
| 47 | /** |
| 48 | * Registers the proxy page. It does not actually add a link to the dashboard. |
| 49 | * |
| 50 | * @codeCoverageIgnore |
| 51 | * |
| 52 | * @return void |
| 53 | */ |
| 54 | public function add_proxy_page() { |
| 55 | add_dashboard_page( '', '', 'read', self::PAGE_IDENTIFIER, '' ); |
| 56 | } |
| 57 | |
| 58 | /** |
| 59 | * Renders the requested proxy page and exits to prevent the WordPress UI from loading. |
| 60 | * |
| 61 | * @codeCoverageIgnore |
| 62 | * |
| 63 | * @return void |
| 64 | */ |
| 65 | public function handle_proxy_page() { |
| 66 | $this->render_proxy_page(); |
| 67 | |
| 68 | // Prevent the WordPress UI from loading. |
| 69 | exit; |
| 70 | } |
| 71 | |
| 72 | /** |
| 73 | * Renders the requested proxy page. |
| 74 | * |
| 75 | * This is separated from the exits to be able to test it. |
| 76 | * |
| 77 | * @return void |
| 78 | */ |
| 79 | public function render_proxy_page() { |
| 80 | $proxy_options = $this->determine_proxy_options(); |
| 81 | if ( $proxy_options === [] ) { |
| 82 | // Do not accept any other file than implemented. |
| 83 | $this->set_header( 'HTTP/1.0 501 Requested file not implemented' ); |
| 84 | return; |
| 85 | } |
| 86 | |
| 87 | // Set the headers before serving the remote file. |
| 88 | $this->set_header( 'Content-Type: ' . $proxy_options['content_type'] ); |
| 89 | $this->set_header( 'Cache-Control: max-age=' . self::CACHE_CONTROL_MAX_AGE ); |
| 90 | |
| 91 | try { |
| 92 | echo $this->get_remote_url_body( $proxy_options['url'] ); |
| 93 | } |
| 94 | catch ( Exception $e ) { |
| 95 | /* |
| 96 | * Reset the file headers because the loading failed. |
| 97 | * |
| 98 | * Note: Due to supporting PHP 5.2 `header_remove` can not be used here. |
| 99 | * Overwrite the headers instead. |
| 100 | */ |
| 101 | $this->set_header( 'Content-Type: text/plain' ); |
| 102 | $this->set_header( 'Cache-Control: max-age=0' ); |
| 103 | |
| 104 | $this->set_header( 'HTTP/1.0 500 ' . $e->getMessage() ); |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | /** |
| 109 | * Tries to load the given url via `wp_remote_get`. |
| 110 | * |
| 111 | * @codeCoverageIgnore |
| 112 | * |
| 113 | * @param string $url The url to load. |
| 114 | * |
| 115 | * @return string The body of the response. |
| 116 | * |
| 117 | * @throws Exception When `wp_remote_get` returned an error. |
| 118 | * @throws Exception When the response code is not 200. |
| 119 | */ |
| 120 | protected function get_remote_url_body( $url ) { |
| 121 | $response = wp_remote_get( $url ); |
| 122 | |
| 123 | if ( $response instanceof WP_Error ) { |
| 124 | throw new Exception( 'Unable to retrieve file from MyYoast' ); |
| 125 | } |
| 126 | |
| 127 | if ( wp_remote_retrieve_response_code( $response ) !== 200 ) { |
| 128 | throw new Exception( 'Received unexpected response from MyYoast' ); |
| 129 | } |
| 130 | |
| 131 | return wp_remote_retrieve_body( $response ); |
| 132 | } |
| 133 | |
| 134 | /** |
| 135 | * Determines the proxy options based on the file and plugin version arguments. |
| 136 | * |
| 137 | * When the file is known it returns an array like this: |
| 138 | * <code> |
| 139 | * $array = array( |
| 140 | * 'content_type' => 'the content type' |
| 141 | * 'url' => 'the url, possibly with the plugin version' |
| 142 | * ) |
| 143 | * </code> |
| 144 | * |
| 145 | * @return array Empty for an unknown file. See format above for known files. |
| 146 | */ |
| 147 | protected function determine_proxy_options() { |
| 148 | if ( $this->get_proxy_file() === 'research-webworker' ) { |
| 149 | return [ |
| 150 | 'content_type' => 'text/javascript; charset=UTF-8', |
| 151 | 'url' => 'https://my.yoast.com/api/downloads/file/analysis-worker?plugin_version=' . $this->get_plugin_version(), |
| 152 | ]; |
| 153 | } |
| 154 | |
| 155 | return []; |
| 156 | } |
| 157 | |
| 158 | /** |
| 159 | * Checks if the current page is the MyYoast proxy page. |
| 160 | * |
| 161 | * @codeCoverageIgnore |
| 162 | * |
| 163 | * @return bool True when the page request parameter equals the proxy page. |
| 164 | */ |
| 165 | protected function is_proxy_page() { |
| 166 | return filter_input( INPUT_GET, 'page' ) === self::PAGE_IDENTIFIER; |
| 167 | } |
| 168 | |
| 169 | /** |
| 170 | * Returns the proxy file from the HTTP request parameters. |
| 171 | * |
| 172 | * @codeCoverageIgnore |
| 173 | * |
| 174 | * @return string The sanitized file request parameter. |
| 175 | */ |
| 176 | protected function get_proxy_file() { |
| 177 | return filter_input( INPUT_GET, 'file', FILTER_SANITIZE_STRING ); |
| 178 | } |
| 179 | |
| 180 | /** |
| 181 | * Returns the plugin version from the HTTP request parameters. |
| 182 | * |
| 183 | * @codeCoverageIgnore |
| 184 | * |
| 185 | * @return string The sanitized plugin_version request parameter. |
| 186 | */ |
| 187 | protected function get_plugin_version() { |
| 188 | $plugin_version = filter_input( INPUT_GET, 'plugin_version', FILTER_SANITIZE_STRING ); |
| 189 | // Replace slashes to secure against requiring a file from another path. |
| 190 | $plugin_version = str_replace( [ '/', '\\' ], '_', $plugin_version ); |
| 191 | |
| 192 | return $plugin_version; |
| 193 | } |
| 194 | |
| 195 | /** |
| 196 | * Sets the HTTP header. |
| 197 | * |
| 198 | * This is a tiny helper function to enable better testing. |
| 199 | * |
| 200 | * @codeCoverageIgnore |
| 201 | * |
| 202 | * @param string $header The header to set. |
| 203 | * |
| 204 | * @return void |
| 205 | */ |
| 206 | protected function set_header( $header ) { |
| 207 | header( $header ); |
| 208 | } |
| 209 | } |
| 210 |