98b1cfd0 by Jeff Balicki

courses

Signed-off-by: Jeff <jeff@gotenzing.com>
1 parent 6526179b
Showing 192 changed files with 2925 additions and 1 deletions
1
2 RewriteEngine on
3 RewriteRule ^product/(.*)$ /courses/$1 [R=302,NC,L]
4
1 # BEGIN WordPress 5 # BEGIN WordPress
2 # The directives (lines) between "BEGIN WordPress" and "END WordPress" are 6 # The directives (lines) between "BEGIN WordPress" and "END WordPress" are
3 # dynamically generated, and should only be modified via WordPress filters. 7 # dynamically generated, and should only be modified via WordPress filters.
...@@ -12,4 +16,5 @@ RewriteCond %{REQUEST_FILENAME} !-d ...@@ -12,4 +16,5 @@ RewriteCond %{REQUEST_FILENAME} !-d
12 RewriteRule . /index.php [L] 16 RewriteRule . /index.php [L]
13 </IfModule> 17 </IfModule>
14 18
15 # END WordPress
...\ No newline at end of file ...\ No newline at end of file
19 # END WordPress
20
......
1 # Redirection
2
3 [![Build Status](https://travis-ci.org/johngodley/redirection.svg?branch=master)](https://travis-ci.org/johngodley/redirection)
4
5 Redirection is a WordPress plugin to manage 301 redirections, keep track of 404 errors, and generally tidy up any loose ends your site may have. This is particularly useful if you are migrating pages from an old website, or are changing the directory of your WordPress installation.
6
7 Note: this is the current 'trunk' version of Redirection. It may be newer than what is in the WordPress.org plugin repository, and should be considered experimental.
8
9 ## Installation
10 Redirection can be installed by visiting the WordPress.org plugin page:
11
12 https://wordpress.org/plugins/redirection/
13
14 ## Customisation
15
16 ### Request Information
17
18 The following WordPress filters are available for customisation of a server requests:
19
20 - `redirection_request_url` - The request URL
21 - `redirection_request_agent` - The request user agent
22 - `redirection_request_referrer` - The request referrer
23 - `redirection_request_ip` - The request IP address
24
25 ### Logging
26
27 The following WordPress filters are available for customisation of logged data:
28
29 - `redirection_404_data` - Data to be inserted into the 404 table
30 - `redirection_log_data` - Data to be inserted into the redirect log table
31
32 ### Redirect source and target
33
34 - `redirection_url_source` - The original URL used before matching a request. Return false to stop any redirection
35 - `redirection_url_target` - The target URL after a request has been matched (and after any regular expression captures have been replaced). Return false to stop any redirection
36
37 ### Dynamic URL data
38
39 The following special words can be inserted into a target URL:
40
41 - `%userid%` - Insert user's ID
42 - `%userlogin%` - Insert user's login name
43 - `%userurl%` - Insert user's custom URL
44
45 ### Management
46
47 - `redirection_permalink_changed` - return boolean if a post's permalink has changed
48 - `redirection_remove_existing` - fired when a post changes permalink and we need to clear existing redirects that might affect it
49
50 Additionally, if the target URL is a number without any slashes then Redirection will treat it as a post ID and redirect to the full URL for that post.
51
52 ## Support
53
54 Please raise any bug reports or enhancement requests here. Pull requests are always welcome.
55
56 You can find a more detailed description of the plugin on the [Redirection home page](http://urbangiraffe.com/plugins/redirection/)
57
58 Translations can be added here:
59
60 https://translate.wordpress.org/projects/wp-plugins/redirection
1 <?php
2
3 /**
4 * Return an error to the client, and trigger the WordPress error page
5 */
6 class Error_Action extends Red_Action {
7 /**
8 * Set WordPress to show the error page
9 *
10 * @return void
11 */
12 public function run() {
13 wp_reset_query();
14
15 // Set the query to be a 404
16 set_query_var( 'is_404', true );
17
18 // Return the 404 page
19 add_filter( 'template_include', [ $this, 'template_include' ] );
20
21 // Clear any posts if this is actually a valid URL
22 add_filter( 'pre_handle_404', [ $this, 'pre_handle_404' ] );
23
24 // Ensure the appropriate http code is returned
25 add_action( 'wp', [ $this, 'wp' ] );
26 }
27
28 /**
29 * Output selected HTTP code, as well as redirection header
30 *
31 * @return void
32 */
33 public function wp() {
34 status_header( $this->code );
35 nocache_headers();
36
37 global $wp_version;
38
39 if ( version_compare( $wp_version, '5.1', '<' ) ) {
40 header( 'X-Redirect-Agent: redirection' );
41 } else {
42 header( 'X-Redirect-By: redirection' );
43 }
44 }
45
46 public function pre_handle_404() {
47 global $wp_query;
48
49 // Page comments plugin interferes with this
50 $wp_query->posts = [];
51 return false;
52 }
53
54 public function template_include() {
55 return get_404_template();
56 }
57
58 public function name() {
59 return __( 'Error (404)', 'redirection' );
60 }
61 }
1 <?php
2
3 /**
4 * The 'do nothing' action. This really does nothing, and is used to short-circuit Redirection so that it doesn't trigger other redirects.
5 */
6 class Nothing_Action extends Red_Action {
7 /**
8 * Issue an action when nothing happens. This stops further processing.
9 *
10 * @return void
11 */
12 public function run() {
13 do_action( 'redirection_do_nothing', $this->get_target() );
14 }
15
16 public function name() {
17 return __( 'Do nothing (ignore)', 'redirection' );
18 }
19 }
1 <?php
2
3 require_once dirname( __FILE__ ) . '/url.php';
4
5 /**
6 * A 'pass through' action. Matches a rewrite rather than a redirect, and uses PHP to fetch data from a remote URL.
7 */
8 class Pass_Action extends Url_Action {
9 /**
10 * Process an external passthrough - a URL that lives external to this server.
11 *
12 * @param String $url Target URL.
13 * @return void
14 */
15 public function process_external( $url ) {
16 // This is entirely at the user's risk. The $url is set by the user
17 // phpcs:ignore
18 echo wp_remote_fopen( $url );
19 }
20
21 /**
22 * Process an internal passthrough - a URL that lives on the same server. Here we change the request URI and continue without making a remote request.
23 *
24 * @param String $target Target URL.
25 * @return void
26 */
27 public function process_internal( $target ) {
28 // Another URL on the server
29 $pos = strpos( $target, '?' );
30 $_SERVER['REQUEST_URI'] = $target;
31 $_SERVER['PATH_INFO'] = $target;
32
33 if ( $pos ) {
34 $_SERVER['QUERY_STRING'] = substr( $target, $pos + 1 );
35 $_SERVER['PATH_INFO'] = $target;
36
37 // Take the query params in the target and make them the params for this request
38 parse_str( $_SERVER['QUERY_STRING'], $_GET );
39 }
40 }
41
42 /**
43 * Is a URL external?
44 *
45 * @param String $target URL to test.
46 * @return boolean
47 */
48 public function is_external( $target ) {
49 return substr( $target, 0, 7 ) === 'http://' || substr( $target, 0, 8 ) === 'https://';
50 }
51
52 /**
53 * Pass the data from the target
54 *
55 * @return void
56 */
57 public function run() {
58 // External target
59 $target = $this->get_target();
60 if ( $target === null ) {
61 return;
62 }
63
64 if ( $this->is_external( $target ) ) {
65 // Pass on to an external request, echo the results, and then stop
66 $this->process_external( $target );
67 exit();
68 }
69
70 // Change the request and carry on
71 $this->process_internal( $target );
72 }
73
74 public function name() {
75 return __( 'Pass-through', 'redirection' );
76 }
77 }
1 <?php
2
3 require_once dirname( __FILE__ ) . '/url.php';
4
5 /**
6 * URL action - redirect to a URL
7 */
8 class Random_Action extends Url_Action {
9 /**
10 * Get a random URL
11 *
12 * @return string|null
13 */
14 private function get_random_url() {
15 // Pick a random WordPress page
16 global $wpdb;
17
18 $id = $wpdb->get_var( "SELECT ID FROM {$wpdb->prefix}posts WHERE post_status='publish' AND post_password='' AND post_type='post' ORDER BY RAND() LIMIT 0,1" );
19 if ( $id ) {
20 $url = get_permalink( $id );
21
22 if ( $url ) {
23 return $url;
24 }
25 }
26
27 return null;
28 }
29
30 /**
31 * Run this action. May not return from this function.
32 *
33 * @return void
34 */
35 public function run() {
36 $target = $this->get_random_url();
37
38 if ( $target ) {
39 $this->redirect_to( $target );
40 }
41 }
42
43 public function needs_target() {
44 return false;
45 }
46
47 public function name() {
48 return __( 'Redirect to random post', 'redirection' );
49 }
50 }
1 <?php
2
3 /**
4 * URL action - redirect to a URL
5 */
6 class Url_Action extends Red_Action {
7 /**
8 * Redirect to a URL
9 *
10 * @param string $target Target URL.
11 * @return void
12 */
13 protected function redirect_to( $target ) {
14 // This is a known redirect, possibly extenal
15 // phpcs:ignore
16 $redirect = wp_redirect( $target, $this->get_code(), 'redirection' );
17
18 if ( $redirect ) {
19 /** @psalm-suppress InvalidGlobal */
20 global $wp_version;
21
22 if ( version_compare( $wp_version, '5.1', '<' ) ) {
23 header( 'X-Redirect-Agent: redirection' );
24 }
25
26 die();
27 }
28 }
29
30 /**
31 * Run this action. May not return from this function.
32 *
33 * @return void
34 */
35 public function run() {
36 $target = $this->get_target();
37
38 if ( $target !== null ) {
39 $this->redirect_to( $target );
40 }
41 }
42
43 /**
44 * Does this action need a target?
45 *
46 * @return boolean
47 */
48 public function needs_target() {
49 return true;
50 }
51
52 public function name() {
53 return __( 'Redirect to URL', 'redirection' );
54 }
55 }
1 <?php
2 /**
3 * @api {get} /redirection/v1/404 Get 404 logs
4 * @apiName GetLogs
5 * @apiDescription Get a paged list of 404 logs after applying a set of filters and result ordering.
6 * @apiGroup 404
7 *
8 * @apiUse 404QueryParams
9 *
10 * @apiUse 404List
11 * @apiUse 401Error
12 * @apiUse 404Error
13 */
14
15 /**
16 * @api {post} /redirection/v1/bulk/404/:type Bulk action
17 * @apiName BulkAction
18 * @apiDescription Delete 404 logs by ID
19 * @apiGroup 404
20 *
21 * @apiParam (URL) {String="delete"} :type Type of bulk action that is applied to every log ID.
22 *
23 * @apiParam (Query Parameter) {String[]} [items] Array of group IDs to perform the action on
24 * @apiParam (Query Parameter) {Boolean=false} [global] Perform action globally using the filter parameters
25 * @apiUse 404QueryParams
26 *
27 * @apiUse 404List
28 * @apiUse 401Error
29 * @apiUse 404Error
30 * @apiUse 400MissingError
31 */
32
33 /**
34 * @apiDefine 404QueryParams 404 log query parameters
35 *
36 * @apiParam (Query Parameter) {String} [filterBy[ip]] Filter the results by the supplied IP
37 * @apiParam (Query Parameter) {String} [filterBy[url]] Filter the results by the supplied URL
38 * @apiParam (Query Parameter) {String} [filterBy[url-]exact] Filter the results by the exact URL (not a substring match, as per `url`)
39 * @apiParam (Query Parameter) {String} [filterBy[referrer]] Filter the results by the supplied referrer
40 * @apiParam (Query Parameter) {String} [filterBy[agent]] Filter the results by the supplied user agent
41 * @apiParam (Query Parameter) {String} [filterBy[target]] Filter the results by the supplied redirect target
42 * @apiParam (Query Parameter) {String} [filterBy[domain]] Filter the results by the supplied domain name
43 * @apiParam (Query Parameter) {String="head","get","post"} [filterBy[method]] Filter the results by the supplied HTTP request method
44 * @apiParam (Query Parameter) {Integer} [filterBy[http]] Filter the results by the supplied redirect HTTP code
45 * @apiParam (Query Parameter) {string="ip","url"} [orderby] Order by IP or URL
46 * @apiParam (Query Parameter) {String="asc","desc"} [direction] Direction to order the results by (ascending or descending)
47 * @apiParam (Query Parameter) {Integer{1...200}} [per_page=25] Number of results per request
48 * @apiParam (Query Parameter) {Integer} [page=0] Current page of results
49 * @apiParam (Query Parameter) {String="ip","url"} [groupBy] Group by IP or URL
50 */
51
52 /**
53 * @apiDefine 404List
54 *
55 * @apiSuccess {Object[]} items Array of 404 log objects
56 * @apiSuccess {Integer} items.id ID of 404 log entry
57 * @apiSuccess {String} items.created Date the 404 log entry was recorded
58 * @apiSuccess {Integer} items.created_time Unix time value for `created`
59 * @apiSuccess {Integer} items.url The requested URL that caused the 404 log entry
60 * @apiSuccess {String} items.agent User agent of the client initiating the request
61 * @apiSuccess {Integer} items.referrer Referrer of the client initiating the request
62 * @apiSuccess {Integer} total Number of items
63 *
64 * @apiSuccessExample {json} Success 200:
65 * HTTP/1.1 200 OK
66 * {
67 * "items": [
68 * {
69 * "id": 3,
70 * "created": "2019-01-01 12:12:00,
71 * "created_time": "12345678",
72 * "url": "/the-url",
73 * "agent": "FancyBrowser",
74 * "referrer": "http://site.com/previous/,
75 * }
76 * ],
77 * "total": 1
78 * }
79 */
80
81 /**
82 * 404 API endpoint
83 */
84 class Redirection_Api_404 extends Redirection_Api_Filter_Route {
85 /**
86 * 404 API endpoint constructor
87 *
88 * @param String $namespace Namespace.
89 */
90 public function __construct( $namespace ) {
91 $orders = [ 'url', 'ip', 'total', 'count', '' ];
92 $filters = [ 'ip', 'url-exact', 'referrer', 'agent', 'url', 'domain', 'method', 'http' ];
93
94 register_rest_route( $namespace, '/404', array(
95 'args' => $this->get_filter_args( $orders, $filters ),
96 $this->get_route( WP_REST_Server::READABLE, 'route_404', [ $this, 'permission_callback_manage' ] ),
97 ) );
98
99 register_rest_route( $namespace, '/bulk/404/(?P<bulk>delete)', array(
100 $this->get_route( WP_REST_Server::EDITABLE, 'route_bulk', [ $this, 'permission_callback_delete' ] ),
101 'args' => array_merge( $this->get_filter_args( $orders, $filters ), [
102 'items' => [
103 'description' => 'Comma separated list of item IDs to perform action on',
104 'type' => 'array',
105 'items' => [
106 'description' => 'Item ID',
107 'type' => [ 'string', 'number' ],
108 ],
109 ],
110 ] ),
111 ) );
112 }
113
114 /**
115 * Checks a manage capability
116 *
117 * @param WP_REST_Request $request Request.
118 * @return Bool
119 */
120 public function permission_callback_manage( WP_REST_Request $request ) {
121 return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_404_MANAGE );
122 }
123
124 /**
125 * Checks a delete capability
126 *
127 * @param WP_REST_Request $request Request.
128 * @return Bool
129 */
130 public function permission_callback_delete( WP_REST_Request $request ) {
131 return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_404_DELETE );
132 }
133
134 /**
135 * Get 404 log
136 *
137 * @param WP_REST_Request $request The request.
138 * @return WP_Error|array Return an array of results, or a WP_Error
139 */
140 public function route_404( WP_REST_Request $request ) {
141 return $this->get_404( $request->get_params() );
142 }
143
144 /**
145 * Perform action on 404s
146 *
147 * @param WP_REST_Request $request The request.
148 * @return WP_Error|array Return an array of results, or a WP_Error
149 */
150 public function route_bulk( WP_REST_Request $request ) {
151 $params = $request->get_params();
152
153 if ( isset( $params['items'] ) && is_array( $params['items'] ) ) {
154 $items = $params['items'];
155
156 foreach ( $items as $item ) {
157 if ( is_numeric( $item ) ) {
158 Red_404_Log::delete( intval( $item, 10 ) );
159 } elseif ( isset( $params['groupBy'] ) ) {
160 $group_by = sanitize_text_field( $params['groupBy'] );
161 $delete_by = 'url-exact';
162
163 if ( in_array( $group_by, [ 'ip', 'agent' ], true ) ) {
164 $delete_by = $group_by;
165 }
166
167 Red_404_Log::delete_all( [ 'filterBy' => [ $delete_by => $item ] ] );
168 }
169 }
170
171 if ( isset( $params['groupBy'] ) && $params['groupBy'] === 'url-exact' ) {
172 unset( $params['groupBy'] );
173 }
174 } elseif ( isset( $params['global'] ) && $params['global'] ) {
175 Red_404_Log::delete_all( $params );
176 }
177
178 return $this->get_404( $params );
179 }
180
181 /**
182 * Get 404 log
183 *
184 * @param array $params The request.
185 * @return WP_Error|array Return an array of results, or a WP_Error
186 */
187 private function get_404( array $params ) {
188 if ( isset( $params['groupBy'] ) && in_array( $params['groupBy'], [ 'ip', 'url', 'agent', 'url-exact' ], true ) ) {
189 $group_by = sanitize_text_field( $params['groupBy'] );
190 if ( $group_by === 'url-exact' ) {
191 $group_by = 'url';
192 }
193
194 return Red_404_Log::get_grouped( $group_by, $params );
195 }
196
197 return Red_404_Log::get_filtered( $params );
198 }
199 }
1 <?php
2
3 /**
4 * @api {get} /redirection/v1/export/:module/:format Export redirects
5 * @apiName Export
6 * @apiDescription Export redirects for a module to Apache, CSV, Nginx, or JSON format
7 * @apiGroup Import/Export
8 *
9 * @apiParam (URL) {String="1","2","3","all"} :module The module to export, with 1 being WordPress, 2 is Apache, and 3 is Nginx
10 * @apiParam (URL) {String="csv","apache","nginx","json"} :format The format of the export
11 *
12 * @apiSuccess {String} data Exported data
13 * @apiSuccess {Integer} total Number of items exported
14 *
15 * @apiUse 401Error
16 * @apiUse 404Error
17 * @apiError (Error 400) redirect_export_invalid_module Invalid module
18 * @apiErrorExample {json} 404 Error Response:
19 * HTTP/1.1 400 Bad Request
20 * {
21 * "code": "redirect_export_invalid_module",
22 * "message": "Invalid module"
23 * }
24 */
25 class Redirection_Api_Export extends Redirection_Api_Route {
26 public function __construct( $namespace ) {
27 register_rest_route( $namespace, '/export/(?P<module>1|2|3|all)/(?P<format>csv|apache|nginx|json)', array(
28 $this->get_route( WP_REST_Server::READABLE, 'route_export', [ $this, 'permission_callback_manage' ] ),
29 ) );
30 }
31
32 public function permission_callback_manage( WP_REST_Request $request ) {
33 return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_IO_MANAGE );
34 }
35
36 public function route_export( WP_REST_Request $request ) {
37 $module = sanitize_text_field( $request['module'] );
38 $format = 'json';
39
40 if ( in_array( $request['format'], [ 'csv', 'apache', 'nginx', 'json' ], true ) ) {
41 $format = sanitize_text_field( $request['format'] );
42 }
43
44 $export = Red_FileIO::export( $module, $format );
45 if ( $export === false ) {
46 return $this->add_error_details( new WP_Error( 'redirect_export_invalid_module', 'Invalid module' ), __LINE__ );
47 }
48
49 return array(
50 'data' => $export['data'],
51 'total' => $export['total'],
52 );
53 }
54 }
1 <?php
2
3 /**
4 * @api {get} /redirection/v1/import/file/:group_id Import redirects
5 * @apiName Import
6 * @apiDescription Import redirects from CSV, JSON, or Apache .htaccess
7 * @apiGroup Import/Export
8 *
9 * @apiParam (URL) {Integer} :group_id The group ID to import into
10 * @apiParam (File) {File} file The multipart form upload containing the file to import
11 *
12 * @apiSuccess {Integer} imported Number of items imported
13 *
14 * @apiUse 401Error
15 * @apiUse 404Error
16 * @apiError (Error 400) redirect_import_invalid_group Invalid group
17 * @apiErrorExample {json} 404 Error Response:
18 * HTTP/1.1 400 Bad Request
19 * {
20 * "code": "redirect_import_invalid_group",
21 * "message": "Invalid group"
22 * }
23 * @apiError (Error 400) redirect_import_invalid_file Invalid file upload
24 * @apiErrorExample {json} 404 Error Response:
25 * HTTP/1.1 400 Bad Request
26 * {
27 * "code": "redirect_import_invalid_file",
28 * "message": "Invalid file upload"
29 * }
30 */
31 class Redirection_Api_Import extends Redirection_Api_Route {
32 public function __construct( $namespace ) {
33 register_rest_route( $namespace, '/import/file/(?P<group_id>\d+)', array(
34 $this->get_route( WP_REST_Server::EDITABLE, 'route_import_file', [ $this, 'permission_callback_manage' ] ),
35 ) );
36
37 register_rest_route( $namespace, '/import/plugin', array(
38 $this->get_route( WP_REST_Server::READABLE, 'route_plugin_import_list', [ $this, 'permission_callback_manage' ] ),
39 ) );
40
41 register_rest_route( $namespace, '/import/plugin', array(
42 $this->get_route( WP_REST_Server::EDITABLE, 'route_plugin_import', [ $this, 'permission_callback_manage' ] ),
43 ) );
44 }
45
46 public function permission_callback_manage( WP_REST_Request $request ) {
47 return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_IO_MANAGE );
48 }
49
50 public function route_plugin_import_list( WP_REST_Request $request ) {
51 include_once dirname( __DIR__ ) . '/models/importer.php';
52
53 return array( 'importers' => Red_Plugin_Importer::get_plugins() );
54 }
55
56 public function route_plugin_import( WP_REST_Request $request ) {
57 include_once dirname( __DIR__ ) . '/models/importer.php';
58
59 $params = $request->get_params( $request );
60 $groups = Red_Group::get_all();
61 $plugins = is_array( $request['plugin'] ) ? $request['plugin'] : [ $request['plugin'] ];
62 $plugins = array_map( 'sanitize_text_field', $plugins );
63 $total = 0;
64
65 foreach ( $plugins as $plugin ) {
66 $total += Red_Plugin_Importer::import( $plugin, $groups[0]['id'] );
67 }
68
69 return [ 'imported' => $total ];
70 }
71
72 public function route_import_file( WP_REST_Request $request ) {
73 $upload = $request->get_file_params();
74 $upload = isset( $upload['file'] ) ? $upload['file'] : false;
75 $group_id = intval( $request['group_id'], 10 );
76
77 if ( $upload && is_uploaded_file( $upload['tmp_name'] ) ) {
78 $count = Red_FileIO::import( $group_id, $upload );
79
80 if ( $count !== false ) {
81 return array(
82 'imported' => $count,
83 );
84 }
85
86 return $this->add_error_details( new WP_Error( 'redirect_import_invalid_group', 'Invalid group' ), __LINE__ );
87 }
88
89 return $this->add_error_details( new WP_Error( 'redirect_import_invalid_file', 'Invalid file' ), __LINE__ );
90 }
91
92 }
1 <?php
2
3 /**
4 * @api {get} /redirection/v1/log Get logs
5 * @apiName GetLogs
6 * @apiDescription Get a paged list of redirect logs after applying a set of filters and result ordering.
7 * @apiGroup Log
8 *
9 * @apiUse LogQueryParams
10 *
11 * @apiUse LogList
12 * @apiUse 401Error
13 * @apiUse 404Error
14 */
15
16 /**
17 * @api {post} /redirection/v1/log Delete logs
18 * @apiName DeleteLogs
19 * @apiDescription Delete logs by filter. If no filter is supplied then all entries will be deleted. The endpoint will return the next page of results after.
20 * performing the action, based on the supplied query parameters. This information can be used to refresh a list displayed to the client.
21 * @apiGroup Log
22 *
23 * @apiParam (Query Parameter) {String} filterBy[ip] Filter the results by the supplied IP
24 * @apiParam (Query Parameter) {String} filterBy[url] Filter the results by the supplied URL
25 * @apiParam (Query Parameter) {String} filterBy[url-exact] Filter the results by the exact URL (not a substring match, as per `url`)
26 * @apiParam (Query Parameter) {String} filterBy[referrer] Filter the results by the supplied referrer
27 * @apiParam (Query Parameter) {String} filterBy[agent] Filter the results by the supplied user agent
28 * @apiParam (Query Parameter) {String} filterBy[target] Filter the results by the supplied redirect target
29 *
30 * @apiUse LogList
31 * @apiUse 401Error
32 * @apiUse 404Error
33 */
34
35 /**
36 * @api {post} /redirection/v1/bulk/log/:type Bulk action
37 * @apiName BulkAction
38 * @apiDescription Delete logs by ID
39 * @apiGroup Log
40 *
41 * @apiParam (URL) {String="delete"} :type Type of bulk action that is applied to every log ID.
42 * @apiParam (Query Parameter) {String[]} [items] Array of group IDs to perform the action on
43 * @apiParam (Query Parameter) {Boolean=false} [global] Perform action globally using the filter parameters
44 * @apiUse LogQueryParams
45 *
46 * @apiUse LogList
47 * @apiUse 401Error
48 * @apiUse 404Error
49 * @apiUse 400MissingError
50 */
51
52 /**
53 * @apiDefine LogQueryParams Log query parameters
54 *
55 * @apiParam (Query Parameter) {String} [filterBy[ip]] Filter the results by the supplied IP
56 * @apiParam (Query Parameter) {String} [filterBy[url]] Filter the results by the supplied URL
57 * @apiParam (Query Parameter) {String} [filterBy[url-]exact] Filter the results by the exact URL (not a substring match, as per `url`)
58 * @apiParam (Query Parameter) {String} [filterBy[referrer]] Filter the results by the supplied referrer
59 * @apiParam (Query Parameter) {String} [filterBy[agent]] Filter the results by the supplied user agent
60 * @apiParam (Query Parameter) {String} [filterBy[target]] Filter the results by the supplied redirect target
61 * @apiParam (Query Parameter) {String} [filterBy[domain]] Filter the results by the supplied domain name
62 * @apiParam (Query Parameter) {String} [filterBy[redirect_by]] Filter the results by the redirect agent
63 * @apiParam (Query Parameter) {String="head","get","post"} [filterBy[method]] Filter the results by the supplied HTTP request method
64 * @apiParam (Query Parameter) {String="ip","url"} [orderby] Order by IP or URL
65 * @apiParam (Query Parameter) {String="asc","desc"} [direction=desc] Direction to order the results by (ascending or descending)
66 * @apiParam (Query Parameter) {Integer{1...200}} [per_page=25] Number of results per request
67 * @apiParam (Query Parameter) {Integer} [page=0] Current page of results
68 * @apiParam (Query Parameter) {String="ip","url"} [groupBy] Group by IP or URL
69 */
70
71 /**
72 * @apiDefine LogList
73 *
74 * @apiSuccess {Object[]} items Array of log objects
75 * @apiSuccess {Integer} items.id ID of log entry
76 * @apiSuccess {String} items.created Date the log entry was recorded
77 * @apiSuccess {Integer} items.created_time Unix time value for `created`
78 * @apiSuccess {Integer} items.url The requested URL that caused the log entry
79 * @apiSuccess {String} items.agent User agent of the client initiating the request
80 * @apiSuccess {Integer} items.referrer Referrer of the client initiating the request
81 * @apiSuccess {Integer} total Number of items
82 *
83 * @apiSuccessExample {json} Success 200:
84 * HTTP/1.1 200 OK
85 * {
86 * "items": [
87 * {
88 * "id": 3,
89 * "created": "2019-01-01 12:12:00,
90 * "created_time": "12345678",
91 * "url": "/the-url",
92 * "agent": "FancyBrowser",
93 * "referrer": "http://site.com/previous/,
94 * }
95 * ],
96 * "total": 1
97 * }
98 */
99
100 /**
101 * Log API endpoint
102 */
103 class Redirection_Api_Log extends Redirection_Api_Filter_Route {
104 /**
105 * Log API endpoint constructor
106 *
107 * @param String $namespace Namespace.
108 */
109 public function __construct( $namespace ) {
110 $orders = [ 'url', 'ip', 'total', 'count', '' ];
111 $filters = [ 'ip', 'url-exact', 'referrer', 'agent', 'url', 'target', 'domain', 'method', 'http', 'redirect_by' ];
112
113 register_rest_route( $namespace, '/log', array(
114 'args' => $this->get_filter_args( $orders, $filters ),
115 $this->get_route( WP_REST_Server::READABLE, 'route_log', [ $this, 'permission_callback_manage' ] ),
116 ) );
117
118 register_rest_route( $namespace, '/bulk/log/(?P<bulk>delete)', [
119 $this->get_route( WP_REST_Server::EDITABLE, 'route_bulk', [ $this, 'permission_callback_delete' ] ),
120 'args' => array_merge( $this->get_filter_args( $orders, $filters ), [
121 'items' => [
122 'description' => 'Comma separated list of item IDs to perform action on',
123 'type' => 'array',
124 'items' => [
125 'description' => 'Item ID',
126 'type' => [ 'string', 'number' ],
127 ],
128 ],
129 ] ),
130 ] );
131 }
132
133 /**
134 * Checks a manage capability
135 *
136 * @param WP_REST_Request $request Request.
137 * @return Bool
138 */
139 public function permission_callback_manage( WP_REST_Request $request ) {
140 return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_LOG_MANAGE );
141 }
142
143 /**
144 * Checks a delete capability
145 *
146 * @param WP_REST_Request $request Request.
147 * @return Bool
148 */
149 public function permission_callback_delete( WP_REST_Request $request ) {
150 return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_LOG_DELETE );
151 }
152
153 /**
154 * Get log list
155 *
156 * @param WP_REST_Request $request The request.
157 * @return WP_Error|array Return an array of results, or a WP_Error
158 */
159 public function route_log( WP_REST_Request $request ) {
160 return $this->get_logs( $request->get_params() );
161 }
162
163 /**
164 * Perform bulk action on logs
165 *
166 * @param WP_REST_Request $request The request.
167 * @return WP_Error|array Return an array of results, or a WP_Error
168 */
169 public function route_bulk( WP_REST_Request $request ) {
170 $params = $request->get_params();
171
172 if ( isset( $params['items'] ) && is_array( $params['items'] ) ) {
173 $items = $params['items'];
174
175 foreach ( $items as $item ) {
176 if ( is_numeric( $item ) ) {
177 Red_Redirect_Log::delete( intval( $item, 10 ) );
178 } elseif ( isset( $params['groupBy'] ) ) {
179 $delete_by = 'url-exact';
180
181 if ( in_array( $params['groupBy'], [ 'ip', 'agent' ], true ) ) {
182 $delete_by = sanitize_text_field( $params['groupBy'] );
183 }
184
185 Red_Redirect_Log::delete_all( [ 'filterBy' => [ $delete_by => $item ] ] );
186 }
187 }
188 } elseif ( isset( $params['global'] ) && $params['global'] ) {
189 Red_Redirect_Log::delete_all( $params );
190 }
191
192 return $this->route_log( $request );
193 }
194
195 private function get_logs( array $params ) {
196 if ( isset( $params['groupBy'] ) && in_array( $params['groupBy'], [ 'ip', 'url', 'agent' ], true ) ) {
197 return Red_Redirect_Log::get_grouped( sanitize_text_field( $params['groupBy'] ), $params );
198 }
199
200 return Red_Redirect_Log::get_filtered( $params );
201 }
202 }
1 <?php
2
3 /**
4 * 'Plugin' functions for Redirection
5 */
6 class Redirection_Api_Plugin extends Redirection_Api_Route {
7 public function __construct( $namespace ) {
8 register_rest_route( $namespace, '/plugin', array(
9 $this->get_route( WP_REST_Server::READABLE, 'route_status', [ $this, 'permission_callback_manage' ] ),
10 ) );
11
12 register_rest_route( $namespace, '/plugin', array(
13 $this->get_route( WP_REST_Server::EDITABLE, 'route_fixit', [ $this, 'permission_callback_manage' ] ),
14 'args' => [
15 'name' => array(
16 'description' => 'Name',
17 'type' => 'string',
18 ),
19 'value' => array(
20 'description' => 'Value',
21 'type' => 'string',
22 ),
23 ],
24 ) );
25
26 register_rest_route( $namespace, '/plugin/delete', array(
27 $this->get_route( WP_REST_Server::EDITABLE, 'route_delete', [ $this, 'permission_callback_manage' ] ),
28 ) );
29
30 register_rest_route( $namespace, '/plugin/test', array(
31 $this->get_route( WP_REST_Server::ALLMETHODS, 'route_test', [ $this, 'permission_callback_manage' ] ),
32 ) );
33
34 register_rest_route( $namespace, '/plugin/data', array(
35 $this->get_route( WP_REST_Server::EDITABLE, 'route_database', [ $this, 'permission_callback_manage' ] ),
36 'args' => [
37 'upgrade' => [
38 'description' => 'Upgrade parameter',
39 'type' => 'string',
40 'enum' => array(
41 'stop',
42 'skip',
43 'retry',
44 ),
45 ],
46 ],
47 ) );
48 }
49
50 public function permission_callback_manage( WP_REST_Request $request ) {
51 return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_SUPPORT_MANAGE );
52 }
53
54 public function route_status( WP_REST_Request $request ) {
55 include_once dirname( REDIRECTION_FILE ) . '/models/fixer.php';
56
57 $fixer = new Red_Fixer();
58 return $fixer->get_json();
59 }
60
61 public function route_fixit( WP_REST_Request $request ) {
62 include_once dirname( REDIRECTION_FILE ) . '/models/fixer.php';
63
64 $params = $request->get_params();
65 $fixer = new Red_Fixer();
66
67 if ( isset( $params['name'] ) && isset( $params['value'] ) ) {
68 global $wpdb;
69
70 $fixer->save_debug( sanitize_text_field( $params['name'] ), sanitize_text_field( $params['value'] ) );
71
72 $groups = intval( $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}redirection_groups" ), 10 );
73 if ( $groups === 0 ) {
74 Red_Group::create( 'new group', 1 );
75 }
76 } else {
77 $fixer->fix( $fixer->get_status() );
78 }
79
80 return $fixer->get_json();
81 }
82
83 public function route_delete() {
84 if ( is_multisite() ) {
85 return new WP_Error( 'redirect_delete_multi', 'Multisite installations must delete the plugin from the network admin' );
86 }
87
88 $plugin = Redirection_Admin::init();
89 $plugin->plugin_uninstall();
90
91 $current = get_option( 'active_plugins' );
92 $plugin_position = array_search( basename( dirname( REDIRECTION_FILE ) ) . '/' . basename( REDIRECTION_FILE ), $current );
93 if ( $plugin_position !== false ) {
94 array_splice( $current, $plugin_position, 1 );
95 update_option( 'active_plugins', $current );
96 }
97
98 return array( 'location' => admin_url() . 'plugins.php' );
99 }
100
101 public function route_test( WP_REST_Request $request ) {
102 return array(
103 'success' => true,
104 );
105 }
106
107 public function route_database( WP_REST_Request $request ) {
108 $params = $request->get_params();
109 $status = new Red_Database_Status();
110 $upgrade = false;
111
112 if ( isset( $params['upgrade'] ) && in_array( $params['upgrade'], [ 'stop', 'skip' ], true ) ) {
113 $upgrade = sanitize_text_field( $params['upgrade'] );
114 }
115
116 // Check upgrade
117 if ( ! $status->needs_updating() && ! $status->needs_installing() ) {
118 /* translators: version number */
119 $status->set_error( sprintf( __( 'Your database does not need updating to %s.', 'redirection' ), REDIRECTION_DB_VERSION ) );
120
121 return $status->get_json();
122 }
123
124 if ( $upgrade === 'stop' ) {
125 $status->stop_update();
126 } elseif ( $upgrade === 'skip' ) {
127 $status->set_next_stage();
128 }
129
130 if ( $upgrade === false || $status->get_current_stage() ) {
131 $database = new Red_Database();
132 $database->apply_upgrade( $status );
133 }
134
135 return $status->get_json();
136 }
137 }
1 <?php
2 /**
3 * @api {get} /redirection/v1/setting Get settings
4 * @apiName GetSettings
5 * @apiDescription Get all settings for Redirection. This includes user-configurable settings, as well as necessary WordPress settings.
6 * @apiGroup Settings
7 *
8 * @apiUse SettingItem
9 * @apiUse 401Error
10 * @apiUse 404Error
11 */
12
13 /**
14 * @api {post} /redirection/v1/setting Update settings
15 * @apiName UpdateSettings
16 * @apiDescription Update Redirection settings. Note you can do partial updates, and only the values specified will be changed.
17 * @apiGroup Settings
18 *
19 * @apiParam {Object} settings An object containing all the settings to update
20 * @apiParamExample {json} settings:
21 * {
22 * "expire_redirect": 14,
23 * "https": false
24 * }
25 *
26 * @apiUse SettingItem
27 * @apiUse 401Error
28 * @apiUse 404Error
29 */
30
31 /**
32 * @apiDefine SettingItem Settings
33 * Redirection settings
34 *
35 * @apiSuccess {Object[]} settings An object containing all settings
36 * @apiSuccess {String} settings.expire_redirect
37 * @apiSuccess {String} settings.token
38 * @apiSuccess {String} settings.monitor_post
39 * @apiSuccess {String[]} settings.monitor_types
40 * @apiSuccess {String} settings.associated_redirect
41 * @apiSuccess {String} settings.auto_target
42 * @apiSuccess {String} settings.expire_redirect
43 * @apiSuccess {String} settings.expire_404
44 * @apiSuccess {String} settings.modules
45 * @apiSuccess {String} settings.newsletter
46 * @apiSuccess {String} settings.redirect_cache
47 * @apiSuccess {String} settings.ip_logging
48 * @apiSuccess {String} settings.last_group_id
49 * @apiSuccess {String} settings.rest_api
50 * @apiSuccess {String} settings.https
51 * @apiSuccess {String} settings.headers
52 * @apiSuccess {String} settings.database
53 * @apiSuccess {String} settings.relocate Relocate this site to the specified domain (and path)
54 * @apiSuccess {String="www","nowww",""} settings.preferred_domain Preferred canonical domain
55 * @apiSuccess {String[]} settings.aliases Array of domains that will be redirected to the current WordPress site
56 * @apiSuccess {Object[]} groups An array of groups
57 * @apiSuccess {String} groups.label Name of the group
58 * @apiSuccess {Integer} groups.value Group ID
59 * @apiSuccess {String} installed The path that WordPress is installed in
60 * @apiSuccess {Boolean} canDelete True if Redirection can be deleted, false otherwise (on multisite, for example)
61 * @apiSuccess {String[]} post_types Array of WordPress post types
62 *
63 * @apiSuccessExample {json} Success-Response:
64 * HTTP/1.1 200 OK
65 * {
66 * "settings": {
67 * "expire_redirect": 7,
68 * "https": true
69 * },
70 * "groups": [
71 * { label: 'My group', value: 5 }
72 * ],
73 * "installed": "/var/html/wordpress",
74 * "canDelete": true,
75 * "post_types": [
76 * "post",
77 * "page"
78 * ]
79 * }
80 */
81
82 class Redirection_Api_Settings extends Redirection_Api_Route {
83 public function __construct( $namespace ) {
84 register_rest_route( $namespace, '/setting', array(
85 $this->get_route( WP_REST_Server::READABLE, 'route_settings', [ $this, 'permission_callback_manage' ] ),
86 $this->get_route( WP_REST_Server::EDITABLE, 'route_save_settings', [ $this, 'permission_callback_manage' ] ),
87 ) );
88 }
89
90 public function route_settings( WP_REST_Request $request ) {
91 if ( ! function_exists( 'get_home_path' ) ) {
92 include_once ABSPATH . '/wp-admin/includes/file.php';
93 }
94
95 return [
96 'settings' => red_get_options(),
97 'groups' => $this->groups_to_json( Red_Group::get_for_select() ),
98 'installed' => get_home_path(),
99 'canDelete' => ! is_multisite(),
100 'post_types' => red_get_post_types(),
101 ];
102 }
103
104 public function permission_callback_manage( WP_REST_Request $request ) {
105 return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_OPTION_MANAGE ) || Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_SITE_MANAGE );
106 }
107
108 public function route_save_settings( WP_REST_Request $request ) {
109 $params = $request->get_params();
110 $result = true;
111
112 if ( isset( $params['location'] ) && strlen( $params['location'] ) > 0 ) {
113 $module = Red_Module::get( 2 );
114 $result = $module->can_save( sanitize_text_field( $params['location'] ) );
115 }
116
117 red_set_options( $params );
118
119 $settings = $this->route_settings( $request );
120 if ( is_wp_error( $result ) ) {
121 $settings['warning'] = $result->get_error_message();
122 }
123
124 return $settings;
125 }
126
127 private function groups_to_json( $groups, $depth = 0 ) {
128 $items = array();
129
130 foreach ( $groups as $text => $value ) {
131 if ( is_array( $value ) && $depth === 0 ) {
132 $items[] = (object) array(
133 'label' => $text,
134 'value' => $this->groups_to_json( $value, 1 ),
135 );
136 } else {
137 $items[] = (object) array(
138 'label' => $value,
139 'value' => $text,
140 );
141 }
142 }
143
144 return $items;
145 }
146 }
1 <?php
2
3 require_once __DIR__ . '/api-group.php';
4 require_once __DIR__ . '/api-redirect.php';
5 require_once __DIR__ . '/api-log.php';
6 require_once __DIR__ . '/api-404.php';
7 require_once __DIR__ . '/api-settings.php';
8 require_once __DIR__ . '/api-plugin.php';
9 require_once __DIR__ . '/api-import.php';
10 require_once __DIR__ . '/api-export.php';
11
12 define( 'REDIRECTION_API_NAMESPACE', 'redirection/v1' );
13
14 /**
15 * @apiDefine 401Error
16 *
17 * @apiError (Error 401) rest_forbidden You are not authorized to access this API endpoint
18 * @apiErrorExample {json} 401 Error Response:
19 * HTTP/1.1 401 Bad Request
20 * {
21 * "code": "rest_forbidden",
22 * "message": "Sorry, you are not allowed to do that."
23 * }
24 */
25
26 /**
27 * @apiDefine 404Error
28 *
29 * @apiError (Error 404) rest_no_route Endpoint not found
30 * @apiErrorExample {json} 404 Error Response:
31 * HTTP/1.1 404 Not Found
32 * {
33 * "code": "rest_no_route",
34 * "message": "No route was found matching the URL and request method"
35 * }
36 */
37
38 /**
39 * @apiDefine 400Error
40 *
41 * @apiError rest_forbidden You are not authorized to access this API endpoint
42 * @apiErrorExample {json} 400 Error Response:
43 * HTTP/1.1 400 Bad Request
44 * {
45 * "error": "invalid",
46 * "message": "Invalid request"
47 * }
48 */
49
50 /**
51 * @apiDefine 400MissingError
52 * @apiError (Error 400) rest_missing_callback_param Some required parameters are not present or not in the correct format
53 * @apiErrorExample {json} 400 Error Response:
54 * HTTP/1.1 400 Bad Request
55 * {
56 * "code": "rest_missing_callback_param",
57 * "message": "Missing parameter(s): PARAM"
58 * }
59 */
60 class Redirection_Api_Route {
61 protected function add_error_details( WP_Error $error, $line, $code = 400 ) {
62 global $wpdb;
63
64 $data = array(
65 'status' => $code,
66 'error_code' => $line,
67 );
68
69 if ( isset( $wpdb->last_error ) && $wpdb->last_error ) {
70 $data['wpdb'] = $wpdb->last_error;
71 }
72
73 $error->add_data( $data );
74 return $error;
75 }
76
77 public function permission_callback( WP_REST_Request $request ) {
78 return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_PLUGIN );
79 }
80
81 public function get_route( $method, $callback, $permissions = false ) {
82 return [
83 'methods' => $method,
84 'callback' => [ $this, $callback ],
85 'permission_callback' => $permissions ? $permissions : [ $this, 'permission_callback' ],
86 ];
87 }
88 }
89
90 class Redirection_Api_Filter_Route extends Redirection_Api_Route {
91 public function validate_filter( $value, $request, $param ) {
92 $fields = $request->get_attributes()['args']['filterBy']['filter_fields'];
93
94 if ( ! is_array( $value ) ) {
95 return new WP_Error( 'rest_invalid_param', 'Filter is not an array', array( 'status' => 400 ) );
96 }
97
98 if ( ! empty( $fields ) ) {
99 foreach ( array_keys( $value ) as $key ) {
100 if ( ! in_array( $key, $fields, true ) ) {
101 return new WP_Error( 'rest_invalid_param', 'Filter type is not supported: ' . $key, array( 'status' => 400 ) );
102 }
103 }
104 }
105
106 return true;
107 }
108
109 protected function get_filter_args( $order_fields, $filters = [] ) {
110 return [
111 'filterBy' => [
112 'description' => 'Field to filter by',
113 'validate_callback' => [ $this, 'validate_filter' ],
114 'filter_fields' => $filters,
115 ],
116 'orderby' => [
117 'description' => 'Field to order results by',
118 'type' => 'string',
119 'enum' => $order_fields,
120 ],
121 'direction' => [
122 'description' => 'Direction of ordered results',
123 'type' => 'string',
124 'default' => 'desc',
125 'enum' => [ 'asc', 'desc' ],
126 ],
127 'per_page' => [
128 'description' => 'Number of results per page',
129 'type' => 'integer',
130 'default' => 25,
131 'minimum' => 5,
132 'maximum' => RED_MAX_PER_PAGE,
133 ],
134 'page' => [
135 'description' => 'Page offset',
136 'type' => 'integer',
137 'minimum' => 0,
138 'default' => 0,
139 ],
140 ];
141 }
142
143 /**
144 * Register a bulk action route
145 *
146 * @param String $namespace Namespace.
147 * @param String $route Route.
148 * @param Array $orders
149 * @param Array $filters
150 * @param Object $callback
151 * @param boolean $permissions
152 * @return void
153 */
154 public function register_bulk( $namespace, $route, $orders, $filters, $callback, $permissions = false ) {
155 register_rest_route( $namespace, $route, array(
156 $this->get_route( WP_REST_Server::EDITABLE, $callback, $permissions ),
157 'args' => array_merge( $this->get_filter_args( $orders, $filters ), [
158 'items' => [
159 'description' => 'Comma separated list of item IDs to perform action on',
160 'type' => 'array',
161 'items' => [
162 'type' => 'string',
163 ],
164 ],
165 ] ),
166 ) );
167 }
168 }
169
170 class Redirection_Api {
171 private static $instance = null;
172 private $routes = array();
173
174 public static function init() {
175 if ( is_null( self::$instance ) ) {
176 self::$instance = new Redirection_Api();
177 }
178
179 return self::$instance;
180 }
181
182 public function __construct() {
183 global $wpdb;
184
185 $wpdb->hide_errors();
186
187 $this->routes[] = new Redirection_Api_Redirect( REDIRECTION_API_NAMESPACE );
188 $this->routes[] = new Redirection_Api_Group( REDIRECTION_API_NAMESPACE );
189 $this->routes[] = new Redirection_Api_Log( REDIRECTION_API_NAMESPACE );
190 $this->routes[] = new Redirection_Api_404( REDIRECTION_API_NAMESPACE );
191 $this->routes[] = new Redirection_Api_Settings( REDIRECTION_API_NAMESPACE );
192 $this->routes[] = new Redirection_Api_Plugin( REDIRECTION_API_NAMESPACE );
193 $this->routes[] = new Redirection_Api_Import( REDIRECTION_API_NAMESPACE );
194 $this->routes[] = new Redirection_Api_Export( REDIRECTION_API_NAMESPACE );
195 }
196 }
1 <?php
2
3 abstract class Red_Database_Upgrader {
4 private $queries = [];
5 private $live = true;
6
7 /**
8 * Return an array of all the stages for an upgrade
9 *
10 * @return array stage name => reason
11 */
12 abstract public function get_stages();
13
14 public function get_reason( $stage ) {
15 $stages = $this->get_stages();
16
17 if ( isset( $stages[ $stage ] ) ) {
18 return $stages[ $stage ];
19 }
20
21 return 'Unknown';
22 }
23
24 /**
25 * Run a particular stage on the current upgrader
26 *
27 * @return Red_Database_Status
28 */
29 public function perform_stage( Red_Database_Status $status ) {
30 global $wpdb;
31
32 $stage = $status->get_current_stage();
33 if ( $this->has_stage( $stage ) && method_exists( $this, $stage ) ) {
34 try {
35 $this->$stage( $wpdb );
36 $status->set_ok( $this->get_reason( $stage ) );
37 } catch ( Exception $e ) {
38 $status->set_error( $e->getMessage() );
39 }
40 } else {
41 $status->set_error( 'No stage found for upgrade ' . $stage );
42 }
43 }
44
45 public function get_queries_for_stage( $stage ) {
46 global $wpdb;
47
48 $this->queries = [];
49 $this->live = false;
50 $this->$stage( $wpdb, false );
51 $this->live = true;
52
53 return $this->queries;
54 }
55
56 /**
57 * Returns the current database charset
58 *
59 * @return string Database charset
60 */
61 public function get_charset() {
62 global $wpdb;
63
64 $charset_collate = '';
65 if ( ! empty( $wpdb->charset ) ) {
66 // Fix some common invalid charset values
67 $fixes = [
68 'utf-8',
69 'utf',
70 ];
71
72 $charset = $wpdb->charset;
73 if ( in_array( strtolower( $charset ), $fixes, true ) ) {
74 $charset = 'utf8';
75 }
76
77 $charset_collate = "DEFAULT CHARACTER SET $charset";
78 }
79
80 if ( ! empty( $wpdb->collate ) ) {
81 $charset_collate .= " COLLATE=$wpdb->collate";
82 }
83
84 return $charset_collate;
85 }
86
87 /**
88 * Performs a $wpdb->query, and throws an exception if an error occurs
89 *
90 * @return bool true if query is performed ok, otherwise an exception is thrown
91 */
92 protected function do_query( $wpdb, $sql ) {
93 if ( ! $this->live ) {
94 $this->queries[] = $sql;
95 return true;
96 }
97
98 // These are known queries without user input
99 // phpcs:ignore
100 $result = $wpdb->query( $sql );
101
102 if ( $result === false ) {
103 /* translators: 1: SQL string */
104 throw new Exception( sprintf( 'Failed to perform query "%s"', $sql ) );
105 }
106
107 return true;
108 }
109
110 /**
111 * Load a database upgrader class
112 *
113 * @return object Database upgrader
114 */
115 public static function get( $version ) {
116 include_once dirname( __FILE__ ) . '/schema/' . str_replace( [ '..', '/' ], '', $version['file'] );
117
118 return new $version['class'];
119 }
120
121 private function has_stage( $stage ) {
122 return in_array( $stage, array_keys( $this->get_stages() ), true );
123 }
124 }
1 <?php
2
3 require_once __DIR__ . '/database-status.php';
4 require_once __DIR__ . '/database-upgrader.php';
5
6 class Red_Database {
7 /**
8 * Get all upgrades for a database version
9 *
10 * @return array Array of versions from self::get_upgrades()
11 */
12 public function get_upgrades_for_version( $current_version, $current_stage ) {
13 if ( empty( $current_version ) ) {
14 return [
15 [
16 'version' => REDIRECTION_DB_VERSION,
17 'file' => 'latest.php',
18 'class' => 'Red_Latest_Database',
19 ],
20 ];
21 }
22
23 $upgraders = [];
24 $found = false;
25
26 foreach ( $this->get_upgrades() as $upgrade ) {
27 if ( ! $found ) {
28 $upgrader = Red_Database_Upgrader::get( $upgrade );
29
30 $stage_present = in_array( $current_stage, array_keys( $upgrader->get_stages() ), true );
31 $same_version = $current_stage === false && version_compare( $upgrade['version'], $current_version, 'gt' );
32
33 if ( $stage_present || $same_version ) {
34 $found = true;
35 }
36 }
37
38 if ( $found ) {
39 $upgraders[] = $upgrade;
40 }
41 }
42
43 return $upgraders;
44 }
45
46 /**
47 * Apply a particular upgrade stage
48 *
49 * @return mixed Result for upgrade
50 */
51 public function apply_upgrade( Red_Database_Status $status ) {
52 $upgraders = $this->get_upgrades_for_version( $status->get_current_version(), $status->get_current_stage() );
53
54 if ( count( $upgraders ) === 0 ) {
55 $status->set_error( 'No upgrades found for version ' . $status->get_current_version() );
56 return;
57 }
58
59 if ( $status->get_current_stage() === false ) {
60 if ( $status->needs_installing() ) {
61 $status->start_install( $upgraders );
62 } else {
63 $status->start_upgrade( $upgraders );
64 }
65 }
66
67 // Look at first upgrade
68 $upgrader = Red_Database_Upgrader::get( $upgraders[0] );
69
70 // Perform the upgrade
71 $upgrader->perform_stage( $status );
72
73 if ( ! $status->is_error() ) {
74 $status->set_next_stage();
75 }
76 }
77
78 public static function apply_to_sites( $callback ) {
79 if ( is_multisite() && ( is_network_admin() || defined( 'WP_CLI' ) && WP_CLI ) ) {
80 $total = get_sites( [ 'count' => true ] );
81 $per_page = 100;
82
83 // Paginate through all sites and apply the callback
84 for ( $offset = 0; $offset < $total; $offset += $per_page ) {
85 array_map( function( $site ) use ( $callback ) {
86 switch_to_blog( $site->blog_id );
87
88 $callback();
89
90 restore_current_blog();
91 }, get_sites( [ 'number' => $per_page, 'offset' => $offset ] ) );
92 }
93
94 return;
95 }
96
97 $callback();
98 }
99
100 /**
101 * Get latest database installer
102 *
103 * @return object Red_Latest_Database
104 */
105 public static function get_latest_database() {
106 include_once dirname( __FILE__ ) . '/schema/latest.php';
107
108 return new Red_Latest_Database();
109 }
110
111 /**
112 * List of all upgrades and their associated file
113 *
114 * @return array Database upgrade array
115 */
116 public function get_upgrades() {
117 return [
118 [
119 'version' => '2.0.1',
120 'file' => '201.php',
121 'class' => 'Red_Database_201',
122 ],
123 [
124 'version' => '2.1.16',
125 'file' => '216.php',
126 'class' => 'Red_Database_216',
127 ],
128 [
129 'version' => '2.2',
130 'file' => '220.php',
131 'class' => 'Red_Database_220',
132 ],
133 [
134 'version' => '2.3.1',
135 'file' => '231.php',
136 'class' => 'Red_Database_231',
137 ],
138 [
139 'version' => '2.3.2',
140 'file' => '232.php',
141 'class' => 'Red_Database_232',
142 ],
143 [
144 'version' => '2.3.3',
145 'file' => '233.php',
146 'class' => 'Red_Database_233',
147 ],
148 [
149 'version' => '2.4',
150 'file' => '240.php',
151 'class' => 'Red_Database_240',
152 ],
153 [
154 'version' => '4.0',
155 'file' => '400.php',
156 'class' => 'Red_Database_400',
157 ],
158 [
159 'version' => '4.1',
160 'file' => '410.php',
161 'class' => 'Red_Database_410',
162 ],
163 [
164 'version' => '4.2',
165 'file' => '420.php',
166 'class' => 'Red_Database_420',
167 ],
168 ];
169 }
170 }
1 <?php
2
3 // Note: not localised as the messages aren't important enough
4 class Red_Database_201 extends Red_Database_Upgrader {
5 public function get_stages() {
6 return [
7 'add_title_201' => 'Add titles to redirects',
8 ];
9 }
10
11 protected function add_title_201( $wpdb ) {
12 return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` ADD `title` varchar(50) NULL" );
13 }
14 }
1 <?php
2
3 // Note: not localised as the messages aren't important enough
4 class Red_Database_216 extends Red_Database_Upgrader {
5 public function get_stages() {
6 return [
7 'add_group_indices_216' => 'Add indices to groups',
8 'add_redirect_indices_216' => 'Add indices to redirects',
9 ];
10 }
11
12 protected function add_group_indices_216( $wpdb ) {
13 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_groups` ADD INDEX(module_id)" );
14 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_groups` ADD INDEX(status)" );
15
16 return true;
17 }
18
19 protected function add_redirect_indices_216( $wpdb ) {
20 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` ADD INDEX(url(191))" );
21 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` ADD INDEX(status)" );
22 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` ADD INDEX(regex)" );
23
24 return true;
25 }
26 }
1 <?php
2
3 // Note: not localised as the messages aren't important enough
4 class Red_Database_220 extends Red_Database_Upgrader {
5 public function get_stages() {
6 return [
7 'add_group_indices_220' => 'Add group indices to redirects',
8 'add_log_indices_220' => 'Add indices to logs',
9 ];
10 }
11
12 protected function add_group_indices_220( $wpdb ) {
13 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` ADD INDEX `group_idpos` (`group_id`,`position`)" );
14 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` ADD INDEX `group` (`group_id`)" );
15 return true;
16 }
17
18 protected function add_log_indices_220( $wpdb ) {
19 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD INDEX `created` (`created`)" );
20 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD INDEX `redirection_id` (`redirection_id`)" );
21 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD INDEX `ip` (`ip`)" );
22 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD INDEX `group_id` (`group_id`)" );
23 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD INDEX `module_id` (`module_id`)" );
24 return true;
25 }
26 }
1 <?php
2
3 // Note: not localised as the messages aren't important enough
4 class Red_Database_231 extends Red_Database_Upgrader {
5 public function get_stages() {
6 return [
7 'remove_404_module_231' => 'Remove 404 module',
8 'create_404_table_231' => 'Create 404 table',
9 ];
10 }
11
12 protected function remove_404_module_231( $wpdb ) {
13 return $this->do_query( $wpdb, "UPDATE {$wpdb->prefix}redirection_groups SET module_id=1 WHERE module_id=3" );
14 }
15
16 protected function create_404_table_231( $wpdb ) {
17 $this->do_query( $wpdb, $this->get_404_table( $wpdb ) );
18 }
19
20 private function get_404_table( $wpdb ) {
21 $charset_collate = $this->get_charset();
22
23 return "CREATE TABLE `{$wpdb->prefix}redirection_404` (
24 `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
25 `created` datetime NOT NULL,
26 `url` varchar(255) NOT NULL DEFAULT '',
27 `agent` varchar(255) DEFAULT NULL,
28 `referrer` varchar(255) DEFAULT NULL,
29 `ip` int(10) unsigned NOT NULL,
30 PRIMARY KEY (`id`),
31 KEY `created` (`created`),
32 KEY `url` (`url`(191)),
33 KEY `ip` (`ip`),
34 KEY `referrer` (`referrer`(191))
35 ) $charset_collate";
36 }
37 }
1 <?php
2
3 // Note: not localised as the messages aren't important enough
4 class Red_Database_232 extends Red_Database_Upgrader {
5 public function get_stages() {
6 return [
7 'remove_modules_232' => 'Remove module table',
8 ];
9 }
10
11 protected function remove_modules_232( $wpdb ) {
12 $this->do_query( $wpdb, "DROP TABLE IF EXISTS {$wpdb->prefix}redirection_modules" );
13 return true;
14 }
15 }
1 <?php
2
3 // Note: not localised as the messages aren't important enough
4 class Red_Database_233 extends Red_Database_Upgrader {
5 public function get_stages() {
6 return [
7 'fix_invalid_groups_233' => 'Migrate any groups with invalid module ID',
8 ];
9 }
10
11 protected function fix_invalid_groups_233( $wpdb ) {
12 $this->do_query( $wpdb, "UPDATE {$wpdb->prefix}redirection_groups SET module_id=1 WHERE module_id > 2" );
13
14 $latest = Red_Database::get_latest_database();
15 return $latest->create_groups( $wpdb );
16 }
17 }
1 <?php
2
3 /**
4 * There are several problems with 2.3.3 => 2.4 that this attempts to cope with:
5 * - some sites have a misconfigured IP column
6 * - some sites don't have any IP column
7 */
8 class Red_Database_240 extends Red_Database_Upgrader {
9 public function get_stages() {
10 return [
11 'convert_int_ip_to_varchar_240' => 'Convert integer IP values to support IPv6',
12 'expand_log_ip_column_240' => 'Expand IP size in logs to support IPv6',
13 'convert_title_to_text_240' => 'Expand size of redirect titles',
14 'add_missing_index_240' => 'Add missing IP index to 404 logs',
15 ];
16 }
17
18 private function has_ip_index( $wpdb ) {
19 $wpdb->hide_errors();
20 $existing = $wpdb->get_row( "SHOW CREATE TABLE `{$wpdb->prefix}redirection_404`", ARRAY_N );
21 $wpdb->show_errors();
22
23 if ( isset( $existing[1] ) && strpos( strtolower( $existing[1] ), 'key `ip` (' ) !== false ) {
24 return true;
25 }
26
27 return false;
28 }
29
30 protected function has_varchar_ip( $wpdb ) {
31 $wpdb->hide_errors();
32 $existing = $wpdb->get_row( "SHOW CREATE TABLE `{$wpdb->prefix}redirection_404`", ARRAY_N );
33 $wpdb->show_errors();
34
35 if ( isset( $existing[1] ) && strpos( strtolower( $existing[1] ), '`ip` varchar(45)' ) !== false ) {
36 return true;
37 }
38
39 return false;
40 }
41
42 protected function has_int_ip( $wpdb ) {
43 $wpdb->hide_errors();
44 $existing = $wpdb->get_row( "SHOW CREATE TABLE `{$wpdb->prefix}redirection_404`", ARRAY_N );
45 $wpdb->show_errors();
46
47 if ( isset( $existing[1] ) && strpos( strtolower( $existing[1] ), '`ip` int' ) !== false ) {
48 return true;
49 }
50
51 return false;
52 }
53
54 protected function convert_int_ip_to_varchar_240( $wpdb ) {
55 if ( $this->has_int_ip( $wpdb ) ) {
56 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` ADD `ipaddress` VARCHAR(45) DEFAULT NULL AFTER `ip`" );
57 $this->do_query( $wpdb, "UPDATE {$wpdb->prefix}redirection_404 SET ipaddress=INET_NTOA(ip)" );
58 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` DROP `ip`" );
59 return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` CHANGE `ipaddress` `ip` VARCHAR(45) DEFAULT NULL" );
60 }
61
62 return true;
63 }
64
65 protected function expand_log_ip_column_240( $wpdb ) {
66 return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` CHANGE `ip` `ip` VARCHAR(45) DEFAULT NULL" );
67 }
68
69 protected function add_missing_index_240( $wpdb ) {
70 if ( $this->has_ip_index( $wpdb ) ) {
71 // Remove index
72 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` DROP INDEX ip" );
73 }
74
75 // Ensure we have an IP column
76 $this->convert_int_ip_to_varchar_240( $wpdb );
77 if ( ! $this->has_varchar_ip( $wpdb ) ) {
78 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` ADD `ip` VARCHAR(45) DEFAULT NULL" );
79 }
80
81 // Finally add the index
82 return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` ADD INDEX `ip` (`ip`)" );
83 }
84
85 protected function convert_title_to_text_240( $wpdb ) {
86 return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` CHANGE `title` `title` text" );
87 }
88 }
1 <?php
2
3 class Red_Database_400 extends Red_Database_Upgrader {
4 public function get_stages() {
5 return [
6 'add_match_url_400' => 'Add a matched URL column',
7 'add_match_url_index' => 'Add match URL index',
8 'add_redirect_data_400' => 'Add column to store new flags',
9 'convert_existing_urls_400' => 'Convert existing URLs to new format',
10 ];
11 }
12
13 private function has_column( $wpdb, $column ) {
14 $existing = $wpdb->get_row( "SHOW CREATE TABLE `{$wpdb->prefix}redirection_items`", ARRAY_N );
15
16 if ( isset( $existing[1] ) && strpos( strtolower( $existing[1] ), strtolower( $column ) ) !== false ) {
17 return true;
18 }
19
20 return false;
21 }
22
23 private function has_match_index( $wpdb ) {
24 $existing = $wpdb->get_row( "SHOW CREATE TABLE `{$wpdb->prefix}redirection_items`", ARRAY_N );
25
26 if ( isset( $existing[1] ) && strpos( strtolower( $existing[1] ), 'key `match_url' ) !== false ) {
27 return true;
28 }
29
30 return false;
31 }
32
33 protected function add_match_url_400( $wpdb ) {
34 if ( ! $this->has_column( $wpdb, '`match_url` varchar(2000)' ) ) {
35 return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` ADD `match_url` VARCHAR(2000) NULL DEFAULT NULL AFTER `url`" );
36 }
37
38 return true;
39 }
40
41 protected function add_match_url_index( $wpdb ) {
42 if ( ! $this->has_match_index( $wpdb ) ) {
43 return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` ADD INDEX `match_url` (`match_url`(191))" );
44 }
45 }
46
47 protected function add_redirect_data_400( $wpdb ) {
48 if ( ! $this->has_column( $wpdb, '`match_data` TEXT' ) ) {
49 return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` ADD `match_data` TEXT NULL DEFAULT NULL AFTER `match_url`" );
50 }
51
52 return true;
53 }
54
55 protected function convert_existing_urls_400( $wpdb ) {
56 // All regex get match_url=regex
57 $this->do_query( $wpdb, "UPDATE `{$wpdb->prefix}redirection_items` SET match_url='regex' WHERE regex=1" );
58
59 // Remove query part from all URLs and lowercase
60 $this->do_query( $wpdb, "UPDATE `{$wpdb->prefix}redirection_items` SET match_url=LOWER(url) WHERE regex=0" );
61
62 // Set exact match if query param present
63 $this->do_query( $wpdb, $wpdb->prepare( "UPDATE `{$wpdb->prefix}redirection_items` SET match_data=%s WHERE regex=0 AND match_url LIKE '%?%'", '{"source":{"flag_query":"exactorder"}}' ) );
64
65 // Trim the last / from a URL
66 $this->do_query( $wpdb, "UPDATE `{$wpdb->prefix}redirection_items` SET match_url=LEFT(match_url,LENGTH(match_url)-1) WHERE regex=0 AND match_url != '/' AND RIGHT(match_url, 1) = '/'" );
67 $this->do_query( $wpdb, "UPDATE `{$wpdb->prefix}redirection_items` SET match_url=REPLACE(match_url, '/?', '?') WHERE regex=0" );
68
69 // Any URL that is now empty becomes /
70 return $this->do_query( $wpdb, "UPDATE `{$wpdb->prefix}redirection_items` SET match_url='/' WHERE match_url=''" );
71 }
72 }
1 <?php
2
3 class Red_Database_410 extends Red_Database_Upgrader {
4 public function get_stages() {
5 return [
6 'handle_double_slash' => 'Support double-slash URLs',
7 ];
8 }
9
10 protected function handle_double_slash( $wpdb ) {
11 // Update any URL with a double slash at the end
12 $this->do_query( $wpdb, "UPDATE `{$wpdb->prefix}redirection_items` SET match_url=LOWER(LEFT(SUBSTRING_INDEX(url, '?', 1),LENGTH(SUBSTRING_INDEX(url, '?', 1)) - 1)) WHERE RIGHT(SUBSTRING_INDEX(url, '?', 1), 2) = '//' AND regex=0" );
13
14 // Any URL that is now empty becomes /
15 return $this->do_query( $wpdb, "UPDATE `{$wpdb->prefix}redirection_items` SET match_url='/' WHERE match_url=''" );
16 }
17 }
1 <?php
2
3 class Red_Database_420 extends Red_Database_Upgrader {
4 public function get_stages() {
5 return [
6 'add_extra_logging' => 'Add extra logging support',
7 'remove_module_id' => 'Remove module ID from logs',
8 'remove_group_id' => 'Remove group ID from logs',
9 'add_extra_404' => 'Add extra 404 logging support',
10 ];
11 }
12
13 protected function remove_module_id( $wpdb ) {
14 if ( ! $this->has_module_id( $wpdb ) ) {
15 return true;
16 }
17
18 return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` DROP `module_id`" );
19 }
20
21 protected function remove_group_id( $wpdb ) {
22 if ( ! $this->has_group_id( $wpdb ) ) {
23 return true;
24 }
25
26 return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` DROP `group_id`" );
27 }
28
29 private function has_module_id( $wpdb ) {
30 $existing = $wpdb->get_row( "SHOW CREATE TABLE `{$wpdb->prefix}redirection_logs`", ARRAY_N );
31
32 if ( isset( $existing[1] ) && strpos( strtolower( $existing[1] ), 'module_id' ) !== false ) {
33 return true;
34 }
35
36 return false;
37 }
38
39 private function has_group_id( $wpdb ) {
40 $existing = $wpdb->get_row( "SHOW CREATE TABLE `{$wpdb->prefix}redirection_logs`", ARRAY_N );
41
42 if ( isset( $existing[1] ) && strpos( strtolower( $existing[1] ), 'group_id' ) !== false ) {
43 return true;
44 }
45
46 return false;
47 }
48
49 private function has_log_domain( $wpdb ) {
50 $existing = $wpdb->get_row( "SHOW CREATE TABLE `{$wpdb->prefix}redirection_logs`", ARRAY_N );
51
52 if ( isset( $existing[1] ) && strpos( strtolower( $existing[1] ), 'domain` varchar' ) !== false ) {
53 return true;
54 }
55
56 return false;
57 }
58
59 private function has_404_domain( $wpdb ) {
60 $existing = $wpdb->get_row( "SHOW CREATE TABLE `{$wpdb->prefix}redirection_404`", ARRAY_N );
61
62 if ( isset( $existing[1] ) && strpos( strtolower( $existing[1] ), 'domain` varchar' ) !== false ) {
63 return true;
64 }
65
66 return false;
67 }
68
69 protected function add_extra_logging( $wpdb ) {
70 if ( $this->has_log_domain( $wpdb ) ) {
71 return true;
72 }
73
74 // Update any URL with a double slash at the end
75 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD `domain` VARCHAR(255) NULL DEFAULT NULL AFTER `url`" );
76 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD `http_code` INT(11) unsigned NOT NULL DEFAULT 0 AFTER `referrer`" );
77 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD `request_method` VARCHAR(10) NULL DEFAULT NULL AFTER `http_code`" );
78 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD `redirect_by` VARCHAR(50) NULL DEFAULT NULL AFTER `request_method`" );
79 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD `request_data` MEDIUMTEXT NULL DEFAULT NULL AFTER `request_method`" );
80
81 return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` CHANGE COLUMN `agent` `agent` MEDIUMTEXT NULL" );
82 }
83
84 protected function add_extra_404( $wpdb ) {
85 if ( $this->has_404_domain( $wpdb ) ) {
86 return true;
87 }
88
89 // Update any URL with a double slash at the end
90 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` ADD `domain` VARCHAR(255) NULL DEFAULT NULL AFTER `url`" );
91 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` ADD `http_code` INT(11) unsigned NOT NULL DEFAULT 0 AFTER `referrer`" );
92 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` ADD `request_method` VARCHAR(10) NULL DEFAULT NULL AFTER `http_code`" );
93 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` ADD `request_data` MEDIUMTEXT NULL DEFAULT NULL AFTER `request_method`" );
94
95 // Same as log table
96 $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` DROP INDEX `url`" );
97 return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` CHANGE COLUMN `url` `url` MEDIUMTEXT NOT NULL" );
98 }
99 }
1 <?php
2
3 /**
4 * Latest database schema
5 */
6 class Red_Latest_Database extends Red_Database_Upgrader {
7 public function get_stages() {
8 return [
9 /* translators: displayed when installing the plugin */
10 'create_tables' => __( 'Install Redirection tables', 'redirection' ),
11 /* translators: displayed when installing the plugin */
12 'create_groups' => __( 'Create basic data', 'redirection' ),
13 ];
14 }
15
16 /**
17 * Install the latest database
18 *
19 * @return bool|WP_Error true if installed, WP_Error otherwise
20 */
21 public function install() {
22 global $wpdb;
23
24 foreach ( $this->get_stages() as $stage => $info ) {
25 $result = $this->$stage( $wpdb );
26
27 if ( is_wp_error( $result ) ) {
28 if ( $wpdb->last_error ) {
29 $result->add_data( $wpdb->last_error );
30 }
31
32 return $result;
33 }
34 }
35
36 red_set_options( array( 'database' => REDIRECTION_DB_VERSION ) );
37 return true;
38 }
39
40 /**
41 * Remove the database and any options (including unused ones)
42 */
43 public function remove() {
44 global $wpdb;
45
46 $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}redirection_items" );
47 $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}redirection_logs" );
48 $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}redirection_groups" );
49 $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}redirection_modules" );
50 $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}redirection_404" );
51
52 delete_option( 'redirection_lookup' );
53 delete_option( 'redirection_post' );
54 delete_option( 'redirection_root' );
55 delete_option( 'redirection_index' );
56 delete_option( 'redirection_options' );
57 delete_option( Red_Database_Status::OLD_DB_VERSION );
58 }
59
60 /**
61 * Return any tables that are missing from the database
62 *
63 * @return array Array of missing table names
64 */
65 public function get_missing_tables() {
66 global $wpdb;
67
68 $tables = array_keys( $this->get_all_tables() );
69 $missing = [];
70
71 foreach ( $tables as $table ) {
72 $result = $wpdb->query( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
73
74 if ( intval( $result, 10 ) !== 1 ) {
75 $missing[] = $table;
76 }
77 }
78
79 return $missing;
80 }
81
82 /**
83 * Get table schema for latest database tables
84 *
85 * @return array Database schema array
86 */
87 public function get_table_schema() {
88 global $wpdb;
89
90 $tables = array_keys( $this->get_all_tables() );
91 $show = array();
92
93 foreach ( $tables as $table ) {
94 // These are known queries without user input
95 // phpcs:ignore
96 $row = $wpdb->get_row( 'SHOW CREATE TABLE ' . $table, ARRAY_N );
97
98 if ( $row ) {
99 $show = array_merge( $show, explode( "\n", $row[1] ) );
100 $show[] = '';
101 } else {
102 /* translators: 1: table name */
103 $show[] = sprintf( __( 'Table "%s" is missing', 'redirection' ), $table );
104 }
105 }
106
107 return $show;
108 }
109
110 /**
111 * Return array of table names and table schema
112 *
113 * @return array
114 */
115 public function get_all_tables() {
116 global $wpdb;
117
118 $charset_collate = $this->get_charset();
119
120 return array(
121 "{$wpdb->prefix}redirection_items" => $this->create_items_sql( $wpdb->prefix, $charset_collate ),
122 "{$wpdb->prefix}redirection_groups" => $this->create_groups_sql( $wpdb->prefix, $charset_collate ),
123 "{$wpdb->prefix}redirection_logs" => $this->create_log_sql( $wpdb->prefix, $charset_collate ),
124 "{$wpdb->prefix}redirection_404" => $this->create_404_sql( $wpdb->prefix, $charset_collate ),
125 );
126 }
127
128 /**
129 * Creates default group information
130 */
131 public function create_groups( $wpdb, $is_live = true ) {
132 if ( ! $is_live ) {
133 return true;
134 }
135
136 $defaults = [
137 [
138 'name' => __( 'Redirections', 'redirection' ),
139 'module_id' => 1,
140 'position' => 0,
141 ],
142 [
143 'name' => __( 'Modified Posts', 'redirection' ),
144 'module_id' => 1,
145 'position' => 1,
146 ],
147 ];
148
149 $existing_groups = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}redirection_groups" );
150
151 // Default groups
152 if ( intval( $existing_groups, 10 ) === 0 ) {
153 $wpdb->insert( $wpdb->prefix . 'redirection_groups', $defaults[0] );
154 $wpdb->insert( $wpdb->prefix . 'redirection_groups', $defaults[1] );
155 }
156
157 $group = $wpdb->get_row( "SELECT * FROM {$wpdb->prefix}redirection_groups LIMIT 1" );
158 if ( $group ) {
159 red_set_options( array( 'last_group_id' => $group->id ) );
160 }
161
162 return true;
163 }
164
165 /**
166 * Creates all the tables
167 */
168 public function create_tables( $wpdb ) {
169 global $wpdb;
170
171 foreach ( $this->get_all_tables() as $table => $sql ) {
172 $sql = preg_replace( '/[ \t]{2,}/', '', $sql );
173 $this->do_query( $wpdb, $sql );
174 }
175
176 return true;
177 }
178
179 private function create_items_sql( $prefix, $charset_collate ) {
180 return "CREATE TABLE IF NOT EXISTS `{$prefix}redirection_items` (
181 `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
182 `url` mediumtext NOT NULL,
183 `match_url` VARCHAR(2000) DEFAULT NULL,
184 `match_data` TEXT,
185 `regex` INT(11) unsigned NOT NULL DEFAULT '0',
186 `position` INT(11) unsigned NOT NULL DEFAULT '0',
187 `last_count` INT(10) unsigned NOT NULL DEFAULT '0',
188 `last_access` datetime NOT NULL DEFAULT '1970-01-01 00:00:00',
189 `group_id` INT(11) NOT NULL DEFAULT '0',
190 `status` enum('enabled','disabled') NOT NULL DEFAULT 'enabled',
191 `action_type` VARCHAR(20) NOT NULL,
192 `action_code` INT(11) unsigned NOT NULL,
193 `action_data` MEDIUMTEXT,
194 `match_type` VARCHAR(20) NOT NULL,
195 `title` TEXT,
196 PRIMARY KEY (`id`),
197 KEY `url` (`url`(191)),
198 KEY `status` (`status`),
199 KEY `regex` (`regex`),
200 KEY `group_idpos` (`group_id`,`position`),
201 KEY `group` (`group_id`),
202 KEY `match_url` (`match_url`(191))
203 ) $charset_collate";
204 }
205
206 private function create_groups_sql( $prefix, $charset_collate ) {
207 return "CREATE TABLE IF NOT EXISTS `{$prefix}redirection_groups` (
208 `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
209 `name` VARCHAR(50) NOT NULL,
210 `tracking` INT(11) NOT NULL DEFAULT '1',
211 `module_id` INT(11) unsigned NOT NULL DEFAULT '0',
212 `status` enum('enabled','disabled') NOT NULL DEFAULT 'enabled',
213 `position` INT(11) unsigned NOT NULL DEFAULT '0',
214 PRIMARY KEY (`id`),
215 KEY `module_id` (`module_id`),
216 KEY `status` (`status`)
217 ) $charset_collate";
218 }
219
220 private function create_log_sql( $prefix, $charset_collate ) {
221 return "CREATE TABLE IF NOT EXISTS `{$prefix}redirection_logs` (
222 `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
223 `created` datetime NOT NULL,
224 `url` MEDIUMTEXT NOT NULL,
225 `domain` VARCHAR(255) DEFAULT NULL,
226 `sent_to` MEDIUMTEXT,
227 `agent` MEDIUMTEXT,
228 `referrer` MEDIUMTEXT,
229 `http_code` INT(11) unsigned NOT NULL DEFAULT '0',
230 `request_method` VARCHAR(10) DEFAULT NULL,
231 `request_data` MEDIUMTEXT,
232 `redirect_by` VARCHAR(50) DEFAULT NULL,
233 `redirection_id` INT(11) unsigned DEFAULT NULL,
234 `ip` VARCHAR(45) DEFAULT NULL,
235 PRIMARY KEY (`id`),
236 KEY `created` (`created`),
237 KEY `redirection_id` (`redirection_id`),
238 KEY `ip` (`ip`)
239 ) $charset_collate";
240 }
241
242 private function create_404_sql( $prefix, $charset_collate ) {
243 return "CREATE TABLE IF NOT EXISTS `{$prefix}redirection_404` (
244 `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
245 `created` datetime NOT NULL,
246 `url` MEDIUMTEXT NOT NULL,
247 `domain` VARCHAR(255) DEFAULT NULL,
248 `agent` VARCHAR(255) DEFAULT NULL,
249 `referrer` VARCHAR(255) DEFAULT NULL,
250 `http_code` INT(11) unsigned NOT NULL DEFAULT '0',
251 `request_method` VARCHAR(10) DEFAULT NULL,
252 `request_data` MEDIUMTEXT,
253 `ip` VARCHAR(45) DEFAULT NULL,
254 PRIMARY KEY (`id`),
255 KEY `created` (`created`),
256 KEY `referrer` (`referrer`(191)),
257 KEY `ip` (`ip`)
258 ) $charset_collate";
259 }
260 }
1 <?php
2
3 class Red_Apache_File extends Red_FileIO {
4 public function force_download() {
5 parent::force_download();
6
7 header( 'Content-Type: application/octet-stream' );
8 header( 'Content-Disposition: attachment; filename="' . $this->export_filename( 'htaccess' ) . '"' );
9 }
10
11 public function get_data( array $items, array $groups ) {
12 include_once dirname( dirname( __FILE__ ) ) . '/models/htaccess.php';
13
14 $htaccess = new Red_Htaccess();
15
16 foreach ( $items as $item ) {
17 $htaccess->add( $item );
18 }
19
20 return $htaccess->get() . PHP_EOL;
21 }
22
23 public function load( $group, $filename, $data ) {
24 // Remove any comments
25 $data = str_replace( "\n", "\r", $data );
26
27 // Split it into lines
28 $lines = array_filter( explode( "\r", $data ) );
29 $count = 0;
30
31 foreach ( (array) $lines as $line ) {
32 $item = $this->get_as_item( $line );
33
34 if ( $item ) {
35 $item['group_id'] = $group;
36 $redirect = Red_Item::create( $item );
37
38 if ( ! is_wp_error( $redirect ) ) {
39 $count++;
40 }
41 }
42 }
43
44 return $count;
45 }
46
47 public function get_as_item( $line ) {
48 $item = false;
49
50 if ( preg_match( '@rewriterule\s+(.*?)\s+(.*?)\s+(\[.*\])*@i', $line, $matches ) > 0 ) {
51 $item = array(
52 'url' => $this->regex_url( $matches[1] ),
53 'match_type' => 'url',
54 'action_type' => 'url',
55 'action_data' => array( 'url' => $this->decode_url( $matches[2] ) ),
56 'action_code' => $this->get_code( $matches[3] ),
57 'regex' => $this->is_regex( $matches[1] ),
58 );
59 } elseif ( preg_match( '@Redirect\s+(.*?)\s+"(.*?)"\s+(.*)@i', $line, $matches ) > 0 || preg_match( '@Redirect\s+(.*?)\s+(.*?)\s+(.*)@i', $line, $matches ) > 0 ) {
60 $item = array(
61 'url' => $this->decode_url( $matches[2] ),
62 'match_type' => 'url',
63 'action_type' => 'url',
64 'action_data' => array( 'url' => $this->decode_url( $matches[3] ) ),
65 'action_code' => $this->get_code( $matches[1] ),
66 );
67 } elseif ( preg_match( '@Redirect\s+"(.*?)"\s+(.*)@i', $line, $matches ) > 0 || preg_match( '@Redirect\s+(.*?)\s+(.*)@i', $line, $matches ) > 0 ) {
68 $item = array(
69 'url' => $this->decode_url( $matches[1] ),
70 'match_type' => 'url',
71 'action_type' => 'url',
72 'action_data' => array( 'url' => $this->decode_url( $matches[2] ) ),
73 'action_code' => 302,
74 );
75 } elseif ( preg_match( '@Redirectmatch\s+(.*?)\s+(.*?)\s+(.*)@i', $line, $matches ) > 0 ) {
76 $item = array(
77 'url' => $this->decode_url( $matches[2] ),
78 'match_type' => 'url',
79 'action_type' => 'url',
80 'action_data' => array( 'url' => $this->decode_url( $matches[3] ) ),
81 'action_code' => $this->get_code( $matches[1] ),
82 'regex' => true,
83 );
84 } elseif ( preg_match( '@Redirectmatch\s+(.*?)\s+(.*)@i', $line, $matches ) > 0 ) {
85 $item = array(
86 'url' => $this->decode_url( $matches[1] ),
87 'match_type' => 'url',
88 'action_type' => 'url',
89 'action_data' => array( 'url' => $this->decode_url( $matches[2] ) ),
90 'action_code' => 302,
91 'regex' => true,
92 );
93 }
94
95 if ( $item ) {
96 $item['action_type'] = 'url';
97 $item['match_type'] = 'url';
98
99 if ( $item['action_code'] === 0 ) {
100 $item['action_type'] = 'pass';
101 }
102
103 return $item;
104 }
105
106 return false;
107 }
108
109 private function decode_url( $url ) {
110 $url = rawurldecode( $url );
111
112 // Replace quoted slashes
113 $url = preg_replace( '@\\\/@', '/', $url );
114
115 // Ensure escaped '.' is still escaped
116 $url = preg_replace( '@\\\\.@', '\\\\.', $url );
117 return $url;
118 }
119
120 private function is_str_regex( $url ) {
121 $regex = '()[]$^?+.';
122 $escape = false;
123 $len = strlen( $url );
124
125 for ( $x = 0; $x < $len; $x++ ) {
126 $escape = false;
127 $char = substr( $url, $x, 1 );
128
129 if ( $char === '\\' ) {
130 $escape = true;
131 } elseif ( strpos( $regex, $char ) !== false && ! $escape ) {
132 return true;
133 }
134 }
135
136 return false;
137 }
138
139 private function is_regex( $url ) {
140 if ( $this->is_str_regex( $url ) ) {
141 $tmp = ltrim( $url, '^' );
142 $tmp = rtrim( $tmp, '$' );
143
144 if ( $this->is_str_regex( $tmp ) ) {
145 return true;
146 }
147 }
148
149 return false;
150 }
151
152 private function regex_url( $url ) {
153 $url = $this->decode_url( $url );
154
155 if ( $this->is_str_regex( $url ) ) {
156 $tmp = ltrim( $url, '^' );
157 $tmp = rtrim( $tmp, '$' );
158
159 if ( $this->is_str_regex( $tmp ) ) {
160 return '^/' . ltrim( $tmp, '/' );
161 }
162
163 return '/' . ltrim( $tmp, '/' );
164 }
165
166 return $this->decode_url( $url );
167 }
168
169 private function get_code( $code ) {
170 if ( strpos( $code, '301' ) !== false || stripos( $code, 'permanent' ) !== false ) {
171 return 301;
172 }
173
174 if ( strpos( $code, '302' ) !== false ) {
175 return 302;
176 }
177
178 if ( strpos( $code, '307' ) !== false || stripos( $code, 'seeother' ) !== false ) {
179 return 307;
180 }
181
182 if ( strpos( $code, '404' ) !== false || stripos( $code, 'forbidden' ) !== false || strpos( $code, 'F' ) !== false ) {
183 return 404;
184 }
185
186 if ( strpos( $code, '410' ) !== false || stripos( $code, 'gone' ) !== false || strpos( $code, 'G' ) !== false ) {
187 return 410;
188 }
189
190 return 302;
191 }
192 }
1 <?php
2
3 class Red_Csv_File extends Red_FileIO {
4 const CSV_SOURCE = 0;
5 const CSV_TARGET = 1;
6 const CSV_REGEX = 2;
7 const CSV_CODE = 3;
8
9 public function force_download() {
10 parent::force_download();
11
12 header( 'Content-Type: text/csv' );
13 header( 'Content-Disposition: attachment; filename="' . $this->export_filename( 'csv' ) . '"' );
14 }
15
16 public function get_data( array $items, array $groups ) {
17 $lines = [ implode( ',', array( 'source', 'target', 'regex', 'code', 'type', 'match', 'hits', 'title', 'status' ) ) ];
18
19 foreach ( $items as $line ) {
20 $lines[] = $this->item_as_csv( $line );
21 }
22
23 return implode( PHP_EOL, $lines ) . PHP_EOL;
24 }
25
26 public function item_as_csv( $item ) {
27 $data = $item->match->get_data();
28
29 if ( isset( $data['url'] ) ) {
30 $data = $data['url'];
31 } else {
32 $data = '/unknown';
33 }
34
35 if ( $item->get_action_code() > 400 && $item->get_action_code() < 500 ) {
36 $data = '';
37 }
38
39 $csv = array(
40 $item->get_url(),
41 $data,
42 $item->is_regex() ? 1 : 0,
43 $item->get_action_code(),
44 $item->get_action_type(),
45 $item->get_hits(),
46 $item->get_title(),
47 $item->is_enabled() ? 'active' : 'disabled',
48 );
49
50 $csv = array_map( array( $this, 'escape_csv' ), $csv );
51 return implode( ',', $csv );
52 }
53
54 public function escape_csv( $item ) {
55 if ( is_numeric( $item ) ) {
56 return $item;
57 }
58
59 return '"' . str_replace( '"', '""', $item ) . '"';
60 }
61
62 public function load( $group, $filename, $data ) {
63 ini_set( 'auto_detect_line_endings', true );
64
65 $file = fopen( $filename, 'r' );
66
67 ini_set( 'auto_detect_line_endings', false );
68
69 if ( $file ) {
70 $separators = [
71 ',',
72 ';',
73 '|',
74 ];
75
76 foreach ( $separators as $separator ) {
77 fseek( $file, 0 );
78 $count = $this->load_from_file( $group, $file, $separator );
79
80 if ( $count > 0 ) {
81 return $count;
82 }
83 }
84 }
85
86 return 0;
87 }
88
89 public function load_from_file( $group_id, $file, $separator ) {
90 global $wpdb;
91
92 $count = 0;
93
94 while ( ( $csv = fgetcsv( $file, 5000, $separator ) ) ) {
95 $item = $this->csv_as_item( $csv, $group_id );
96
97 if ( $item && $this->item_is_valid( $item ) ) {
98 $created = Red_Item::create( $item );
99
100 // The query log can use up all the memory
101 $wpdb->queries = [];
102
103 if ( ! is_wp_error( $created ) ) {
104 $count++;
105 }
106 }
107 }
108
109 return $count;
110 }
111
112 private function item_is_valid( array $csv ) {
113 if ( strlen( $csv['url'] ) === 0 ) {
114 return false;
115 }
116
117 if ( $csv['action_data']['url'] === $csv['url'] ) {
118 return false;
119 }
120
121 return true;
122 }
123
124 private function get_valid_code( $code ) {
125 if ( get_status_header_desc( $code ) !== '' ) {
126 return intval( $code, 10 );
127 }
128
129 return 301;
130 }
131
132 private function get_action_type( $code ) {
133 if ( $code > 400 && $code < 500 ) {
134 return 'error';
135 }
136
137 return 'url';
138 }
139
140 public function csv_as_item( $csv, $group ) {
141 if ( count( $csv ) > 1 && $csv[ self::CSV_SOURCE ] !== 'source' && $csv[ self::CSV_TARGET ] !== 'target' ) {
142 $code = isset( $csv[ self::CSV_CODE ] ) ? $this->get_valid_code( $csv[ self::CSV_CODE ] ) : 301;
143
144 return array(
145 'url' => trim( $csv[ self::CSV_SOURCE ] ),
146 'action_data' => array( 'url' => trim( $csv[ self::CSV_TARGET ] ) ),
147 'regex' => isset( $csv[ self::CSV_REGEX ] ) ? $this->parse_regex( $csv[ self::CSV_REGEX ] ) : $this->is_regex( $csv[ self::CSV_SOURCE ] ),
148 'group_id' => $group,
149 'match_type' => 'url',
150 'action_type' => $this->get_action_type( $code ),
151 'action_code' => $code,
152 );
153 }
154
155 return false;
156 }
157
158 private function parse_regex( $value ) {
159 return intval( $value, 10 ) === 1 ? true : false;
160 }
161
162 private function is_regex( $url ) {
163 $regex = '()[]$^*';
164
165 if ( strpbrk( $url, $regex ) === false ) {
166 return false;
167 }
168
169 return true;
170 }
171 }
1 <?php
2
3 class Red_Json_File extends Red_FileIO {
4 public function force_download() {
5 parent::force_download();
6
7 header( 'Content-Type: application/json' );
8 header( 'Content-Disposition: attachment; filename="' . $this->export_filename( 'json' ) . '"' );
9 }
10
11 public function get_data( array $items, array $groups ) {
12 $version = red_get_plugin_data( dirname( dirname( __FILE__ ) ) . '/redirection.php' );
13
14 $items = array(
15 'plugin' => array(
16 'version' => trim( $version['Version'] ),
17 'date' => date( 'r' ),
18 ),
19 'groups' => $groups,
20 'redirects' => array_map( function( $item ) {
21 return $item->to_json();
22 }, $items ),
23 );
24
25 return wp_json_encode( $items, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) . PHP_EOL;
26 }
27
28 public function load( $group, $filename, $data ) {
29 global $wpdb;
30
31 $count = 0;
32 $json = @json_decode( $data, true );
33 if ( $json === false ) {
34 return 0;
35 }
36
37 // Import groups
38 $groups = array();
39 $group_map = array();
40
41 if ( isset( $json['groups'] ) ) {
42 foreach ( $json['groups'] as $group ) {
43 $old_group_id = $group['id'];
44 unset( $group['id'] );
45
46 $group = Red_Group::create( $group['name'], $group['module_id'], $group['enabled'] ? true : false );
47 if ( $group ) {
48 $group_map[ $old_group_id ] = $group->get_id();
49 }
50 }
51 }
52
53 unset( $json['groups'] );
54
55 // Import redirects
56 if ( isset( $json['redirects'] ) ) {
57 foreach ( $json['redirects'] as $pos => $redirect ) {
58 unset( $redirect['id'] );
59
60 if ( ! isset( $group_map[ $redirect['group_id'] ] ) ) {
61 $new_group = Red_Group::create( 'Group', 1 );
62 $group_map[ $redirect['group_id'] ] = $new_group->get_id();
63 }
64
65 if ( $redirect['match_type'] === 'url' && isset( $redirect['action_data'] ) && ! is_array( $redirect['action_data'] ) ) {
66 $redirect['action_data'] = array( 'url' => $redirect['action_data'] );
67 }
68
69 $redirect['group_id'] = $group_map[ $redirect['group_id'] ];
70 Red_Item::create( $redirect );
71 $count++;
72
73 // Helps reduce memory usage
74 unset( $json['redirects'][ $pos ] );
75 $wpdb->queries = array();
76 $wpdb->num_queries = 0;
77 }
78 }
79
80 return $count;
81 }
82 }
1 <?php
2
3 class Red_Nginx_File extends Red_FileIO {
4 public function force_download() {
5 parent::force_download();
6
7 header( 'Content-Type: application/octet-stream' );
8 header( 'Content-Disposition: attachment; filename="' . $this->export_filename( 'nginx' ) . '"' );
9 }
10
11 public function get_data( array $items, array $groups ) {
12 $lines = array();
13 $version = red_get_plugin_data( dirname( dirname( __FILE__ ) ) . '/redirection.php' );
14
15 $lines[] = '# Created by Redirection';
16 $lines[] = '# ' . date( 'r' );
17 $lines[] = '# Redirection ' . trim( $version['Version'] ) . ' - https://redirection.me';
18 $lines[] = '';
19 $lines[] = 'server {';
20
21 $parts = array();
22 foreach ( $items as $item ) {
23 if ( $item->is_enabled() ) {
24 $parts[] = $this->get_nginx_item( $item );
25 }
26 }
27
28 $lines = array_merge( $lines, array_filter( $parts ) );
29
30 $lines[] = '}';
31 $lines[] = '';
32 $lines[] = '# End of Redirection';
33
34 return implode( PHP_EOL, $lines ) . PHP_EOL;
35 }
36
37 private function get_redirect_code( Red_Item $item ) {
38 if ( $item->get_action_code() === 301 ) {
39 return 'permanent';
40 }
41 return 'redirect';
42 }
43
44 public function load( $group, $data, $filename = '' ) {
45 return 0;
46 }
47
48 private function get_nginx_item( Red_Item $item ) {
49 $target = 'add_' . $item->get_match_type();
50
51 if ( method_exists( $this, $target ) ) {
52 return ' ' . $this->$target( $item, $item->get_match_data() );
53 }
54
55 return false;
56 }
57
58 private function add_url( Red_Item $item, array $match_data ) {
59 return $this->get_redirect( $item->get_url(), $item->get_action_data(), $this->get_redirect_code( $item ), $match_data['source'], $item->source_flags->is_regex() );
60 }
61
62 private function add_agent( Red_Item $item, array $match_data ) {
63 if ( $item->match->url_from ) {
64 $lines[] = 'if ( $http_user_agent ~* ^' . $item->match->user_agent . '$ ) {';
65 $lines[] = ' ' . $this->get_redirect( $item->get_url(), $item->match->url_from, $this->get_redirect_code( $item ), $match_data['source'] );
66 $lines[] = ' }';
67 }
68
69 if ( $item->match->url_notfrom ) {
70 $lines[] = 'if ( $http_user_agent !~* ^' . $item->match->user_agent . '$ ) {';
71 $lines[] = ' ' . $this->get_redirect( $item->get_url(), $item->match->url_notfrom, $this->get_redirect_code( $item ), $match_data['source'] );
72 $lines[] = ' }';
73 }
74
75 return implode( "\n", $lines );
76 }
77
78 private function add_referrer( Red_Item $item, array $match_data ) {
79 if ( $item->match->url_from ) {
80 $lines[] = 'if ( $http_referer ~* ^' . $item->match->referrer . '$ ) {';
81 $lines[] = ' ' . $this->get_redirect( $item->get_url(), $item->match->url_from, $this->get_redirect_code( $item ), $match_data['source'] );
82 $lines[] = ' }';
83 }
84
85 if ( $item->match->url_notfrom ) {
86 $lines[] = 'if ( $http_referer !~* ^' . $item->match->referrer . '$ ) {';
87 $lines[] = ' ' . $this->get_redirect( $item->get_url(), $item->match->url_notfrom, $this->get_redirect_code( $item ), $match_data['source'] );
88 $lines[] = ' }';
89 }
90
91 return implode( "\n", $lines );
92 }
93
94 private function get_redirect( $line, $target, $code, $source, $regex = false ) {
95 $line = ltrim( $line, '^' );
96 $line = rtrim( $line, '$' );
97
98 $source_url = new Red_Url_Encode( $line, $regex );
99 $target_url = new Red_Url_Encode( $target );
100
101 // Remove any existing start/end from a regex
102 $from = $source_url->get_as_source();
103 $from = ltrim( $from, '^' );
104 $from = rtrim( $from, '$' );
105
106 if ( isset( $source['flag_case'] ) && $source['flag_case'] ) {
107 $from = '(?i)^' . $from;
108 } else {
109 $from = '^' . $from;
110 }
111
112 return 'rewrite ' . $from . '$ ' . $target_url->get_as_target() . ' ' . $code . ';';
113 }
114 }
1 <?php
2
3 class Red_Rss_File extends Red_FileIO {
4 public function force_download() {
5 header( 'Content-type: text/xml; charset=' . get_option( 'blog_charset' ), true );
6 }
7
8 public function get_data( array $items, array $groups ) {
9 $xml = '<?xml version="1.0" encoding="' . get_option( 'blog_charset' ) . '"?' . ">\r\n";
10 ob_start();
11 ?>
12 <rss version="2.0"
13 xmlns:content="http://purl.org/rss/1.0/modules/content/"
14 xmlns:wfw="http://wellformedweb.org/CommentAPI/"
15 xmlns:dc="http://purl.org/dc/elements/1.1/">
16 <channel>
17 <title>Redirection - <?php bloginfo_rss( 'name' ); ?></title>
18 <link><?php esc_url( bloginfo_rss( 'url' ) ); ?></link>
19 <description><?php esc_html( bloginfo_rss( 'description' ) ); ?></description>
20 <pubDate><?php echo esc_html( mysql2date( 'D, d M Y H:i:s +0000', get_lastpostmodified( 'GMT' ), false ) ); ?></pubDate>
21 <generator>
22 <?php echo esc_html( 'http://wordpress.org/?v=' ); ?>
23 <?php bloginfo_rss( 'version' ); ?>
24 </generator>
25 <language><?php echo esc_html( get_option( 'rss_language' ) ); ?></language>
26
27 <?php foreach ( $items as $log ) : ?>
28 <item>
29 <title><?php echo esc_html( $log->get_url() ); ?></title>
30 <link><![CDATA[<?php echo esc_url( home_url() ) . esc_url( $log->get_url() ); ?>]]></link>
31 <pubDate><?php echo esc_html( date( 'D, d M Y H:i:s +0000', intval( $log->get_last_hit(), 10 ) ) ); ?></pubDate>
32 <guid isPermaLink="false"><?php echo esc_html( $log->get_id() ); ?></guid>
33 <description><?php echo esc_html( $log->get_url() ); ?></description>
34 </item>
35 <?php endforeach; ?>
36 </channel>
37 </rss>
38 <?php
39 $xml .= ob_get_contents();
40 ob_end_clean();
41
42 return $xml;
43 }
44
45 function load( $group, $data, $filename = '' ) {
46 }
47 }
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.