'string', 'enum' => array( 'plugin', 'theme' ), 'default' => 'plugin', ); register_rest_route( 'trackpluse/v1', '/search', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( __CLASS__, 'search' ), 'permission_callback' => array( __CLASS__, 'allowed' ), 'args' => array( 'type' => $type, 'search' => array( 'type' => 'string', 'required' => true, 'minLength' => 2, 'maxLength' => 100, 'sanitize_callback' => 'sanitize_text_field', ), ), ) ); register_rest_route( 'trackpluse/v1', '/details', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( __CLASS__, 'details' ), 'permission_callback' => array( __CLASS__, 'allowed' ), 'args' => array( 'plugins' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ), 'default' => array(), ), 'themes' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ), 'default' => array(), ), ), ) ); } /** * Only those who can change the settings. * * @return bool */ public static function allowed() { return current_user_can( 'manage_options' ); } /** * Plugins or themes on WordPress.org matching the words typed. * * @param WP_REST_Request $request The request. * @return WP_REST_Response|WP_Error */ public static function search( $request ) { $search = (string) $request['search']; if ( 'theme' === $request['type'] ) { require_once ABSPATH . 'wp-admin/includes/theme.php'; $result = themes_api( 'query_themes', array( 'search' => $search, 'per_page' => self::RESULTS, 'fields' => array( 'screenshot_url' => true, 'active_installs' => true, 'description' => false, 'sections' => false, 'rating' => false, 'ratings' => false, 'num_ratings' => false, 'downloaded' => false, 'downloadlink' => false, 'last_updated' => false, 'homepage' => false, 'tags' => false, 'requires' => false, 'requires_php' => false, ), ) ); $items = is_wp_error( $result ) || ! isset( $result->themes ) ? null : (array) $result->themes; } else { require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; $result = plugins_api( 'query_plugins', array( 'search' => $search, 'per_page' => self::RESULTS, 'fields' => array( 'icons' => true, 'active_installs' => true, 'short_description' => false, 'description' => false, 'sections' => false, 'tags' => false, 'rating' => false, 'ratings' => false, 'num_ratings' => false, 'compatibility' => false, 'homepage' => false, 'donate_link' => false, 'versions' => false, 'banners' => false, 'contributors' => false, 'screenshots' => false, 'downloaded' => false, 'downloadlink' => false, 'last_updated' => false, 'added' => false, 'requires' => false, 'tested' => false, 'requires_php' => false, 'support_threads' => false, 'support_threads_resolved' => false, ), ) ); $items = is_wp_error( $result ) || ! isset( $result->plugins ) ? null : (array) $result->plugins; } if ( null === $items ) { return new WP_Error( 'trackpluse_search_failed', __( 'WordPress.org’s search isn’t answering. Type a slug and press Enter instead.', 'trackpluse' ), array( 'status' => 503 ) ); } $found = array(); foreach ( $items as $item ) { $item = (array) $item; $slug = isset( $item['slug'] ) ? (string) $item['slug'] : ''; if ( '' === $slug ) { continue; } $found[] = array( 'slug' => $slug, 'name' => self::text( isset( $item['name'] ) ? $item['name'] : $slug ), 'author' => self::author( isset( $item['author'] ) ? $item['author'] : '' ), 'icon' => self::image( $item ), 'installs' => TrackPluse_Format::installs( isset( $item['active_installs'] ) ? (int) $item['active_installs'] : 0 ), ); } return rest_ensure_response( $found ); } /** * Names and icons of chosen slugs, so the pickers show more than the slug. * * @param WP_REST_Request $request The request. * @return WP_REST_Response|WP_Error */ public static function details( $request ) { $plugins = TrackPluse_Settings::slugs( (array) $request['plugins'] ); $themes = TrackPluse_Settings::slugs( (array) $request['themes'] ); $stats = TrackPluse_Api::stats( '', $plugins, $themes, 5 ); if ( is_wp_error( $stats ) ) { return new WP_Error( $stats->get_error_code(), $stats->get_error_message(), array( 'status' => 503 ) ); } $found = array( 'plugin' => array(), 'theme' => array(), ); foreach ( $stats['data'] as $row ) { $type = isset( $row['type'] ) && 'theme' === $row['type'] ? 'theme' : 'plugin'; $slug = isset( $row['slug'] ) ? (string) $row['slug'] : ''; if ( '' !== $slug ) { $found[ $type ][ $slug ] = array( 'slug' => $slug, 'name' => self::text( isset( $row['name'] ) ? $row['name'] : $slug ), 'icon' => TrackPluse_Format::icon( isset( $row['icon_url'] ) ? $row['icon_url'] : '' ), 'installs' => TrackPluse_Format::installs( isset( $row['active_installs'] ) ? (int) $row['active_installs'] : 0 ), ); } } // Objects, not lists, even when nothing was found. return rest_ensure_response( array( 'plugin' => (object) $found['plugin'], 'theme' => (object) $found['theme'], ) ); } /** * Plain text from a WordPress.org name, which can hold entities and tags. * * @param mixed $value The name. * @return string */ private static function text( $value ) { return trim( html_entity_decode( wp_strip_all_tags( (string) $value ), ENT_QUOTES, 'UTF-8' ) ); } /** * The author's name: plugins give a link, themes a username or an array. * * @param mixed $author The API's author field. * @return string */ private static function author( $author ) { if ( is_array( $author ) || is_object( $author ) ) { $author = (array) $author; $author = isset( $author['display_name'] ) ? $author['display_name'] : ( isset( $author['user_nicename'] ) ? $author['user_nicename'] : '' ); } return self::text( $author ); } /** * A plugin's icon or a theme's screenshot, from WordPress.org's servers only. * * @param array $item One search result. * @return string */ private static function image( $item ) { if ( isset( $item['icons'] ) && is_array( $item['icons'] ) ) { foreach ( array( '1x', '2x', 'svg', 'default' ) as $size ) { if ( ! empty( $item['icons'][ $size ] ) ) { return TrackPluse_Format::icon( (string) $item['icons'][ $size ] ); } } } $url = isset( $item['screenshot_url'] ) ? (string) $item['screenshot_url'] : ''; // Theme screenshots come without a scheme: //ts.w.org/…. return TrackPluse_Format::icon( 0 === strpos( $url, '//' ) ? 'https:' . $url : $url ); } }