initial
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
/**
|
||||
* @api {get} /redirection/v1/404 Get 404 logs
|
||||
* @apiName GetLogs
|
||||
* @apiDescription Get a paged list of 404 logs after applying a set of filters and result ordering.
|
||||
* @apiGroup 404
|
||||
*
|
||||
* @apiUse 404QueryParams
|
||||
*
|
||||
* @apiUse 404List
|
||||
* @apiUse 401Error
|
||||
* @apiUse 404Error
|
||||
*/
|
||||
|
||||
/**
|
||||
* @api {post} /redirection/v1/bulk/404/:type Bulk action
|
||||
* @apiName BulkAction
|
||||
* @apiDescription Delete 404 logs by ID
|
||||
* @apiGroup 404
|
||||
*
|
||||
* @apiParam (URL) {String="delete"} :type Type of bulk action that is applied to every log ID.
|
||||
*
|
||||
* @apiParam (Query Parameter) {String[]} [items] Array of group IDs to perform the action on
|
||||
* @apiParam (Query Parameter) {Boolean=false} [global] Perform action globally using the filter parameters
|
||||
* @apiUse 404QueryParams
|
||||
*
|
||||
* @apiUse 404List
|
||||
* @apiUse 401Error
|
||||
* @apiUse 404Error
|
||||
* @apiUse 400MissingError
|
||||
*/
|
||||
|
||||
/**
|
||||
* @apiDefine 404QueryParams 404 log query parameters
|
||||
*
|
||||
* @apiParam (Query Parameter) {String} [filterBy[ip]] Filter the results by the supplied IP
|
||||
* @apiParam (Query Parameter) {String} [filterBy[url]] Filter the results by the supplied URL
|
||||
* @apiParam (Query Parameter) {String} [filterBy[url-]exact] Filter the results by the exact URL (not a substring match, as per `url`)
|
||||
* @apiParam (Query Parameter) {String} [filterBy[referrer]] Filter the results by the supplied referrer
|
||||
* @apiParam (Query Parameter) {String} [filterBy[agent]] Filter the results by the supplied user agent
|
||||
* @apiParam (Query Parameter) {String} [filterBy[target]] Filter the results by the supplied redirect target
|
||||
* @apiParam (Query Parameter) {String} [filterBy[domain]] Filter the results by the supplied domain name
|
||||
* @apiParam (Query Parameter) {String="head","get","post"} [filterBy[method]] Filter the results by the supplied HTTP request method
|
||||
* @apiParam (Query Parameter) {Integer} [filterBy[http]] Filter the results by the supplied redirect HTTP code
|
||||
* @apiParam (Query Parameter) {string="ip","url"} [orderby] Order by IP or URL
|
||||
* @apiParam (Query Parameter) {String="asc","desc"} [direction] Direction to order the results by (ascending or descending)
|
||||
* @apiParam (Query Parameter) {Integer{1...200}} [per_page=25] Number of results per request
|
||||
* @apiParam (Query Parameter) {Integer} [page=0] Current page of results
|
||||
* @apiParam (Query Parameter) {String="ip","url"} [groupBy] Group by IP or URL
|
||||
*/
|
||||
|
||||
/**
|
||||
* @apiDefine 404List
|
||||
*
|
||||
* @apiSuccess {Object[]} items Array of 404 log objects
|
||||
* @apiSuccess {Integer} items.id ID of 404 log entry
|
||||
* @apiSuccess {String} items.created Date the 404 log entry was recorded
|
||||
* @apiSuccess {Integer} items.created_time Unix time value for `created`
|
||||
* @apiSuccess {Integer} items.url The requested URL that caused the 404 log entry
|
||||
* @apiSuccess {String} items.agent User agent of the client initiating the request
|
||||
* @apiSuccess {Integer} items.referrer Referrer of the client initiating the request
|
||||
* @apiSuccess {Integer} total Number of items
|
||||
*
|
||||
* @apiSuccessExample {json} Success 200:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "items": [
|
||||
* {
|
||||
* "id": 3,
|
||||
* "created": "2019-01-01 12:12:00,
|
||||
* "created_time": "12345678",
|
||||
* "url": "/the-url",
|
||||
* "agent": "FancyBrowser",
|
||||
* "referrer": "http://site.com/previous/,
|
||||
* }
|
||||
* ],
|
||||
* "total": 1
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @phpstan-type Log404Response array{
|
||||
* items: list<array<string, mixed>|object>,
|
||||
* total: int
|
||||
* }
|
||||
*
|
||||
* 404 API endpoint
|
||||
*/
|
||||
class Redirection_Api_404 extends Redirection_Api_Filter_Route {
|
||||
/**
|
||||
* 404 API endpoint constructor
|
||||
*
|
||||
* @param non-falsy-string $api_namespace Namespace.
|
||||
*/
|
||||
public function __construct( $api_namespace ) {
|
||||
$orders = [ 'url', 'ip', 'total', 'count', '' ];
|
||||
$filters = [ 'ip', 'url-exact', 'referrer', 'agent', 'url', 'domain', 'method', 'http' ];
|
||||
|
||||
// GET /404 - List 404 logs
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/404',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'route_404' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_manage' ],
|
||||
'args' => $this->get_filter_args( $orders, $filters ),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// POST /bulk/404/:bulk - Bulk delete 404 logs
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/bulk/404/(?P<bulk>delete)',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => [ $this, 'route_bulk' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_delete' ],
|
||||
'args' => array_merge(
|
||||
$this->get_filter_args( $orders, $filters ),
|
||||
[
|
||||
'items' => [
|
||||
'description' => 'Comma separated list of item IDs to perform action on',
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'description' => 'Item ID',
|
||||
'type' => [ 'string', 'number' ],
|
||||
],
|
||||
],
|
||||
]
|
||||
),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a manage capability
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request Request.
|
||||
* @return bool
|
||||
*/
|
||||
public function permission_callback_manage( WP_REST_Request $request ) {
|
||||
return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_404_MANAGE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a delete capability
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request Request.
|
||||
* @return bool
|
||||
*/
|
||||
public function permission_callback_delete( WP_REST_Request $request ) {
|
||||
return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_404_DELETE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get 404 log
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request The request.
|
||||
* @return Log404Response
|
||||
*/
|
||||
public function route_404( WP_REST_Request $request ) {
|
||||
return $this->get_404( $request->get_params() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform action on 404s
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request The request.
|
||||
* @return Log404Response
|
||||
*/
|
||||
public function route_bulk( WP_REST_Request $request ) {
|
||||
$params = $request->get_params();
|
||||
|
||||
if ( isset( $params['items'] ) && is_array( $params['items'] ) && count( $params['items'] ) > 0 ) {
|
||||
$items = $params['items'];
|
||||
|
||||
foreach ( $items as $item ) {
|
||||
if ( is_numeric( $item ) ) {
|
||||
Red_404_Log::delete( intval( $item, 10 ) );
|
||||
} elseif ( isset( $params['groupBy'] ) ) {
|
||||
$group_by = sanitize_text_field( $params['groupBy'] );
|
||||
$delete_by = 'url-exact';
|
||||
|
||||
if ( in_array( $group_by, [ 'ip', 'agent' ], true ) ) {
|
||||
$delete_by = $group_by;
|
||||
}
|
||||
|
||||
Red_404_Log::delete_all( [ 'filterBy' => [ $delete_by => $item ] ] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $params['groupBy'] ) && $params['groupBy'] === 'url-exact' ) {
|
||||
unset( $params['groupBy'] );
|
||||
}
|
||||
} elseif ( isset( $params['global'] ) && $params['global'] !== false ) {
|
||||
Red_404_Log::delete_all( $params );
|
||||
}
|
||||
|
||||
return $this->get_404( $params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get 404 log
|
||||
*
|
||||
* @param array<string, mixed> $params The request parameters.
|
||||
* @return Log404Response
|
||||
*/
|
||||
private function get_404( array $params ) {
|
||||
if ( isset( $params['groupBy'] ) && in_array( $params['groupBy'], [ 'ip', 'url', 'agent', 'url-exact' ], true ) ) {
|
||||
$group_by = sanitize_text_field( $params['groupBy'] );
|
||||
if ( $group_by === 'url-exact' ) {
|
||||
$group_by = 'url';
|
||||
}
|
||||
|
||||
return Red_404_Log::get_grouped( $group_by, $params );
|
||||
}
|
||||
|
||||
return Red_404_Log::get_filtered( $params );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
class Redirection_Api {
|
||||
/**
|
||||
* @var Redirection_Api|null
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* @var array<int, Redirection_Api_Route>
|
||||
* @phpstan-ignore property.onlyWritten
|
||||
*/
|
||||
private $routes = array();
|
||||
|
||||
/**
|
||||
* @return Redirection_Api
|
||||
*/
|
||||
public static function init() {
|
||||
if ( is_null( self::$instance ) ) {
|
||||
self::$instance = new Redirection_Api();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
|
||||
$wpdb->hide_errors();
|
||||
|
||||
$this->routes[] = new Redirection_Api_Redirect( REDIRECTION_API_NAMESPACE );
|
||||
$this->routes[] = new Redirection_Api_Group( REDIRECTION_API_NAMESPACE );
|
||||
$this->routes[] = new Redirection_Api_Log( REDIRECTION_API_NAMESPACE );
|
||||
$this->routes[] = new Redirection_Api_404( REDIRECTION_API_NAMESPACE );
|
||||
$this->routes[] = new Redirection_Api_Settings( REDIRECTION_API_NAMESPACE );
|
||||
$this->routes[] = new Redirection_Api_Plugin( REDIRECTION_API_NAMESPACE );
|
||||
$this->routes[] = new Redirection_Api_Import( REDIRECTION_API_NAMESPACE );
|
||||
$this->routes[] = new Redirection_Api_Export( REDIRECTION_API_NAMESPACE );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @api {get} /redirection/v1/export/:module/:format Export redirects
|
||||
* @apiName Export
|
||||
* @apiDescription Export redirects for a module to Apache, CSV, Nginx, or JSON format
|
||||
* @apiGroup Import/Export
|
||||
*
|
||||
* @apiParam (URL) {String="1","2","3","all"} :module The module to export, with 1 being WordPress, 2 is Apache, and 3 is Nginx
|
||||
* @apiParam (URL) {String="csv","apache","nginx","json"} :format The format of the export
|
||||
*
|
||||
* @apiSuccess {String} data Exported data
|
||||
* @apiSuccess {Integer} total Number of items exported
|
||||
*
|
||||
* @apiUse 401Error
|
||||
* @apiUse 404Error
|
||||
* @apiError redirect_export_invalid_module Invalid module
|
||||
* @apiErrorExample {json} 404 Error Response:
|
||||
* HTTP/1.1 400 Bad Request
|
||||
* {
|
||||
* "code": "redirect_export_invalid_module",
|
||||
* "message": "Invalid module"
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @phpstan-type ExportResponse array{
|
||||
* data: string,
|
||||
* total: int
|
||||
* }
|
||||
*/
|
||||
class Redirection_Api_Export extends Redirection_Api_Route {
|
||||
/**
|
||||
* Export API endpoint constructor
|
||||
*
|
||||
* @param non-falsy-string $api_namespace Namespace.
|
||||
*/
|
||||
public function __construct( $api_namespace ) {
|
||||
// GET /export/:module/:format - Export redirects to specified format
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/export/(?P<module>1|2|3|all)/(?P<format>csv|apache|nginx|json)',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'route_export' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_manage' ],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user has permission to manage import/export
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request
|
||||
* @return bool
|
||||
*/
|
||||
public function permission_callback_manage( WP_REST_Request $request ) {
|
||||
return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_IO_MANAGE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Export redirects to a specified format
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request
|
||||
* @return ExportResponse|WP_Error
|
||||
*/
|
||||
public function route_export( WP_REST_Request $request ) {
|
||||
$module = sanitize_text_field( $request['module'] );
|
||||
$format = 'json';
|
||||
|
||||
if ( in_array( $request['format'], [ 'csv', 'apache', 'nginx', 'json' ], true ) ) {
|
||||
$format = sanitize_text_field( $request['format'] );
|
||||
}
|
||||
|
||||
$export = Red_FileIO::export( $module, $format );
|
||||
if ( $export === false ) {
|
||||
return $this->add_error_details( new WP_Error( 'redirect_export_invalid_module', 'Invalid module' ), __LINE__ );
|
||||
}
|
||||
|
||||
return array(
|
||||
'data' => $export['data'],
|
||||
'total' => $export['total'],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
class Redirection_Api_Filter_Route extends Redirection_Api_Route {
|
||||
/**
|
||||
* Validate filter param against allowed fields
|
||||
*
|
||||
* @param mixed $value Provided filter value.
|
||||
* @param WP_REST_Request $request Request.
|
||||
* @param string $param Param name.
|
||||
*
|
||||
* @phpstan-param mixed $value
|
||||
* @phpstan-param WP_REST_Request<array<string, mixed>> $request
|
||||
* @phpstan-param string $param
|
||||
* @phpstan-return true|WP_Error
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public function validate_filter( $value, $request, $param ) {
|
||||
$attrs = $request->get_attributes();
|
||||
/** @var array<string, mixed> $attrs */
|
||||
$fields = [];
|
||||
if ( isset( $attrs['args'] ) && is_array( $attrs['args'] ) ) {
|
||||
$args = $attrs['args'];
|
||||
if ( isset( $args['filterBy'] ) && is_array( $args['filterBy'] ) ) {
|
||||
$filter_by = $args['filterBy'];
|
||||
if ( isset( $filter_by['filter_fields'] ) && is_array( $filter_by['filter_fields'] ) ) {
|
||||
/** @var list<string> $fields */
|
||||
$fields = array_values( array_map( 'strval', $filter_by['filter_fields'] ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// An empty string means no filter was set (client sent filterBy= with nothing) — normalize to an empty array
|
||||
if ( $value === '' ) {
|
||||
$request->set_param( $param, [] );
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! is_array( $value ) ) {
|
||||
return new WP_Error( 'rest_invalid_param', 'Filter is not an array', array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
/** @var array<string, mixed> $value */
|
||||
|
||||
if ( count( $fields ) > 0 ) {
|
||||
foreach ( array_keys( $value ) as $key ) {
|
||||
if ( ! in_array( $key, $fields, true ) ) {
|
||||
return new WP_Error( 'rest_invalid_param', 'Filter type is not supported: ' . $key, array( 'status' => 400 ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build shared filter args
|
||||
*
|
||||
* @param list<string> $order_fields Fields allowed for orderby.
|
||||
* @param list<string> $filters Fields allowed to filter by.
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function get_filter_args( $order_fields, $filters = [] ) {
|
||||
// Safety check: ensure Red_Item class is loaded (prevents fatal errors during incomplete plugin updates)
|
||||
// Use a fallback value if the class doesn't exist
|
||||
$max_per_page = 200; // Default fallback value
|
||||
if ( class_exists( 'Red_Item' ) ) {
|
||||
$max_per_page = Red_Item::MAX_PER_PAGE;
|
||||
}
|
||||
|
||||
return [
|
||||
'filterBy' => [
|
||||
'description' => 'Field to filter by',
|
||||
'validate_callback' => [ $this, 'validate_filter' ],
|
||||
'filter_fields' => $filters,
|
||||
],
|
||||
'orderby' => [
|
||||
'description' => 'Field to order results by',
|
||||
'type' => 'string',
|
||||
'enum' => $order_fields,
|
||||
],
|
||||
'direction' => [
|
||||
'description' => 'Direction of ordered results',
|
||||
'type' => 'string',
|
||||
'default' => 'desc',
|
||||
'enum' => [ 'asc', 'desc' ],
|
||||
],
|
||||
'per_page' => [
|
||||
'description' => 'Number of results per page',
|
||||
'type' => 'integer',
|
||||
'default' => 25,
|
||||
'minimum' => 5,
|
||||
'maximum' => $max_per_page,
|
||||
],
|
||||
'page' => [
|
||||
'description' => 'Page offset',
|
||||
'type' => 'integer',
|
||||
'minimum' => 0,
|
||||
'default' => 0,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a bulk action endpoint for a resource
|
||||
*
|
||||
* @param non-falsy-string $namespace REST namespace.
|
||||
* @param non-falsy-string $route REST route.
|
||||
* @param list<string> $orders Allowed order fields.
|
||||
* @param list<string> $filters Allowed filter fields.
|
||||
* @param string $callback Handler method on this class.
|
||||
* @param callable|array{0:self,1:string}|false $permissions Permission callback or false for default.
|
||||
* @return void
|
||||
*/
|
||||
public function register_bulk( $namespace, $route, $orders, $filters, $callback, $permissions = false ) {
|
||||
register_rest_route(
|
||||
$namespace,
|
||||
$route,
|
||||
array_merge(
|
||||
$this->get_route( WP_REST_Server::EDITABLE, $callback, $permissions ),
|
||||
[
|
||||
'args' => array_merge(
|
||||
$this->get_filter_args( $orders, $filters ),
|
||||
[
|
||||
'items' => [
|
||||
'description' => 'Comma separated list of item IDs to perform action on',
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @api {get} /redirection/v1/group Get groups
|
||||
* @apiName GetGroups
|
||||
* @apiDescription Get a paged list of groups based after applying a set of filters and result ordering.
|
||||
* @apiGroup Group
|
||||
*
|
||||
* @apiUse GroupQueryParams
|
||||
*
|
||||
* @apiUse GroupList
|
||||
* @apiUse 401Error
|
||||
* @apiUse 404Error
|
||||
*/
|
||||
|
||||
/**
|
||||
* @api {post} /redirection/v1/group Create group
|
||||
* @apiName CreateGroup
|
||||
* @apiDescription Create a new group, and return a paged list of groups.
|
||||
* @apiGroup Group
|
||||
*
|
||||
* @apiUse GroupItem
|
||||
* @apiUse GroupQueryParams
|
||||
*
|
||||
* @apiUse GroupList
|
||||
* @apiUse 401Error
|
||||
* @apiUse 404Error
|
||||
* @apiError (Error 400) redirect_group_invalid Invalid group or parameters
|
||||
* @apiErrorExample {json} 404 Error Response:
|
||||
* HTTP/1.1 400 Bad Request
|
||||
* {
|
||||
* "code": "redirect_group_invalid",
|
||||
* "message": "Invalid group or parameters"
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @api {post} /redirection/v1/group/:id Update group
|
||||
* @apiName UpdateGroup
|
||||
* @apiDescription Update an existing group.
|
||||
* @apiGroup Group
|
||||
*
|
||||
* @apiParam (URL) {Integer} :id Group ID to update
|
||||
* @apiUse GroupList
|
||||
*
|
||||
* @apiSuccess {String} item The updated group
|
||||
* @apiSuccess {Integer} item.id ID of group
|
||||
* @apiSuccess {String} item.name Name of this group
|
||||
* @apiSuccess {Boolean} item.enabled `true` if group (and redirects) are enabled, `false` otherwise
|
||||
* @apiSuccess {Integer} item.redirects Number of redirects in this group
|
||||
* @apiSuccess {String} item.moduleName Name of the module this group belongs to
|
||||
* @apiSuccess {Integer} item.module_id ID of the module this group belongs to
|
||||
*
|
||||
* @apiUse 401Error
|
||||
* @apiUse 404Error
|
||||
*
|
||||
* @apiError (Error 400) redirect_group_invalid Invalid group or parameters
|
||||
* @apiErrorExample {json} 404 Error Response:
|
||||
* HTTP/1.1 400 Bad Request
|
||||
* {
|
||||
* "code": "redirect_group_invalid",
|
||||
* "message": "Invalid group or parameters"
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @api {post} /redirection/v1/bulk/group/:type Bulk action
|
||||
* @apiName BulkAction
|
||||
* @apiDescription Enable, disable, and delete a set of groups. The endpoint will return the next page of results after.
|
||||
* performing the action, based on the supplied query parameters. This information can be used to refresh a list displayed to the client.
|
||||
* @apiGroup Group
|
||||
*
|
||||
* @apiParam (URL) {String="delete","enable","disable"} :type Type of bulk action that is applied to every group ID.
|
||||
* Enabling or disabling a group will also enable or disable all redirects in that group
|
||||
*
|
||||
* @apiParam (Query Parameter) {String[]} [items] Array of group IDs to perform the action on
|
||||
* @apiParam (Query Parameter) {Boolean=false} [global] Perform action globally using the filter parameters
|
||||
* @apiUse GroupQueryParams
|
||||
*
|
||||
* @apiUse GroupList
|
||||
* @apiUse 401Error
|
||||
* @apiUse 404Error
|
||||
* @apiUse 400MissingError
|
||||
*/
|
||||
|
||||
/**
|
||||
* @apiDefine GroupQueryParams
|
||||
*
|
||||
* @apiParam (Query Parameter) {String} [filterBy[name]] Filter the results by the supplied name
|
||||
* @apiParam (Query Parameter) {String="enabled","disabled"} [filterBy[status]] Filter the results by the supplied status
|
||||
* @apiParam (Query Parameter) {Integer="1","2","3"} [filterBy[module]] Filter the results by the supplied module ID
|
||||
* @apiParam (Query Parameter) {String="name"} [orderby] Order in which results are returned
|
||||
* @apiParam (Query Parameter) {String="asc","desc"} [direction=desc] Direction to order the results by (ascending or descending)
|
||||
* @apiParam (Query Parameter) {Integer{1...200}} [per_page=25] Number of results per request
|
||||
* @apiParam (Query Parameter) {Integer} [page=0] Current page of results
|
||||
*/
|
||||
|
||||
/**
|
||||
* @apiDefine GroupItem
|
||||
*
|
||||
* @apiParam (JSON Body) {String} name Name of the group
|
||||
* @apiParam (JSON Body) {Integer="1","2","3"} moduleID Module ID of the group, with 1 being WordPress, 2 is Apache, and 3 is Nginx
|
||||
*/
|
||||
|
||||
/**
|
||||
* @apiDefine GroupList
|
||||
*
|
||||
* @apiSuccess {Object[]} items Array of group objects
|
||||
* @apiSuccess {Integer} items.id ID of group
|
||||
* @apiSuccess {String} items.name Name of this group
|
||||
* @apiSuccess {Boolean} items.enabled `true` if group (and redirects) are enabled, `false` otherwise
|
||||
* @apiSuccess {Integer} items.redirects Number of redirects in this group
|
||||
* @apiSuccess {String} items.moduleName Name of the module this group belongs to
|
||||
* @apiSuccess {Integer} items.module_id ID of the module this group belongs to
|
||||
* @apiSuccess {Integer} total Number of items
|
||||
*
|
||||
* @apiSuccessExample {json} Success 200:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "items": [
|
||||
* {
|
||||
* "id": 3,
|
||||
* "enabled": true,
|
||||
* "moduleName": "WordPress",
|
||||
* "module_id": 1,
|
||||
* "name": "Redirections",
|
||||
* "redirects": 0,
|
||||
* }
|
||||
* ],
|
||||
* "total": 1
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* Group API endpoint
|
||||
*
|
||||
* @phpstan-type GroupListResponse array{
|
||||
* items: list<array<string, mixed>>,
|
||||
* total: int
|
||||
* }
|
||||
*/
|
||||
class Redirection_Api_Group extends Redirection_Api_Filter_Route {
|
||||
/**
|
||||
* 404 API endpoint constructor
|
||||
*
|
||||
* @param non-falsy-string $api_namespace Namespace.
|
||||
*/
|
||||
public function __construct( $api_namespace ) {
|
||||
$orders = [ 'name', 'id', '' ];
|
||||
$filters = [ 'status', 'module', 'name' ];
|
||||
|
||||
// GET /group - List groups
|
||||
// POST /group - Create group
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/group',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'route_list' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_manage' ],
|
||||
'args' => $this->get_filter_args( $orders, $filters ),
|
||||
],
|
||||
[
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => [ $this, 'route_create' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_add' ],
|
||||
'args' => array_merge(
|
||||
$this->get_filter_args( $orders, $filters ),
|
||||
$this->get_group_args()
|
||||
),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// POST /group/:id - Update group
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/group/(?P<id>[\d]+)',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => [ $this, 'route_update' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_add' ],
|
||||
'args' => $this->get_group_args(),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// POST /bulk/group/:bulk - Bulk actions on groups
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/bulk/group/(?P<bulk>delete|enable|disable)',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => [ $this, 'route_bulk' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_bulk' ],
|
||||
'args' => array_merge(
|
||||
$this->get_filter_args( $orders, $filters ),
|
||||
[
|
||||
'items' => [
|
||||
'description' => 'Comma separated list of item IDs to perform action on',
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'description' => 'Item ID',
|
||||
'type' => [ 'string', 'number' ],
|
||||
],
|
||||
],
|
||||
]
|
||||
),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a manage capability
|
||||
*
|
||||
* Access to group data is required by the CAP_GROUP_MANAGE and CAP_REDIRECT_MANAGE caps
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request Request.
|
||||
* @return bool
|
||||
*/
|
||||
public function permission_callback_manage( WP_REST_Request $request ) {
|
||||
return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_GROUP_MANAGE ) || Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_REDIRECT_MANAGE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a bulk capability
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request Request.
|
||||
* @return bool
|
||||
*/
|
||||
public function permission_callback_bulk( WP_REST_Request $request ) {
|
||||
if ( $request['bulk'] === 'delete' ) {
|
||||
return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_GROUP_DELETE );
|
||||
}
|
||||
|
||||
return $this->permission_callback_add( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a create capability
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request Request.
|
||||
* @return bool
|
||||
*/
|
||||
public function permission_callback_add( WP_REST_Request $request ) {
|
||||
return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_GROUP_ADD );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
private function get_group_args() {
|
||||
return array(
|
||||
'moduleId' => array(
|
||||
'description' => 'Module ID',
|
||||
'type' => 'integer',
|
||||
'minimum' => 0,
|
||||
'maximum' => 3,
|
||||
'required' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'description' => 'Group name',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'status' => [
|
||||
'description' => 'Status of the group',
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get group list
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request The request.
|
||||
* @return GroupListResponse
|
||||
*/
|
||||
public function route_list( WP_REST_Request $request ) {
|
||||
return Red_Group::get_filtered( $request->get_params() ); // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a group
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request The request.
|
||||
* @return GroupListResponse|WP_Error
|
||||
*/
|
||||
public function route_create( WP_REST_Request $request ) {
|
||||
$params = $request->get_params();
|
||||
$name = '';
|
||||
$module = 0;
|
||||
|
||||
if ( isset( $params['name'] ) ) {
|
||||
$name = sanitize_text_field( $params['name'] );
|
||||
}
|
||||
|
||||
if ( isset( $params['moduleId'] ) ) {
|
||||
$module = intval( $params['moduleId'], 10 );
|
||||
}
|
||||
|
||||
$group = Red_Group::create( $name, $module );
|
||||
|
||||
if ( $group !== false ) {
|
||||
return Red_Group::get_filtered( $params ); // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
return $this->add_error_details( new WP_Error( 'redirect_group_invalid', 'Invalid group or parameters' ), __LINE__ );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a group
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request The request.
|
||||
* @return array{item: array<string, mixed>}|WP_Error
|
||||
*/
|
||||
public function route_update( WP_REST_Request $request ) {
|
||||
$params = $request->get_params();
|
||||
$group = Red_Group::get( intval( $request['id'], 10 ) );
|
||||
|
||||
if ( $group !== false ) {
|
||||
$result = $group->update( $params );
|
||||
|
||||
if ( $result !== false ) {
|
||||
return array( 'item' => $group->to_json() );
|
||||
}
|
||||
}
|
||||
|
||||
return $this->add_error_details( new WP_Error( 'redirect_group_invalid', 'Invalid group details' ), __LINE__ );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform action on groups
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request The request.
|
||||
* @return GroupListResponse|WP_Error
|
||||
*/
|
||||
public function route_bulk( WP_REST_Request $request ) {
|
||||
$params = $request->get_params();
|
||||
$action = $request['bulk'];
|
||||
|
||||
$items = [];
|
||||
if ( isset( $params['items'] ) && is_array( $params['items'] ) ) {
|
||||
// Array of integers, sanitized below
|
||||
$items = $params['items'];
|
||||
} elseif ( isset( $params['global'] ) && $params['global'] !== false ) {
|
||||
// Groups have additional actions that fire and so we need to action them individually
|
||||
$groups = Red_Group::get_all( $params );
|
||||
$items = array_column( $groups, 'id' );
|
||||
}
|
||||
|
||||
foreach ( $items as $item ) {
|
||||
$group = Red_Group::get( intval( $item, 10 ) );
|
||||
|
||||
if ( is_object( $group ) ) {
|
||||
if ( $action === 'delete' ) {
|
||||
$group->delete();
|
||||
} elseif ( $action === 'disable' ) {
|
||||
$group->disable();
|
||||
} elseif ( $action === 'enable' ) {
|
||||
$group->enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->route_list( $request );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @api {get} /redirection/v1/import/file/:group_id Import redirects
|
||||
* @apiName Import
|
||||
* @apiDescription Import redirects from CSV, JSON, or Apache .htaccess
|
||||
* @apiGroup Import/Export
|
||||
*
|
||||
* @apiParam (URL) {Integer} :group_id The group ID to import into
|
||||
* @apiParam (File) {File} file The multipart form upload containing the file to import
|
||||
*
|
||||
* @apiSuccess {Integer} imported Number of items imported
|
||||
*
|
||||
* @apiUse 401Error
|
||||
* @apiUse 404Error
|
||||
* @apiError (Error 400) redirect_import_invalid_group Invalid group
|
||||
* @apiErrorExample {json} 404 Error Response:
|
||||
* HTTP/1.1 400 Bad Request
|
||||
* {
|
||||
* "code": "redirect_import_invalid_group",
|
||||
* "message": "Invalid group"
|
||||
* }
|
||||
* @apiError (Error 400) redirect_import_invalid_file Invalid file upload
|
||||
* @apiErrorExample {json} 404 Error Response:
|
||||
* HTTP/1.1 400 Bad Request
|
||||
* {
|
||||
* "code": "redirect_import_invalid_file",
|
||||
* "message": "Invalid file upload"
|
||||
* }
|
||||
*/
|
||||
/**
|
||||
* @phpstan-type ImportPluginPayload array{
|
||||
* plugin?: string|list<string>
|
||||
* }
|
||||
* @phpstan-type ImportFileParams array{
|
||||
* file?: array{
|
||||
* tmp_name: string,
|
||||
* name: string,
|
||||
* size: int,
|
||||
* type: string,
|
||||
* error: int
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
class Redirection_Api_Import extends Redirection_Api_Route {
|
||||
/**
|
||||
* @param non-falsy-string $api_namespace REST namespace.
|
||||
*/
|
||||
public function __construct( $api_namespace ) {
|
||||
// POST /import/file/:group_id - Import from file upload
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/import/file/(?P<group_id>\d+)',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => [ $this, 'route_import_file' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_manage' ],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// GET/POST /import/plugin - List or import from plugins
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/import/plugin',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'route_plugin_import_list' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_manage' ],
|
||||
],
|
||||
[
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => [ $this, 'route_plugin_import' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_manage' ],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission callback used for import routes.
|
||||
*
|
||||
* @param WP_REST_Request $_request Request (unused).
|
||||
* @phpstan-param WP_REST_Request<array<string, mixed>> $_request
|
||||
* @return bool
|
||||
*/
|
||||
public function permission_callback_manage( WP_REST_Request $_request ) {
|
||||
return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_IO_MANAGE );
|
||||
}
|
||||
|
||||
/**
|
||||
* List available plugin importers.
|
||||
*
|
||||
* @param WP_REST_Request $request Request.
|
||||
* @phpstan-param WP_REST_Request<array<string, mixed>> $request
|
||||
* @phpstan-return array{importers: array<int, mixed>}
|
||||
* @return array{importers: array}
|
||||
*/
|
||||
public function route_plugin_import_list( WP_REST_Request $request ) {
|
||||
include_once dirname( __DIR__ ) . '/models/importer.php';
|
||||
|
||||
return array( 'importers' => Red_Plugin_Importer::get_plugins() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import redirects using selected plugin importers.
|
||||
*
|
||||
* @param WP_REST_Request $request Request.
|
||||
* @phpstan-param WP_REST_Request<array<string, mixed>> $request
|
||||
* @phpstan-return array{imported: int}|WP_Error
|
||||
* @return array{imported: int}|WP_Error
|
||||
*/
|
||||
public function route_plugin_import( WP_REST_Request $request ) {
|
||||
include_once dirname( __DIR__ ) . '/models/importer.php';
|
||||
|
||||
$params = $request->get_params();
|
||||
/** @var ImportPluginPayload $params */
|
||||
$groups = Red_Group::get_all();
|
||||
/** @var array<array{id: int, name: string, redirects: int, module_id: int, moduleName: string, enabled: bool, default?: bool}> $groups */
|
||||
$plugin_param = $params['plugin'] ?? $request->get_param( 'plugin' );
|
||||
if ( is_array( $plugin_param ) ) {
|
||||
$plugins = array_map( 'strval', $plugin_param );
|
||||
} elseif ( $plugin_param === null ) {
|
||||
$plugins = [];
|
||||
} else {
|
||||
$plugins = [ (string) $plugin_param ];
|
||||
}
|
||||
/** @var list<string> $plugins */
|
||||
$plugins = array_map( 'sanitize_text_field', $plugins );
|
||||
$total = 0;
|
||||
|
||||
if ( count( $groups ) === 0 ) {
|
||||
return $this->add_error_details(
|
||||
new WP_Error( 'redirect_import_invalid_group', 'No groups are available for import' ),
|
||||
__LINE__
|
||||
);
|
||||
}
|
||||
|
||||
foreach ( $plugins as $plugin ) {
|
||||
$total += Red_Plugin_Importer::import( $plugin, $groups[0]['id'] );
|
||||
}
|
||||
|
||||
return [ 'imported' => $total ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Import redirects from an uploaded file.
|
||||
*
|
||||
* @param WP_REST_Request $request Request.
|
||||
* @phpstan-param WP_REST_Request<array<string, mixed>> $request
|
||||
* @phpstan-return array{imported: int}|WP_Error
|
||||
* @return array{imported: int}|WP_Error
|
||||
*/
|
||||
public function route_import_file( WP_REST_Request $request ) {
|
||||
$file_params = $request->get_file_params();
|
||||
/** @var ImportFileParams $file_params */
|
||||
$group_id = intval( $request['group_id'], 10 );
|
||||
|
||||
if ( ! isset( $file_params['file'] ) || ! is_uploaded_file( $file_params['file']['tmp_name'] ) ) {
|
||||
return $this->add_error_details( new WP_Error( 'redirect_import_invalid_file', 'Invalid file upload' ), __LINE__ );
|
||||
}
|
||||
|
||||
$upload = $file_params['file'];
|
||||
$parts = pathinfo( $upload['name'] );
|
||||
$extension = isset( $parts['extension'] ) ? strtolower( $parts['extension'] ) : '';
|
||||
|
||||
// JSON imports don't need a group, but all other formats do
|
||||
if ( $extension !== 'json' ) {
|
||||
$group = Red_Group::get( $group_id );
|
||||
if ( $group === false ) {
|
||||
return $this->add_error_details( new WP_Error( 'redirect_import_invalid_group', 'Invalid group' ), __LINE__ );
|
||||
}
|
||||
}
|
||||
|
||||
$count = Red_FileIO::import( $group_id, $upload );
|
||||
|
||||
// Import failure returns 0, but 0 can also mean no valid redirects in file
|
||||
// For JSON files, pre-validate to distinguish between invalid JSON and empty/no-redirects
|
||||
if ( $count === 0 && $extension === 'json' ) {
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local file read
|
||||
$content = file_get_contents( $upload['tmp_name'] );
|
||||
if ( $content !== false ) {
|
||||
json_decode( $content, true );
|
||||
if ( json_last_error() !== JSON_ERROR_NONE ) {
|
||||
return $this->add_error_details(
|
||||
new WP_Error( 'redirect_import_invalid_json', 'Invalid JSON file: ' . json_last_error_msg() ),
|
||||
__LINE__
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'imported' => $count,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @api {get} /redirection/v1/log Get logs
|
||||
* @apiName GetLogs
|
||||
* @apiDescription Get a paged list of redirect logs after applying a set of filters and result ordering.
|
||||
* @apiGroup Log
|
||||
*
|
||||
* @apiUse LogQueryParams
|
||||
*
|
||||
* @apiUse LogList
|
||||
* @apiUse 401Error
|
||||
* @apiUse 404Error
|
||||
*/
|
||||
|
||||
/**
|
||||
* @api {post} /redirection/v1/log Delete logs
|
||||
* @apiName DeleteLogs
|
||||
* @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.
|
||||
* performing the action, based on the supplied query parameters. This information can be used to refresh a list displayed to the client.
|
||||
* @apiGroup Log
|
||||
*
|
||||
* @apiParam (Query Parameter) {String} filterBy[ip] Filter the results by the supplied IP
|
||||
* @apiParam (Query Parameter) {String} filterBy[url] Filter the results by the supplied URL
|
||||
* @apiParam (Query Parameter) {String} filterBy[url-exact] Filter the results by the exact URL (not a substring match, as per `url`)
|
||||
* @apiParam (Query Parameter) {String} filterBy[referrer] Filter the results by the supplied referrer
|
||||
* @apiParam (Query Parameter) {String} filterBy[agent] Filter the results by the supplied user agent
|
||||
* @apiParam (Query Parameter) {String} filterBy[target] Filter the results by the supplied redirect target
|
||||
*
|
||||
* @apiUse LogList
|
||||
* @apiUse 401Error
|
||||
* @apiUse 404Error
|
||||
*/
|
||||
|
||||
/**
|
||||
* @api {post} /redirection/v1/bulk/log/:type Bulk action
|
||||
* @apiName BulkAction
|
||||
* @apiDescription Delete logs by ID
|
||||
* @apiGroup Log
|
||||
*
|
||||
* @apiParam (URL) {String="delete"} :type Type of bulk action that is applied to every log ID.
|
||||
* @apiParam (Query Parameter) {String[]} [items] Array of group IDs to perform the action on
|
||||
* @apiParam (Query Parameter) {Boolean=false} [global] Perform action globally using the filter parameters
|
||||
* @apiUse LogQueryParams
|
||||
*
|
||||
* @apiUse LogList
|
||||
* @apiUse 401Error
|
||||
* @apiUse 404Error
|
||||
* @apiUse 400MissingError
|
||||
*/
|
||||
|
||||
/**
|
||||
* @apiDefine LogQueryParams Log query parameters
|
||||
*
|
||||
* @apiParam (Query Parameter) {String} [filterBy[ip]] Filter the results by the supplied IP
|
||||
* @apiParam (Query Parameter) {String} [filterBy[url]] Filter the results by the supplied URL
|
||||
* @apiParam (Query Parameter) {String} [filterBy[url-]exact] Filter the results by the exact URL (not a substring match, as per `url`)
|
||||
* @apiParam (Query Parameter) {String} [filterBy[referrer]] Filter the results by the supplied referrer
|
||||
* @apiParam (Query Parameter) {String} [filterBy[agent]] Filter the results by the supplied user agent
|
||||
* @apiParam (Query Parameter) {String} [filterBy[target]] Filter the results by the supplied redirect target
|
||||
* @apiParam (Query Parameter) {String} [filterBy[domain]] Filter the results by the supplied domain name
|
||||
* @apiParam (Query Parameter) {String} [filterBy[redirect_by]] Filter the results by the redirect agent
|
||||
* @apiParam (Query Parameter) {String="head","get","post"} [filterBy[method]] Filter the results by the supplied HTTP request method
|
||||
* @apiParam (Query Parameter) {String="ip","url"} [orderby] Order by IP or URL
|
||||
* @apiParam (Query Parameter) {String="asc","desc"} [direction=desc] Direction to order the results by (ascending or descending)
|
||||
* @apiParam (Query Parameter) {Integer{1...200}} [per_page=25] Number of results per request
|
||||
* @apiParam (Query Parameter) {Integer} [page=0] Current page of results
|
||||
* @apiParam (Query Parameter) {String="ip","url"} [groupBy] Group by IP or URL
|
||||
*/
|
||||
|
||||
/**
|
||||
* @apiDefine LogList
|
||||
*
|
||||
* @apiSuccess {Object[]} items Array of log objects
|
||||
* @apiSuccess {Integer} items.id ID of log entry
|
||||
* @apiSuccess {String} items.created Date the log entry was recorded
|
||||
* @apiSuccess {Integer} items.created_time Unix time value for `created`
|
||||
* @apiSuccess {Integer} items.url The requested URL that caused the log entry
|
||||
* @apiSuccess {String} items.agent User agent of the client initiating the request
|
||||
* @apiSuccess {Integer} items.referrer Referrer of the client initiating the request
|
||||
* @apiSuccess {Integer} total Number of items
|
||||
*
|
||||
* @apiSuccessExample {json} Success 200:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "items": [
|
||||
* {
|
||||
* "id": 3,
|
||||
* "created": "2019-01-01 12:12:00,
|
||||
* "created_time": "12345678",
|
||||
* "url": "/the-url",
|
||||
* "agent": "FancyBrowser",
|
||||
* "referrer": "http://site.com/previous/,
|
||||
* }
|
||||
* ],
|
||||
* "total": 1
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @phpstan-type RedirectLogResponse array{
|
||||
* items: list<array<string, mixed>|object>,
|
||||
* total: int
|
||||
* }
|
||||
*
|
||||
* Log API endpoint
|
||||
*/
|
||||
class Redirection_Api_Log extends Redirection_Api_Filter_Route {
|
||||
/**
|
||||
* Log API endpoint constructor
|
||||
*
|
||||
* @param non-falsy-string $api_namespace Namespace.
|
||||
*/
|
||||
public function __construct( $api_namespace ) {
|
||||
$orders = [ 'url', 'ip', 'total', 'count', '' ];
|
||||
$filters = [ 'ip', 'url-exact', 'referrer', 'agent', 'url', 'target', 'domain', 'method', 'http', 'redirect_by' ];
|
||||
|
||||
// GET /log - Get redirect logs
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/log',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'route_log' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_manage' ],
|
||||
'args' => $this->get_filter_args( $orders, $filters ),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// POST /bulk/log/:bulk - Bulk delete logs
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/bulk/log/(?P<bulk>delete)',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => [ $this, 'route_bulk' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_delete' ],
|
||||
'args' => array_merge(
|
||||
$this->get_filter_args( $orders, $filters ),
|
||||
[
|
||||
'items' => [
|
||||
'description' => 'Comma separated list of item IDs to perform action on',
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'description' => 'Item ID',
|
||||
'type' => [ 'string', 'number' ],
|
||||
],
|
||||
],
|
||||
]
|
||||
),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a manage capability
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request Request.
|
||||
* @return bool
|
||||
*/
|
||||
public function permission_callback_manage( WP_REST_Request $request ) {
|
||||
return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_LOG_MANAGE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a delete capability
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request Request.
|
||||
* @return bool
|
||||
*/
|
||||
public function permission_callback_delete( WP_REST_Request $request ) {
|
||||
return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_LOG_DELETE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get log list
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request The request.
|
||||
* @return RedirectLogResponse
|
||||
*/
|
||||
public function route_log( WP_REST_Request $request ) {
|
||||
return $this->get_logs( $request->get_params() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform bulk action on logs
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request The request.
|
||||
* @return RedirectLogResponse
|
||||
*/
|
||||
public function route_bulk( WP_REST_Request $request ) {
|
||||
$params = $request->get_params();
|
||||
|
||||
if ( isset( $params['items'] ) && is_array( $params['items'] ) && count( $params['items'] ) > 0 ) {
|
||||
$items = $params['items'];
|
||||
|
||||
foreach ( $items as $item ) {
|
||||
if ( is_numeric( $item ) ) {
|
||||
Red_Redirect_Log::delete( intval( $item, 10 ) );
|
||||
} elseif ( isset( $params['groupBy'] ) ) {
|
||||
$delete_by = 'url-exact';
|
||||
|
||||
if ( in_array( $params['groupBy'], [ 'ip', 'agent' ], true ) ) {
|
||||
$delete_by = sanitize_text_field( $params['groupBy'] );
|
||||
}
|
||||
|
||||
Red_Redirect_Log::delete_all( [ 'filterBy' => [ $delete_by => $item ] ] ); // @phpstan-ignore-line
|
||||
}
|
||||
}
|
||||
} elseif ( isset( $params['global'] ) && $params['global'] !== false ) {
|
||||
Red_Redirect_Log::delete_all( $params );
|
||||
}
|
||||
|
||||
return $this->route_log( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get redirect logs
|
||||
*
|
||||
* @param array<string, mixed> $params The request parameters.
|
||||
* @return RedirectLogResponse
|
||||
*/
|
||||
private function get_logs( array $params ) {
|
||||
if ( isset( $params['groupBy'] ) && in_array( $params['groupBy'], [ 'ip', 'url', 'agent' ], true ) ) {
|
||||
return Red_Redirect_Log::get_grouped( sanitize_text_field( $params['groupBy'] ), $params );
|
||||
}
|
||||
|
||||
return Red_Redirect_Log::get_filtered( $params );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 'Plugin' functions for Redirection
|
||||
*
|
||||
* @phpstan-import-type DatabaseStatus from Red_Database_Status
|
||||
* @phpstan-import-type FixerJson from Red_Fixer
|
||||
*/
|
||||
class Redirection_Api_Plugin extends Redirection_Api_Route {
|
||||
/**
|
||||
* Register REST routes for plugin actions
|
||||
*
|
||||
* @param non-falsy-string $api_namespace REST namespace.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $api_namespace ) {
|
||||
// GET/POST /plugin - Get plugin status or run fixer
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/plugin',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'route_status' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_manage' ],
|
||||
],
|
||||
[
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => [ $this, 'route_fixit' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_manage' ],
|
||||
'args' => [
|
||||
'name' => [
|
||||
'description' => 'Name',
|
||||
'type' => 'string',
|
||||
],
|
||||
'value' => [
|
||||
'description' => 'Value',
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// POST /plugin/delete - Delete plugin data
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/plugin/delete',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => [ $this, 'route_delete' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_manage' ],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// ALL /plugin/test - Test endpoint
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/plugin/test',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::ALLMETHODS,
|
||||
'callback' => [ $this, 'route_test' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_setup' ],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// POST /plugin/data - Database upgrade/status
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/plugin/data',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => [ $this, 'route_database' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_setup' ],
|
||||
'args' => [
|
||||
'upgrade' => [
|
||||
'description' => 'Upgrade parameter',
|
||||
'type' => 'string',
|
||||
'enum' => [
|
||||
'stop',
|
||||
'skip',
|
||||
'retry',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// POST /plugin/finish - Finish setup wizard
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/plugin/finish',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => [ $this, 'route_finish' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_setup' ],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// POST /plugin/fix - Fix database status
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/plugin/fix',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => [ $this, 'route_fix_status' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_setup' ],
|
||||
'args' => [
|
||||
'reason' => [
|
||||
'description' => 'Reason for fix',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
],
|
||||
'current' => [
|
||||
'description' => 'Current version',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission callback for manage-level actions
|
||||
*
|
||||
* @param WP_REST_Request $request REST request.
|
||||
* @phpstan-param WP_REST_Request<array<string, mixed>> $request
|
||||
* @return bool
|
||||
*/
|
||||
public function permission_callback_manage( WP_REST_Request $request ) {
|
||||
return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_SUPPORT_MANAGE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission callback for setup and database upgrade actions
|
||||
*
|
||||
* @param WP_REST_Request $request REST request.
|
||||
* @phpstan-param WP_REST_Request<array<string, mixed>> $request
|
||||
* @return bool
|
||||
*/
|
||||
public function permission_callback_setup( WP_REST_Request $request ) {
|
||||
return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_OPTION_MANAGE ) ||
|
||||
$this->permission_callback_manage( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fixer status/debug details
|
||||
*
|
||||
* @param WP_REST_Request $request REST request.
|
||||
* @phpstan-param WP_REST_Request<array<string, mixed>> $request
|
||||
* @return FixerJson
|
||||
*/
|
||||
public function route_status( WP_REST_Request $request ) {
|
||||
include_once dirname( REDIRECTION_FILE ) . '/models/fixer.php';
|
||||
|
||||
$fixer = new Red_Fixer();
|
||||
return $fixer->get_json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run fixer or save a specific debug setting
|
||||
*
|
||||
* @param WP_REST_Request $request REST request.
|
||||
* @phpstan-param WP_REST_Request<array<string, mixed>> $request
|
||||
* @return FixerJson|WP_Error
|
||||
*/
|
||||
public function route_fixit( WP_REST_Request $request ) {
|
||||
include_once dirname( REDIRECTION_FILE ) . '/models/fixer.php';
|
||||
|
||||
$params = $request->get_params();
|
||||
$fixer = new Red_Fixer();
|
||||
|
||||
if ( isset( $params['name'] ) && isset( $params['value'] ) ) {
|
||||
global $wpdb;
|
||||
|
||||
$fixer->save_debug( sanitize_text_field( $params['name'] ), sanitize_text_field( $params['value'] ) );
|
||||
|
||||
$groups = intval( $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}redirection_groups" ), 10 );
|
||||
if ( $groups === 0 ) {
|
||||
$group = Red_Group::create( __( 'Redirections', 'redirection' ), 1 );
|
||||
|
||||
if ( $group === false ) {
|
||||
return $this->add_error_details( new WP_Error( 'redirect_group_create_failed', 'Unable to create group' ), __LINE__ );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$result = $fixer->fix( $fixer->get_status() );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $this->add_error_details( $result, __LINE__ );
|
||||
}
|
||||
}
|
||||
|
||||
return $fixer->get_json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete plugin (single-site only) and redirect back to plugins page
|
||||
*
|
||||
* @return array{location: string}|WP_Error
|
||||
*/
|
||||
public function route_delete() {
|
||||
if ( is_multisite() ) {
|
||||
return new WP_Error( 'redirect_delete_multi', 'Multisite installations must delete the plugin from the network admin' );
|
||||
}
|
||||
|
||||
Redirection_Admin::plugin_uninstall();
|
||||
|
||||
$current = get_option( 'active_plugins' );
|
||||
$plugin_position = array_search( basename( dirname( REDIRECTION_FILE ) ) . '/' . basename( REDIRECTION_FILE ), $current, true );
|
||||
if ( $plugin_position !== false ) {
|
||||
array_splice( $current, (int) $plugin_position, 1 );
|
||||
update_option( 'active_plugins', $current );
|
||||
}
|
||||
|
||||
return array( 'location' => admin_url() . 'plugins.php' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple plugin test endpoint
|
||||
*
|
||||
* @param WP_REST_Request $request REST request.
|
||||
* @phpstan-param WP_REST_Request<array<string, mixed>> $request
|
||||
* @return array{success: true}
|
||||
*/
|
||||
public function route_test( WP_REST_Request $request ) {
|
||||
return array(
|
||||
'success' => true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Database status/upgrade orchestration
|
||||
*
|
||||
* @param WP_REST_Request $request REST request.
|
||||
* @phpstan-param WP_REST_Request<array<string, mixed>> $request
|
||||
* @return DatabaseStatus
|
||||
*/
|
||||
public function route_database( WP_REST_Request $request ) {
|
||||
$params = $request->get_params();
|
||||
$status = new Red_Database_Status();
|
||||
/** @var string|false $upgrade */
|
||||
$upgrade = false;
|
||||
|
||||
if ( isset( $params['upgrade'] ) && in_array( $params['upgrade'], [ 'stop', 'skip' ], true ) ) {
|
||||
$upgrade = sanitize_text_field( $params['upgrade'] );
|
||||
}
|
||||
|
||||
// Check upgrade
|
||||
if ( ! $status->needs_updating() && ! $status->needs_installing() ) {
|
||||
/* translators: version number */
|
||||
$status->set_error( sprintf( __( 'Your database does not need updating to %s.', 'redirection' ), REDIRECTION_DB_VERSION ) );
|
||||
|
||||
return $status->get_json();
|
||||
}
|
||||
|
||||
if ( $upgrade === 'stop' ) {
|
||||
$status->stop_update();
|
||||
} elseif ( $upgrade === 'skip' ) {
|
||||
$status->set_next_stage();
|
||||
}
|
||||
|
||||
$should_upgrade = $upgrade === false || $status->get_current_stage() !== false;
|
||||
if ( $should_upgrade ) {
|
||||
$database = new Red_Database();
|
||||
$database->apply_upgrade( $status );
|
||||
}
|
||||
|
||||
return $status->get_json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish setup wizard
|
||||
*
|
||||
* @return array{success: true}
|
||||
*/
|
||||
public function route_finish() {
|
||||
$status = new Red_Database_Status();
|
||||
$status->finish();
|
||||
|
||||
return array( 'success' => true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix database status after manual upgrade
|
||||
*
|
||||
* @param WP_REST_Request $request REST request.
|
||||
* @phpstan-param WP_REST_Request<array<string, mixed>> $request
|
||||
* @return array{success: true, database: DatabaseStatus}|WP_Error
|
||||
*/
|
||||
public function route_fix_status( WP_REST_Request $request ) {
|
||||
global $wpdb;
|
||||
|
||||
$params = $request->get_params();
|
||||
$reason = isset( $params['reason'] ) ? sanitize_text_field( $params['reason'] ) : '';
|
||||
$current = isset( $params['current'] ) ? sanitize_text_field( $params['current'] ) : '';
|
||||
|
||||
// Validate required parameters
|
||||
if ( $reason === '' ) {
|
||||
return $this->add_error_details(
|
||||
new WP_Error( 'redirection_invalid_reason', 'Missing or invalid reason parameter' ),
|
||||
__LINE__
|
||||
);
|
||||
}
|
||||
|
||||
if ( $current === '' ) {
|
||||
return $this->add_error_details(
|
||||
new WP_Error( 'redirection_invalid_version', 'Missing or invalid current version parameter' ),
|
||||
__LINE__
|
||||
);
|
||||
}
|
||||
|
||||
$status = new Red_Database_Status();
|
||||
|
||||
if ( $reason === 'database' ) {
|
||||
$status->save_db_version( $current );
|
||||
|
||||
// After manual database install, ensure default groups are created
|
||||
$latest = Red_Database::get_latest_database();
|
||||
$latest->create_groups( $wpdb, true );
|
||||
|
||||
$status->finish();
|
||||
} else {
|
||||
return $this->add_error_details(
|
||||
new WP_Error( 'redirection_unsupported_reason', "Unsupported reason: $reason" ),
|
||||
__LINE__
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'database' => $status->get_json(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,458 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @api {get} /redirection/v1/redirect Get redirects
|
||||
* @apiName GetRedirects
|
||||
* @apiDescription Get a paged list of redirects based after applying a set of filters and result ordering.
|
||||
* @apiGroup Redirect
|
||||
*
|
||||
* @apiUse RedirectQueryParams
|
||||
*
|
||||
* @apiUse RedirectList
|
||||
* @apiUse 401Error
|
||||
* @apiUse 404Error
|
||||
*/
|
||||
|
||||
/**
|
||||
* @api {post} /redirection/v1/redirect Create redirect
|
||||
* @apiName CreateRedirect
|
||||
* @apiDescription Create a new redirect, and return a paged list of redirects.
|
||||
* @apiGroup Redirect
|
||||
*
|
||||
* @apiUse RedirectItem
|
||||
* @apiUse RedirectQueryParams
|
||||
*
|
||||
* @apiUse RedirectList
|
||||
* @apiUse 401Error
|
||||
* @apiUse 404Error
|
||||
* @apiError (Error 400) redirect_create_failed Failed to create redirect
|
||||
* @apiErrorExample {json} 404 Error Response:
|
||||
* HTTP/1.1 400 Bad Request
|
||||
* {
|
||||
* "code": "redirect_create_failed",
|
||||
* "message": "Failed to create redirect"
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @api {post} /redirection/v1/redirect/:id Update redirect
|
||||
* @apiName UpdateRedirect
|
||||
* @apiDescription Update an existing redirect.
|
||||
* @apiGroup Redirect
|
||||
*
|
||||
* @apiParam (URL) {Integer} :id Redirect ID to update
|
||||
*
|
||||
* @apiUse RedirectItem
|
||||
*
|
||||
* @apiUse RedirectList
|
||||
* @apiUse 401Error
|
||||
* @apiUse 404Error
|
||||
* @apiError (Error 400) redirect_update_failed Failed to update redirect
|
||||
* @apiErrorExample {json} 404 Error Response:
|
||||
* HTTP/1.1 400 Bad Request
|
||||
* {
|
||||
* "code": "redirect_update_failed",
|
||||
* "message": "Failed to update redirect"
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @api {post} /redirection/v1/bulk/redirect/:type Bulk action
|
||||
* @apiName BulkAction
|
||||
* @apiDescription Enable, disable, and delete a set of redirects. The endpoint will return the next page of results after.
|
||||
* performing the action, based on the supplied query parameters. This information can be used to refresh a list displayed to the client.
|
||||
* @apiGroup Redirect
|
||||
*
|
||||
* @apiParam (URL) {String="delete","enable","disable","reset"} :type Type of bulk action that is applied to every item.
|
||||
* @apiParam (Query Parameter) {String[]} [items] Array of redirect IDs to perform the action on
|
||||
* @apiParam (Query Parameter) {Boolean=false} [global] Perform action globally using the filter parameters
|
||||
* @apiUse RedirectQueryParams
|
||||
*
|
||||
* @apiUse RedirectList
|
||||
* @apiUse 401Error
|
||||
* @apiUse 404Error
|
||||
* @apiUse 400MissingError
|
||||
*/
|
||||
|
||||
/**
|
||||
* @apiDefine RedirectItem Redirect
|
||||
* All data associated with a redirect
|
||||
*
|
||||
* @apiParam {String="enabled","disabled"} status Status of the redirect
|
||||
* @apiParam {Integer} position Redirect position, used to determine order multiple redirects occur
|
||||
* @apiParam {Object} match_data Additional match parameters
|
||||
* @apiParam {Object} match_data.source Match against the source
|
||||
* @apiParam {Boolean} match_data.source.flag_regex `true` for regular expression, `false` otherwise
|
||||
* @apiParam {String="ignore","exact","pass"} match_data.source.flag_query Which query parameter matching to use
|
||||
* @apiParam {Boolean} match_data.source.flag_case] `true` for case insensitive matches, `false` otherwise
|
||||
* @apiParam {Boolean} match_data.source.flag_trailing] `true` to ignore trailing slashes, `false` otherwise
|
||||
* @apiParam {Object} match_data.options Options for the redirect match
|
||||
* @apiParam {Boolean} match_data.options.log_exclude `true` to exclude this from any logs, `false` otherwise (default)
|
||||
* @apiParam {Boolean} regex True for regular expression, `false` otherwise
|
||||
* @apiParam {String} url The source URL
|
||||
* @apiParam {String="url","referrer","agent","login","header","custom","cookie","role","server","ip","page","language"} match_type What URL matching to use
|
||||
* @apiParam {String} [title] A descriptive title for the redirect, or empty for no title
|
||||
* @apiParam {Integer} group_id The group this redirect belongs to
|
||||
* @apiParam {String} action_type What to do when the URL is matched
|
||||
* @apiParam {Integer} action_code The HTTP code to return
|
||||
* @apiParam {Object} action_data Any data associated with the `action_type` and `match_type`. For example, the target URL
|
||||
*/
|
||||
|
||||
/**
|
||||
* @apiDefine RedirectList A list of redirects
|
||||
* A list of redirects
|
||||
*
|
||||
* @apiSuccess {Object[]} items Array of redirect objects
|
||||
* @apiSuccess {Integer} items.id ID of redirect
|
||||
* @apiSuccess {String} items.url Source URL to match
|
||||
* @apiSuccess {String} items.match_url Match URL
|
||||
* @apiSuccess {Object} items.match_data Match against the source
|
||||
* @apiSuccess {String} items.match_type What URL matching to use
|
||||
* @apiSuccess {String} items.action_type What to do when the URL is matched
|
||||
* @apiSuccess {Integer} items.action_code The HTTP code to return
|
||||
* @apiSuccess {String} items.action_data Any data associated with the action_type. For example, the target URL
|
||||
* @apiSuccess {String} items.title Optional A descriptive title for the redirect, or empty for no title
|
||||
* @apiSuccess {String} items.hits Number of hits this redirect has received
|
||||
* @apiSuccess {String} items.regex True for regular expression, false otherwise
|
||||
* @apiSuccess {String} items.group_id The group this redirect belongs to
|
||||
* @apiSuccess {String} items.position Redirect position, used to determine order multiple redirects occur
|
||||
* @apiSuccess {String} items.last_access The date this redirect was last hit
|
||||
* @apiSuccess {String} items.status Status of the redirect
|
||||
* @apiSuccess {Integer} total Number of items
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "items": [
|
||||
* {
|
||||
* id: 3,
|
||||
* url: "/source",
|
||||
* match_url: "/source",
|
||||
* match_data: "",
|
||||
* action_code: "",
|
||||
* action_type: "",
|
||||
* action_data: "",
|
||||
* match_type: "url",
|
||||
* title: "Redirect title",
|
||||
* hits: 5,
|
||||
* regex: true,
|
||||
* group_id: 15,
|
||||
* position: 1,
|
||||
* last_access: "2019-01-01 01:01:01"
|
||||
* status: "enabled"
|
||||
* }
|
||||
* ],
|
||||
* "total": 1
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @apiDefine RedirectQueryParams
|
||||
*
|
||||
* @apiParam (Query Parameter) {String="enabled","disabled"} [filterBy[status]] Filter the results by the supplied status
|
||||
* @apiParam (Query Parameter) {String} [filterBy[url]] Filter the results by the supplied URL
|
||||
* @apiParam (Query Parameter) {String="regular","plain"} [filterBy[url-match]] Filter the results by `regular` expressions or non regular expressions
|
||||
* @apiParam (Query Parameter) {String} [filterBy[match]] Filter the results by the supplied match type
|
||||
* @apiParam (Query Parameter) {String} [filterBy[action]] Filter the results by the supplied action type
|
||||
* @apiParam (Query Parameter) {Integer} [filterBy[http]] Filter the results by the supplied redirect HTTP code
|
||||
* @apiParam (Query Parameter) {String="year","month","all"} [filterBy[access]] Filter the results by how long the redirect was last accessed
|
||||
* @apiParam (Query Parameter) {String} [filterBy[target]] Filter the results by the supplied redirect target
|
||||
* @apiParam (Query Parameter) {String} [filterBy[title]] Filter the results by the supplied redirect title
|
||||
* @apiParam (Query Parameter) {Integer} [filterBy[group]] Filter the results by the supplied redirect group ID
|
||||
* @apiParam (Query Parameter) {Integer} [filterBy[id]] Filter the results to the redirect ID
|
||||
* @apiParam (Query Parameter) {Integer="1","2","3"} [filterBy[module]] Filter the results by the supplied module ID
|
||||
* @apiParam (Query Parameter) {String="source","last_count","last_access","position","id"} [orderby=id] Order in which results are returned
|
||||
* @apiParam (Query Parameter) {String="asc","desc"} [direction=desc] Direction to order the results by (ascending or descending)
|
||||
* @apiParam (Query Parameter) {Integer{1...200}} [per_page=25] Number of results per request
|
||||
* @apiParam (Query Parameter) {Integer} [page=0] Current page of results
|
||||
*/
|
||||
|
||||
/**
|
||||
* @phpstan-type RedirectListResponse array{
|
||||
* items: list<array<string, mixed>>,
|
||||
* total: int
|
||||
* }
|
||||
*
|
||||
* Redirect API endpoint
|
||||
*/
|
||||
class Redirection_Api_Redirect extends Redirection_Api_Filter_Route {
|
||||
/**
|
||||
* Redirect API endpoint constructor
|
||||
*
|
||||
* @param non-falsy-string $api_namespace Namespace.
|
||||
*/
|
||||
public function __construct( $api_namespace ) {
|
||||
$orders = [ 'source', 'last_count', 'last_access', 'position', 'id', '' ];
|
||||
$filters = [ 'status', 'url-match', 'match', 'action', 'http', 'access', 'url', 'target', 'title', 'group', 'id' ];
|
||||
|
||||
// GET /redirect - List redirects
|
||||
// POST /redirect - Create redirect
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/redirect',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'route_list' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_manage' ],
|
||||
'args' => $this->get_filter_args( $orders, $filters ),
|
||||
],
|
||||
[
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => [ $this, 'route_create' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_add' ],
|
||||
'args' => $this->get_filter_args( $orders, $filters ),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// POST /redirect/:id - Update redirect
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/redirect/(?P<id>[\d]+)',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => [ $this, 'route_update' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_add' ],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// GET /redirect/post - Search for posts
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/redirect/post',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'route_match_post' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_manage' ],
|
||||
'args' => [
|
||||
'text' => [
|
||||
'description' => 'Text to match',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// POST /bulk/redirect/:bulk - Bulk actions on redirects
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/bulk/redirect/(?P<bulk>delete|enable|disable|reset)',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => [ $this, 'route_bulk' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_bulk' ],
|
||||
'args' => array_merge(
|
||||
$this->get_filter_args( $orders, $filters ),
|
||||
[
|
||||
'global' => [
|
||||
'description' => 'Apply bulk action globally, as per filters',
|
||||
'type' => 'boolean',
|
||||
],
|
||||
'items' => [
|
||||
'description' => 'Array of IDs to perform action on',
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'description' => 'Item ID',
|
||||
'type' => [ 'string', 'number' ],
|
||||
],
|
||||
],
|
||||
]
|
||||
),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a manage capability
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request Request.
|
||||
* @return bool
|
||||
*/
|
||||
public function permission_callback_manage( WP_REST_Request $request ) {
|
||||
return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_REDIRECT_MANAGE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a bulk capability
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request Request.
|
||||
* @return bool
|
||||
*/
|
||||
public function permission_callback_bulk( WP_REST_Request $request ) {
|
||||
if ( $request['bulk'] === 'delete' ) {
|
||||
return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_REDIRECT_DELETE );
|
||||
}
|
||||
|
||||
return $this->permission_callback_add( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a create capability
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request Request.
|
||||
* @return bool
|
||||
*/
|
||||
public function permission_callback_add( WP_REST_Request $request ) {
|
||||
return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_REDIRECT_ADD );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get redirect list
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request The request.
|
||||
* @return RedirectListResponse
|
||||
*/
|
||||
public function route_list( WP_REST_Request $request ) {
|
||||
return Red_Item::get_filtered( $request->get_params() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new redirect(s)
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request The request.
|
||||
* @return RedirectListResponse|WP_Error
|
||||
*/
|
||||
public function route_create( WP_REST_Request $request ) {
|
||||
$params = $request->get_params();
|
||||
$urls = array();
|
||||
|
||||
if ( isset( $params['url'] ) ) {
|
||||
$urls = array( $params['url'] );
|
||||
|
||||
if ( is_array( $params['url'] ) ) {
|
||||
$urls = $params['url'];
|
||||
}
|
||||
|
||||
// Remove duplicates
|
||||
$unique = [];
|
||||
foreach ( $urls as $url ) {
|
||||
$unique[ $url ] = $url;
|
||||
}
|
||||
|
||||
foreach ( $unique as $url ) {
|
||||
$params['url'] = $url;
|
||||
|
||||
// Data is sanitized in the create function
|
||||
$redirect = Red_Item::create( $params );
|
||||
|
||||
if ( is_wp_error( $redirect ) ) {
|
||||
return $this->add_error_details( $redirect, __LINE__ );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->route_list( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update redirect
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request The request.
|
||||
* @return array{item: array<string, mixed>}|WP_Error
|
||||
*/
|
||||
public function route_update( WP_REST_Request $request ) {
|
||||
$params = $request->get_params();
|
||||
$redirect = Red_Item::get_by_id( intval( $params['id'], 10 ) );
|
||||
|
||||
if ( $redirect !== false ) {
|
||||
$result = $redirect->update( $params );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $this->add_error_details( $result, __LINE__ );
|
||||
}
|
||||
|
||||
return [ 'item' => $redirect->to_json() ];
|
||||
}
|
||||
|
||||
return $this->add_error_details( new WP_Error( 'redirect_update_failed', 'Invalid redirect details' ), __LINE__ );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform bulk action on redirects
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request The request.
|
||||
* @return RedirectListResponse|WP_Error
|
||||
*/
|
||||
public function route_bulk( WP_REST_Request $request ) {
|
||||
$params = $request->get_params();
|
||||
$action = sanitize_text_field( $request['bulk'] );
|
||||
|
||||
if ( isset( $params['items'] ) && is_array( $params['items'] ) && count( $params['items'] ) > 0 ) {
|
||||
$items = $params['items'];
|
||||
|
||||
foreach ( $items as $item ) {
|
||||
$redirect = Red_Item::get_by_id( intval( $item, 10 ) );
|
||||
|
||||
if ( $redirect === false ) {
|
||||
return $this->add_error_details( new WP_Error( 'redirect_bulk_failed', 'Invalid redirect' ), __LINE__ );
|
||||
}
|
||||
|
||||
if ( $action === 'delete' ) {
|
||||
$redirect->delete();
|
||||
} elseif ( $action === 'disable' ) {
|
||||
$redirect->disable();
|
||||
} elseif ( $action === 'enable' ) {
|
||||
$redirect->enable();
|
||||
} elseif ( $action === 'reset' ) {
|
||||
$redirect->reset();
|
||||
}
|
||||
}
|
||||
} elseif ( isset( $params['global'] ) && $params['global'] !== false ) {
|
||||
// Params are sanitized in the filter class
|
||||
if ( $action === 'delete' ) {
|
||||
Red_Item::delete_all( $params );
|
||||
} elseif ( $action === 'reset' ) {
|
||||
Red_Item::reset_all( $params );
|
||||
} elseif ( $action === 'enable' || $action === 'disable' ) {
|
||||
Red_Item::set_status_all( $action, $params );
|
||||
}
|
||||
}
|
||||
|
||||
return $this->route_list( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a post
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request The request.
|
||||
* @return list<array{title: string, value: string|false}>
|
||||
*/
|
||||
public function route_match_post( WP_REST_Request $request ) {
|
||||
global $wpdb;
|
||||
|
||||
$params = $request->get_params();
|
||||
$search = sanitize_text_field( $params['text'] );
|
||||
$results = [];
|
||||
|
||||
$posts = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT ID,post_title,post_name FROM $wpdb->posts WHERE post_status='publish' AND (post_title LIKE %s OR post_name LIKE %s) " .
|
||||
"AND post_type IN ('post','page')",
|
||||
'%' . $wpdb->esc_like( $search ) . '%',
|
||||
'%' . $wpdb->esc_like( $search ) . '%'
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( (array) $posts as $post ) {
|
||||
$title = $post->post_name;
|
||||
if ( strpos( $post->post_title, $search ) !== false ) {
|
||||
$title = $post->post_title;
|
||||
}
|
||||
|
||||
$results[] = [
|
||||
'title' => $title,
|
||||
'value' => get_permalink( $post->ID ),
|
||||
];
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
class Redirection_Api_Route {
|
||||
/**
|
||||
* Add DB error details to a WP_Error
|
||||
*
|
||||
* @param WP_Error $error Error instance.
|
||||
* @param string|int $line Error code/identifier.
|
||||
* @param int $code HTTP status code.
|
||||
* @return WP_Error
|
||||
*/
|
||||
protected function add_error_details( WP_Error $error, $line, $code = 400 ) {
|
||||
global $wpdb;
|
||||
|
||||
$data = array(
|
||||
'status' => $code,
|
||||
'error_code' => $line,
|
||||
);
|
||||
|
||||
if ( isset( $wpdb->last_error ) && $wpdb->last_error ) {
|
||||
$data['wpdb'] = $wpdb->last_error;
|
||||
}
|
||||
|
||||
$error->add_data( $data );
|
||||
return $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default permission callback for API routes
|
||||
*
|
||||
* @param WP_REST_Request $request REST request.
|
||||
* @phpstan-param WP_REST_Request<array<string, mixed>> $request
|
||||
* @return bool
|
||||
*/
|
||||
public function permission_callback( WP_REST_Request $request ) {
|
||||
return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_PLUGIN );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a REST route config
|
||||
*
|
||||
* @param mixed $method Allowed methods (WP_REST_Server constants).
|
||||
* @param string $callback Method name on this class.
|
||||
* @param callable|array{0:self,1:string}|false $permissions Permission callback or false for default.
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function get_route( $method, $callback, $permissions = false ) {
|
||||
return [
|
||||
'methods' => $method,
|
||||
'callback' => [ $this, $callback ],
|
||||
'permission_callback' => $permissions ? $permissions : [ $this, 'permission_callback' ],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
/**
|
||||
* @api {get} /redirection/v1/setting Get settings
|
||||
* @apiName GetSettings
|
||||
* @apiDescription Get all settings for Redirection. This includes user-configurable settings, as well as necessary WordPress settings.
|
||||
* @apiGroup Settings
|
||||
*
|
||||
* @apiUse SettingItem
|
||||
* @apiUse 401Error
|
||||
* @apiUse 404Error
|
||||
*/
|
||||
|
||||
/**
|
||||
* @api {post} /redirection/v1/setting Update settings
|
||||
* @apiName UpdateSettings
|
||||
* @apiDescription Update Redirection settings. Note you can do partial updates, and only the values specified will be changed.
|
||||
* @apiGroup Settings
|
||||
*
|
||||
* @apiParam {Object} settings An object containing all the settings to update
|
||||
* @apiParamExample {json} settings:
|
||||
* {
|
||||
* "expire_redirect": 14,
|
||||
* "https": false
|
||||
* }
|
||||
*
|
||||
* @apiUse SettingItem
|
||||
* @apiUse 401Error
|
||||
* @apiUse 404Error
|
||||
*/
|
||||
|
||||
/**
|
||||
* @apiDefine SettingItem Settings
|
||||
* Redirection settings
|
||||
*
|
||||
* @apiSuccess {Object[]} settings An object containing all settings
|
||||
* @apiSuccess {String} settings.expire_redirect
|
||||
* @apiSuccess {String} settings.token
|
||||
* @apiSuccess {String} settings.monitor_post
|
||||
* @apiSuccess {String[]} settings.monitor_types
|
||||
* @apiSuccess {String} settings.associated_redirect
|
||||
* @apiSuccess {String} settings.auto_target
|
||||
* @apiSuccess {String} settings.expire_redirect
|
||||
* @apiSuccess {String} settings.expire_404
|
||||
* @apiSuccess {String} settings.modules
|
||||
* @apiSuccess {String} settings.redirect_cache
|
||||
* @apiSuccess {String} settings.ip_logging
|
||||
* @apiSuccess {String} settings.last_group_id
|
||||
* @apiSuccess {String} settings.rest_api
|
||||
* @apiSuccess {String} settings.https
|
||||
* @apiSuccess {String} settings.headers
|
||||
* @apiSuccess {String} settings.database
|
||||
* @apiSuccess {String} settings.relocate Relocate this site to the specified domain (and path)
|
||||
* @apiSuccess {String="www","nowww",""} settings.preferred_domain Preferred canonical domain
|
||||
* @apiSuccess {String[]} settings.aliases Array of domains that will be redirected to the current WordPress site
|
||||
* @apiSuccess {Object[]} groups An array of groups
|
||||
* @apiSuccess {String} groups.label Name of the group
|
||||
* @apiSuccess {Integer} groups.value Group ID
|
||||
* @apiSuccess {String} installed The path that WordPress is installed in
|
||||
* @apiSuccess {Boolean} canDelete True if Redirection can be deleted, false otherwise (on multisite, for example)
|
||||
* @apiSuccess {String[]} post_types Array of WordPress post types
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "settings": {
|
||||
* "expire_redirect": 7,
|
||||
* "https": true
|
||||
* },
|
||||
* "groups": [
|
||||
* { label: 'My group', value: 5 }
|
||||
* ],
|
||||
* "installed": "/var/html/wordpress",
|
||||
* "canDelete": true,
|
||||
* "post_types": [
|
||||
* "post",
|
||||
* "page"
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @phpstan-import-type RedirectionOptions from Red_Options
|
||||
*
|
||||
* @phpstan-type SettingsResponse array{
|
||||
* settings: RedirectionOptions,
|
||||
* groups: array<int, object>,
|
||||
* installed: string,
|
||||
* canDelete: bool,
|
||||
* post_types: array<int|string>
|
||||
* }
|
||||
* @phpstan-type SettingsResponseWithWarning array{
|
||||
* settings: RedirectionOptions,
|
||||
* groups: array<int, object>,
|
||||
* installed: string,
|
||||
* canDelete: bool,
|
||||
* post_types: array<int|string>,
|
||||
* warning?: string
|
||||
* }
|
||||
*/
|
||||
class Redirection_Api_Settings extends Redirection_Api_Route {
|
||||
/**
|
||||
* Settings API endpoint constructor
|
||||
*
|
||||
* @param non-falsy-string $api_namespace Namespace.
|
||||
*/
|
||||
public function __construct( $api_namespace ) {
|
||||
// GET /setting - Get settings
|
||||
// POST /setting - Update settings
|
||||
register_rest_route(
|
||||
$api_namespace,
|
||||
'/setting',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'route_settings' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_manage' ],
|
||||
],
|
||||
[
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => [ $this, 'route_save_settings' ],
|
||||
'permission_callback' => [ $this, 'permission_callback_manage' ],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all settings for Redirection
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request
|
||||
* @return SettingsResponse
|
||||
*/
|
||||
public function route_settings( WP_REST_Request $request ) {
|
||||
if ( ! function_exists( 'get_home_path' ) ) {
|
||||
include_once ABSPATH . '/wp-admin/includes/file.php';
|
||||
}
|
||||
|
||||
return [
|
||||
'settings' => Red_Options::get(),
|
||||
'groups' => $this->groups_to_json( Red_Group::get_for_select() ),
|
||||
'installed' => get_home_path(),
|
||||
'canDelete' => ! is_multisite(),
|
||||
'post_types' => red_get_post_types(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user has permission to manage settings
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request
|
||||
* @return bool
|
||||
*/
|
||||
public function permission_callback_manage( WP_REST_Request $request ) {
|
||||
return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_OPTION_MANAGE ) || Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_SITE_MANAGE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save settings for Redirection
|
||||
*
|
||||
* @param WP_REST_Request<array<string, mixed>> $request
|
||||
* @return SettingsResponseWithWarning
|
||||
*/
|
||||
public function route_save_settings( WP_REST_Request $request ) {
|
||||
$params = $request->get_params();
|
||||
$result = true;
|
||||
|
||||
if ( isset( $params['location'] ) && strlen( $params['location'] ) > 0 ) {
|
||||
$module = Red_Module::get( 2 );
|
||||
if ( $module !== false && $module instanceof Apache_Module ) {
|
||||
$result = $module->can_save( sanitize_text_field( $params['location'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
red_set_options( $params );
|
||||
|
||||
$settings = $this->route_settings( $request );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
$settings['warning'] = $result->get_error_message();
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert groups array to JSON format
|
||||
*
|
||||
* @param array<string|int, mixed> $groups Groups array from Red_Group::get_for_select()
|
||||
* @param int $depth Current recursion depth
|
||||
* @return array<int, object>
|
||||
*/
|
||||
private function groups_to_json( $groups, $depth = 0 ) {
|
||||
$items = array();
|
||||
|
||||
foreach ( $groups as $text => $value ) {
|
||||
if ( is_array( $value ) && $depth === 0 ) {
|
||||
$items[] = (object) array(
|
||||
'label' => $text,
|
||||
'value' => $this->groups_to_json( $value, 1 ),
|
||||
);
|
||||
} else {
|
||||
$items[] = (object) array(
|
||||
'label' => $value,
|
||||
'value' => $text,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/api-route.php';
|
||||
require_once __DIR__ . '/api-filter-route.php';
|
||||
require_once __DIR__ . '/api-group.php';
|
||||
require_once __DIR__ . '/api-redirect.php';
|
||||
require_once __DIR__ . '/api-log.php';
|
||||
require_once __DIR__ . '/api-404.php';
|
||||
require_once __DIR__ . '/api-settings.php';
|
||||
require_once __DIR__ . '/api-plugin.php';
|
||||
require_once __DIR__ . '/api-import.php';
|
||||
require_once __DIR__ . '/api-export.php';
|
||||
require_once __DIR__ . '/api-core.php';
|
||||
|
||||
define( 'REDIRECTION_API_NAMESPACE', 'redirection/v1' );
|
||||
|
||||
/**
|
||||
* @apiDefine 401Error
|
||||
*
|
||||
* @apiError (Error 401) rest_forbidden You are not authorized to access this API endpoint
|
||||
* @apiErrorExample {json} 401 Error Response:
|
||||
* HTTP/1.1 401 Bad Request
|
||||
* {
|
||||
* "code": "rest_forbidden",
|
||||
* "message": "Sorry, you are not allowed to do that."
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @apiDefine 404Error
|
||||
*
|
||||
* @apiError (Error 404) rest_no_route Endpoint not found
|
||||
* @apiErrorExample {json} 404 Error Response:
|
||||
* HTTP/1.1 404 Not Found
|
||||
* {
|
||||
* "code": "rest_no_route",
|
||||
* "message": "No route was found matching the URL and request method"
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @apiDefine 400Error
|
||||
*
|
||||
* @apiError rest_forbidden You are not authorized to access this API endpoint
|
||||
* @apiErrorExample {json} 400 Error Response:
|
||||
* HTTP/1.1 400 Bad Request
|
||||
* {
|
||||
* "error": "invalid",
|
||||
* "message": "Invalid request"
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @apiDefine 400MissingError
|
||||
*
|
||||
* @apiError (Error 400) rest_missing_callback_param Some required parameters are not present or not in the correct format
|
||||
* @apiErrorExample {json} 400 Error Response:
|
||||
* HTTP/1.1 400 Bad Request
|
||||
* {
|
||||
* "code": "rest_missing_callback_param",
|
||||
* "message": "Missing parameter(s): PARAM"
|
||||
* }
|
||||
*/
|
||||
Reference in New Issue
Block a user