├── .codeclimate.yml ├── .gitignore ├── .travis.yml ├── admin ├── class-wp-rest-api-log-admin-list-table.php ├── class-wp-rest-api-log-admin.php ├── index.php └── partials │ ├── admin-help.php │ ├── entry-property-links.php │ ├── wp-rest-api-log-api-is-disabled.php │ └── wp-rest-api-log-view-entry.php ├── assets ├── screenshot-1.png └── screenshot-2.png ├── badges.md ├── bin ├── install-wp-core.sh └── run-php-tests.sh ├── composer.json ├── dist ├── css │ └── admin.css └── js │ └── admin.js ├── includes ├── class-wp-rest-api-log-activator.php ├── class-wp-rest-api-log-common.php ├── class-wp-rest-api-log-controller.php ├── class-wp-rest-api-log-db.php ├── class-wp-rest-api-log-delete-response.php ├── class-wp-rest-api-log-elasticpress.php ├── class-wp-rest-api-log-entry.php ├── class-wp-rest-api-log-filters.php ├── class-wp-rest-api-log-i18n.php ├── class-wp-rest-api-log-post-type.php ├── class-wp-rest-api-log-request-response-base.php ├── class-wp-rest-api-log-request.php ├── class-wp-rest-api-log-response-base.php ├── class-wp-rest-api-log-response.php ├── class-wp-rest-api-log-routes-response.php ├── class-wp-rest-api-log-taxonomies.php ├── class-wp-rest-api-log.php ├── index.php ├── settings │ ├── class-wp-rest-api-log-settings-base.php │ ├── class-wp-rest-api-log-settings-elasticpress.php │ ├── class-wp-rest-api-log-settings-general.php │ ├── class-wp-rest-api-log-settings-help.php │ ├── class-wp-rest-api-log-settings-routes.php │ └── class-wp-rest-api-log-settings.php └── wp-cli │ ├── class-wp-rest-api-log-wp-cli-log.php │ └── setup.php ├── index.php ├── languages └── wp-rest-api-log.pot ├── license.txt ├── package-lock.json ├── package.json ├── phpunit.xml.dist ├── readme.md ├── readme.txt ├── tests ├── bootstrap.php ├── test-common.php ├── test-filters.php ├── test-settings.php └── test-taxonomies.php ├── uninstall.php └── wp-rest-api-log.php /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | csslint: 4 | enabled: true 5 | duplication: 6 | enabled: true 7 | config: 8 | languages: 9 | - javascript 10 | - php 11 | eslint: 12 | enabled: true 13 | fixme: 14 | enabled: true 15 | phpmd: 16 | enabled: true 17 | checks: 18 | Controversial/CamelCaseClassName: 19 | enabled: false 20 | Controversial/CamelCaseParameterName: 21 | enabled: false 22 | Controversial/CamelCaseVariableName: 23 | enabled: false 24 | Controversial/CamelCaseMethodName: 25 | enabled: false 26 | Controversial/CamelCasePropertyName: 27 | enabled: false 28 | CleanCode/ElseExpression: 29 | enabled: false 30 | CleanCode/StaticAccess: 31 | enabled: false 32 | Naming/ShortVariable: 33 | enabled: false 34 | ratings: 35 | paths: 36 | - "**.css" 37 | - "**.js" 38 | - "**.php" 39 | exclude_paths: 40 | - tests/ 41 | - features/ 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components 2 | /node_modules 3 | /vendor 4 | composer.lock 5 | npm-debug.log 6 | /admin/css/*.css 7 | /admin/css/*.map 8 | /admin/js/*.js 9 | /release 10 | tmp 11 | .phpunit.result.cache 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.0 6 | 7 | # Declare which versions of WordPress to test against. 8 | # Also declare whether or not to test in Multisite. 9 | env: 10 | - WP_VERSION=master WP_MULTISITE=0 11 | - WP_VERSION=4.7 WP_MULTISITE=0 12 | - WP_VERSION=4.8 WP_MULTISITE=0 13 | - WP_VERSION=4.9 WP_MULTISITE=0 14 | - WP_VERSION=5.0 WP_MULTISITE=0 15 | - WP_VERSION=5.1 WP_MULTISITE=0 16 | - WP_VERSION=master WP_MULTISITE=1 17 | - WP_VERSION=4.7 WP_MULTISITE=1 18 | - WP_VERSION=4.8 WP_MULTISITE=1 19 | - WP_VERSION=4.9 WP_MULTISITE=1 20 | - WP_VERSION=5.0 WP_MULTISITE=1 21 | - WP_VERSION=5.1 WP_MULTISITE=1 22 | 23 | services: 24 | - mysql 25 | 26 | install: 27 | - composer install 28 | 29 | # Use this to prepare your build for testing. 30 | # e.g. copy database configurations, environment variables, etc. 31 | # Failures in this section will result in build status 'errored'. 32 | before_script: 33 | - pwd 34 | # Set up WordPress installation. 35 | - export WP_DEVELOP_DIR=/tmp/wordpress/ 36 | - mkdir -p $WP_DEVELOP_DIR 37 | # Use the Git mirror of WordPress. 38 | - git clone --depth=1 --branch="$WP_VERSION" git://develop.git.wordpress.org/ $WP_DEVELOP_DIR 39 | # Set up WordPress configuration. 40 | - cd $WP_DEVELOP_DIR 41 | - echo $WP_DEVELOP_DIR 42 | - cp wp-tests-config-sample.php wp-tests-config.php 43 | - sed -i "s/youremptytestdbnamehere/wordpress_test/" wp-tests-config.php 44 | - sed -i "s/yourusernamehere/root/" wp-tests-config.php 45 | - sed -i "s/yourpasswordhere//" wp-tests-config.php 46 | # Create WordPress database. 47 | - mysql -e 'CREATE DATABASE wordpress_test;' -uroot 48 | # Switch back to the plugin dir 49 | - cd $TRAVIS_BUILD_DIR 50 | -------------------------------------------------------------------------------- /admin/class-wp-rest-api-log-admin-list-table.php: -------------------------------------------------------------------------------- 1 | post_type ) { 35 | 36 | // turn off items 37 | unset( $actions['edit'] ); 38 | unset( $actions['inline hide-if-no-js'] ); 39 | 40 | wp_enqueue_script( 'wp-rest-api-log-admin' ); 41 | 42 | } 43 | 44 | return $actions; 45 | } 46 | 47 | 48 | public function custom_columns( $columns ) { 49 | 50 | unset( $columns['author'] ); 51 | $columns['method'] = 'Method'; 52 | $columns = array( 53 | 'cb' => '', 54 | 'date' => __( 'Date' ), 55 | 'method' => __( 'Method', 'wp-rest-api-log' ), 56 | 'title' => __( 'Title' ), 57 | 'status' => __( 'Status', 'wp-rest-api-log' ), 58 | 'elapsed' => __( 'Elapsed Time', 'wp-rest-api-log' ), 59 | 'length' => __( 'Response Length', 'wp-rest-api-log' ), 60 | 'ip-address' => __( 'IP Address', 'wp-rest-api-log' ), 61 | 'user' => __( 'User', 'wp-rest-api-log' ), 62 | ); 63 | 64 | 65 | return $columns; 66 | } 67 | 68 | 69 | public function custom_column( $column, $post_id ) { 70 | $entry = $this->get_entry( $post_id ); 71 | 72 | if ( ! empty( $entry ) ) { 73 | 74 | switch ( $column ) { 75 | case 'method': 76 | echo esc_html( $entry->method ); 77 | break; 78 | 79 | case 'status': 80 | echo esc_html( $entry->status ); 81 | break; 82 | 83 | case 'elapsed': 84 | echo esc_html( number_format( $entry->milliseconds ) . 'ms' ); 85 | break; 86 | 87 | case 'length': 88 | echo esc_html( number_format( strlen( $entry->response->body ) ) ); 89 | break; 90 | 91 | case 'user': 92 | echo esc_html( $entry->user ); 93 | break; 94 | 95 | case 'ip-address': 96 | $ip_address_display = apply_filters( WP_REST_API_Log_Common::PLUGIN_NAME . '-setting-get', 97 | 'general', 98 | 'ip-address-display', 99 | 'ip_address' 100 | ); 101 | 102 | if ( 'http_x_forwarded_for' === $ip_address_display ) { 103 | echo esc_html( $entry->http_x_forwarded_for ); 104 | } else { 105 | echo esc_html( $entry->ip_address ); 106 | } 107 | break; 108 | } 109 | 110 | } 111 | 112 | } 113 | 114 | /** 115 | * Adds additional dropdowns for filtering. 116 | * 117 | * @param string $post_type The post type. 118 | */ 119 | public function add_dropdowns( $post_type ) { 120 | if ( WP_REST_API_Log_Db::POST_TYPE === $post_type ) { 121 | foreach ( $this->get_dropdown_taxonomies() as $taxonomy ) { 122 | WP_REST_API_Log_Common::dropdown_terms( $taxonomy ); 123 | } 124 | } 125 | } 126 | 127 | /** 128 | * Gets a list of taxonomies used for admin list table dropdowns. 129 | * 130 | * @return array 131 | */ 132 | public function get_dropdown_taxonomies() { 133 | $taxonomies = [ 134 | WP_REST_API_Log_DB::TAXONOMY_METHOD, 135 | WP_REST_API_Log_DB::TAXONOMY_STATUS, 136 | WP_REST_API_Log_DB::TAXONOMY_SOURCE, 137 | ]; 138 | 139 | return apply_filters( WP_REST_API_Log_Common::PLUGIN_NAME . '-taxonomy-dropdowns', $taxonomies ); 140 | } 141 | 142 | private function get_entry( $post_id ) { 143 | if ( empty( $this->_post ) || $post_id !== $this->_post_id ) { 144 | $this->_post = new WP_REST_API_Log_Entry( $post_id ); 145 | $this->_post_id = $post_id; 146 | } 147 | return $this->_post; 148 | } 149 | 150 | /** 151 | * Removes the Edit option from bulk actions 152 | * 153 | * @param array $actions 154 | * @return array 155 | */ 156 | public function remove_edit_bulk_action( $actions ) { 157 | unset( $actions['edit'] ); 158 | return $actions; 159 | } 160 | 161 | /** 162 | * Adds taxonomy queries to the admin list table query. 163 | * 164 | * @param WP_Query $query The query. 165 | * @return void 166 | */ 167 | public function add_tax_queries( $query ) { 168 | 169 | if ( is_admin() && $query->is_main_query() ) { 170 | 171 | if ( function_exists( 'get_current_screen' ) ) { 172 | $screen = get_current_screen(); 173 | 174 | if ( 'edit-' . WP_REST_API_Log_Db::POST_TYPE !== $screen->id ) { 175 | return; 176 | } 177 | } 178 | 179 | $tax_query = [ 180 | 'relation' => 'AND', 181 | ]; 182 | 183 | foreach ( $this->get_dropdown_taxonomies() as $taxonomy ) { 184 | 185 | $get = filter_var_array( 186 | $_GET, 187 | [ 188 | $taxonomy => WP_REST_API_Log_Common::filter_strip_all_tags(), 189 | ] 190 | ); 191 | 192 | if ( ! empty( $get[ $taxonomy ] ) ) { 193 | $tax_query[] = [ 194 | 'taxonomy' => $taxonomy, 195 | 'field' => 'slug', 196 | 'terms' => $get[ $taxonomy ], 197 | ]; 198 | } 199 | } 200 | 201 | if ( count( $tax_query ) > 1 ) { 202 | $query->set( 'tax_query', $tax_query ); 203 | } 204 | } 205 | } 206 | } 207 | } -------------------------------------------------------------------------------- /admin/class-wp-rest-api-log-admin.php: -------------------------------------------------------------------------------- 1 | wp_create_nonce( 'wp_rest' ), 94 | 'endpoints' => array( 95 | 'purge_entries' => rest_url( WP_REST_API_Log_Common::PLUGIN_NAME . '/batch-purge-all' ), 96 | ), 97 | ); 98 | 99 | // Ensure admin URLs in SSL get pointed to SSL on the frontend. 100 | if ( is_ssl() ) { 101 | foreach ( $data['endpoints'] as &$endpoint ) { 102 | $endpoint = set_url_scheme( $endpoint, 'https' ); 103 | } 104 | } 105 | 106 | wp_localize_script( 'wp-rest-api-log-admin', 'WP_REST_API_Log_Admin_Data', $data ); 107 | } 108 | 109 | /** 110 | * Displays the log entry template 111 | * 112 | * @return void 113 | */ 114 | static public function display_log_entry() { 115 | 116 | include_once apply_filters( 'wp-rest-api-log-admin-view-entry-template', WP_REST_API_LOG_PATH . 'admin/partials/wp-rest-api-log-view-entry.php' ); 117 | 118 | self::enqueue_scripts(); 119 | } 120 | 121 | /** 122 | * Creates a permalink for a log enty. 123 | * 124 | * @param string $permalink Default permalink. 125 | * @param int|WP_Post $post Post ID or object. 126 | * @return string 127 | */ 128 | static public function entry_permalink( $permalink, $post ) { 129 | $post = get_post( $post ); 130 | if ( WP_REST_API_Log_DB::POST_TYPE === $post->post_type ) { 131 | $permalink = add_query_arg( array( 132 | 'page' => WP_REST_API_Log_Common::PLUGIN_NAME . '-view-entry', 133 | 'id' => urlencode( $post->ID ), 134 | ), admin_url( 'tools.php' ) ); 135 | } 136 | return $permalink; 137 | } 138 | 139 | 140 | private function plugin_name() { 141 | return WP_REST_API_Log_Common::PLUGIN_NAME . '-admin'; 142 | } 143 | 144 | 145 | /** 146 | * Removes the wp-rest-api-log post type from the link query args 147 | * 148 | * @param array $query Query args. 149 | * @return array 150 | */ 151 | static public function wp_link_query_args( $query ) { 152 | 153 | if ( isset( $query['post_type'] ) && is_array( $query['post_type'] ) ) { 154 | for ( $i = count( $query['post_type'] ) - 1; $i >= 0; $i-- ) { 155 | if ( WP_REST_API_Log_DB::POST_TYPE === $query['post_type'][ $i ] ) { 156 | unset( $query['post_type'][ $i ] ); 157 | break; 158 | } 159 | } 160 | } 161 | 162 | return $query; 163 | } 164 | 165 | 166 | /** 167 | * Adjusts the title tag when viewing a log entry 168 | * 169 | * @param string $admin_title 170 | * @param string $title 171 | * @return string 172 | */ 173 | static public function admin_title( $admin_title, $title ) { 174 | $screen = get_current_screen(); 175 | if ( ! empty( $screen ) && 'tools_page_wp-rest-api-log-view-entry' === $screen->id ) { 176 | $admin_title = __( 'REST API Log Entry', 'wp-rest-api-log' ) . $admin_title; 177 | } 178 | return $admin_title; 179 | } 180 | 181 | 182 | /** 183 | * Adds capabilities for the custom post type to the administrator role 184 | * 185 | * @param array $allcaps All of the user's capabilities. 186 | * @param array $caps The requested capabilities. 187 | * @param array $args Requested cap, user ID, and object ID. 188 | */ 189 | static public function add_admin_caps( $allcaps, $caps, $args ) { 190 | 191 | // Get the user 192 | $user = get_userdata( $args[1] ); 193 | 194 | // Give the administrator role access to the custom post type 195 | if ( ! empty( $user ) && ! empty( $user->roles ) && in_array( 'administrator', $user->roles ) ) { 196 | 197 | $post_type = get_post_type_object( WP_REST_API_Log_DB::POST_TYPE ); 198 | 199 | if ( ! empty( $post_type ) ) { 200 | $allcaps[ $post_type->cap->edit_posts ] = true; 201 | $allcaps[ $post_type->cap->edit_others_posts ] = true; 202 | $allcaps[ $post_type->cap->delete_posts ] = true; 203 | $allcaps[ $post_type->cap->read_post ] = true; 204 | $allcaps[ $post_type->cap->edit_post ] = true; 205 | $allcaps[ $post_type->cap->delete_post ] = true; 206 | } 207 | 208 | } 209 | 210 | return $allcaps; 211 | } 212 | 213 | /** 214 | * Adds additional links to the row on the plugins page. 215 | * 216 | * @param array $actions An array of plugin action links. 217 | * @param string $plugin_file Path to the plugin file relative to the plugins directory. 218 | * @param array $plugin_data An array of plugin data. 219 | * @param string $context The plugin context. Defaults are 'All', 'Active', 220 | * 'Inactive', 'Recently Activated', 'Upgrade', 221 | * 'Must-Use', 'Drop-ins', 'Search'. 222 | */ 223 | static public function plugin_action_links( $actions, $plugin_file, $plugin_data, $context ) { 224 | 225 | if ( is_plugin_active( $plugin_file ) && current_user_can( 'manage_options' ) ) { 226 | 227 | // Build the URL for the settings page. 228 | $url = add_query_arg( 229 | 'page', 230 | rawurlencode( WP_REST_API_Log_Settings::$settings_page ), 231 | admin_url( 'admin.php' ) 232 | ); 233 | 234 | // Add the anchor tag to the list of plugin links. 235 | $new_actions = array( 236 | 'settings' => sprintf( '%2$s', 237 | esc_url( $url ), 238 | esc_html__( 'Settings' ) 239 | ), 240 | 'log' => sprintf( '%2$s', 241 | esc_url( admin_url( 'edit.php?post_type=wp-rest-api-log' ) ), 242 | esc_html__( 'Log' ) 243 | ) 244 | ); 245 | 246 | $actions = array_merge( $actions, $new_actions ); 247 | } 248 | 249 | return $actions; 250 | } 251 | 252 | /** 253 | * Enqueues scripts based on current screent. 254 | * 255 | * @return void 256 | */ 257 | static public function maybe_enqueue_scripts() { 258 | $screen = get_current_screen(); 259 | 260 | $screen_ids = array( 261 | 'settings_page_wp-rest-api-log-settings', 262 | 'edit-wp-rest-api-log', 263 | ); 264 | 265 | if ( in_array( $screen->id, $screen_ids ) ) { 266 | self::enqueue_scripts(); 267 | } 268 | } 269 | 270 | /** 271 | * Enqueues admin scripts and styles. 272 | * 273 | * @return void 274 | */ 275 | static public function enqueue_scripts() { 276 | wp_enqueue_script( 'wp-rest-api-log-admin-highlight-js' ); 277 | wp_enqueue_style( 'wp-rest-api-log-admin-highlight-js' ); 278 | 279 | wp_enqueue_script( 'wp-rest-api-log-admin-clipboard-js' ); 280 | 281 | wp_enqueue_script( 'wp-rest-api-log-admin' ); 282 | wp_enqueue_style( 'wp-rest-api-log-admin' ); 283 | } 284 | 285 | /** 286 | * Displays the property links (Download, Copy) for a log entry. 287 | * 288 | * @param array $args 289 | * @return void 290 | */ 291 | static public function display_entry_property_links( $args ) { 292 | 293 | $args = wp_parse_args( $args, array( 294 | 'rr' => '', 295 | 'property' => '', 296 | 'download_urls' => array(), 297 | 'entry' => null, 298 | ) 299 | ); 300 | 301 | include apply_filters( 'wp-rest-api-log-admin-view-entry-links-template', WP_REST_API_LOG_PATH . 'admin/partials/entry-property-links.php' ); 302 | } 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /admin/index.php: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 |

8 |

9 | : pete@petenelson.com
10 | : @CodeGeekATX
11 | : https://github.com/petenelson/wp-rest-api-log
12 |

13 | 14 |
15 | -------------------------------------------------------------------------------- /admin/partials/entry-property-links.php: -------------------------------------------------------------------------------- 1 | 5 |

6 | | 7 | 8 |

9 | -------------------------------------------------------------------------------- /admin/partials/wp-rest-api-log-api-is-disabled.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |

4 | 5 |
6 |

7 |
8 | 9 |
-------------------------------------------------------------------------------- /admin/partials/wp-rest-api-log-view-entry.php: -------------------------------------------------------------------------------- 1 | cap->read_post, $id ) ) { 11 | wp_die( 12 | '

' . esc_html__( 'Cheatin’ uh?' ) . '

' . 13 | '

' . esc_html__( 'You are not allowed to read posts in this post type.', 'wp-rest-api-log' ) . '

', 14 | 403 15 | ); 16 | } 17 | 18 | if ( ! empty( $id ) ) { 19 | $entry = new WP_REST_API_Log_Entry( $id ); 20 | } 21 | 22 | if ( empty( $entry->ID ) ) { 23 | wp_die( 24 | '

' . esc_html_e( 'Invalid WP REST API Log Entry ID', 'wp-rest-api-log' ) . '

', 25 | 404 26 | ); 27 | } 28 | 29 | // HTML encode some of the values for display in the admin partial. 30 | $entry = WP_REST_API_Log_API_Request_Response_Base::esc_html_fields( $entry ); 31 | 32 | $entry = apply_filters( 'wp-rest-api-log-display-entry', $entry ); 33 | 34 | $body_content = ! empty( $entry->request->body ) ? $entry->request->body : ''; 35 | 36 | if ( 'ElasticPress' === $entry->source ) { 37 | // These request bodies are base64 encoded JSON. 38 | if ( ! empty( $body_content ) ) { 39 | $body_object = json_decode( base64_decode( $body_content ) ); 40 | $body_content = ''; 41 | } 42 | } 43 | 44 | $json_display_options = array( 45 | 'request' => array( 46 | 'headers' => ! empty( $entry->request->headers ) ? JSON_PRETTY_PRINT : 0, 47 | 'query_params' => ! empty( $entry->request->query_params ) ? JSON_PRETTY_PRINT : 0, 48 | 'body_params' => ! empty( $entry->request->body_params ) ? JSON_PRETTY_PRINT : 0, 49 | ), 50 | 'response' => array( 51 | 'headers' => ! empty( $entry->response->headers ) ? JSON_PRETTY_PRINT : 0, 52 | ), 53 | ); 54 | 55 | $json_display_options = apply_filters( 'wp-rest-api-log-json-display-options', $json_display_options, $entry ); 56 | 57 | $classes = apply_filters( 'wp-rest-api-log-entry-display-classes', array( 'wrap', 'wp-rest-api-log-entry' ), $entry ); 58 | 59 | $download_urls = WP_REST_API_Log_Controller::get_download_urls( $entry ); 60 | 61 | ?> 62 |
63 | 64 |

route ); ?>

65 | 66 |
67 | 68 | 69 | 70 |
71 |

72 | 73 |
74 |
    75 |
  • : time ); ?>
  • 76 |
  • : source ); ?>
  • 77 |
  • : method ); ?>
  • 78 |
  • : status ); ?>
  • 79 |
  • : milliseconds ) ); ?>ms
  • 80 |
  • : response->body ) ) ); ?>
  • 81 |
  • : user ); ?>
  • 82 |
  • : ip_address ); ?>
  • 83 | http_x_forwarded_for ) ) : ?> 84 |
  • : http_x_forwarded_for ); ?>
  • 85 | 86 |
87 |
88 |
89 | 90 | 91 | 92 |
93 |

94 |
95 | 'request', 97 | 'property' => 'headers', 98 | 'download_urls' => $download_urls, 99 | 'entry' => $entry, 100 | ) 101 | ); ?> 102 |
request->headers, $json_display_options['request']['headers'] ) ); ?>
103 |
104 |
105 | 106 | 107 | 108 |
109 |

110 |
111 | 'request', 113 | 'property' => 'query_params', 114 | 'download_urls' => $download_urls, 115 | 'entry' => $entry, 116 | ) 117 | ); ?> 118 |
request->query_params, $json_display_options['request']['query_params'] ) ); ?>
119 |
120 |
121 | 122 | 123 | 124 |
125 |

126 |
127 | 'request', 129 | 'property' => 'body_params', 130 | 'download_urls' => $download_urls, 131 | 'entry' => $entry, 132 | ) 133 | ); ?> 134 |
request->body_params,
137 | 						$json_display_options['request']['body_params']
138 | 					) );
139 | 					?>
140 |
141 |
142 | 143 | 144 | 145 | 146 | 147 |
148 |

149 | 150 |
151 | 'request', 153 | 'property' => 'body', 154 | 'download_urls' => $download_urls, 155 | 'entry' => $entry, 156 | ) 157 | ); ?> 158 |
159 |
160 | 161 | 162 |
163 | 'request', 165 | 'property' => 'body', 166 | 'download_urls' => $download_urls, 167 | 'entry' => $entry, 168 | ) 169 | ); ?> 170 |
171 |
172 | 173 |
174 | 175 | 176 | 177 | 178 |
179 |

180 |
181 | 'response', 183 | 'property' => 'headers', 184 | 'download_urls' => $download_urls, 185 | 'entry' => $entry, 186 | ) 187 | ); ?> 188 |
response->headers, $json_display_options['response']['headers'] ) ); ?>
189 |
190 |
191 | 192 | 193 | 194 |
195 |

196 |
197 | 'response', 199 | 'property' => 'body', 200 | 'download_urls' => $download_urls, 201 | 'entry' => $entry, 202 | ) 203 | ); ?> 204 |
response->body ); ?>
205 |
206 |
207 | 208 | 209 | 210 |
211 | 212 |
213 | 0 ) { 24 | // Hook for copying to clipboard. 25 | var clipboard = new ClipboardJS( '.wp-rest-api-log-entry-copy-property' ); 26 | } 27 | }, 28 | 29 | highlightBlocks: function() { 30 | 31 | if ( this.entry_element.length > 0 ) { 32 | this.entry_element.find( 'code' ).each( function( i, block ) { 33 | hljs.highlightBlock( block ); 34 | } ); 35 | } 36 | 37 | }, 38 | 39 | adjustPostsListsTable: function() { 40 | if ( $( 'body' ).hasClass( 'post-type-wp-rest-api-log') ) { 41 | $( '.wp-list-table' ).removeClass( 'fixed' ); 42 | } 43 | }, 44 | 45 | migrateLegacyDB: function() { 46 | $.get( ajaxurl, { "action":WP_REST_API_Log_Migrate_Data.action, "nonce":WP_REST_API_Log_Migrate_Data.nonce } ); 47 | }, 48 | 49 | batchPurgeLog: function() { 50 | 51 | $.ajax( { 52 | url: WP_REST_API_Log_Admin_Data.endpoints.purge_entries, 53 | method: 'DELETE', 54 | beforeSend: function ( xhr ) { 55 | xhr.setRequestHeader( 'X-WP-Nonce', WP_REST_API_Log_Admin_Data.nonce ); 56 | } 57 | } ).done( function( response ) { 58 | 59 | if ( response && response.entries_left_formatted ) { 60 | $( '.wp-rest-api-log-purge-all-status' ).text( response.entries_left_formatted ); 61 | if ( response.entries_left > 0 ) { 62 | WP_REST_API_Log.batchPurgeLog(); 63 | } 64 | } 65 | } ); 66 | }, 67 | 68 | purgeLog: function( e ) { 69 | e.preventDefault(); 70 | 71 | // Turn off the button. 72 | $( '.wp-rest-api-log-purge-all' ).addClass( 'hidden' ); 73 | 74 | // Turn on the spinner. 75 | $( '.wp-rest-api-log-purge-all-spinner' ).removeClass( 'hidden' ).addClass( 'is-active' ); 76 | $( '.wp-rest-api-log-purge-all-status' ).removeClass( 'hidden' ).addClass( 'is-active' ); 77 | 78 | WP_REST_API_Log.batchPurgeLog(); 79 | } 80 | }; 81 | 82 | $( document ).ready( function() { 83 | WP_REST_API_Log.init(); 84 | } ); 85 | 86 | })( jQuery ); 87 | -------------------------------------------------------------------------------- /includes/class-wp-rest-api-log-activator.php: -------------------------------------------------------------------------------- 1 | '', 53 | 'hide_empty' => false, 54 | 'all_items' => '', 55 | ] 56 | ); 57 | 58 | // Default the selected slug to the query string if nothing was passed. 59 | $selected_slug = ! empty( $args['selected'] ) ? $args['selected'] : $get_taxonomy; 60 | $all_items = ! empty( $args['all_label'] ) ? $args['all_label'] : $tax_obj->labels->all_items; 61 | 62 | $term_query = new \WP_Term_Query( 63 | [ 64 | 'taxonomy' => $taxonomy, 65 | 'orderby' => 'count', 66 | 'order' => 'DESC', 67 | ] 68 | ); 69 | 70 | ?> 71 | 74 | 75 | 84 | FILTER_CALLBACK, 95 | 'options' => '\wp_strip_all_tags', 96 | ]; 97 | } 98 | 99 | /** 100 | * Gets a $_GET querystring parameter. 101 | * 102 | * @return string 103 | */ 104 | static public function get_string_query_param( $param ) { 105 | 106 | $get = filter_var_array( 107 | $_GET, 108 | [ 109 | $param => self::filter_strip_all_tags(), 110 | ] 111 | ); 112 | 113 | return $get[ $param ]; 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /includes/class-wp-rest-api-log-controller.php: -------------------------------------------------------------------------------- 1 | array( WP_REST_Server::READABLE ), 20 | 'callback' => array( __CLASS__, 'get_items' ), 21 | 'permission_callback' => array( __CLASS__, 'get_permissions_check' ), 22 | 'args' => array( 23 | 'from' => array( 24 | 'default' => '', 25 | ), 26 | 'to' => array( 27 | 'default' => current_time( 'mysql' ), 28 | ), 29 | // 'fields' => array( 30 | // 'default' => 'basic', 31 | // ), 32 | 'route' => array( 33 | 'default' => '', 34 | ), 35 | 'route-match-type' => array( 36 | 'sanitize_callback' => 'sanitize_key', 37 | 'default' => 'exact', 38 | ), 39 | // 'id' => array( 40 | // 'sanitize_callback' => 'absint', 41 | // 'default' => 0, 42 | // ), 43 | 'after-id' => array( 44 | 'sanitize_callback' => 'absint', 45 | 'default' => 0, 46 | ), 47 | 'before-id' => array( 48 | 'sanitize_callback' => 'absint', 49 | 'default' => 0, 50 | ), 51 | 'page' => array( 52 | 'sanitize_callback' => 'absint', 53 | 'default' => 1, 54 | ), 55 | 'records-per-page' => array( 56 | 'sanitize_callback' => 'absint', 57 | 'default' => 20, 58 | ), 59 | // 'response_type' => array( 60 | // 'default' => 'json', 61 | // ), 62 | // 'params' => array( 63 | // ), 64 | ), 65 | ) ); 66 | 67 | 68 | register_rest_route( WP_REST_API_Log_Common::PLUGIN_NAME, '/entry/(?P[\d]+)', array( 69 | 'methods' => array( WP_REST_Server::READABLE ), 70 | 'callback' => array( __CLASS__, 'get_item' ), 71 | 'permission_callback' => array( __CLASS__, 'get_permissions_check' ), 72 | 'args' => array( 73 | 'id' => array( 74 | 'sanitize_callback' => 'absint', 75 | 'validate_callback' => array( __CLASS__, 'validate_entry_id' ), 76 | 'default' => 0, 77 | ), 78 | ), 79 | ) ); 80 | 81 | register_rest_route( WP_REST_API_Log_Common::PLUGIN_NAME, '/entry', array( 82 | 'methods' => array( WP_REST_Server::DELETABLE ), 83 | 'callback' => array( __CLASS__, 'delete_items' ), 84 | 'permission_callback' => array( __CLASS__, 'delete_items_permissions_check' ), 85 | 'args' => array( // TODO refator delete, this won't work with $_REQUESTs 86 | 'older-than-seconds' => array( 87 | 'sanitize_callback' => 'absint', // TODO add validate callback 88 | 'default' => DAY_IN_SECONDS * 30, 89 | ), 90 | ), 91 | ) ); 92 | 93 | // Route to delete all log entries. 94 | register_rest_route( WP_REST_API_Log_Common::PLUGIN_NAME, '/entries', array( 95 | 'methods' => array( WP_REST_Server::DELETABLE ), 96 | 'callback' => array( __CLASS__, 'purge_log' ), 97 | 'permission_callback' => array( __CLASS__, 'delete_items_permissions_check' ), 98 | ) ); 99 | 100 | // Route to delete a batch of log entries. 101 | register_rest_route( WP_REST_API_Log_Common::PLUGIN_NAME, '/batch-purge-all', array( 102 | 'methods' => array( WP_REST_Server::DELETABLE ), 103 | 'callback' => array( __CLASS__, 'batch_purge_log' ), 104 | 'permission_callback' => array( __CLASS__, 'delete_items_permissions_check' ), 105 | ) ); 106 | 107 | register_rest_route( WP_REST_API_Log_Common::PLUGIN_NAME, '/routes', array( 108 | 'methods' => array( WP_REST_Server::READABLE ), 109 | 'callback' => array( __CLASS__, 'get_routes' ), 110 | 'permission_callback' => array( __CLASS__, 'get_permissions_check' ), 111 | ) ); 112 | } 113 | 114 | /** 115 | * Returns a list of routes available for download. 116 | * 117 | * @return array 118 | */ 119 | static public function get_download_routes() { 120 | 121 | $routes = array( 122 | 'request' => array( 123 | 'body_params', 124 | 'query_params', 125 | 'body', 126 | 'headers', 127 | ), 128 | 'response' => array( 129 | 'body', 130 | 'headers', 131 | ) 132 | ); 133 | 134 | return apply_filters( 'wp-rest-api-log-download-routes', $routes ); 135 | } 136 | 137 | /** 138 | * Gets REST API endpoint URLs to download entry properties. 139 | * 140 | * @param object $entry REST API Log Entry. 141 | * @return array 142 | */ 143 | static public function get_download_urls( $entry ) { 144 | 145 | $download_routes = self::get_download_routes(); 146 | $download_urls = array(); 147 | 148 | foreach( $download_routes as $rr => $properties ) { 149 | $download_urls[ $rr ] = array(); 150 | 151 | foreach( $properties as $property ) { 152 | $url = rest_url( "/wp-rest-api-log/entry/{$entry->ID}/{$rr}/{$property}/download" ); 153 | if ( is_ssl() ) { 154 | $url = set_url_scheme( $url, 'https' ); 155 | } 156 | 157 | // Create a hash for this request. 158 | $hash = wp_hash( wp_nonce_tick() . "wp-rest-api-log-download-{$rr}-{$property}" ); 159 | 160 | // Add the hash to the URL. 161 | $url = add_query_arg( 'hash', $hash, $url ); 162 | 163 | $download_urls[ $rr ][ $property ] = $url; 164 | } 165 | } 166 | 167 | return apply_filters( 'wp-rest-api-log-download-urls', $download_urls, $entry ); 168 | } 169 | 170 | /** 171 | * Registers the routes to download portions of an entry. 172 | * 173 | * @return void 174 | */ 175 | static public function register_download_routes() { 176 | 177 | foreach ( self::get_download_routes() as $request_response => $properties ) { 178 | foreach( $properties as $property ) { 179 | 180 | register_rest_route( WP_REST_API_Log_Common::PLUGIN_NAME, "/entry/(?P[\d]+)/(?P{$request_response})/(?P{$property})/download", array( 181 | 'methods' => array( WP_REST_Server::READABLE ), 182 | 'callback' => array( __CLASS__, 'download_json' ), 183 | 'permission_callback' => array( __CLASS__, 'download_permissions_check' ), 184 | 'args' => array( 185 | 'rr' => array( 186 | 'required' => true, 187 | 'sanitize_callback' => 'sanitize_text_field', 188 | ), 189 | 'property' => array( 190 | 'required' => true, 191 | 'sanitize_callback' => 'sanitize_text_field', 192 | ), 193 | 'hash' => array( 194 | 'required' => true, 195 | 'sanitize_callback' => 'sanitize_text_field', 196 | ), 197 | 'id' => array( 198 | 'required' => true, 199 | 'sanitize_callback' => 'absint', 200 | 'validate_callback' => array( __CLASS__, 'validate_entry_id' ), 201 | ), 202 | ), 203 | ) ); 204 | } 205 | } 206 | } 207 | 208 | static public function get_items( WP_REST_Request $request ) { 209 | 210 | $args = array( 211 | 'id' => $request['id'], 212 | 'page' => $request['page'], 213 | 'records_per_page' => $request['records-per-page'], 214 | 'after_id' => $request['after-id'], 215 | 'before_id' => $request['before-id'], 216 | 'from' => $request['from'], 217 | 'to' => $request['to'], 218 | 'method' => $request['method'], 219 | 'status' => $request['status'], 220 | 'route' => $request['route'], 221 | 'route_match_type' => $request['route-match-type'], 222 | 'params' => $request['params'], 223 | ); 224 | 225 | $db = new WP_REST_API_Log_DB(); 226 | $posts = $db->search( $args ); 227 | 228 | return rest_ensure_response( WP_REST_API_Log_Entry::from_posts( $posts ) ); 229 | } 230 | 231 | static public function get_item( WP_REST_Request $request ) { 232 | return rest_ensure_response( self::get_entry( $request['id'] ) ); 233 | } 234 | 235 | static public function invalid_entry_id_error( $id ) { 236 | return new WP_Error( 'invalid_entry_id', sprintf( __( 'Invalid REST API Log ID %d.', 'wp-rest-api-log' ), $id ), array( 'status' => 404 ) ); 237 | } 238 | 239 | static public function get_permissions_check() { 240 | return apply_filters( WP_REST_API_Log_Common::PLUGIN_NAME . '-can-view-entries', current_user_can( 'read_' . WP_REST_API_Log_DB::POST_TYPE ) ); 241 | } 242 | 243 | static public function download_permissions_check( WP_REST_Request $request ) { 244 | 245 | $rr = ! empty( $request['rr'] ) ? sanitize_text_field( $request['rr'] ) : ''; 246 | $property = ! empty( $request['property'] ) ? sanitize_text_field( $request['property'] ) : ''; 247 | $hash = ! empty( $request['hash'] ) ? sanitize_text_field( $request['hash'] ) : ''; 248 | 249 | if ( ! empty( $rr ) && ! empty( $property ) && ! empty( $hash ) ) { 250 | return $hash === wp_hash( wp_nonce_tick() . "wp-rest-api-log-download-{$rr}-{$property}" ); 251 | } else { 252 | return false; 253 | } 254 | } 255 | 256 | static public function delete_items_permissions_check() { 257 | return apply_filters( WP_REST_API_Log_Common::PLUGIN_NAME . '-can-delete-entries', current_user_can( 'delete_' . WP_REST_API_Log_DB::POST_TYPE ) ); 258 | } 259 | 260 | static public function validate_entry_id( $id ) { 261 | if ( $id < 1 ) { 262 | return invalid_entry_id_error( $id ); 263 | } else { 264 | 265 | // Verify that the entry exists. 266 | $entry = self::get_entry( $id ); 267 | 268 | return ! empty( $entry ) ? true : self::invalid_entry_id_error( $id ); 269 | } 270 | } 271 | 272 | static public function get_entry( $id ) { 273 | 274 | $post = get_post( $id ); 275 | 276 | if ( ! empty( $post ) && WP_REST_API_Log_DB::POST_TYPE === $post->post_type ) { 277 | return new WP_REST_API_Log_Entry( $post ); 278 | } else { 279 | return false; 280 | } 281 | } 282 | 283 | static public function get_routes( WP_REST_Request $request ) { 284 | 285 | global $wpdb; 286 | 287 | $query = $wpdb->prepare( "select distinct post_title from {$wpdb->posts} where post_type = %s and post_title is not null order by post_type", 288 | WP_REST_API_Log_DB::POST_TYPE ); 289 | 290 | $routes = $wpdb->get_col( $query ); 291 | 292 | return rest_ensure_response( $routes ); 293 | 294 | } 295 | 296 | 297 | static public function delete_items( WP_REST_Request $request ) { 298 | // TODO refactor 299 | $args = array( 300 | 'older_than_seconds' => $request['older-than-seconds'], 301 | ); 302 | 303 | $db = new WP_REST_API_Log_DB(); 304 | return rest_ensure_response( new WP_REST_API_Log_Delete_Response( $db->delete( $args ) ) ); 305 | } 306 | 307 | /** 308 | * Handler to purge all log entries. 309 | * 310 | * @return WP_REST_Response 311 | */ 312 | static public function purge_log() { 313 | WP_REST_API_Log_DB::purge_all_log_entries(); 314 | return rest_ensure_response( array( 'success' => true ) ); 315 | } 316 | 317 | /** 318 | * Handler to purge a batch of log entries. 319 | * 320 | * @return WP_REST_Response 321 | */ 322 | public static function batch_purge_log() { 323 | 324 | // Don't log this request. 325 | add_filter( WP_REST_API_Log_Common::PLUGIN_NAME . '-bypass-insert', '__return_true' ); 326 | 327 | $query_args = array( 328 | 'update_post_term_cache' => false, 329 | 'update_post_meta_cache' => false, 330 | 'post_type' => WP_REST_API_Log_DB::POST_TYPE, 331 | 'fields' => 'ids', 332 | 'posts_per_page' => 50, 333 | 'orderby' => 'date', 334 | 'order' => 'ASC', 335 | ); 336 | 337 | $query_args = apply_filters( 'wp-rest-api-log-batch-purge-query-args', $query_args ); 338 | 339 | $query = new WP_Query( $query_args ); 340 | 341 | // Turn off term counting. 342 | wp_defer_term_counting( true ); 343 | 344 | // Delete this batch of log entries. 345 | foreach ( $query->posts as $post_id ) { 346 | wp_delete_post( $post_id, true ); 347 | } 348 | 349 | wp_defer_term_counting( false ); 350 | 351 | // Run this again to get the total count of items left. 352 | $query_args['posts_per_page'] = 1; 353 | $query = new WP_Query( $query_args ); 354 | 355 | $response = array( 356 | 'entries_left' => $query->found_posts, 357 | 'entries_left_formatted' => sprintf( __( '%s entries remaining...' ), number_format( $query->found_posts ) ), 358 | ); 359 | 360 | return rest_ensure_response( $response ); 361 | } 362 | 363 | /** 364 | * Handler for setting up the filter to allow downloading of files. 365 | * 366 | * @param WP_REST_Request $request REST request. 367 | * @return WP_REST_Response 368 | */ 369 | static public function download_json( WP_REST_Request $request ) { 370 | 371 | $entry = self::get_entry( $request['id'] ); 372 | 373 | add_filter( 'rest_pre_serve_request', array( __CLASS__, 'download_json_pre_serve_request' ), 10, 4 ); 374 | 375 | return rest_ensure_response( 376 | array( 377 | 'wp-rest-api-log-download' => true, 378 | 'entry' => $entry 379 | ) 380 | ); 381 | } 382 | 383 | /** 384 | * Filter hook to download entry properties as a file. 385 | * 386 | * @param bool $served Whether the request has already been served. 387 | * @param WP_HTTP_ResponseInterface $response Result to send to the client. Usually a WP_REST_Response. 388 | * @param WP_REST_Request $request Request used to generate the response. 389 | * @param WP_REST_Server $server Server instance. 390 | * @return bool 391 | */ 392 | static public function download_json_pre_serve_request( $served, $response, $request, $server ) { 393 | 394 | $data = $server->response_to_data( $response, false ); 395 | 396 | // Is this a download request? 397 | if ( is_array( $data ) && ! empty( $data['wp-rest-api-log-download'] ) && ! empty( $data['entry'] ) ) { 398 | 399 | $entry = $data['entry']; 400 | 401 | // Request or response. 402 | $rr = $request['rr']; 403 | 404 | // Property. 405 | $property = $request['property']; 406 | 407 | // Get the property value. 408 | $value = $entry->{$rr}->{$property}; 409 | 410 | // Default the file extension to json. 411 | $ext = 'json'; 412 | 413 | // Determine what we're going to send to the browser. 414 | if ( is_object( $value ) || is_array( $value ) ) { 415 | $value = json_encode( $value, JSON_PRETTY_PRINT ); 416 | } else { 417 | 418 | // See if this is a JSON field. 419 | $obj = json_decode( $value ); 420 | if ( null === $obj ) { 421 | 422 | // Might still be a JSON string though. 423 | $check_json = trim( $value ); 424 | $is_json = false; 425 | if ( ! empty( $check_json ) ) { 426 | $is_json = '{' === substr( $check_json, 0, 1 ) 427 | && '}' === substr( $check_json, strlen( $check_json ) - 1 ); 428 | } 429 | 430 | if ( ! $is_json ) { 431 | header( 'Content-Type: text/plain' ); 432 | $ext = "txt"; 433 | } 434 | } 435 | } 436 | 437 | // Create a filename for the download. 438 | $filename = sanitize_file_name( "{$entry->route}-{$rr}-{$property}-{$entry->ID}.{$ext}" ); 439 | 440 | // Allow filename filtering. 441 | $filename = apply_filters( 'wp-rest-api-log-download-filename', $filename, $entry ); 442 | 443 | // Set the content disposition for a download. 444 | header( 'Content-Disposition: attachment; filename=' . $filename ); 445 | 446 | // Output the field value. 447 | echo $value; 448 | 449 | // Tell the REST API that we handled this ourselves. 450 | $served = true; 451 | } 452 | 453 | return $served; 454 | } 455 | 456 | } 457 | 458 | } 459 | -------------------------------------------------------------------------------- /includes/class-wp-rest-api-log-db.php: -------------------------------------------------------------------------------- 1 | $strip_all_tags, 54 | 'HTTP_X_FORWARDED_FOR' => $strip_all_tags, 55 | 'REQUEST_METHOD' => $strip_all_tags, 56 | ] 57 | ); 58 | 59 | $args = wp_parse_args( $args, array( 60 | 'time' => current_time( 'mysql' ), 61 | 'ip_address' => $server[ 'REMOTE_ADDR' ], 62 | 'user' => $current_user->user_login, 63 | 'http_x_forwarded_for' => $server[ 'HTTP_X_FORWARDED_FOR' ], 64 | 'route' => '', 65 | 'source' => 'WP REST API', 66 | 'method' => $server[ 'REQUEST_METHOD' ], 67 | 'status' => 200, 68 | 'request' => array( 69 | 'body' => '', 70 | ), 71 | 'response' => array( 72 | 'body' => '', 73 | ), 74 | 'milliseconds' => 0, 75 | 76 | // This can be a K/V array of additional post meta to store. 77 | 'post_meta' => array(), 78 | 79 | ) 80 | ); 81 | 82 | if ( empty( $args['milliseconds'] ) ) { 83 | global $wp_rest_api_log_start; 84 | $now = WP_REST_API_Log_Common::current_milliseconds(); 85 | $args['milliseconds'] = absint( $now - $wp_rest_api_log_start ); 86 | } 87 | 88 | // Setup the post content with JSON from the response. 89 | $post_content = wp_json_encode( $args['response']['body'], JSON_PRETTY_PRINT ); 90 | 91 | // Replace \n with PHP_EOL. 92 | $post_content = str_replace( '\n', PHP_EOL, $post_content ); 93 | $args['request']['body'] = str_replace( '\n', PHP_EOL, $args['request']['body'] ); 94 | 95 | // Allow filtering. 96 | $args = apply_filters( self::plugin_name() . '-pre-insert', $args ); 97 | 98 | $new_post = array( 99 | 'post_author' => 0, 100 | 'post_type' => self::POST_TYPE, 101 | 'post_title' => $args['route'], 102 | 'post_content' => $post_content, 103 | 'post_status' => 'publish', 104 | 105 | // Append a random string to the end to attempt a unique post slug 106 | // route names will often be the same, so this helps WordPress from 107 | // having to loop through several times while generating a unique 108 | // post slug. 109 | 'post_name' => sanitize_title( $args['route'] ) . '-' . wp_generate_password( 6, true ), 110 | 111 | ); 112 | 113 | // Allow filtering. 114 | $new_post = apply_filters( self::plugin_name() . '-pre-insert-new-post', $new_post, $args ); 115 | 116 | $post_id = wp_insert_post( $new_post ); 117 | 118 | if ( ! empty( $post_id ) ) { 119 | $this->insert_post_terms( $post_id, $args ); 120 | $this->insert_post_meta( $post_id, $args ); 121 | 122 | $this->insert_request_meta( $post_id, $args ); 123 | $this->insert_response_meta( $post_id, $args ); 124 | 125 | global $wp_rest_api_log_new_entry_id; 126 | $wp_rest_api_log_new_entry_id = $post_id; 127 | 128 | } 129 | 130 | return $post_id; 131 | } 132 | 133 | 134 | private function insert_post_terms( $post_id, $args ) { 135 | 136 | // sanitize and store method 137 | if ( ! WP_REST_API_Log_Common::is_valid_method( $args['method'] ) ) { 138 | $args['method'] = 'GET'; 139 | } 140 | wp_set_post_terms( $post_id, $args['method'], self::TAXONOMY_METHOD ); 141 | 142 | // store status code 143 | $args['status'] = absint( $args['status'] ); 144 | wp_set_post_terms( $post_id, $args['status'], self::TAXONOMY_STATUS ); 145 | 146 | // store the source 147 | wp_set_post_terms( $post_id, $args['source'], self::TAXONOMY_SOURCE ); 148 | 149 | } 150 | 151 | 152 | private function insert_post_meta( $post_id, $args ) { 153 | 154 | $meta = array( 155 | self::POST_META_IP_ADDRESS => $args['ip_address'], 156 | self::POST_META_REQUEST_USER => $args['user'], 157 | self::POST_META_HTTP_X_FORWARDED_FOR => $args['http_x_forwarded_for'], 158 | self::POST_META_MILLISECONDS => $args['milliseconds'], 159 | self::POST_META_REQUEST_BODY => $args['request']['body'], 160 | ); 161 | 162 | foreach ( $meta as $key => $value ) { 163 | if ( is_array( $value ) && 1 === count( $value ) ) { 164 | $value = $value[0]; 165 | } 166 | if ( ! empty( $value ) ) { 167 | add_post_meta( $post_id, $key, $value ); 168 | } 169 | } 170 | 171 | // log any additional post meta 172 | if ( ! empty( $args['post_meta'] ) && is_array( $args['post_meta'] ) ) { 173 | 174 | foreach( $args['post_meta'] as $key => $value ){ 175 | add_post_meta( $post_id, $key, $value ); 176 | } 177 | 178 | } 179 | 180 | } 181 | 182 | 183 | private function insert_request_meta( $post_id, $args ) { 184 | 185 | $request = 'request'; 186 | $types = array( 'headers', 'query_params', 'body_params' ); 187 | 188 | foreach( $types as $type ) { 189 | 190 | if ( ! empty( $args[ $request ][ $type ] ) ) { 191 | foreach ( $args[ $request ][ $type ] as $key => $value ) { 192 | 193 | if ( is_array( $value ) && 194 | 1 === count( $value ) && 195 | 'headers' === $type ) { 196 | $value = reset( $value ); 197 | } 198 | 199 | if ( ! empty( $value ) ) { 200 | add_post_meta( $post_id, "{$request}_{$type}|{$key}", $value ); 201 | } 202 | 203 | } 204 | } 205 | } 206 | 207 | } 208 | 209 | 210 | private function insert_response_meta( $post_id, $args ) { 211 | 212 | $response = 'response'; 213 | $types = array( 'headers' ); 214 | 215 | foreach( $types as $type ) { 216 | 217 | if ( ! empty( $args[ $response ][ $type ] ) ) { 218 | foreach ( $args[ $response ][ $type ] as $key => $value ) { 219 | 220 | if ( is_array( $value ) && 221 | 1 === count( $value ) && 222 | 'headers' === $type ) { 223 | $value = reset( $value ); 224 | } 225 | 226 | if ( ! empty( $value ) ) { 227 | add_post_meta( $post_id, "{$response}_{$type}|{$key}", $value ); 228 | } 229 | 230 | } 231 | } 232 | } 233 | 234 | } 235 | 236 | 237 | public function search( $args = array() ) { 238 | 239 | $args = wp_parse_args( $args, 240 | array( 241 | 'after_id' => 0, 242 | 'before_id' => 0, 243 | 'from' => '', 244 | 'to' => current_time( 'mysql' ), 245 | 'route' => '', 246 | 'route_match_type' => 'exact', 247 | 'method' => false, 248 | 'status' => false, 249 | 'page' => 1, 250 | 'posts_per_page' => 50, 251 | 'params' => array(), 252 | ) 253 | ); 254 | 255 | $query_args = array( 256 | 'post_type' => self::POST_TYPE, 257 | 'posts_per_page' => $args['posts_per_page'], 258 | 'paged' => $args['page'], 259 | 'date_query' => array(), 260 | 'tax_query' => array( 'relation' => 'AND' ), 261 | ); 262 | 263 | if ( ! empty( $args['fields'] ) ) { 264 | $query_args['fields'] = $args['fields']; 265 | } 266 | 267 | if ( ! empty( $args['id'] ) ) { 268 | $query_args['p'] = $args['id']; 269 | } 270 | 271 | // dates 272 | if ( ! empty( $args['from'] ) ) { 273 | $query_args['date_query']['after'] = $args['from']; 274 | } 275 | 276 | if ( ! empty( $args['to'] ) ) { 277 | $query_args['date_query']['before'] = $args['to']; 278 | } 279 | 280 | // route, handled by posts_where filter 281 | if ( ! empty( $args['route'] ) ) { 282 | $query_args['_wp-rest-api-log-route'] = $args['route']; 283 | $query_args['_wp-rest-api-log-route-match-type'] = $args['route_match_type']; 284 | } 285 | 286 | // post id, handled by posts_where filter 287 | if ( ! empty( $args['after_id'] ) ) { 288 | $query_args['_wp-rest-api-log-after-id'] = $args['after_id']; 289 | } 290 | 291 | if ( ! empty( $args['before_id'] ) ) { 292 | $query_args['_wp-rest-api-log-before-id'] = $args['before_id']; 293 | } 294 | 295 | // HTTP Method 296 | if ( ! empty( $args['method'] ) ) { 297 | $query_args['tax_query'][] = array( 298 | 'taxonomy' => self::TAXONOMY_METHOD, 299 | 'field' => 'slug', 300 | 'terms' => explode( ',', $args['method'] ), 301 | ); 302 | } 303 | 304 | // HTTP Status 305 | if ( ! empty( $args['status'] ) ) { 306 | $query_args['tax_query'][] = array( 307 | 'taxonomy' => self::TAXONOMY_STATUS, 308 | 'field' => 'slug', 309 | 'terms' => explode( ',', $args['status'] ), 310 | ); 311 | } 312 | 313 | $posts = array(); 314 | $query = new WP_Query( $query_args ); 315 | 316 | if ( $query->have_posts() ) { 317 | $posts = $query->posts; 318 | } 319 | 320 | return $posts; 321 | 322 | } 323 | 324 | /** 325 | * Adds custom where statement for routes 326 | * 327 | * @param string $where original SQL 328 | * @param object $query WP_Query 329 | * @return string 330 | */ 331 | public function add_where_route( $where, $query ) { 332 | 333 | $route = $query->get( '_wp-rest-api-log-route' ); 334 | if ( ! empty( $route ) ) { 335 | 336 | global $wpdb; 337 | 338 | $route_match_type = $query->get( '_wp-rest-api-log-route-match-type' ); 339 | $route_start = ''; 340 | $route_end = ''; 341 | 342 | switch ( $route_match_type ) { 343 | 344 | case 'starts_with': 345 | $route_end = '%'; 346 | break; 347 | 348 | case 'ends_with': 349 | $route_start = '%'; 350 | break; 351 | 352 | case 'wildcard': 353 | $route_start = '%'; 354 | $route_end = '%'; 355 | break; 356 | } 357 | 358 | $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_title like %s", $route_start . $route . $route_end ); 359 | 360 | } 361 | 362 | return $where; 363 | } 364 | 365 | 366 | /** 367 | * Adds custom where statement for post id ranges 368 | * 369 | * @param string $where original SQL 370 | * @param object $query WP_Query 371 | * @return string 372 | */ 373 | public function add_where_post_id( $where, $query ) { 374 | 375 | global $wpdb; 376 | 377 | $after_id = $query->get( '_wp-rest-api-log-after-id' ); 378 | if ( ! empty( $after_id ) ) { 379 | $where .= $wpdb->prepare( " AND {$wpdb->posts}.ID > %d", $after_id ); 380 | } 381 | 382 | $before_id = $query->get( '_wp-rest-api-log-before-id' ); 383 | if ( ! empty( $before_id ) ) { 384 | $where .= $wpdb->prepare( " AND {$wpdb->posts}.ID < %d", $before_id ); 385 | } 386 | 387 | return $where; 388 | } 389 | 390 | 391 | /** 392 | * Gets a list of log IDs from the legacy custom table that need 393 | * to be migrated to a custom post type 394 | * 395 | * @return array 396 | */ 397 | public function get_log_ids_to_migrate() { 398 | 399 | global $wpdb; 400 | 401 | $existing_tables = $wpdb->get_col( "SHOW TABLES LIKE '{$wpdb->prefix}wp_rest_api_log%';" ); 402 | $log_ids = array(); 403 | 404 | if ( ! empty( $existing_tables ) ) { 405 | 406 | $ids = $wpdb->get_col( "select id from {$wpdb->prefix}wp_rest_api_log" ); 407 | 408 | foreach ( $ids as $id ) { 409 | 410 | $query = new WP_Query( array( 411 | 'posts_per_page' => 1, 412 | 'update_post_meta_cache' => false, 413 | 'update_post_term_cache' => false, 414 | 'post_type' => self::POST_TYPE, 415 | 'meta_key' => '_wp_rest_api_log_migrated_id', 416 | 'meta_value' => $id, 417 | 'fields' => 'ids', 418 | 'post_status' => 'publish', 419 | ) 420 | ); 421 | 422 | if ( ! $query->have_posts() ) { 423 | $log_ids[] = $id; 424 | } 425 | 426 | } 427 | 428 | } 429 | 430 | return $log_ids; 431 | 432 | } 433 | 434 | /** 435 | * Migrates single record from the initial version of the plugin's 436 | * custom tables to a custom post type 437 | * 438 | * @return int 439 | */ 440 | public function migrate_db_record( $id ) { 441 | 442 | global $wpdb; 443 | 444 | $log = $wpdb->get_row( $wpdb->prepare( "select * from {$wpdb->prefix}wp_rest_api_log where id = %d", $id ) ); 445 | $meta_rows = $wpdb->get_results( $wpdb->prepare( "select * from {$wpdb->prefix}wp_rest_api_logmeta where log_id = %d", $log->id ) ); 446 | 447 | $args = array( 448 | 'time' => $log->time, 449 | 'ip_address' => $log->ip_address, 450 | 'route' => $log->route, 451 | 'method' => $log->method, 452 | 'status' => $log->status, 453 | 'request' => array( 454 | 'body' => $log->request_body, 455 | ), 456 | 'response' => array( 457 | 'body' => json_decode( $log->response_body ), 458 | ), 459 | 'milliseconds' => $log->milliseconds, 460 | ); 461 | 462 | 463 | foreach( $meta_rows as $meta_row ) { 464 | 465 | switch ( $meta_row->meta_type ) { 466 | case 'header': 467 | $meta_type = 'headers'; 468 | break; 469 | case 'query': 470 | $meta_type = 'query_params'; 471 | break; 472 | } 473 | 474 | if ( ! empty( $meta_type ) ) { 475 | $args[ $meta_row->meta_request_response ][ $meta_type ][ $meta_row->meta_key ] = $meta_row->meta_value; 476 | } 477 | 478 | } 479 | 480 | $post_id = $this->insert( $args ); 481 | 482 | // save the legacy ID so we don't migrate it again 483 | add_post_meta( $post_id, '_wp_rest_api_log_migrated_id', $id ); 484 | 485 | // manually update the post dates 486 | $wpdb->update( 487 | $wpdb->posts, 488 | array( 489 | 'post_date' => $log->time, 490 | 'post_date_gmt' => $log->time, 491 | 'post_modified' => $log->time, 492 | 'post_modified_gmt' => $log->time, 493 | ), 494 | array( 495 | 'ID' => $post_id, // where clause 496 | ) 497 | ); 498 | 499 | return $post_id; 500 | 501 | } 502 | 503 | 504 | /** 505 | * Returns a list of all log entry IDs in the database. 506 | * 507 | * @return int 508 | */ 509 | static public function get_all_log_ids( ) { 510 | 511 | $query = new WP_Query( array( 512 | 'update_post_term_cache' => false, 513 | 'update_post_meta_cache' => false, 514 | 'no_found_rows' => true, 515 | 'post_type' => WP_REST_API_Log_DB::POST_TYPE, 516 | 'fields' => 'ids', 517 | 'posts_per_page' => -1, 518 | ) 519 | ); 520 | 521 | return $query->posts; 522 | } 523 | 524 | /** 525 | * Purges all log entries in the database. 526 | * 527 | * @return void 528 | */ 529 | static public function purge_all_log_entries() { 530 | 531 | $post_ids = self::get_all_log_ids(); 532 | 533 | foreach( $post_ids as $post_id ) { 534 | wp_delete_post( $post_id, true ); 535 | } 536 | } 537 | 538 | 539 | } // end class 540 | 541 | } 542 | -------------------------------------------------------------------------------- /includes/class-wp-rest-api-log-delete-response.php: -------------------------------------------------------------------------------- 1 | populate_response( $data ); 15 | } 16 | 17 | } 18 | 19 | 20 | private function populate_response( $data ) { 21 | 22 | $this->args = $data->args; 23 | $this->older_than_date = $data->older_than_date; 24 | $this->records_affected = $data->records_affected; 25 | 26 | } 27 | 28 | 29 | } 30 | 31 | } 32 | 33 | -------------------------------------------------------------------------------- /includes/class-wp-rest-api-log-elasticpress.php: -------------------------------------------------------------------------------- 1 | $route, 121 | 'method' => '', 122 | 'status' => '', 123 | 'source' => 'ElasticPress', 124 | 'milliseconds' => 0, 125 | 'request' => array( 126 | 'body_params' => array(), 127 | 'headers' => array(), 128 | 'body' => '', 129 | ), 130 | 'response' => array( 131 | 'body' => '', 132 | 'headers' => array(), 133 | ), 134 | ); 135 | 136 | // Add elapsed time. 137 | if ( ! empty( $query['time_start'] ) && ! empty( $query['time_finish'] ) ) { 138 | $args['milliseconds'] = absint( ( $query['time_finish'] * 1000 ) - ( $query['time_start'] * 1000 ) ); 139 | } 140 | 141 | if ( ! empty( $query['args'] ) ) { 142 | 143 | // Store the JSON sent to ElasticSearch. 144 | if ( ! empty( $query['args']['body'] ) ) { 145 | $args['request']['body'] = base64_encode( $query['args']['body'] ); 146 | } 147 | 148 | // Add the method. 149 | if ( ! empty( $query['args']['method'] ) ) { 150 | $args['method'] = $query['args']['method']; 151 | } 152 | } 153 | 154 | if ( ! empty( $query['request'] ) && is_array( $query['request'] ) ) { 155 | 156 | // This is actually the response headers. 157 | if ( ! empty( $query['request']['headers'] ) && is_array( $query['request']['headers'] ) ) { 158 | 159 | foreach ( $query['request']['headers'] as $header => $value ) { 160 | $args['response']['headers'][ $header ] = $value; 161 | } 162 | } 163 | 164 | // Store the HTTP response code. 165 | if ( ! empty( $query['request']['response'] ) && ! empty( $query['request']['response']['code'] ) ) { 166 | $args['status'] = $query['request']['response']['code']; 167 | } 168 | 169 | // Store the response body. 170 | if ( ! empty( $query['request']['body'] ) ) { 171 | $args['response']['body'] = json_decode( $query['request']['body'] ); 172 | } 173 | } 174 | 175 | // Log the EP request/response. 176 | do_action( WP_REST_API_Log_Common::PLUGIN_NAME . '-insert', $args ); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /includes/class-wp-rest-api-log-entry.php: -------------------------------------------------------------------------------- 1 | array( 'href' => '' ) ); 91 | 92 | private $_post; 93 | 94 | 95 | public function __construct( $post = null ) { 96 | 97 | if ( is_int( $post ) ) { 98 | $post = get_post( $post ); 99 | } 100 | 101 | if ( is_object( $post ) ) { 102 | $this->_post = $post; 103 | $this->load(); 104 | } 105 | } 106 | 107 | 108 | private function load() { 109 | 110 | $this->request = new WP_REST_API_Log_API_Request( $this->_post ); 111 | $this->response = new WP_REST_API_Log_API_Response( $this->_post ); 112 | 113 | $this->load_post_data(); 114 | $this->load_post_meta(); 115 | $this->load_taxonomies(); 116 | 117 | } 118 | 119 | private function load_post_data() { 120 | $this->ID = $this->_post->ID; 121 | $this->route = $this->_post->post_title; 122 | $this->time = $this->_post->post_date; 123 | $this->time_gmt = $this->_post->post_date_gmt; 124 | 125 | if ( function_exists( 'rest_url' ) ) { 126 | $this->_links['self']['href'] = rest_url( WP_REST_API_Log_Common::PLUGIN_NAME . '/entry/' . $this->ID ); 127 | } 128 | } 129 | 130 | private function load_post_meta() { 131 | 132 | $post_id = $this->_post->ID; 133 | 134 | $this->ip_address = get_post_meta( $post_id, WP_REST_API_Log_DB::POST_META_IP_ADDRESS, true ); 135 | $this->user = get_post_meta( $post_id, WP_REST_API_Log_DB::POST_META_REQUEST_USER, true ); 136 | $this->http_x_forwarded_for = get_post_meta( $post_id, WP_REST_API_Log_DB::POST_META_HTTP_X_FORWARDED_FOR, true ); 137 | $this->milliseconds = absint( get_post_meta( $post_id, WP_REST_API_Log_DB::POST_META_MILLISECONDS, true ) ); 138 | 139 | } 140 | 141 | private function load_taxonomies() { 142 | $post_id = $this->_post->ID; 143 | 144 | $this->method = $this->get_first_term_name( $post_id, WP_REST_API_Log_DB::TAXONOMY_METHOD ); 145 | $this->status = $this->get_first_term_name( $post_id, WP_REST_API_Log_DB::TAXONOMY_STATUS ); 146 | $this->source = $this->get_first_term_name( $post_id, WP_REST_API_Log_DB::TAXONOMY_SOURCE ); 147 | 148 | } 149 | 150 | /** 151 | * Gets the first term name for the supplied post and taxonomy. 152 | * 153 | * @param int|WP_Post $post Post ID or object. 154 | * @param string $taxonomy Taxonomy slug. 155 | * @return string 156 | */ 157 | public function get_first_term_name( $post, $taxonomy ) { 158 | 159 | // Uses get_the_terms() since wp_get_object_terms() does not 160 | // do any caching. 161 | $terms = get_the_terms( $post, $taxonomy ); 162 | 163 | return ! empty( $terms ) && is_array( $terms ) ? $terms[0]->name : ''; 164 | } 165 | 166 | 167 | } 168 | 169 | } 170 | 171 | -------------------------------------------------------------------------------- /includes/class-wp-rest-api-log-filters.php: -------------------------------------------------------------------------------- 1 | __( 'All', 'wp-rest-api-log' ), 15 | 'log_matches' => __( 'Only Matching Filters', 'wp-rest-api-log' ), 16 | 'exclude_matches' => __( 'Exclude Matching Filters', 'wp-rest-api-log' ), 17 | ); 18 | } 19 | 20 | 21 | /** 22 | * Converts a route filter into regex pattern. 23 | * 24 | * @param string $route_filter Route filter, may include * wildcards. 25 | * @return string 26 | */ 27 | public static function route_to_regex( $route_filter ) { 28 | 29 | if ( ! empty( $route_filter ) ) { 30 | 31 | // If it starts with a carat, treat it as regex and 32 | // make no changes. 33 | if ( '^' === substr( $route_filter, 0, 1 ) ) { 34 | return $route_filter; 35 | } else { 36 | 37 | // Replace wildcard with regex wildcard. 38 | $route_filter = str_replace( '*', '.*', $route_filter ); 39 | 40 | // Add the start of the match. 41 | $route_filter = '^' . $route_filter; 42 | 43 | // Add the end of the match. 44 | $route_filter .= '$'; 45 | 46 | // Convert backslash to literals. 47 | $route_filter = str_replace( '/', "\/", $route_filter ); 48 | } 49 | 50 | } 51 | 52 | return $route_filter; 53 | } 54 | 55 | /** 56 | * Determines if the supplied route can be logged. 57 | * 58 | * @param string $route Route (ex: /wp/v2). 59 | * @return bool 60 | */ 61 | static public function can_log_route( $route ) { 62 | 63 | // Get the filter mode. 64 | $route_logging_mode = apply_filters( 'wp-rest-api-log-setting-get', 'routes', 'route-log-matching-mode' ); 65 | 66 | // If no logging mode is set, we can log the route. 67 | if ( empty( $route_logging_mode ) ) { 68 | return true; 69 | } 70 | 71 | // Get the route filters. 72 | $route_filters = apply_filters( 'wp-rest-api-log-setting-get', 'routes', 'route-filters' ); 73 | $route_filters = array_values( array_map( 'trim', explode( "\n", $route_filters ) ) ); 74 | 75 | // If we're set to exclude matching filters, but we have no filters, 76 | // then the route can be logged 77 | if ( 'exclude_matches' === $route_logging_mode && empty( $route_filters ) ) { 78 | return true; 79 | } 80 | 81 | // Loop through the filters and apply each one to the route. 82 | foreach( $route_filters as $route_filter ) { 83 | if ( empty( $route_filter ) ) { 84 | continue; 85 | } 86 | 87 | $regex = self::route_to_regex( $route_filter ); 88 | 89 | //preg_match() returns 1 if the pattern matches given subject, 90 | //0 if it does not, or FALSE if an error occurred. 91 | $match = preg_match( '/' . $regex . '/', $route ); 92 | 93 | // We can log this if the mode is set to log only matches. 94 | if ( 1 === $match && 'log_matches' === $route_logging_mode ) { 95 | return true; 96 | } 97 | 98 | // We cannot log this if the mode is set to exclude matches. 99 | if ( 1 === $match && 'exclude_matches' === $route_logging_mode ) { 100 | return false; 101 | } 102 | } 103 | 104 | // At this point, we can only log the match if we're set to exclude 105 | // mode and the loop above did not find a match. 106 | return 'exclude_matches' === $route_logging_mode; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /includes/class-wp-rest-api-log-i18n.php: -------------------------------------------------------------------------------- 1 | esc_html__( 'REST API Log Entries', 'ms-research' ), 25 | 'singular_name' => esc_html__( 'REST API Log Entry', 'ms-research' ), 26 | 'add_new' => esc_html__( 'Add New REST API Log Entries', 'ms-research' ), 27 | 'add_new_item' => esc_html__( 'Add New REST API Log Entry', 'ms-research' ), 28 | 'new_item' => esc_html__( 'New REST API Log Entry', 'ms-research' ), 29 | 'edit_item' => esc_html__( 'Edit Publication Page', 'ms-research' ), 30 | 'view_item' => esc_html__( 'View REST API Log Entry', 'ms-research' ), 31 | 'all_items' => esc_html__( 'All REST API Log Entries', 'ms-research' ), 32 | 'search_items' => esc_html__( 'Search Entries', 'ms-research' ), 33 | 'not_found' => esc_html__( 'No REST API Log Entries found', 'ms-research' ), 34 | 'not_found_in_trash' => esc_html__( 'No REST API Log Entries found in Trash', 'ms-research' ), 35 | ); 36 | 37 | return apply_filters( WP_REST_API_Log_Common::PLUGIN_NAME . '-post-type-labels', $labels ); 38 | } 39 | 40 | 41 | static public function get_post_type_args() { 42 | 43 | $args = array( 44 | 'labels' => self::get_post_type_labels(), 45 | 'show_in_rest' => true, 46 | 'rest_base' => WP_REST_API_Log_DB::POST_TYPE, // allows the CPT to show up in the native API 47 | 'hierarchical' => false, 48 | 'public' => false, 49 | 'show_ui' => true, 50 | 'show_in_menu' => 'tools.php', 51 | 'show_in_admin_bar' => false, 52 | 'show_in_nav_menus' => false, 53 | 'publicly_queryable' => true, 54 | 'exclude_from_search' => true, 55 | 'has_archive' => false, 56 | 'query_var' => false, 57 | 'can_export' => true, 58 | 'rewrite' => false, 59 | 'map_meta_cap' => false, 60 | 'capabilities' => array( 61 | 'read_post' => 'read_' . WP_REST_API_Log_DB::POST_TYPE, 62 | 'delete_post' => 'delete_' . WP_REST_API_Log_DB::POST_TYPE, 63 | 'delete_posts' => 'delete_' . WP_REST_API_Log_DB::POST_TYPE . 's', 64 | 'edit_posts' => 'edit_' . WP_REST_API_Log_DB::POST_TYPE . 's', 65 | 'edit_post' => 'edit_' . WP_REST_API_Log_DB::POST_TYPE, 66 | 'create_posts' => 'create_' . WP_REST_API_Log_DB::POST_TYPE . 's', 67 | ), 68 | 'supports' => array( 'title', 'author', 'excerpt' ), 69 | ); 70 | 71 | return apply_filters( WP_REST_API_Log_Common::PLUGIN_NAME . '-register-post-type', $args ); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /includes/class-wp-rest-api-log-request-response-base.php: -------------------------------------------------------------------------------- 1 | _type = $type; 23 | 24 | if ( is_int( $post ) ) { 25 | $post = get_post( $post ); 26 | } 27 | 28 | if ( is_object( $post ) ) { 29 | $this->_post = $post; 30 | $this->load(); 31 | } 32 | } 33 | 34 | protected function set_type( $type ) { 35 | $this->_type = $type; 36 | } 37 | 38 | 39 | private function load() { 40 | 41 | $this->headers = $this->get_post_meta_array( 'headers' ); 42 | 43 | if ( 'request' === $this->_type ) { 44 | $this->body = get_post_meta( $this->_post->ID, '_request_body', true ); 45 | if ( false === $this->body) { 46 | $this->body = ''; 47 | } 48 | } else { 49 | $this->body = $this->_post->post_content; 50 | } 51 | 52 | } 53 | 54 | 55 | protected function get_post_meta_array( $type ) { 56 | 57 | $meta = array(); 58 | 59 | if ( ! is_object( $this->_post ) ) { 60 | return $meta; 61 | } 62 | 63 | if ( empty( $this->_meta ) ) { 64 | $this->_meta = get_post_meta( $this->_post->ID ); 65 | } 66 | 67 | if ( empty( $this->_meta ) ) { 68 | return $meta; 69 | } 70 | 71 | // loop through the post meta, find the keys for the array 72 | foreach ( $this->_meta as $key => $value ) { 73 | 74 | // ex: _request_headers|Expires 75 | // ex: _request_headers|Content-type 76 | $look_for = "{$this->_type}_{$type}|"; 77 | $pos = stripos( $key, $look_for ); 78 | 79 | if ( 0 === $pos ) { 80 | 81 | $meta_name = substr( $key, strlen( $look_for ) ); 82 | 83 | if ( is_array( $value ) && 1 === count( $value ) ) { 84 | $meta[ $meta_name ] = maybe_unserialize( $value[0] ); 85 | } else { 86 | $meta[ $meta_name ] = maybe_unserialize( $value ); 87 | } 88 | 89 | 90 | } 91 | 92 | } 93 | 94 | return $meta; 95 | 96 | } 97 | 98 | /** 99 | * Runs esc_html() on various fields for display in the admin. 100 | * 101 | * @param object $entry REST API Log Entry 102 | * @return object 103 | */ 104 | static public function esc_html_fields( $entry ) { 105 | 106 | // Get the list of request fiels. 107 | $request_fields = array( 108 | 'query_params', 109 | 'headers', 110 | ); 111 | 112 | $request_fields = apply_filters( WP_REST_API_Log_Common::PLUGIN_NAME . '-esc-html-request-fields', $request_fields ); 113 | 114 | 115 | // Get the list of response fiels. 116 | $response_fields = array( 117 | 'headers', 118 | ); 119 | 120 | $response_fields = apply_filters( WP_REST_API_Log_Common::PLUGIN_NAME . '-esc-html-response-fields', $response_fields ); 121 | 122 | // Run esc_html on the request fields. 123 | foreach( $request_fields as $field ) { 124 | if ( is_array( $entry->request->$field ) ) { 125 | array_walk_recursive( 126 | $entry->request->$field, 127 | function ( &$v, &$k ) { 128 | $v = esc_html( $v ); 129 | $k = esc_html( $k ); 130 | } 131 | ); 132 | } else { 133 | $entry->request->$field = esc_html( $entry->request->$field ); 134 | } 135 | } 136 | 137 | // Run esc_html on the response fields. 138 | foreach( $response_fields as $field ) { 139 | if ( is_array( $entry->response->$field ) ) { 140 | array_walk_recursive( 141 | $entry->response->$field, 142 | function ( &$v, &$k ) { 143 | $v = esc_html( $v ); 144 | $k = esc_html( $k ); 145 | } 146 | ); 147 | } else { 148 | $entry->response->$field = esc_html( $entry->response->$field ); 149 | } 150 | } 151 | 152 | return $entry; 153 | 154 | } 155 | 156 | 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /includes/class-wp-rest-api-log-request.php: -------------------------------------------------------------------------------- 1 | load(); 15 | } 16 | 17 | 18 | private function load() { 19 | 20 | $this->body_params = parent::get_post_meta_array( 'body_params' ); 21 | $this->query_params = parent::get_post_meta_array( 'query_params' ); 22 | 23 | } 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /includes/class-wp-rest-api-log-response-base.php: -------------------------------------------------------------------------------- 1 | populate_response( $data ); 15 | } 16 | 17 | } 18 | 19 | 20 | private function populate_response( $data ) { 21 | 22 | $this->routes = $data; 23 | $this->records_affected = count( $this->routes ); 24 | 25 | } 26 | 27 | 28 | 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /includes/class-wp-rest-api-log-taxonomies.php: -------------------------------------------------------------------------------- 1 | array( 22 | 'name' => __( 'Method', 'wp-rest-api-log' ), 23 | 'singular_name' => __( 'Methods', 'wp-rest-api-log' ), 24 | ), 25 | 26 | WP_REST_API_Log_DB::TAXONOMY_STATUS => array( 27 | 'name' => __( 'Status', 'wp-rest-api-log' ), 28 | 'singular_name' => __( 'Statuses', 'wp-rest-api-log' ), 29 | ), 30 | 31 | WP_REST_API_Log_DB::TAXONOMY_SOURCE => array( 32 | 'name' => __( 'Log Source', 'wp-rest-api-log' ), 33 | 'singular_name' => __( 'Log Sources', 'wp-rest-api-log' ), 34 | ), 35 | ); 36 | 37 | $taxonomies = apply_filters( WP_REST_API_Log_Common::PLUGIN_NAME . '-custom-taxonomies', $taxonomies ); 38 | 39 | foreach ( $taxonomies as $taxonomy => $labels ) { 40 | 41 | $args = array( 42 | 'labels' => $labels, 43 | 'public' => false, 44 | 'show_in_nav_menus' => false, 45 | 'show_admin_column' => false, 46 | 'hierarchical' => false, 47 | 'show_tagcloud' => false, 48 | 'show_ui' => false, 49 | 'query_var' => false, 50 | 'rewrite' => false, 51 | 'capabilities' => array(), 52 | ); 53 | 54 | $args = apply_filters( WP_REST_API_Log_Common::PLUGIN_NAME . '-register-taxonomy-args', $args, $taxonomy ); 55 | 56 | register_taxonomy( $taxonomy, array( WP_REST_API_Log_DB::POST_TYPE ), $args ); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /includes/class-wp-rest-api-log.php: -------------------------------------------------------------------------------- 1 | ID; 37 | // } 38 | // } 39 | 40 | // return $user_id; 41 | 42 | // } ); 43 | 44 | } 45 | 46 | 47 | /** 48 | * Logs the REST API request & response right before it returns the data to the client. 49 | * 50 | * @param bool $served True if the response was served by something other than the REST API, otherwise false, 51 | * @param object $result REST API response data. 52 | * @param object $request REST API request data. 53 | * @param object $rest_server REST API server. 54 | * @return bool $served 55 | */ 56 | static public function log_rest_api_response( $served, $result, $request, $rest_server ) { 57 | 58 | // don't log anything if logging is not enabled 59 | $logging_enabled = apply_filters( WP_REST_API_Log_Common::PLUGIN_NAME . '-setting-is-enabled', 60 | true, 61 | 'general', 62 | 'logging-enabled' 63 | ); 64 | 65 | if ( ! $logging_enabled ) { 66 | return $served; 67 | } 68 | 69 | 70 | // Allow specific requests to not be logged 71 | $bypass_insert = apply_filters( WP_REST_API_Log_Common::PLUGIN_NAME . '-bypass-insert', false, $result, $request, $rest_server ); 72 | if ( $bypass_insert ) { 73 | return $served; 74 | } 75 | 76 | // Determine if this route should be logged based on route filters. 77 | $route = $request->get_route(); 78 | $can_log_route = WP_REST_API_Log_Filters::can_log_route( $route ); 79 | 80 | // Allow this to be filtered. 81 | $can_log_route = apply_filters( 'wp-rest-api-log-can-log-route', $can_log_route, $route, $request, $result, $rest_server ); 82 | 83 | 84 | // Exit out if we can't log this route. 85 | if ( ! $can_log_route ) { 86 | return $served; 87 | } 88 | 89 | $current_user = wp_get_current_user(); 90 | 91 | $server = filter_var_array( 92 | $_SERVER, 93 | [ 94 | 'REMOTE_ADDR' => WP_REST_API_Log_Common::filter_strip_all_tags(), 95 | 'HTTP_X_FORWARDED_FOR' => WP_REST_API_Log_Common::filter_strip_all_tags(), 96 | ] 97 | ); 98 | 99 | $args = array( 100 | 'ip_address' => $server[ 'REMOTE_ADDR' ], 101 | 'user' => $current_user->user_login, 102 | 'http_x_forwarded_for' => $server[ 'HTTP_X_FORWARDED_FOR' ], 103 | 'route' => $route, 104 | 'method' => $request->get_method(), 105 | 'status' => $result->get_status(), 106 | 'request' => array( 107 | 'body' => $request->get_body(), 108 | 'headers' => $request->get_headers(), 109 | 'query_params' => $request->get_query_params(), 110 | 'body_params' => $request->get_body_params(), 111 | ), 112 | 'response' => array( 113 | 'body' => $result, 114 | 'headers' => self::get_response_headers( $result ), 115 | ), 116 | ); 117 | 118 | do_action( WP_REST_API_Log_Common::PLUGIN_NAME . '-insert', $args ); 119 | 120 | return $served; 121 | } 122 | 123 | static public function get_response_headers( $result ) { 124 | // headers_list returns an array of headers like this: Content-Type: application/json; 125 | // we want a key/value array 126 | if ( function_exists( 'headers_list' ) ) { 127 | $headers = array(); 128 | foreach ( headers_list() as $header ) { 129 | $header = explode( ':', $header ); 130 | if ( count( $header ) > 1 ) { 131 | 132 | // Grab the header name. 133 | $header_name = array_shift( $header ); 134 | 135 | // Grab any remaining items in the array as the value 136 | $header_value = implode( '', $header ); 137 | 138 | $headers[ $header_name ] = trim( $header_value ); 139 | } 140 | } 141 | return $headers; 142 | } else { 143 | return $result->get_headers(); 144 | } 145 | } 146 | 147 | static public function create_purge_cron() { 148 | if ( ! wp_next_scheduled( 'wp-rest-api-log-purge-old-records' ) ) { 149 | wp_schedule_event( time() + 60, 'hourly', 'wp-rest-api-log-purge-old-records' ); 150 | } 151 | } 152 | 153 | /** 154 | * Gets old REST API Log record IDs. 155 | * 156 | * @param int $days_old How many days back to go. 157 | * @return array 158 | */ 159 | public static function get_old_log_ids( $days_old ) { 160 | 161 | if ( empty( $days_old ) && 0 !== $days_old ) { 162 | $days_old = WP_REST_API_Log_Settings_General::setting_get( 'general', 'purge-days' ); 163 | } 164 | 165 | if ( empty( $days_old ) && 0 !== $days_old ) { 166 | return array(); 167 | } 168 | 169 | $db = new WP_REST_API_Log_DB(); 170 | $args = array( 171 | 'fields' => 'ids', 172 | 'to' => date( 'Y-m-d H:i', current_time( 'timestamp' ) - ( DAY_IN_SECONDS * $days_old ) ), 173 | 'posts_per_page' => -1, 174 | 'update_post_meta_cache' => false, 175 | 'update_term_meta_cache' => false, 176 | ); 177 | 178 | $ids = $db->search( $args ); 179 | 180 | return $ids; 181 | } 182 | 183 | /** 184 | * Purges old REST API Log records. 185 | * 186 | * @param int $days_old How many days back to go. 187 | * @param boolean $dry_run Is this a dry run? 188 | * @return int 189 | */ 190 | static public function purge_old_records( $days_old = false, $dry_run = false ) { 191 | 192 | if ( empty( $days_old ) ) { 193 | $days_old = WP_REST_API_Log_Settings_General::setting_get( 'general', 'purge-days' ); 194 | } 195 | 196 | $days_old = absint( $days_old ); 197 | if ( empty( $days_old ) ) { 198 | return; 199 | } 200 | 201 | $ids = self::get_old_log_ids( $days_old ); 202 | 203 | $number_deleted = 0; 204 | 205 | if ( ! empty( $ids ) && is_array( $ids ) ) { 206 | foreach ( $ids as $id ) { 207 | if ( ! $dry_run ) { 208 | wp_delete_post( $id, true ); 209 | } 210 | $number_deleted++; 211 | } 212 | } 213 | 214 | return $number_deleted; 215 | 216 | } 217 | 218 | static public function bypass_common_routes( $bypass_insert, $result, $request, $rest_server ) { 219 | 220 | // Ignore our own plugin. 221 | $ignore_routes = array( 222 | '/wp-rest-api-log', 223 | ); 224 | 225 | // See if the oembed route is ignored. 226 | if ( '1' === apply_filters( 'wp-rest-api-log-setting-get', 'routes', 'ignore-core-oembed' ) ) { 227 | $ignore_routes[] = '/oembed/1.0/embed'; 228 | } 229 | 230 | foreach ( $ignore_routes as $route ) { 231 | if ( stripos( $request->get_route(), $route ) !== false ) { 232 | return true; 233 | } 234 | } 235 | 236 | return $bypass_insert; 237 | 238 | } 239 | 240 | } // end class 241 | 242 | } 243 | -------------------------------------------------------------------------------- /includes/index.php: -------------------------------------------------------------------------------- 1 | __( 'General', 'wp-rest-api-log' ), 55 | ); 56 | } 57 | 58 | 59 | static public function setting_is_enabled( $key, $setting ) { 60 | return '1' === self::setting_get( $key, $setting, '0' ); 61 | } 62 | 63 | static public function filter_setting_is_enabled( $enabled, $key, $setting ) { 64 | return self::setting_is_enabled( $key, $setting ); 65 | } 66 | 67 | static public function setting_get( $key, $setting, $value = '' ) { 68 | 69 | 70 | $args = wp_parse_args( get_option( self::options_key( $key ) ), 71 | array( 72 | $setting => $value, 73 | ) 74 | ); 75 | 76 | return $args[ $setting ]; 77 | } 78 | 79 | 80 | static public function options_key( $key ) { 81 | return self::$settings_page . "-{$key}"; 82 | } 83 | 84 | static public function settings_input( $args ) { 85 | 86 | $args = wp_parse_args( $args, 87 | array( 88 | 'name' => '', 89 | 'key' => '', 90 | 'maxlength' => 50, 91 | 'size' => 30, 92 | 'after' => '', 93 | 'type' => 'text', 94 | 'min' => 0, 95 | 'max' => 0, 96 | 'step' => 1, 97 | ) 98 | ); 99 | 100 | $name = $args['name']; 101 | $key = $args['key']; 102 | $maxlength = $args['maxlength']; 103 | $size = $args['size']; 104 | $after = $args['after']; 105 | $type = $args['type']; 106 | $min = $args['min']; 107 | $max = $args['max']; 108 | $step = $args['step']; 109 | 110 | $option = get_option( $key ); 111 | $value = isset( $option[ $name ] ) ? esc_attr( $option[ $name ] ) : ''; 112 | 113 | $min_max_step = ''; 114 | if ( $type === 'number' ) { 115 | $min = intval( $args['min'] ); 116 | $max = intval( $args['max'] ); 117 | $step = intval( $args['step'] ); 118 | $min_max_step = " step='{$step}' min='{$min}' max='{$max}' "; 119 | } 120 | 121 | echo "
"; 122 | 123 | self::output_after( $after ); 124 | 125 | } 126 | 127 | 128 | static public function settings_check_radio_list( $args ) { 129 | 130 | $args = wp_parse_args( $args, 131 | array( 132 | 'name' => '', 133 | 'type' => 'checkbox', 134 | 'key' => '', 135 | 'items' => array(), 136 | 'after' => '', 137 | 'legend' => '', 138 | 'default' => array(), 139 | ) 140 | ); 141 | 142 | $name = $args['name']; 143 | $type = $args['type']; 144 | $key = $args['key']; 145 | $items = $args['items']; 146 | $after = $args['after']; 147 | $legend = $args['legend']; 148 | $default = $args['default']; 149 | 150 | $option = get_option( $key ); 151 | $values = isset( $option[ $name ] ) ? $option[ $name ] : ''; 152 | if ( ! is_array( $values ) && ! empty( $values ) ) { 153 | $values = array( $values ); 154 | } 155 | 156 | if ( empty( $values ) && ! empty ( $default ) ) { 157 | $values = $default; 158 | } 159 | 160 | $input_name = "{$key}[{$name}]"; 161 | if ( 'checkbox' === $type ) { 162 | $input_name .= '[]'; 163 | } 164 | 165 | ?> 166 |
167 | 168 | 169 | 170 | 171 | $value_dispay ) : $id = $key . '_' . $name . '_' . sanitize_key( $value ); ?> 172 | /> 173 | 174 |
175 | 176 |
177 | '', 188 | 'key' => '', 189 | 'rows' => 10, 190 | 'cols' => 40, 191 | 'after' => '', 192 | ) 193 | ); 194 | 195 | $name = $args['name']; 196 | $key = $args['key']; 197 | $rows = $args['rows']; 198 | $cols = $args['cols']; 199 | $after = $args['after']; 200 | 201 | 202 | $option = get_option( $key ); 203 | $value = isset( $option[$name] ) ? esc_attr( $option[$name] ) : ''; 204 | 205 | printf( '
', 206 | esc_attr( $name ), 207 | esc_attr( "{$key}[{$name}]" ), 208 | esc_attr( $rows ), 209 | esc_attr( $cols ), 210 | $value 211 | ); 212 | 213 | self::output_after( $after ); 214 | } 215 | 216 | 217 | static public function settings_yes_no( $args ) { 218 | 219 | extract( wp_parse_args( $args, 220 | array( 221 | 'name' => '', 222 | 'key' => '', 223 | 'after' => '', 224 | ) 225 | ) ); 226 | 227 | $option = get_option( $key ); 228 | $value = isset( $option[ $name ] ) ? esc_attr( $option[ $name ] ) : ''; 229 | 230 | if ( empty( $value ) ) { 231 | $value = '0'; 232 | } 233 | 234 | echo '
'; 235 | echo " "; 236 | echo " "; 237 | echo '
'; 238 | 239 | self::output_after( $after ); 240 | 241 | } 242 | 243 | 244 | static public function output_after( $after ) { 245 | if ( ! empty( $after ) ) { 246 | echo wp_kses_post( $after ); 247 | } 248 | } 249 | 250 | 251 | } 252 | 253 | } 254 | -------------------------------------------------------------------------------- /includes/settings/class-wp-rest-api-log-settings-elasticpress.php: -------------------------------------------------------------------------------- 1 | '1', 27 | ); 28 | } 29 | 30 | 31 | static public function register_elasticpress_settings() { 32 | $key = self::$settings_key; 33 | 34 | register_setting( $key, $key, array( __CLASS__, 'sanitize_settings') ); 35 | 36 | $section = 'elasticpress'; 37 | 38 | add_settings_section( $section, '', null, $key ); 39 | 40 | add_settings_field( 'logging-enabled', __( 'Log ElasticPress API Calls', 'wp-rest-api-log' ), array( __CLASS__, 'settings_yes_no' ), $key, $section, 41 | array( 'key' => $key, 'name' => 'logging-enabled', 'after' => '' ) ); 42 | 43 | } 44 | 45 | 46 | static public function sanitize_settings( $settings ) { 47 | 48 | return $settings; 49 | } 50 | 51 | 52 | } 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /includes/settings/class-wp-rest-api-log-settings-general.php: -------------------------------------------------------------------------------- 1 | '1', 29 | 'purge-days' => '7', 30 | ); 31 | } 32 | 33 | 34 | static public function register_general_settings() { 35 | $key = self::$settings_key; 36 | 37 | register_setting( $key, $key, array( __CLASS__, 'sanitize_settings') ); 38 | 39 | $section = 'general'; 40 | 41 | add_settings_section( $section, '', null, $key ); 42 | 43 | add_settings_field( 'logging-enabled', __( 'Enabled', 'wp-rest-api-log' ), array( __CLASS__, 'settings_yes_no' ), $key, $section, 44 | array( 'key' => $key, 'name' => 'logging-enabled', 'after' => '' ) ); 45 | 46 | $purge_button_html = self::get_purge_button_html(); 47 | 48 | add_settings_field( 'purge-days', __( 'Days to Retain Old Entries', 'wp-rest-api-log' ), array( __CLASS__, 'settings_input' ), $key, $section, 49 | array( 50 | 'key' => $key, 51 | 'name' => 'purge-days', 52 | 'after' => '

' . wp_kses_post( 53 | sprintf( __( 'Entries older than this will be automatically cleaned up, leave blank to keep all entries. %1$s', 'wp-rest-api-log' ), 54 | $purge_button_html 55 | ) 56 | ) . '

', 57 | 'size' => 3, 58 | 'maxlength' => 3, 59 | ) 60 | ); 61 | 62 | add_settings_field( 'ip-address-display', __( 'IP Address Display', 'wp-rest-api-log' ), array( __CLASS__, 'settings_check_radio_list' ), $key, $section, 63 | array( 64 | 'key' => $key, 65 | 'name' => 'ip-address-display', 66 | 'type' => 'radio', 67 | 'after' => '

' . __( 'Sets the IP address displayed in the list of log entries.', 'wp-rest-api-log' ) . '

', 68 | 'items' => array( 69 | 'ip_address' => __( 'IP Address', 'wp-rest-api-log' ), 70 | 'http_x_forwarded_for' => __( 'HTTP X Forwarded For', 'wp-rest-api-log' ), 71 | ), 72 | 'default' => array( 'ip_address' ), 73 | ) 74 | ); 75 | 76 | } 77 | 78 | 79 | static public function sanitize_settings( $settings ) { 80 | 81 | $settings['purge-days'] = empty( $settings['purge-days'] ) ? '' : absint( $settings['purge-days'] ); 82 | 83 | if ( 0 === $settings['purge-days'] ) { 84 | $settings['purge-days'] = ''; 85 | } 86 | 87 | return $settings; 88 | } 89 | 90 | /** 91 | * Displays an admin notice about running the plugin in production. 92 | * 93 | * @return void 94 | */ 95 | public static function display_db_notice() { 96 | 97 | $screen = get_current_screen(); 98 | 99 | if ( 'settings_page_wp-rest-api-log-settings' === $screen->id && false === get_option( 'wp-rest-api-log-db-notice-dismissed' ) ) { 100 | ?> 101 |
102 |

103 |
104 | 105 | 112 | WP_REST_API_Log_Common::filter_strip_all_tags(), 130 | ] 131 | ); 132 | 133 | if ( $data['page'] === WP_REST_API_Log_Settings_Base::$settings_page ) { 134 | $total_count = absint( count( WP_REST_API_Log_DB::get_all_log_ids() ) ); 135 | } 136 | 137 | if ( ! empty( $total_count ) ) { 138 | ob_start(); 139 | ?> 140 | 141 |

142 | 143 | 144 | 145 | 146 |

147 | 148 | 149 | 150 | '1', 40 | 'route-log-matching-mode' => '', 41 | 'route-filters' => '', 42 | ); 43 | } 44 | 45 | /** 46 | * Registers settings sections and fields for the Routes tab. 47 | * 48 | * @return void 49 | */ 50 | static public function register_routes_settings() { 51 | $key = self::$settings_key; 52 | 53 | register_setting( $key, $key, array( __CLASS__, 'sanitize_settings') ); 54 | 55 | $section = 'routes'; 56 | 57 | add_settings_section( $section, '', null, $key ); 58 | 59 | add_settings_field( 60 | 'ignore-core-oembed', 61 | __( 'Ignore core oEmbed', 'wp-rest-api-log' ), 62 | array( __CLASS__, 'settings_yes_no' ), 63 | $key, 64 | $section, 65 | array( 66 | 'key' => $key, 67 | 'name' => 'ignore-core-oembed', 68 | 'after' => '

' . __( 'Built-in /oembed/1.0/embed route', 'wp-rest-api-log' ) . '

', 69 | ) 70 | ); 71 | 72 | add_settings_field( 73 | 'route-log-matching-mode', 74 | __( 'Route Logging Mode', 'wp-rest-api-log' ), 75 | array( __CLASS__, 'settings_check_radio_list' ), 76 | $key, 77 | $section, 78 | array( 79 | 'key' => $key, 80 | 'name' => 'route-log-matching-mode', 81 | 'type' => 'radio', 82 | 'items' => WP_REST_API_Log_Filters::filter_modes(), 83 | 'default' => array( '' ), 84 | ) 85 | ); 86 | 87 | add_settings_field( 88 | 'route-filters', 89 | __( 'Route Filters', 'wp-rest-api-log' ), 90 | array( __CLASS__, 'settings_textarea' ), 91 | $key, 92 | $section, 93 | array( 94 | 'key' => $key, 95 | 'name' => 'route-filters', 96 | 'after' => ' 97 |

' . __( 'One route per line, examples', 'wp-rest-api-log' ) . '

98 |
    99 |
  • ' . __( 'Exact Match', 'wp-rest-api-log' ) . ': /wp/v2/posts
  • 100 |
  • ' . __( 'Wildcard Match', 'wp-rest-api-log' ) . ': /wp/v2/*
  • 101 |
  • ' . __( 'Regex', 'wp-rest-api-log' ) . ': ^\/wp\/v2\/.*$
  • 102 |
103 |

' . __( 'Regex matches must start with ^', 'wp-rest-api-log' ) . '

', 104 | ) 105 | ); 106 | } 107 | 108 | /** 109 | * Sanitzes the route settings. 110 | * 111 | * @param array $settings List of settings. 112 | * @return array 113 | */ 114 | static public function sanitize_settings( $settings ) { 115 | 116 | // Sanitize string fields. 117 | $string_fields = array( 118 | 'ignore-core-oembed', 119 | 'route-log-matching-mode', 120 | ); 121 | 122 | foreach( $string_fields as $field ) { 123 | if ( isset( $settings[ $field ] ) ) { 124 | $settings[ $field ] = sanitize_text_field( $settings[ $field ] ); 125 | } 126 | } 127 | 128 | return $settings; 129 | } 130 | 131 | 132 | } 133 | 134 | } 135 | 136 | -------------------------------------------------------------------------------- /includes/settings/class-wp-rest-api-log-settings.php: -------------------------------------------------------------------------------- 1 | 33 |
34 |

35 | REST API Log activated! Please visit the Settings page to customize the settings.', 'wp-rest-api-log' ), esc_url( admin_url( 'options-general.php?page=wp-rest-api-log-settings' ) ) ) ); ?> 36 |

37 |
38 | 83 |
84 | 85 |
86 | 87 | 88 | 93 |
94 |
95 | ' . esc_html__( 'Settings' ) . ' › REST API Log'; 152 | } 153 | 154 | 155 | } 156 | 157 | } -------------------------------------------------------------------------------- /includes/wp-cli/class-wp-rest-api-log-wp-cli-log.php: -------------------------------------------------------------------------------- 1 | get_log_ids_to_migrate(); 92 | 93 | $count = count( $ids ); 94 | if ( 0 === $count ) { 95 | WP_CLI::Line( "There are no more log entries that need to be migrated." ); 96 | return; 97 | } 98 | 99 | $progress_bar = WP_CLI\Utils\make_progress_bar( "Migrating {$count} entries:", $count, 1 ); 100 | $progress_bar->display(); 101 | 102 | foreach ( $ids as $id ) { 103 | $db->migrate_db_record( $id ); 104 | $progress_bar->tick(); 105 | } 106 | 107 | $progress_bar->finish(); 108 | 109 | WP_CLI::Success( "Log entries migrated" ); 110 | 111 | } 112 | 113 | // phpcs:ignore 114 | /** 115 | * Purges old REST API Log records. 116 | * 117 | * ## OPTIONS 118 | * 119 | * [] 120 | * Delete entries older than this many days, defaults to whatever you 121 | * have configured in the plugin settings 122 | * 123 | * --dry-run 124 | * Shows number of entries that would be deleted but does not 125 | * delete them 126 | * 127 | * ## EXAMPLES 128 | * 129 | * wp rest-api-log purge 130 | * 131 | * wp rest-api-log purge 90 132 | * 133 | * @synopsis [] [--dry-run] 134 | */ 135 | function purge( $positional_args, $assoc_args = array() ) { // phpcs:ignore 136 | 137 | $days_old = absint( ! empty( $positional_args[0] ) ? $positional_args[0] : 0 ); 138 | $dry_run = ! empty( $assoc_args['dry-run'] ); 139 | 140 | WP_CLI::Line( 'Getting old REST API log entries...' ); 141 | 142 | $ids = WP_REST_API_Log::get_old_log_ids( $days_old ); 143 | 144 | $count = count( $ids ); 145 | $number_deleted = 0; 146 | 147 | $progress = \WP_CLI\Utils\make_progress_bar( sprintf( 'Deleting %d old log entries', $count ), $count ); 148 | 149 | foreach ( $ids as $id ) { 150 | if ( ! $dry_run ) { 151 | wp_delete_post( $id, true ); 152 | $number_deleted++; 153 | } 154 | 155 | $progress->tick(); 156 | } 157 | 158 | $progress->finish(); 159 | 160 | WP_CLI::Success( sprintf( '%d entries purged', $number_deleted ) ); 161 | 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /includes/wp-cli/setup.php: -------------------------------------------------------------------------------- 1 | array( 'command' => 'rest-api-log', 'class' => 'WP_REST_API_Log_WP_CLI_Log' ), 5 | ); 6 | 7 | foreach ( $commands as $file => $command_info ) { 8 | require_once WP_REST_API_LOG_PATH . 'includes/wp-cli/' . $file; 9 | extract( $command_info ); 10 | WP_CLI::add_command( $command, $class ); 11 | } 12 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | \n" 13 | "Language-Team: LANGUAGE \n" 14 | "X-Generator: grunt-wp-i18n 0.5.4\n" 15 | 16 | #: admin/class-wp-rest-api-log-admin-list-table.php:56 17 | #: admin/partials/wp-rest-api-log-view-entry.php:73 18 | #: release/wp-rest-api-log/admin/class-wp-rest-api-log-admin-list-table.php:56 19 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:71 20 | msgid "Date" 21 | msgstr "" 22 | 23 | #: admin/class-wp-rest-api-log-admin-list-table.php:57 24 | #: admin/class-wp-rest-api-log-admin-list-table.php:122 25 | #: admin/partials/wp-rest-api-log-view-entry.php:75 26 | #: includes/class-wp-rest-api-log-post-type.php:82 27 | #: release/wp-rest-api-log/admin/class-wp-rest-api-log-admin-list-table.php:57 28 | #: release/wp-rest-api-log/admin/class-wp-rest-api-log-admin-list-table.php:122 29 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:73 30 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-post-type.php:82 31 | msgid "Method" 32 | msgstr "" 33 | 34 | #: admin/class-wp-rest-api-log-admin-list-table.php:58 35 | #: release/wp-rest-api-log/admin/class-wp-rest-api-log-admin-list-table.php:58 36 | msgid "Title" 37 | msgstr "" 38 | 39 | #: admin/class-wp-rest-api-log-admin-list-table.php:59 40 | #: admin/class-wp-rest-api-log-admin-list-table.php:137 41 | #: admin/partials/wp-rest-api-log-view-entry.php:76 42 | #: includes/class-wp-rest-api-log-post-type.php:104 43 | #: release/wp-rest-api-log/admin/class-wp-rest-api-log-admin-list-table.php:59 44 | #: release/wp-rest-api-log/admin/class-wp-rest-api-log-admin-list-table.php:137 45 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:74 46 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-post-type.php:104 47 | msgid "Status" 48 | msgstr "" 49 | 50 | #: admin/class-wp-rest-api-log-admin-list-table.php:60 51 | #: admin/partials/wp-rest-api-log-view-entry.php:77 52 | #: release/wp-rest-api-log/admin/class-wp-rest-api-log-admin-list-table.php:60 53 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:75 54 | msgid "Elapsed Time" 55 | msgstr "" 56 | 57 | #: admin/class-wp-rest-api-log-admin-list-table.php:61 58 | #: admin/partials/wp-rest-api-log-view-entry.php:78 59 | #: release/wp-rest-api-log/admin/class-wp-rest-api-log-admin-list-table.php:61 60 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:76 61 | msgid "Response Length" 62 | msgstr "" 63 | 64 | #: admin/class-wp-rest-api-log-admin-list-table.php:62 65 | #: admin/partials/wp-rest-api-log-view-entry.php:80 66 | #: includes/settings/class-wp-rest-api-log-settings-general.php:75 67 | #: release/wp-rest-api-log/admin/class-wp-rest-api-log-admin-list-table.php:62 68 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:78 69 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-general.php:75 70 | msgid "IP Address" 71 | msgstr "" 72 | 73 | #: admin/class-wp-rest-api-log-admin-list-table.php:63 74 | #: admin/partials/wp-rest-api-log-view-entry.php:79 75 | #: release/wp-rest-api-log/admin/class-wp-rest-api-log-admin-list-table.php:63 76 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:77 77 | msgid "User" 78 | msgstr "" 79 | 80 | #: admin/class-wp-rest-api-log-admin-list-table.php:123 81 | #: release/wp-rest-api-log/admin/class-wp-rest-api-log-admin-list-table.php:123 82 | msgid "All Methods" 83 | msgstr "" 84 | 85 | #: admin/class-wp-rest-api-log-admin-list-table.php:138 86 | #: release/wp-rest-api-log/admin/class-wp-rest-api-log-admin-list-table.php:138 87 | msgid "All Statuses" 88 | msgstr "" 89 | 90 | #: admin/class-wp-rest-api-log-admin-list-table.php:152 91 | #: admin/partials/wp-rest-api-log-view-entry.php:74 92 | #: release/wp-rest-api-log/admin/class-wp-rest-api-log-admin-list-table.php:152 93 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:72 94 | msgid "Source" 95 | msgstr "" 96 | 97 | #: admin/class-wp-rest-api-log-admin-list-table.php:153 98 | #: release/wp-rest-api-log/admin/class-wp-rest-api-log-admin-list-table.php:153 99 | msgid "All Sources" 100 | msgstr "" 101 | 102 | #: admin/class-wp-rest-api-log-admin.php:39 103 | #: includes/class-wp-rest-api-log-post-type.php:26 104 | #: release/wp-rest-api-log/admin/class-wp-rest-api-log-admin.php:27 105 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-post-type.php:26 106 | msgid "REST API Log Entries" 107 | msgstr "" 108 | 109 | #. Plugin Name of the plugin/theme 110 | msgid "REST API Log" 111 | msgstr "" 112 | 113 | #: admin/class-wp-rest-api-log-admin.php:177 114 | #: includes/class-wp-rest-api-log-post-type.php:27 115 | #: release/wp-rest-api-log/admin/class-wp-rest-api-log-admin.php:142 116 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-post-type.php:27 117 | msgid "REST API Log Entry" 118 | msgstr "" 119 | 120 | #: admin/class-wp-rest-api-log-admin.php:239 121 | #: includes/settings/class-wp-rest-api-log-settings.php:115 122 | #: release/wp-rest-api-log/admin/class-wp-rest-api-log-admin.php:204 123 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings.php:115 124 | msgid "Settings" 125 | msgstr "" 126 | 127 | #: admin/class-wp-rest-api-log-admin.php:243 128 | #: release/wp-rest-api-log/admin/class-wp-rest-api-log-admin.php:208 129 | msgid "Log" 130 | msgstr "" 131 | 132 | #: admin/partials/admin-help.php:7 133 | #: release/wp-rest-api-log/admin/partials/admin-help.php:7 134 | msgid "Contact" 135 | msgstr "" 136 | 137 | #: admin/partials/admin-help.php:9 138 | #: release/wp-rest-api-log/admin/partials/admin-help.php:9 139 | msgid "E-Mail" 140 | msgstr "" 141 | 142 | #: admin/partials/admin-help.php:10 143 | #: release/wp-rest-api-log/admin/partials/admin-help.php:10 144 | msgid "Twitter" 145 | msgstr "" 146 | 147 | #: admin/partials/admin-help.php:11 148 | #: release/wp-rest-api-log/admin/partials/admin-help.php:11 149 | msgid "GitHub" 150 | msgstr "" 151 | 152 | #: admin/partials/entry-property-links.php:6 153 | msgid "Download" 154 | msgstr "" 155 | 156 | #: admin/partials/entry-property-links.php:7 157 | msgid "Copy" 158 | msgstr "" 159 | 160 | #: admin/partials/wp-rest-api-log-api-is-disabled.php:3 161 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-api-is-disabled.php:3 162 | msgid "WP REST API Log" 163 | msgstr "" 164 | 165 | #: admin/partials/wp-rest-api-log-api-is-disabled.php:6 166 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-api-is-disabled.php:6 167 | msgid "The WP REST API is not enabled" 168 | msgstr "" 169 | 170 | #: admin/partials/wp-rest-api-log-view-entry.php:10 171 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:10 172 | msgid "Cheatin’ uh?" 173 | msgstr "" 174 | 175 | #: admin/partials/wp-rest-api-log-view-entry.php:11 176 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:11 177 | msgid "You are not allowed to read posts in this post type." 178 | msgstr "" 179 | 180 | #: admin/partials/wp-rest-api-log-view-entry.php:22 181 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:22 182 | msgid "Invalid WP REST API Log Entry ID" 183 | msgstr "" 184 | 185 | #: admin/partials/wp-rest-api-log-view-entry.php:62 186 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:60 187 | msgid "Route:" 188 | msgstr "" 189 | 190 | #: admin/partials/wp-rest-api-log-view-entry.php:69 191 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:67 192 | msgid "Details" 193 | msgstr "" 194 | 195 | #: admin/partials/wp-rest-api-log-view-entry.php:82 196 | #: includes/settings/class-wp-rest-api-log-settings-general.php:76 197 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:80 198 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-general.php:76 199 | msgid "HTTP X Forwarded For" 200 | msgstr "" 201 | 202 | #: admin/partials/wp-rest-api-log-view-entry.php:91 203 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:89 204 | msgid "Request Headers" 205 | msgstr "" 206 | 207 | #: admin/partials/wp-rest-api-log-view-entry.php:107 208 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:96 209 | msgid "Query Parameters" 210 | msgstr "" 211 | 212 | #: admin/partials/wp-rest-api-log-view-entry.php:123 213 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:103 214 | msgid "Body Parameters" 215 | msgstr "" 216 | 217 | #: admin/partials/wp-rest-api-log-view-entry.php:141 218 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:112 219 | msgid "Body Content" 220 | msgstr "" 221 | 222 | #: admin/partials/wp-rest-api-log-view-entry.php:172 223 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:125 224 | msgid "Response Headers" 225 | msgstr "" 226 | 227 | #: admin/partials/wp-rest-api-log-view-entry.php:188 228 | msgid "Response Body" 229 | msgstr "" 230 | 231 | #: includes/class-wp-rest-api-log-controller.php:219 232 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-controller.php:141 233 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-controller.php:149 234 | msgid "Invalid REST API Log ID %d." 235 | msgstr "" 236 | 237 | #: includes/class-wp-rest-api-log-filters.php:14 238 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-filters.php:14 239 | msgid "All" 240 | msgstr "" 241 | 242 | #: includes/class-wp-rest-api-log-filters.php:15 243 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-filters.php:15 244 | msgid "Only Matching Filters" 245 | msgstr "" 246 | 247 | #: includes/class-wp-rest-api-log-filters.php:16 248 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-filters.php:16 249 | msgid "Exclude Matching Filters" 250 | msgstr "" 251 | 252 | #: includes/class-wp-rest-api-log-post-type.php:28 253 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-post-type.php:28 254 | msgid "Add New REST API Log Entries" 255 | msgstr "" 256 | 257 | #: includes/class-wp-rest-api-log-post-type.php:29 258 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-post-type.php:29 259 | msgid "Add New REST API Log Entry" 260 | msgstr "" 261 | 262 | #: includes/class-wp-rest-api-log-post-type.php:30 263 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-post-type.php:30 264 | msgid "New REST API Log Entry" 265 | msgstr "" 266 | 267 | #: includes/class-wp-rest-api-log-post-type.php:31 268 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-post-type.php:31 269 | msgid "Edit Publication Page" 270 | msgstr "" 271 | 272 | #: includes/class-wp-rest-api-log-post-type.php:32 273 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-post-type.php:32 274 | msgid "View REST API Log Entry" 275 | msgstr "" 276 | 277 | #: includes/class-wp-rest-api-log-post-type.php:33 278 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-post-type.php:33 279 | msgid "All REST API Log Entries" 280 | msgstr "" 281 | 282 | #: includes/class-wp-rest-api-log-post-type.php:34 283 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-post-type.php:34 284 | msgid "Search Entries" 285 | msgstr "" 286 | 287 | #: includes/class-wp-rest-api-log-post-type.php:35 288 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-post-type.php:35 289 | msgid "No REST API Log Entries found" 290 | msgstr "" 291 | 292 | #: includes/class-wp-rest-api-log-post-type.php:36 293 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-post-type.php:36 294 | msgid "No REST API Log Entries found in Trash" 295 | msgstr "" 296 | 297 | #: includes/class-wp-rest-api-log-post-type.php:83 298 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-post-type.php:83 299 | msgid "Methods" 300 | msgstr "" 301 | 302 | #: includes/class-wp-rest-api-log-post-type.php:105 303 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-post-type.php:105 304 | msgid "Statuses" 305 | msgstr "" 306 | 307 | #: includes/class-wp-rest-api-log-post-type.php:110 308 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-post-type.php:110 309 | msgid "Log Source" 310 | msgstr "" 311 | 312 | #: includes/class-wp-rest-api-log-post-type.php:111 313 | #: release/wp-rest-api-log/includes/class-wp-rest-api-log-post-type.php:111 314 | msgid "Log Sources" 315 | msgstr "" 316 | 317 | #: includes/settings/class-wp-rest-api-log-settings-base.php:54 318 | #: includes/settings/class-wp-rest-api-log-settings-general.php:19 319 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-base.php:54 320 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-general.php:19 321 | msgid "General" 322 | msgstr "" 323 | 324 | #: includes/settings/class-wp-rest-api-log-settings-base.php:235 325 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-base.php:235 326 | msgid "Yes" 327 | msgstr "" 328 | 329 | #: includes/settings/class-wp-rest-api-log-settings-base.php:236 330 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-base.php:236 331 | msgid "No" 332 | msgstr "" 333 | 334 | #: includes/settings/class-wp-rest-api-log-settings-elasticpress.php:19 335 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-elasticpress.php:19 336 | msgid "ElasticPress" 337 | msgstr "" 338 | 339 | #: includes/settings/class-wp-rest-api-log-settings-elasticpress.php:40 340 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-elasticpress.php:40 341 | msgid "Log ElasticPress API Calls" 342 | msgstr "" 343 | 344 | #: includes/settings/class-wp-rest-api-log-settings-general.php:40 345 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-general.php:40 346 | msgid "Enabled" 347 | msgstr "" 348 | 349 | #: includes/settings/class-wp-rest-api-log-settings-general.php:50 350 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-general.php:50 351 | msgid "Purge All %1$s Entries Now" 352 | msgstr "" 353 | 354 | #: includes/settings/class-wp-rest-api-log-settings-general.php:54 355 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-general.php:54 356 | msgid "Days to Retain Old Entries" 357 | msgstr "" 358 | 359 | #: includes/settings/class-wp-rest-api-log-settings-general.php:59 360 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-general.php:59 361 | msgid "" 362 | "Entries older than this will be automatically cleaned up, leave blank to " 363 | "keep all entries. %1$s" 364 | msgstr "" 365 | 366 | #: includes/settings/class-wp-rest-api-log-settings-general.php:68 367 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-general.php:68 368 | msgid "IP Address Display" 369 | msgstr "" 370 | 371 | #: includes/settings/class-wp-rest-api-log-settings-general.php:73 372 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-general.php:73 373 | msgid "Sets the IP address displayed in the list of log entries." 374 | msgstr "" 375 | 376 | #: includes/settings/class-wp-rest-api-log-settings-help.php:19 377 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-help.php:19 378 | msgid "Help" 379 | msgstr "" 380 | 381 | #: includes/settings/class-wp-rest-api-log-settings-routes.php:28 382 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-routes.php:28 383 | msgid "Routes" 384 | msgstr "" 385 | 386 | #: includes/settings/class-wp-rest-api-log-settings-routes.php:61 387 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-routes.php:61 388 | msgid "Ignore core oEmbed" 389 | msgstr "" 390 | 391 | #: includes/settings/class-wp-rest-api-log-settings-routes.php:68 392 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-routes.php:68 393 | msgid "Built-in /oembed/1.0/embed route" 394 | msgstr "" 395 | 396 | #: includes/settings/class-wp-rest-api-log-settings-routes.php:74 397 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-routes.php:74 398 | msgid "Route Logging Mode" 399 | msgstr "" 400 | 401 | #: includes/settings/class-wp-rest-api-log-settings-routes.php:89 402 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-routes.php:89 403 | msgid "Route Filters" 404 | msgstr "" 405 | 406 | #: includes/settings/class-wp-rest-api-log-settings-routes.php:97 407 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-routes.php:97 408 | msgid "One route per line, examples" 409 | msgstr "" 410 | 411 | #: includes/settings/class-wp-rest-api-log-settings-routes.php:99 412 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-routes.php:99 413 | msgid "Exact Match" 414 | msgstr "" 415 | 416 | #: includes/settings/class-wp-rest-api-log-settings-routes.php:100 417 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-routes.php:100 418 | msgid "Wildcard Match" 419 | msgstr "" 420 | 421 | #: includes/settings/class-wp-rest-api-log-settings-routes.php:101 422 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-routes.php:101 423 | msgid "Regex" 424 | msgstr "" 425 | 426 | #: includes/settings/class-wp-rest-api-log-settings-routes.php:103 427 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings-routes.php:103 428 | msgid "Regex matches must start with ^" 429 | msgstr "" 430 | 431 | #: includes/settings/class-wp-rest-api-log-settings.php:35 432 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings.php:35 433 | msgid "" 434 | "REST API Log activated! Please visit the " 435 | "Settings page to customize the settings." 436 | msgstr "" 437 | 438 | #: includes/settings/class-wp-rest-api-log-settings.php:66 439 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings.php:66 440 | msgid "REST API Log Settings" 441 | msgstr "" 442 | 443 | #: includes/settings/class-wp-rest-api-log-settings.php:90 444 | #: release/wp-rest-api-log/includes/settings/class-wp-rest-api-log-settings.php:90 445 | msgid "Save Changes" 446 | msgstr "" 447 | 448 | #: release/wp-rest-api-log/admin/partials/wp-rest-api-log-view-entry.php:132 449 | msgid "Response" 450 | msgstr "" 451 | 452 | #. Plugin URI of the plugin/theme 453 | msgid "https://github.com/petenelson/wp-rest-api-log" 454 | msgstr "" 455 | 456 | #. Description of the plugin/theme 457 | msgid "Logs requests and responses for the REST API" 458 | msgstr "" 459 | 460 | #. Author of the plugin/theme 461 | msgid "Pete Nelson" 462 | msgstr "" -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin St, Fifth Floor, Boston, MA 02110, USA 6 | 7 | Everyone is permitted to copy and distribute verbatim copies 8 | of this license document, but changing it is not allowed. 9 | 10 | Preamble 11 | 12 | The licenses for most software are designed to take away your 13 | freedom to share and change it. By contrast, the GNU General Public 14 | License is intended to guarantee your freedom to share and change free 15 | software--to make sure the software is free for all its users. This 16 | General Public License applies to most of the Free Software 17 | Foundation's software and to any other program whose authors commit to 18 | using it. (Some other Free Software Foundation software is covered by 19 | the GNU Library General Public License instead.) You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | this service if you wish), that you receive source code or can get it 26 | if you want it, that you can change the software or use pieces of it 27 | in new free programs; and that you know you can do these things. 28 | 29 | To protect your rights, we need to make restrictions that forbid 30 | anyone to deny you these rights or to ask you to surrender the rights. 31 | These restrictions translate to certain responsibilities for you if you 32 | distribute copies of the software, or if you modify it. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must give the recipients all the rights that 36 | you have. You must make sure that they, too, receive or can get the 37 | source code. And you must show them these terms so they know their 38 | rights. 39 | 40 | We protect your rights with two steps: (1) copyright the software, and 41 | (2) offer you this license which gives you legal permission to copy, 42 | distribute and/or modify the software. 43 | 44 | Also, for each author's protection and ours, we want to make certain 45 | that everyone understands that there is no warranty for this free 46 | software. If the software is modified by someone else and passed on, we 47 | want its recipients to know that what they have is not the original, so 48 | that any problems introduced by others will not reflect on the original 49 | authors' reputations. 50 | 51 | Finally, any free program is threatened constantly by software 52 | patents. We wish to avoid the danger that redistributors of a free 53 | program will individually obtain patent licenses, in effect making the 54 | program proprietary. To prevent this, we have made it clear that any 55 | patent must be licensed for everyone's free use or not licensed at all. 56 | 57 | The precise terms and conditions for copying, distribution and 58 | modification follow. 59 | 60 | GNU GENERAL PUBLIC LICENSE 61 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 62 | 63 | 0. This License applies to any program or other work which contains 64 | a notice placed by the copyright holder saying it may be distributed 65 | under the terms of this General Public License. The "Program", below, 66 | refers to any such program or work, and a "work based on the Program" 67 | means either the Program or any derivative work under copyright law: 68 | that is to say, a work containing the Program or a portion of it, 69 | either verbatim or with modifications and/or translated into another 70 | language. (Hereinafter, translation is included without limitation in 71 | the term "modification".) Each licensee is addressed as "you". 72 | 73 | Activities other than copying, distribution and modification are not 74 | covered by this License; they are outside its scope. The act of 75 | running the Program is not restricted, and the output from the Program 76 | is covered only if its contents constitute a work based on the 77 | Program (independent of having been made by running the Program). 78 | Whether that is true depends on what the Program does. 79 | 80 | 1. You may copy and distribute verbatim copies of the Program's 81 | source code as you receive it, in any medium, provided that you 82 | conspicuously and appropriately publish on each copy an appropriate 83 | copyright notice and disclaimer of warranty; keep intact all the 84 | notices that refer to this License and to the absence of any warranty; 85 | and give any other recipients of the Program a copy of this License 86 | along with the Program. 87 | 88 | You may charge a fee for the physical act of transferring a copy, and 89 | you may at your option offer warranty protection in exchange for a fee. 90 | 91 | 2. You may modify your copy or copies of the Program or any portion 92 | of it, thus forming a work based on the Program, and copy and 93 | distribute such modifications or work under the terms of Section 1 94 | above, provided that you also meet all of these conditions: 95 | 96 | a) You must cause the modified files to carry prominent notices 97 | stating that you changed the files and the date of any change. 98 | 99 | b) You must cause any work that you distribute or publish, that in 100 | whole or in part contains or is derived from the Program or any 101 | part thereof, to be licensed as a whole at no charge to all third 102 | parties under the terms of this License. 103 | 104 | c) If the modified program normally reads commands interactively 105 | when run, you must cause it, when started running for such 106 | interactive use in the most ordinary way, to print or display an 107 | announcement including an appropriate copyright notice and a 108 | notice that there is no warranty (or else, saying that you provide 109 | a warranty) and that users may redistribute the program under 110 | these conditions, and telling the user how to view a copy of this 111 | License. (Exception: if the Program itself is interactive but 112 | does not normally print such an announcement, your work based on 113 | the Program is not required to print an announcement.) 114 | 115 | These requirements apply to the modified work as a whole. If 116 | identifiable sections of that work are not derived from the Program, 117 | and can be reasonably considered independent and separate works in 118 | themselves, then this License, and its terms, do not apply to those 119 | sections when you distribute them as separate works. But when you 120 | distribute the same sections as part of a whole which is a work based 121 | on the Program, the distribution of the whole must be on the terms of 122 | this License, whose permissions for other licensees extend to the 123 | entire whole, and thus to each and every part regardless of who wrote it. 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wp-rest-api-log", 3 | "version": "1.6.6", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "minimist": { 8 | "version": "1.2.6", 9 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", 10 | "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", 11 | "dev": true 12 | }, 13 | "wp-readme-to-markdown": { 14 | "version": "1.0.1", 15 | "resolved": "https://registry.npmjs.org/wp-readme-to-markdown/-/wp-readme-to-markdown-1.0.1.tgz", 16 | "integrity": "sha512-5ZmQzhNZIBcTLYhIovyhBXucDD+JUhL7YAqfmLzPudCLFRodSLbsWtoiYREG/Z55TN1Zn+Rko5eKH965m1L8SA==", 17 | "dev": true, 18 | "requires": { 19 | "minimist": "^1.2.0" 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wp-rest-api-log", 3 | "version": "1.6.9", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/petenelson/wp-rest-api-log.git" 7 | }, 8 | "license": "GPL-2.0-or-later", 9 | "homepage": "https://github.com/petenelson/wp-rest-api-log", 10 | "scripts": { 11 | "clean": "rm -rf release && mkdir release", 12 | "build": "npm run clean && npm run readme-to-md && npm run copy", 13 | "copy": "npm run copy:php && npm run copy:txt && npm run copy:dist", 14 | "readme-to-md": "wp-readme-to-md --screenshot-url=https://raw.githubusercontent.com/petenelson/wp-rest-api-log/master/assets/{screenshot}.png && npm run badges", 15 | "badges": "awk '/WordPress plugin to log REST API requests and responses/{while(getline line<\"badges.md\"){print line}} //' readme.md >readmetmp && mv readmetmp readme.md", 16 | "copy:php": "cp *.php release && cp -R admin release && cp -R includes release", 17 | "copy:txt": "cp *.txt release", 18 | "copy:dist": "cp -R dist release" 19 | }, 20 | "dependencies": {}, 21 | "devDependencies": { 22 | "wp-readme-to-markdown": "^1.0.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | ./tests/ 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # REST API Log # 2 | **Contributors:** [gungeekatx](https://profiles.wordpress.org/gungeekatx/) 3 | **Tags:** wp rest api, rest api, wp api, api, json, json api, log, logging, elasticpress, elasticsearch 4 | **Donate link:** https://github.com/petenelson/wp-rest-api-log 5 | **Requires at least:** 4.7 6 | **Tested up to:** 6.2 7 | **Stable tag:** 1.7.0 8 | **License:** GPLv2 or later 9 | **License URI:** http://www.gnu.org/licenses/gpl-2.0.html 10 | 11 | [![Code Climate](https://codeclimate.com/github/petenelson/wp-rest-api-log/badges/gpa.svg)](https://codeclimate.com/github/petenelson/wp-rest-api-log) 12 | [![Travis CI](https://travis-ci.org/petenelson/wp-rest-api-log.svg)](https://travis-ci.org/petenelson/wp-rest-api-log) 13 | 14 | WordPress plugin to log REST API requests and responses 15 | 16 | ## Description ## 17 | 18 | WordPress plugin to log [REST API](http://v2.wp-api.org/) requests and responses (for v2 of the API). 19 | 20 | Includes: 21 | 22 | * WordPress admin page to view and search log entries 23 | * API endpoint to access log entries via JSON 24 | * Filters to customize logging 25 | * Custom endpoint logging 26 | * ElasticPress logging 27 | 28 | Find us on [GitHub](https://github.com/petenelson/wp-rest-api-log)! 29 | 30 | Roadmap 31 | 32 | * Better search capabilities for log entries via the REST API endpoint 33 | 34 | 35 | ## Installation ## 36 | 37 | 1. Upload the wp-rest-api-log directory to the `/wp-content/plugins/` directory 38 | 2. Activate the plugin through the 'Plugins' menu in WordPress 39 | 3. Go to Settings -> REST API Log to enable or disable logging 40 | 4. Go to Tools -> REST API Log to start viewing log entries 41 | 42 | 43 | ## Changelog ## 44 | 45 | ### v1.7.0 May 8, 2023 ### 46 | * Fixed bugs related to Method and Status filtering. 47 | * Removed deprecated FILTER_SANITIZE_STRING calls. 48 | * Updated highlight.js version 49 | * Updated clipboard.js version 50 | 51 | ### v1.6.9 September 9, 2022 ### 52 | * Updated highlight.js version 53 | * Updated clipboard.js version 54 | 55 | ### v1.6.8 October 30, 2020 ### 56 | * Updated the Purge All Entries functionality in the admin to purge batches of 25 at a time. 57 | * Updated CLI purge command with a progress bar. 58 | * Fixed ClipboardJS error (props itowhid06) 59 | 60 | ### v1.6.7 March 31, 2019 ### 61 | * Added admin notice about running the plugin on a production server 62 | * Set the default purge days to 7 63 | * Updated clipboard.js version 64 | 65 | ### v1.6.6 November 9, 2018 ### 66 | * Moved taxonomy registration to a separate file, made taxonomies not public to prevent them from automatically showing in Yoast SEO sitemaps 67 | * Updated highlight.js version 68 | * Updated minimum WP version to 4.7 69 | * Updated unit test framework 70 | 71 | ### v1.6.5 July 26, 2017 ### 72 | * Fixed some escaping issues in admin and new-line characters when saving to database (props davidanderson) 73 | * Updated highlight.js and clipboard.js versions 74 | 75 | ### v1.6.4 May 26, 2017 ### 76 | * Fixed an issue with the URL in the settings tabs (props davidanderson) 77 | 78 | ### v1.6.3 March 28, 2017 ### 79 | * Updated logging for multidimensional query parameters (props mnelson4) 80 | 81 | ### v1.6.2 March 10, 2017 ### 82 | * Fixed bug in HTTPS download URLs. 83 | * Fixed bug in download URL permissions. 84 | 85 | ### v1.6.0 March 9, 2017 ### 86 | * Added ability to download request and response fields as JSON files, as well as copy to clipboard. 87 | * Added button on settings page to Purge All Log Entries. 88 | * Tweaked some of the ElasticPress routes that skip logging. 89 | 90 | ### v1.5.2 February 21, 2017 ### 91 | * Fixed a bug with ElasticPress logging getting stuck in a loop regarding the _nodes/plugins URL. 92 | 93 | ### v1.5.1 February 15, 2017 ### 94 | * Removed hidden custom taxonomies from the navigation menu admin (props [phh](https://github.com/phh) for the pull request). 95 | 96 | ### v1.5.0 February 2, 2017 ### 97 | * Added logging for the user making the request (props [drsdre](https://github.com/drsdre) for the pull request). 98 | * Added Settings and Log links from the Plugins page. 99 | * Updated term fetching when viewing log entries for fewer database queries and better performance. 100 | * Updated highlight.js to 9.9.0 101 | 102 | ### v1.4.0 January 23, 2017 ### 103 | * Added the ability to filter routes for logging, either include or exclude specific routes. 104 | 105 | ### v1.3.0 December 5, 2016 ### 106 | * Added support for logging HTTP_X_FORWARDED_FOR, useful for servers behind a proxy or load balancer. 107 | * Changed plugin name to REST API Log 108 | * Changed the wp-rest-api-log post type 'public' setting to false to prevent it from showing up in searches. 109 | * Updated Highlight JS version to 9.7.0 110 | * Updated the internal process for granting administrator role access to the custom post type 111 | * Bug fix: Header values with colons were not being stored correctly. 112 | * Bug fix: Use proper HTML escaping when viewing log entries. 113 | 114 | ### v1.2.0 July 6, 2016 ### 115 | * Added support for [ElasticPress](https://wordpress.org/plugins/elasticpress/) logging 116 | * Fixed undefined constant error on Help page (props vinigarcia87) 117 | 118 | ### v1.1.1 May 15, 2016 ### 119 | * Fixed error during activation (props pavelevap) 120 | 121 | ### v1.1.0 April 28, 2016 ### 122 | * Added cron job to cleanup old log entries 123 | * Added setting to exclude the WP core /oembed API endpoint 124 | * Don't diplay log entries in the Insert Link modal 125 | 126 | ### v1.0.0-beta2 April 10, 2016 ### 127 | * Switched from custom tables to built-in WordPress tables using a custom post type (wp-rest-api-log) 128 | * Method, status, and source are now tracked using taxonomies 129 | * Viewing log entries now uses the standard WordPress admin UI, includes filters for method, status, and source 130 | * Added admin settings with the option to enable or disable logging 131 | * Added WP-CLI support: wp rest-api-log 132 | * Added .pot file to support translations 133 | 134 | **NOTE: if you are upgrading from the previous version, you can run the "wp rest-api-log migrate" WP-CLI command to migrate your existing logs into the new custom post type** 135 | 136 | ### v1.0.0-beta1 July 9, 2015 ### 137 | * Initial release 138 | 139 | 140 | ## Upgrade Notice ## 141 | 142 | ### v1.7.0 May 8, 2023 ### 143 | * Fixed bugs related to Method and Status filtering. 144 | * Removed deprecated FILTER_SANITIZE_STRING calls. 145 | * Updated highlight.js version 146 | * Updated clipboard.js version 147 | 148 | ## Frequently Asked Questions ## 149 | 150 | ### How do I use ElasticPress logging? ### 151 | 152 | [ElasticPress](https://wordpress.org/plugins/elasticpress/) is a plugin than interfaces WordPress to the [ElasticSearch](https://www.elastic.co/products/elasticsearch) search service. Because ElasticSearch has its own REST API for indexing and searching data, it was a natural fit to extend logging support via this REST API Logging plugin. 153 | 154 | You can go into Settings > ElasticPress to enable logging for requests & responses. You can also disable REST API logging if you only need ElasticPress logging. 155 | 156 | 157 | ## Screenshots ## 158 | 159 | ### 1. Sample list of log entries ### 160 | ![Sample list of log entries](https://raw.githubusercontent.com/petenelson/wp-rest-api-log/master/assets/screenshot-1.png) 161 | 162 | ### 2. Sample log entry details ### 163 | ![Sample log entry details](https://raw.githubusercontent.com/petenelson/wp-rest-api-log/master/assets/screenshot-2.png) 164 | 165 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === REST API Log === 2 | Contributors: gungeekatx 3 | Tags: wp rest api, rest api, wp api, api, json, json api, log, logging, elasticpress, elasticsearch 4 | Donate link: https://github.com/petenelson/wp-rest-api-log 5 | Requires at least: 4.7 6 | Tested up to: 6.2 7 | Stable tag: 1.7.0 8 | License: GPLv2 or later 9 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 10 | 11 | WordPress plugin to log REST API requests and responses 12 | 13 | == Description == 14 | 15 | WordPress plugin to log [REST API](http://v2.wp-api.org/) requests and responses (for v2 of the API). 16 | 17 | Includes: 18 | 19 | * WordPress admin page to view and search log entries 20 | * API endpoint to access log entries via JSON 21 | * Filters to customize logging 22 | * Custom endpoint logging 23 | * ElasticPress logging 24 | 25 | Find us on [GitHub](https://github.com/petenelson/wp-rest-api-log)! 26 | 27 | Roadmap 28 | 29 | * Better search capabilities for log entries via the REST API endpoint 30 | 31 | 32 | == Installation == 33 | 34 | 1. Upload the wp-rest-api-log directory to the `/wp-content/plugins/` directory 35 | 2. Activate the plugin through the 'Plugins' menu in WordPress 36 | 3. Go to Settings -> REST API Log to enable or disable logging 37 | 4. Go to Tools -> REST API Log to start viewing log entries 38 | 39 | 40 | == Changelog == 41 | 42 | = v1.7.0 May 8, 2023 = 43 | * Fixed bugs related to Method and Status filtering. 44 | * Removed deprecated FILTER_SANITIZE_STRING calls. 45 | * Updated highlight.js version 46 | * Updated clipboard.js version 47 | 48 | = v1.6.9 September 9, 2022 = 49 | * Updated highlight.js version 50 | * Updated clipboard.js version 51 | 52 | = v1.6.8 October 30, 2020 = 53 | * Updated the Purge All Entries functionality in the admin to purge batches of 25 at a time. 54 | * Updated CLI purge command with a progress bar. 55 | * Fixed ClipboardJS error (props itowhid06) 56 | 57 | = v1.6.7 March 31, 2019 = 58 | * Added admin notice about running the plugin on a production server 59 | * Set the default purge days to 7 60 | * Updated clipboard.js version 61 | 62 | = v1.6.6 November 9, 2018 = 63 | * Moved taxonomy registration to a separate file, made taxonomies not public to prevent them from automatically showing in Yoast SEO sitemaps 64 | * Updated highlight.js version 65 | * Updated minimum WP version to 4.7 66 | * Updated unit test framework 67 | 68 | = v1.6.5 July 26, 2017 = 69 | * Fixed some escaping issues in admin and new-line characters when saving to database (props davidanderson) 70 | * Updated highlight.js and clipboard.js versions 71 | 72 | = v1.6.4 May 26, 2017 = 73 | * Fixed an issue with the URL in the settings tabs (props davidanderson) 74 | 75 | = v1.6.3 March 28, 2017 = 76 | * Updated logging for multidimensional query parameters (props mnelson4) 77 | 78 | = v1.6.2 March 10, 2017 = 79 | * Fixed bug in HTTPS download URLs. 80 | * Fixed bug in download URL permissions. 81 | 82 | = v1.6.0 March 9, 2017 = 83 | * Added ability to download request and response fields as JSON files, as well as copy to clipboard. 84 | * Added button on settings page to Purge All Log Entries. 85 | * Tweaked some of the ElasticPress routes that skip logging. 86 | 87 | = v1.5.2 February 21, 2017 = 88 | * Fixed a bug with ElasticPress logging getting stuck in a loop regarding the _nodes/plugins URL. 89 | 90 | = v1.5.1 February 15, 2017 = 91 | * Removed hidden custom taxonomies from the navigation menu admin (props [phh](https://github.com/phh) for the pull request). 92 | 93 | = v1.5.0 February 2, 2017 = 94 | * Added logging for the user making the request (props [drsdre](https://github.com/drsdre) for the pull request). 95 | * Added Settings and Log links from the Plugins page. 96 | * Updated term fetching when viewing log entries for fewer database queries and better performance. 97 | * Updated highlight.js to 9.9.0 98 | 99 | = v1.4.0 January 23, 2017 = 100 | * Added the ability to filter routes for logging, either include or exclude specific routes. 101 | 102 | = v1.3.0 December 5, 2016 = 103 | * Added support for logging HTTP_X_FORWARDED_FOR, useful for servers behind a proxy or load balancer. 104 | * Changed plugin name to REST API Log 105 | * Changed the wp-rest-api-log post type 'public' setting to false to prevent it from showing up in searches. 106 | * Updated Highlight JS version to 9.7.0 107 | * Updated the internal process for granting administrator role access to the custom post type 108 | * Bug fix: Header values with colons were not being stored correctly. 109 | * Bug fix: Use proper HTML escaping when viewing log entries. 110 | 111 | = v1.2.0 July 6, 2016 = 112 | * Added support for [ElasticPress](https://wordpress.org/plugins/elasticpress/) logging 113 | * Fixed undefined constant error on Help page (props vinigarcia87) 114 | 115 | = v1.1.1 May 15, 2016 = 116 | * Fixed error during activation (props pavelevap) 117 | 118 | = v1.1.0 April 28, 2016 = 119 | * Added cron job to cleanup old log entries 120 | * Added setting to exclude the WP core /oembed API endpoint 121 | * Don't diplay log entries in the Insert Link modal 122 | 123 | = v1.0.0-beta2 April 10, 2016 = 124 | * Switched from custom tables to built-in WordPress tables using a custom post type (wp-rest-api-log) 125 | * Method, status, and source are now tracked using taxonomies 126 | * Viewing log entries now uses the standard WordPress admin UI, includes filters for method, status, and source 127 | * Added admin settings with the option to enable or disable logging 128 | * Added WP-CLI support: wp rest-api-log 129 | * Added .pot file to support translations 130 | 131 | **NOTE: if you are upgrading from the previous version, you can run the "wp rest-api-log migrate" WP-CLI command to migrate your existing logs into the new custom post type** 132 | 133 | = v1.0.0-beta1 July 9, 2015 = 134 | * Initial release 135 | 136 | 137 | == Upgrade Notice == 138 | 139 | = v1.7.0 May 8, 2023 = 140 | * Fixed bugs related to Method and Status filtering. 141 | * Removed deprecated FILTER_SANITIZE_STRING calls. 142 | * Updated highlight.js version 143 | * Updated clipboard.js version 144 | 145 | == Frequently Asked Questions == 146 | 147 | = How do I use ElasticPress logging? = 148 | 149 | [ElasticPress](https://wordpress.org/plugins/elasticpress/) is a plugin than interfaces WordPress to the [ElasticSearch](https://www.elastic.co/products/elasticsearch) search service. Because ElasticSearch has its own REST API for indexing and searching data, it was a natural fit to extend logging support via this REST API Logging plugin. 150 | 151 | You can go into Settings > ElasticPress to enable logging for requests & responses. You can also disable REST API logging if you only need ElasticPress logging. 152 | 153 | 154 | == Screenshots == 155 | 156 | 1. Sample list of log entries 157 | 2. Sample log entry details 158 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | plugin_root = dirname( dirname( __FILE__ ) ); 28 | if ( ! file_exists( $this->plugin_root . '/vendor/autoload.php' ) ) { 29 | throw new Exception( 30 | 'ERROR' . PHP_EOL . PHP_EOL . 31 | 'You must use Composer to install the test suite\'s dependencies.' . PHP_EOL 32 | ); 33 | } 34 | $autoloader = require_once $this->plugin_root . '/vendor/autoload.php'; 35 | 36 | // Give access to tests_add_filter() function. 37 | require_once $wp_develop_dir . '/tests/phpunit/includes/functions.php'; 38 | 39 | tests_add_filter( 'muplugins_loaded', [ $this, 'manually_load_plugin' ] ); 40 | 41 | // Start up the WP testing environment. 42 | require $wp_develop_dir . '/tests/phpunit/includes/bootstrap.php'; 43 | } 44 | 45 | /** 46 | * Manually load the plugin being tested. 47 | */ 48 | public function manually_load_plugin() { 49 | require $this->plugin_root . '/wp-rest-api-log.php'; 50 | } 51 | } 52 | 53 | $wp_rest_api_log_tests = new WP_REST_API_Log_Tests_Bootstrap(); 54 | $wp_rest_api_log_tests->bootstrap(); 55 | -------------------------------------------------------------------------------- /tests/test-common.php: -------------------------------------------------------------------------------- 1 | assertTrue( ! empty( $valid_methods ) ); 19 | $this->assertContains( 'GET', $valid_methods ); 20 | } 21 | 22 | /** 23 | * Test that GET is a valid method 24 | */ 25 | function test_valid_method() { 26 | $valid_methods = WP_REST_API_Log_Common::valid_methods(); 27 | $this->assertTrue( WP_REST_API_Log_Common::is_valid_method( 'GET' ) ); 28 | } 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /tests/test-filters.php: -------------------------------------------------------------------------------- 1 | assertEquals( "^\/wp\/v2$", $route_regex ); 19 | 20 | // Wildcard matches. 21 | $route_regex = WP_REST_API_Log_Filters::route_to_regex( '/wp/*' ); 22 | $this->assertEquals( "^\/wp\/.*$", $route_regex ); 23 | 24 | $route_regex = WP_REST_API_Log_Filters::route_to_regex( '/wp/v2/*' ); 25 | $this->assertEquals( "^\/wp\/v2\/.*$", $route_regex, '/wp/v2/*' ); 26 | 27 | // Regex should have no changes. 28 | $route_regex = WP_REST_API_Log_Filters::route_to_regex( '^/wp/v2/.*' ); 29 | $this->assertEquals( '^/wp/v2/.*', $route_regex ); 30 | 31 | // This is not treated as regex, so it get mangled. 32 | $route_regex = WP_REST_API_Log_Filters::route_to_regex( '.*/wp/v2/$' ); 33 | $this->assertEquals( "^..*\/wp\/v2\/$$", $route_regex ); 34 | } 35 | 36 | public function test_filter_modes() { 37 | $modes = WP_REST_API_Log_Filters::filter_modes(); 38 | $this->assertArrayHasKey( '', $modes ); 39 | $this->assertArrayHasKey( 'log_matches', $modes ); 40 | $this->assertArrayHasKey( 'exclude_matches', $modes ); 41 | } 42 | 43 | public function test_route_logging_all_routes() { 44 | 45 | $option_key = 'wp-rest-api-log-settings-routes'; 46 | 47 | // Set the options to log everything. 48 | $option_value = array( 49 | 'route-log-matching-mode' => '', 50 | ); 51 | 52 | update_option( 'wp-rest-api-log-settings-routes', $option_value ); 53 | 54 | // Make sure we can log any route. 55 | $this->assertTrue( WP_REST_API_Log_Filters::can_log_route( '/wp/v2' ) ); 56 | $this->assertTrue( WP_REST_API_Log_Filters::can_log_route( '/wp/v2/posts' ) ); 57 | $this->assertTrue( WP_REST_API_Log_Filters::can_log_route( '/wp/v2/users' ) ); 58 | 59 | } 60 | 61 | 62 | public function test_route_logging_only_matched_routes() { 63 | 64 | $option_key = 'wp-rest-api-log-settings-routes'; 65 | 66 | // Set the options to log only matching routes. 67 | $option_value = array( 68 | 'route-log-matching-mode' => 'log_matches', 69 | 'route-filters' => 70 | "/wp/v2 71 | /route/wildcard* 72 | ^\/route\/regex-exact$ 73 | ^\/route\/regex-wildcard.*$" 74 | ); 75 | 76 | update_option( 'wp-rest-api-log-settings-routes', $option_value ); 77 | 78 | // Exact match. 79 | $this->assertTrue( WP_REST_API_Log_Filters::can_log_route( '/wp/v2' ), '/wp/v2' ); 80 | 81 | // Basic wildcard. 82 | $this->assertTrue( WP_REST_API_Log_Filters::can_log_route( '/route/wildcard-route' ), '/route/wildcard-route' ); 83 | 84 | // Exact regex 85 | $this->assertTrue( WP_REST_API_Log_Filters::can_log_route( '/route/regex-exact' ), '/route/regex-exact' ); 86 | 87 | // Wildcard regex 88 | $this->assertTrue( WP_REST_API_Log_Filters::can_log_route( '/route/regex-wildcard/test' ), '/route/regex-wildcard/test' ); 89 | 90 | // Test non-matching routes. 91 | $this->assertFalse( WP_REST_API_Log_Filters::can_log_route( '/some/route' ), '/some/route' ); 92 | $this->assertFalse( WP_REST_API_Log_Filters::can_log_route( '/route/wildercard' ), '/route/wildercard' ); 93 | $this->assertFalse( WP_REST_API_Log_Filters::can_log_route( '/route/exact-regex' ), '/route/exact-regex' ); 94 | $this->assertFalse( WP_REST_API_Log_Filters::can_log_route( '/route/regexwildcard' ), '/route/regexwildcard' ); 95 | $this->assertFalse( WP_REST_API_Log_Filters::can_log_route( '/wp/v2/posts' ), '/wp/v2/posts' ); 96 | 97 | } 98 | 99 | public function test_route_logging_excluded_matched_routes() { 100 | 101 | $option_key = 'wp-rest-api-log-settings-routes'; 102 | 103 | // Set the options to log only matching routes. 104 | $option_value = array( 105 | 'route-log-matching-mode' => 'exclude_matches', 106 | 'route-filters' => 107 | "/wp/v2 108 | /route/wildcard* 109 | ^\/route\/regex-exact$ 110 | ^\/route\/regex-wildcard.*$" 111 | ); 112 | 113 | update_option( 'wp-rest-api-log-settings-routes', $option_value ); 114 | 115 | // Exact match. 116 | $this->assertFalse( WP_REST_API_Log_Filters::can_log_route( '/wp/v2' ), '/wp/v2' ); 117 | 118 | // Basic wildcard. 119 | $this->assertFalse( WP_REST_API_Log_Filters::can_log_route( '/route/wildcard-route' ), '/route/wildcard-route' ); 120 | 121 | // Exact regex 122 | $this->assertFalse( WP_REST_API_Log_Filters::can_log_route( '/route/regex-exact' ), '/route/regex-exact' ); 123 | 124 | // Wildcard regex 125 | $this->assertFalse( WP_REST_API_Log_Filters::can_log_route( '/route/regex-wildcard/test' ), '/route/regex-wildcard/test' ); 126 | 127 | // Test non-matching routes, should all be logged. 128 | $this->assertTrue( WP_REST_API_Log_Filters::can_log_route( '/some/route' ), '/some/route' ); 129 | $this->assertTrue( WP_REST_API_Log_Filters::can_log_route( '/route/wildercard' ), '/route/wildercard' ); 130 | $this->assertTrue( WP_REST_API_Log_Filters::can_log_route( '/route/exact-regex' ), '/route/exact-regex' ); 131 | $this->assertTrue( WP_REST_API_Log_Filters::can_log_route( '/route/regexwildcard' ), '/route/regexwildcard' ); 132 | $this->assertTrue( WP_REST_API_Log_Filters::can_log_route( '/wp/v2/posts' ), '/wp/v2/posts' ); 133 | 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /tests/test-settings.php: -------------------------------------------------------------------------------- 1 | assertNotEmpty( $settings ); 20 | $this->assertNotEmpty( $settings['logging-enabled'], 'logging-enabled is empty' ); 21 | 22 | } 23 | 24 | /** 25 | * Test that routes have default settings 26 | */ 27 | function test_default_routes_settings() { 28 | 29 | $settings = WP_REST_API_Log_Settings_Routes::get_default_settings(); 30 | $this->assertNotEmpty( $settings ); 31 | $this->assertNotEmpty( $settings['ignore-core-oembed'], 'ignore-core-oembed is empty' ); 32 | 33 | } 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /tests/test-taxonomies.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf( '\WP_Taxonomy', get_taxonomy( $taxonomy ) ); 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /uninstall.php: -------------------------------------------------------------------------------- 1 | prefix . 'wp_rest_api_log', 11 | $wpdb->prefix . 'wp_rest_api_logmeta', 12 | ); 13 | 14 | foreach ( $tables as $table_name ) { 15 | $wpdb->query( "drop table if exists $table_name"); 16 | } 17 | 18 | 19 | $options = array( 20 | 'wp-rest-api-log-meta-dbversion', 21 | 'wp-rest-api-log-entries-dbversion', 22 | 'wp-rest-api-log-settings-general', 23 | ); 24 | 25 | foreach ( $options as $option ) { 26 | delete_option( $option ); 27 | } 28 | -------------------------------------------------------------------------------- /wp-rest-api-log.php: -------------------------------------------------------------------------------- 1 |