array( 'type' => 'string', 'default' => '' ), 'page' => array( 'type' => 'integer', 'default' => 1, 'minimum' => 1 ), 'per_page' => array( 'type' => 'integer', 'default' => 20, 'minimum' => 1, 'maximum' => 5000 ), 'orderby' => array( 'type' => 'string', 'default' => '' ), 'order' => array( 'type' => 'string', 'default' => 'desc' ), ); register_rest_route( self::NS, '/redirects', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( self::class, 'list_redirects' ), 'permission_callback' => $p, 'args' => $list_args + array( 'origin' => array( 'type' => 'string', 'default' => '' ), 'enabled' => array( 'type' => 'string', 'default' => '' ) ) ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( self::class, 'add_redirect' ), 'permission_callback' => $p ), ) ); register_rest_route( self::NS, '/redirects/(?P\d+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( self::class, 'get_redirect' ), 'permission_callback' => $p ), array( 'methods' => 'POST, PUT, PATCH', 'callback' => array( self::class, 'update_redirect' ), 'permission_callback' => $p ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( self::class, 'delete_redirect' ), 'permission_callback' => $p ), ) ); register_rest_route( self::NS, '/redirects/bulk', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( self::class, 'bulk' ), 'permission_callback' => $p, 'args' => array( 'action' => array( 'type' => 'string', 'enum' => array( 'enable', 'disable', 'delete' ), 'required' => true ), 'ids' => array( 'type' => 'array', 'items' => array( 'type' => 'integer' ), 'required' => true ), ), ) ); register_rest_route( self::NS, '/test', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( self::class, 'test' ), 'permission_callback' => $p, 'args' => array( 'url' => array( 'type' => 'string', 'required' => true ) ), ) ); register_rest_route( self::NS, '/export', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( self::class, 'export' ), 'permission_callback' => $p, ) ); register_rest_route( self::NS, '/import', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( self::class, 'import' ), 'permission_callback' => $p, ) ); register_rest_route( self::NS, '/404', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( self::class, 'list_404' ), 'permission_callback' => $p, 'args' => $list_args ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( self::class, 'clear_404' ), 'permission_callback' => $p ), ) ); register_rest_route( self::NS, '/404/delete', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( self::class, 'delete_404' ), 'permission_callback' => $p, 'args' => array( 'ids' => array( 'type' => 'array', 'items' => array( 'type' => 'integer' ), 'required' => true ) ), ) ); register_rest_route( self::NS, '/settings', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( self::class, 'get_settings' ), 'permission_callback' => $p ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( self::class, 'save_settings' ), 'permission_callback' => $p ), ) ); } /** a row for the screen: numbers as numbers, the target post's title */ public static function present( $row ): array { $post = (int) $row->target_post_id; return array( 'id' => (int) $row->id, 'source' => (string) $row->source, 'match_type' => (string) $row->match_type, 'target' => (string) $row->target, 'target_post_id' => $post, 'target_title' => $post ? get_the_title( $post ) : '', 'code' => (int) $row->code, 'enabled' => (bool) $row->enabled, 'origin' => (string) $row->origin, 'note' => (string) $row->note, 'hits' => (int) $row->hits, 'last_hit' => $row->last_hit ? (string) $row->last_hit : '', 'created' => (string) $row->created, ); } private static function error( \WP_Error $e ) { $data = (array) $e->get_error_data(); return new \WP_REST_Response( array( 'code' => $e->get_error_code(), 'message' => $e->get_error_message(), 'data' => $data + array( 'status' => 400 ) ), 400 ); } public static function list_redirects( WP_REST_Request $r ) { $res = Redirects::query( $r->get_params() ); return array( 'items' => array_map( array( self::class, 'present' ), $res['items'] ), 'total' => $res['total'] ); } public static function add_redirect( WP_REST_Request $r ) { $id = Redirects::add( (array) $r->get_json_params() + array( 'origin' => 'manual' ) ); return is_wp_error( $id ) ? self::error( $id ) : new \WP_REST_Response( self::present( Redirects::get( $id ) ), 201 ); } public static function get_redirect( WP_REST_Request $r ) { $row = Redirects::get( (int) $r['id'] ); return $row ? self::present( $row ) : self::error( new \WP_Error( 'rb_redirection_missing', __( 'This redirect does not exist.', 'rb-links-redirection' ) ) ); } public static function update_redirect( WP_REST_Request $r ) { $id = (int) $r['id']; $res = Redirects::update( $id, (array) $r->get_json_params() ); return is_wp_error( $res ) ? self::error( $res ) : self::present( Redirects::get( $id ) ); } public static function delete_redirect( WP_REST_Request $r ) { return array( 'deleted' => Redirects::delete( array( (int) $r['id'] ) ) ); } public static function bulk( WP_REST_Request $r ) { $ids = (array) $r['ids']; $action = (string) $r['action']; $n = $action === 'delete' ? Redirects::delete( $ids ) : Redirects::set_enabled( $ids, $action === 'enable' ); return array( 'changed' => $n ); } /** what an address would do now: which redirect, where to */ public static function test( WP_REST_Request $r ) { $match = Redirects::match( (string) $r['url'] ); return $match ? array( 'match' => true, 'redirect' => self::present( $match['row'] ), 'to' => $match['to'] ) : array( 'match' => false ); } /** CSV: source, target, code, match_type, enabled, note */ public static function export() { $rows = Redirects::query( array( 'per_page' => 5000, 'orderby' => 'id', 'order' => 'asc' ) )['items']; $csv = self::csv_line( array( 'source', 'target', 'code', 'match_type', 'enabled', 'note' ) ); foreach ( $rows as $row ) { $target = (string) $row->target; if ( $target === '' && (int) $row->target_post_id ) { $target = (string) wp_make_link_relative( (string) get_permalink( (int) $row->target_post_id ) ); } $csv .= self::csv_line( array( $row->source, $target, (int) $row->code, $row->match_type, (int) $row->enabled, $row->note ) ); } return array( 'csv' => $csv, 'count' => count( $rows ) ); } /** * One CSV line the way fputcsv() writes it with no escape character: a cell is * quoted when it holds a comma, a quote, a space, a tab or a line break, and its * quotes are doubled. Built as a string, so no file functions are needed. */ private static function csv_line( array $cells ): string { $out = array(); foreach ( $cells as $cell ) { $cell = (string) $cell; $out[] = preg_match( '/[",\s]/', $cell ) ? '"' . str_replace( '"', '""', $cell ) . '"' : $cell; } return implode( ',', $out ) . "\n"; } /** CSV text in "csv"; a header row is optional. Rows already there are skipped, not doubled. */ public static function import( WP_REST_Request $r ) { $csv = (string) ( $r->get_json_params()['csv'] ?? '' ); if ( trim( $csv ) === '' ) { return self::error( new \WP_Error( 'rb_redirection_import', __( 'The file is empty.', 'rb-links-redirection' ) ) ); } $added = 0; $skipped = 0; $errors = array(); $lines = preg_split( '/\r\n|\r|\n/', $csv ); foreach ( $lines as $i => $line ) { if ( trim( $line ) === '' ) { continue; } $cells = str_getcsv( $line, ',', '"', '' ); // RFC 4180: a quote is doubled, a backslash is just a backslash if ( $i === 0 && strtolower( trim( (string) $cells[0] ) ) === 'source' ) { continue; } $res = Redirects::add( array( 'source' => $cells[0] ?? '', 'target' => $cells[1] ?? '', 'code' => isset( $cells[2] ) && $cells[2] !== '' ? (int) $cells[2] : 301, 'match_type' => isset( $cells[3] ) && $cells[3] !== '' ? trim( $cells[3] ) : 'exact', 'enabled' => isset( $cells[4] ) && $cells[4] !== '' ? (bool) (int) $cells[4] : true, 'note' => $cells[5] ?? '', 'origin' => 'import', ) ); if ( is_wp_error( $res ) ) { if ( $res->get_error_code() === 'rb_redirection_exists' ) { ++$skipped; } elseif ( count( $errors ) < 20 ) { /* translators: 1: line number, 2: what is wrong */ $errors[] = sprintf( __( 'Line %1$d: %2$s', 'rb-links-redirection' ), $i + 1, $res->get_error_message() ); } continue; } ++$added; } return array( 'added' => $added, 'skipped' => $skipped, 'errors' => $errors ); } public static function list_404( WP_REST_Request $r ) { $res = NotFoundLog::query( $r->get_params() ); $items = array(); foreach ( $res['items'] as $row ) { $items[] = array( 'id' => (int) $row->id, 'url' => (string) $row->url, 'hits' => (int) $row->hits, 'referrer' => (string) $row->referrer, 'user_agent' => (string) $row->user_agent, 'first_seen' => (string) $row->first_seen, 'last_seen' => (string) $row->last_seen, 'redirect_id' => (int) $row->redirect_id, ); } return array( 'items' => $items, 'total' => $res['total'] ); } public static function delete_404( WP_REST_Request $r ) { return array( 'deleted' => NotFoundLog::delete( (array) $r['ids'] ) ); } public static function clear_404() { return array( 'deleted' => NotFoundLog::clear() ); } public static function get_settings() { return Settings::all(); } public static function save_settings( WP_REST_Request $r ) { return Settings::save( (array) $r->get_json_params() ); } }