$search = $wpdb->prepare( ' AND ( (link_url LIKE %s) OR (link_name LIKE %s) OR (link_description LIKE %s) ) ', $like, $like, $like ); } $category_query = ''; $join = ''; if ( ! empty( $parsed_args['category'] ) ) { $incategories = wp_parse_id_list( $parsed_args['category'] ); if ( count( $incategories ) ) { foreach ( $incategories as $incat ) { if ( empty( $category_query ) ) { $category_query = ' AND ( tt.term_id = ' . $incat . ' '; } else { $category_query .= ' OR tt.term_id = ' . $incat . ' '; } } } } if ( ! empty( $category_query ) ) { $category_query .= ") AND taxonomy = 'link_category'"; $join = " INNER JOIN $wpdb->term_relationships AS tr ON ($wpdb->links.link_id = tr.object_id) INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_taxonomy_id = tr.term_taxonomy_id"; } if ( $parsed_args['show_updated'] ) { $recently_updated_test = ', IF (DATE_ADD(link_updated, INTERVAL 120 MINUTE) >= NOW(), 1,0) as recently_updated '; } else { $recently_updated_test = ''; } $get_updated = ( $parsed_args['show_updated'] ) ? ', UNIX_TIMESTAMP(link_updated) AS link_updated_f ' : ''; $orderby = strtolower( $parsed_args['orderby'] ); $length = ''; switch ( $orderby ) { case 'length': $length = ', CHAR_LENGTH(link_name) AS length'; break; case 'rand': $orderby = 'rand()'; break; case 'link_id': $orderby = "$wpdb->links.link_id"; break; default: $orderparams = array(); $keys = array( 'link_id', 'link_name', 'link_url', 'link_visible', 'link_rating', 'link_owner', 'link_updated', 'link_notes', 'link_description' ); foreach ( explode( ',', $orderby ) as $ordparam ) { $ordparam = trim( $ordparam ); if ( in_array( 'link_' . $ordparam, $keys, true ) ) { $orderparams[] = 'link_' . $ordparam; } elseif ( in_array( $ordparam, $keys, true ) ) { $orderparams[] = $ordparam; } } $orderby = implode( ',', $orderparams ); } if ( empty( $orderby ) ) { $orderby = 'link_name'; } $order = strtoupper( $parsed_args['order'] ); if ( '' !== $order && ! in_array( $order, array( 'ASC', 'DESC' ), true ) ) { $order = 'ASC'; } $visible = ''; if ( $parsed_args['hide_invisible'] ) { $visible = "AND link_visible = 'Y'"; } $query = "SELECT * $length $recently_updated_test $get_updated FROM $wpdb->links $join WHERE 1=1 $visible $category_query"; $query .= " $exclusions $inclusions $search"; $query .= " ORDER BY $orderby $order"; if ( -1 != $parsed_args['limit'] ) { $query .= ' LIMIT ' . absint( $parsed_args['limit'] ); } $results = $wpdb->get_results( $query ); if ( 'rand()' !== $orderby ) { $cache[ $key ] = $results; wp_cache_set( 'get_bookmarks', $cache, 'bookmark' ); } /** This filter is documented in wp-includes/bookmark.php */ return apply_filters( 'get_bookmarks', $results, $parsed_args ); } /** * Sanitizes all bookmark fields. * * @since 2.3.0 * * @param stdClass|array $bookmark Bookmark row. * @param string $context Optional. How to filter the fields. Default 'display'. * @return stdClass|array Same type as $bookmark but with fields sanitized. */ function sanitize_bookmark( $bookmark, $context = 'display' ) { $fields = array( 'link_id', 'link_url', 'link_name', 'link_image', 'link_target', 'link_category', 'link_description', 'link_visible', 'link_owner', 'link_rating', 'link_updated', 'link_rel', 'link_notes', 'link_rss', ); if ( is_object( $bookmark ) ) { $do_object = true; $link_id = $bookmark->link_id; } else { $do_object = false; $link_id = $bookmark['link_id']; } foreach ( $fields as $field ) { if ( $do_object ) { if ( isset( $bookmark->$field ) ) { $bookmark->$field = sanitize_bookmark_field( $field, $bookmark->$field, $link_id, $context ); } } else { if ( isset( $bookmark[ $field ] ) ) { $bookmark[ $field ] = sanitize_bookmark_field( $field, $bookmark[ $field ], $link_id, $context ); } } } return $bookmark; } /** * Sanitizes a bookmark field. * * Sanitizes the bookmark fields based on what the field name is. If the field * has a strict value set, then it will be tested for that, else a more generic * filtering is applied. After the more strict filter is applied, if the `$context` * is 'raw' then the value is immediately return. * * Hooks exist for the more generic cases. With the 'edit' context, the {@see 'edit_$field'} * filter will be called and passed the `$value` and `$bookmark_id` respectively. * * With the 'db' context, the {@see 'pre_$field'} filter is called and passed the value. * The 'display' context is the final context and has the `$field` has the filter name * and is passed the `$value`, `$bookmark_id`, and `$context`, respectively. * * @since 2.3.0 * * @param string $field The bookmark field. * @param mixed $value The bookmark field value. * @param int $bookmark_id Bookmark ID. * @param string $context How to filter the field value. Accepts 'raw', 'edit', 'db', * 'display', 'attribute', or 'js'. Default 'display'. * @return mixed The filtered value. */ function sanitize_bookmark_field( $field, $value, $bookmark_id, $context ) { $int_fields = array( 'link_id', 'link_rating' ); if ( in_array( $field, $int_fields, true ) ) { $value = (int) $value; } switch ( $field ) { case 'link_category': // array( ints ) $value = array_map( 'absint', (array) $value ); /* * We return here so that the categories aren't filtered. * The 'link_category' filter is for the name of a link category, not an array of a link's link categories. */ return $value; case 'link_visible': // bool stored as Y|N $value = preg_replace( '/[^YNyn]/', '', $value ); break; case 'link_target': // "enum" $targets = array( '_top', '_blank' ); if ( ! in_array( $value, $targets, true ) ) { $value = ''; } break; } if ( 'raw' === $context ) { return $value; } if ( 'edit' === $context ) { /** This filter is documented in wp-includes/post.php */ $value = apply_filters( "edit_{$field}", $value, $bookmark_id ); if ( 'link_notes' === $field ) { $value = esc_html( $value ); // textarea_escaped } else { $value = esc_attr( $value ); } } elseif ( 'db' === $context ) { /** This filter is documented in wp-includes/post.php */ $value = apply_filters( "pre_{$field}", $value ); } else { /** This filter is documented in wp-includes/post.php */ $value = apply_filters( "{$field}", $value, $bookmark_id, $context ); if ( 'attribute' === $context ) { $value = esc_attr( $value ); } elseif ( 'js' === $context ) { $value = esc_js( $value ); } } // Restore the type for integer fields after esc_attr(). if ( in_array( $field, $int_fields, true ) ) { $value = (int) $value; } return $value; } /** * Deletes the bookmark cache. * * @since 2.7.0 * * @param int $bookmark_id Bookmark ID. */ function clean_bookmark_cache( $bookmark_id ) { wp_cache_delete( $bookmark_id, 'bookmark' ); wp_cache_delete( 'get_bookmarks', 'bookmark' ); clean_object_term_cache( $bookmark_id, 'link' ); } al_revisions < 1 ) { // Out-of-bounds, run the query again without LIMIT for total count. unset( $query_args['paged'], $query_args['offset'] ); $count_query = new WP_Query(); $count_query->query( $query_args ); $total_revisions = $count_query->found_posts; } if ( $revisions_query->query_vars['posts_per_page'] > 0 ) { $max_pages = ceil( $total_revisions / (int) $revisions_query->query_vars['posts_per_page'] ); } else { $max_pages = $total_revisions > 0 ? 1 : 0; } if ( $total_revisions > 0 ) { if ( $offset >= $total_revisions ) { return new WP_Error( 'rest_revision_invalid_offset_number', __( 'The offset number requested is larger than or equal to the number of available revisions.' ), array( 'status' => 400 ) ); } elseif ( ! $offset && $page > $max_pages ) { return new WP_Error( 'rest_revision_invalid_page_number', __( 'The page number requested is larger than the number of pages available.' ), array( 'status' => 400 ) ); } } } else { $revisions = array(); $total_revisions = 0; $max_pages = 0; $page = (int) $request['page']; } $response = array(); foreach ( $revisions as $revision ) { $data = $this->prepare_item_for_response( $revision, $request ); $response[] = $this->prepare_response_for_collection( $data ); } $response = rest_ensure_response( $response ); $response->header( 'X-WP-Total', (int) $total_revisions ); $response->header( 'X-WP-TotalPages', (int) $max_pages ); $request_params = $request->get_query_params(); $base_path = rest_url( sprintf( '%s/%s/%d/%s', $this->namespace, $this->parent_base, $request['parent'], $this->rest_base ) ); $base = add_query_arg( urlencode_deep( $request_params ), $base_path ); if ( $page > 1 ) { $prev_page = $page - 1; if ( $prev_page > $max_pages ) { $prev_page = $max_pages; } $prev_link = add_query_arg( 'page', $prev_page, $base ); $response->link_header( 'prev', $prev_link ); } if ( $max_pages > $page ) { $next_page = $page + 1; $next_link = add_query_arg( 'page', $next_page, $base ); $response->link_header( 'next', $next_link ); } return $response; } /** * Checks if a given request has access to get a specific revision. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { return $this->get_items_permissions_check( $request ); } /** * Retrieves one revision from the collection. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $parent = $this->get_parent( $request['parent'] ); if ( is_wp_error( $parent ) ) { return $parent; } $revision = $this->get_revision( $request['id'] ); if ( is_wp_error( $revision ) ) { return $revision; } $response = $this->prepare_item_for_response( $revision, $request ); return rest_ensure_response( $response ); } /** * Checks if a given request has access to delete a revision. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. */ public function delete_item_permissions_check( $request ) { $parent = $this->get_parent( $request['parent'] ); if ( is_wp_error( $parent ) ) { return $parent; } if ( ! current_user_can( 'delete_post', $parent->ID ) ) { return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete revisions of this post.' ), array( 'status' => rest_authorization_required_code() ) ); } $revision = $this->get_revision( $request['id'] ); if ( is_wp_error( $revision ) ) { return $revision; } $response = $this->get_items_permissions_check( $request ); if ( ! $response || is_wp_error( $response ) ) { return $response; } if ( ! current_user_can( 'delete_post', $revision->ID ) ) { return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete this revision.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Deletes a single revision. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function delete_item( $request ) { $revision = $this->get_revision( $request['id'] ); if ( is_wp_error( $revision ) ) { return $revision; } $force = isset( $request['force'] ) ? (bool) $request['force'] : false; // We don't support trashing for revisions. if ( ! $force ) { return new WP_Error( 'rest_trash_not_supported', /* translators: %s: force=true */ sprintf( __( "Revisions do not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) ); } $previous = $this->prepare_item_for_response( $revision, $request ); $result = wp_delete_post( $request['id'], true ); /** * Fires after a revision is deleted via the REST API. * * @since 4.7.0 * * @param WP_Post|false|null $result The revision object (if it was deleted or moved to the Trash successfully) * or false or null (failure). If the revision was moved to the Trash, $result represents * its new state; if it was deleted, $result represents its state before deletion. * @param WP_REST_Request $request The request sent to the API. */ do_action( 'rest_delete_revision', $result, $request ); if ( ! $result ) { return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) ); } $response = new WP_REST_Response(); $response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data(), ) ); return $response; } /** * Determines the allowed query_vars for a get_items() response and prepares * them for WP_Query. * * @since 5.0.0 * * @param array $prepared_args Optional. Prepared WP_Query arguments. Default empty array. * @param WP_REST_Request $request Optional. Full details about the request. * @return array Items query arguments. */ protected function prepare_items_query( $prepared_args = array(), $request = null ) { $query_args = array(); foreach ( $prepared_args as $key => $value ) { /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ $query_args[ $key ] = apply_filters( "rest_query_var-{$key}", $value ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores } // Map to proper WP_Query orderby param. if ( isset( $query_args['orderby'] ) && isset( $request['orderby'] ) ) { $orderby_mappings = array( 'id' => 'ID', 'include' => 'post__in', 'slug' => 'post_name', 'include_slugs' => 'post_name__in', ); if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) { $query_args['orderby'] = $orderby_mappings[ $request['orderby'] ]; } } return $query_args; } /** * Prepares the revision for the REST response. * * @since 4.7.0 * @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support. * * @param WP_Post $item Post revision object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $post = $item; $GLOBALS['post'] = $post; setup_postdata( $post ); $fields = $this->get_fields_for_response( $request ); $data = array(); if ( in_array( 'author', $fields, true ) ) { $data['author'] = (int) $post->post_author; } if ( in_array( 'date', $fields, true ) ) { $data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date ); } if ( in_array( 'date_gmt', $fields, true ) ) { $data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt ); } if ( in_array( 'id', $fields, true ) ) { $data['id'] = $post->ID; } if ( in_array( 'modified', $fields, true ) ) { $data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified ); } if ( in_array( 'modified_gmt', $fields, true ) ) { $data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt ); } if ( in_array( 'parent', $fields, true ) ) { $data['parent'] = (int) $post->post_parent; } if ( in_array( 'slug', $fields, true ) ) { $data['slug'] = $post->post_name; } if ( in_array( 'guid', $fields, true ) ) { $data['guid'] = array( /** This filter is documented in wp-includes/post-template.php */ 'rendered' => apply_filters( 'get_the_guid', $post->guid, $post->ID ), 'raw' => $post->guid, ); } if ( in_array( 'title', $fields, true ) ) { $data['title'] = array( 'raw' => $post->post_title, 'rendered' => get_the_title( $post->ID ), ); } if ( in_array( 'content', $fields, true ) ) { $data['content'] = array( 'raw' => $post->post_content, /** This filter is documented in wp-includes/post-template.php */ 'rendered' => apply_filters( 'the_content', $post->post_content ), ); } if ( in_array( 'excerpt', $fields, true ) ) { $data['excerpt'] = array( 'raw' => $post->post_excerpt, 'rendered' => $this->prepare_excerpt_response( $post->post_excerpt, $post ), ); } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); if ( ! empty( $data['parent'] ) ) { $response->add_link( 'parent', rest_url( rest_get_route_for_post( $data['parent'] ) ) ); } /** * Filters a revision returned from the REST API. * * Allows modification of the revision right before it is returned. * * @since 4.7.0 * * @param WP_REST_Response $response The response object. * @param WP_Post $post The original revision object. * @param WP_REST_Request $request Request used to generate the response. */ return apply_filters( 'rest_prepare_revision', $response, $post, $request ); } /** * Checks the post_date_gmt or modified_gmt and prepare any post or * modified date for single post output. * * @since 4.7.0 * * @param string $date_gmt GMT publication time. * @param string|null $date Optional. Local publication time. Default null. * @return string|null ISO8601/RFC3339 formatted datetime, otherwise null. */ protected function prepare_date_response( $date_gmt, $date = null ) { if ( '0000-00-00 00:00:00' === $date_gmt ) { return null; } if ( isset( $date ) ) { return mysql_to_rfc3339( $date ); } return mysql_to_rfc3339( $date_gmt ); } /** * Retrieves the revision's schema, conforming to JSON Schema. * * @since 4.7.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => "{$this->parent_post_type}-revision", 'type' => 'object', // Base properties for every Revision. 'properties' => array( 'author' => array( 'description' => __( 'The ID for the author of the revision.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), ), 'date' => array( 'description' => __( "The date the revision was published, in the site's timezone." ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit', 'embed' ), ), 'date_gmt' => array( 'description' => __( 'The date the revision was published, as GMT.' ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit' ), ), 'guid' => array( 'description' => __( 'GUID for the revision, as it exists in the database.' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), 'id' => array( 'description' => __( 'Unique identifier for the revision.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), ), 'modified' => array( 'description' => __( "The date the revision was last modified, in the site's timezone." ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit' ), ), 'modified_gmt' => array( 'description' => __( 'The date the revision was last modified, as GMT.' ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit' ), ), 'parent' => array( 'description' => __( 'The ID for the parent of the revision.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), ), 'slug' => array( 'description' => __( 'An alphanumeric identifier for the revision unique to its type.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), ), ), ); $parent_schema = $this->parent_controller->get_item_schema(); if ( ! empty( $parent_schema['properties']['title'] ) ) { $schema['properties']['title'] = $parent_schema['properties']['title']; } if ( ! empty( $parent_schema['properties']['content'] ) ) { $schema['properties']['content'] = $parent_schema['properties']['content']; } if ( ! empty( $parent_schema['properties']['excerpt'] ) ) { $schema['properties']['excerpt'] = $parent_schema['properties']['excerpt']; } if ( ! empty( $parent_schema['properties']['guid'] ) ) { $schema['properties']['guid'] = $parent_schema['properties']['guid']; } $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the query params for collections. * * @since 4.7.0 * * @return array Collection parameters. */ public function get_collection_params() { $query_params = parent::get_collection_params(); $query_params['context']['default'] = 'view'; unset( $query_params['per_page']['default'] ); $query_params['exclude'] = array( 'description' => __( 'Ensure result set excludes specific IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); $query_params['include'] = array( 'description' => __( 'Limit result set to specific IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); $query_params['offset'] = array( 'description' => __( 'Offset the result set by a specific number of items.' ), 'type' => 'integer', ); $query_params['order'] = array( 'description' => __( 'Order sort attribute ascending or descending.' ), 'type' => 'string', 'default' => 'desc', 'enum' => array( 'asc', 'desc' ), ); $query_params['orderby'] = array( 'description' => __( 'Sort collection by object attribute.' ), 'type' => 'string', 'default' => 'date', 'enum' => array( 'date', 'id', 'include', 'relevance', 'slug', 'include_slugs', 'title', ), ); return $query_params; } /** * Checks the post excerpt and prepare it for single post output. * * @since 4.7.0 * * @param string $excerpt The post excerpt. * @param WP_Post $post Post revision object. * @return string Prepared excerpt or empty string. */ protected function prepare_excerpt_response( $excerpt, $post ) { /** This filter is documented in wp-includes/post-template.php */ $excerpt = apply_filters( 'the_excerpt', $excerpt, $post ); if ( empty( $excerpt ) ) { return ''; } return $excerpt; } }