File: /var/www/html/wp-content/plugins/json-rest-api/lib/class-wp-json-media.php
<?php
class WP_JSON_Media extends WP_JSON_Posts {
/**
* Register the media-related routes
*
* @param array $routes Existing routes
* @return array Modified routes
*/
public function register_routes( $routes ) {
$media_routes = array(
'/media' => array(
array( array( $this, 'get_posts' ), WP_JSON_Server::READABLE ),
array( array( $this, 'upload_attachment' ), WP_JSON_Server::CREATABLE ),
),
'/media/(?P<id>\d+)' => array(
array( array( $this, 'get_post' ), WP_JSON_Server::READABLE ),
array( array( $this, 'edit_post' ), WP_JSON_Server::EDITABLE ),
array( array( $this, 'delete_post' ), WP_JSON_Server::DELETABLE ),
),
);
return array_merge( $routes, $media_routes );
}
/**
* Retrieve pages
*
* Overrides the $type to set to 'attachment', then passes through to the post
* endpoints.
*
* @see WP_JSON_Posts::get_posts()
*/
public function get_posts( $filter = array(), $context = 'view', $type = 'attachment', $page = 1 ) {
if ( $type !== 'attachment' ) {
return new WP_Error( 'json_post_invalid_type', __( 'Invalid post type' ), array( 'status' => 400 ) );
}
if ( empty( $filter['post_status'])) {
$filter['post_status'] = array( 'publish', 'inherit' );
// Always allow status queries for attachments
add_filter( 'query_vars', array( $this, 'allow_status_query' ) );
}
$posts = parent::get_posts( $filter, $context, 'attachment', $page );
return $posts;
}
/**
* Add post_status to the available query vars
*
* @param array $vars Query variables
* @return array Filtered query variables
*/
public function allow_status_query( $vars ) {
remove_filter( 'query_vars', array( $this, 'allow_status_query' ) );
$vars[] = 'post_status';
return $vars;
}
/**
* Retrieve a attachment
*
* @see WP_JSON_Posts::get_post()
*/
public function get_post( $id, $context = 'view' ) {
$id = (int) $id;
if ( empty( $id ) ) {
return new WP_Error( 'json_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
}
$post = get_post( $id, ARRAY_A );
if ( $post['post_type'] !== 'attachment' ) {
return new WP_Error( 'json_post_invalid_type', __( 'Invalid post type' ), array( 'status' => 400 ) );
}
return parent::get_post( $id, $context );
}
/**
* Get attachment-specific data
*
* @param array $post
* @return array
*/
protected function prepare_post( $post, $context = 'single' ) {
$data = parent::prepare_post( $post, $context );
if ( is_wp_error( $data ) || $post['post_type'] !== 'attachment' ) {
return $data;
}
// $thumbnail_size = current_theme_supports( 'post-thumbnail' ) ? 'post-thumbnail' : 'thumbnail';
$data['source'] = wp_get_attachment_url( $post['ID'] );
$data['is_image'] = wp_attachment_is_image( $post['ID'] );
$data['attachment_meta'] = wp_get_attachment_metadata( $post['ID'] );
// Ensure empty meta is an empty object
if ( empty( $data['attachment_meta'] ) ) {
$data['attachment_meta'] = new stdClass;
} elseif ( ! empty( $data['attachment_meta']['sizes'] ) ) {
$img_url_basename = wp_basename( $data['source'] );
foreach ($data['attachment_meta']['sizes'] as $size => &$size_data) {
// Use the same method image_downsize() does
$size_data['url'] = str_replace( $img_url_basename, $size_data['file'], $data['source'] );
}
} else {
$data['attachment_meta']['sizes'] = new stdClass;
}
// Override entity meta keys with the correct links
$data['meta'] = array(
'links' => array(
'self' => json_url( '/media/' . $post['ID'] ),
'author' => json_url( '/users/' . $post['post_author'] ),
'collection' => json_url( '/media' ),
'replies' => json_url( '/media/' . $post['ID'] . '/comments' ),
'version-history' => json_url( '/media/' . $post['ID'] . '/revisions' ),
),
);
if ( ! empty( $post['post_parent'] ) ) {
$data['meta']['links']['up'] = json_url( '/media/' . (int) $post['post_parent'] );
}
return apply_filters( 'json_prepare_attachment', $data, $post, $context );
}
/**
* Edit a attachment
*
* @see WP_JSON_Posts::edit_post()
*/
public function edit_post( $id, $data, $_headers = array() ) {
$id = (int) $id;
if ( empty( $id ) ) {
return new WP_Error( 'json_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
}
$post = get_post( $id, ARRAY_A );
if ( empty( $post['ID'] ) ) {
return new WP_Error( 'json_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
}
if ( $post['post_type'] !== 'attachment' ) {
return new WP_Error( 'json_post_invalid_type', __( 'Invalid post type' ), array( 'status' => 400 ) );
}
return parent::edit_post( $id, $data, $_headers );
}
/**
* Delete a attachment
*
* @see WP_JSON_Posts::delete_post()
*/
public function delete_post( $id, $force = false ) {
$id = (int) $id;
if ( empty( $id ) ) {
return new WP_Error( 'json_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
}
$post = get_post( $id, ARRAY_A );
if ( $post['post_type'] !== 'attachment' ) {
return new WP_Error( 'json_post_invalid_type', __( 'Invalid post type' ), array( 'status' => 400 ) );
}
return parent::delete_post( $id, $force );
}
/**
* Upload a new attachment
*
* Creating a new attachment is done in two steps: uploading the data, then
* setting the post. This is achieved by first creating an attachment, then
* editing the post data for it.
*
* @param array $_files Data from $_FILES
* @param array $_headers HTTP headers from the request
* @return array|WP_Error Attachment data or error
*/
public function upload_attachment( $_files, $_headers, $post_id = 0 ) {
$post_type = get_post_type_object( 'attachment' );
if ( $post_id == 0 ) {
$post_parent_type = get_post_type_object( 'post' );
} else {
$post_parent_type = get_post_type_object( get_post_type( $post_id ) );
}
// Make sure we have an int or 0
$post_id = (int) $post_id;
if ( ! $post_type ) {
return new WP_Error( 'json_invalid_post_type', __( 'Invalid post type' ), array( 'status' => 400 ) );
}
// Permissions check - Note: "upload_files" cap is returned for an attachment by $post_type->cap->create_posts
if ( ! current_user_can( $post_type->cap->create_posts ) || ! current_user_can( $post_type->cap->edit_posts ) ) {
return new WP_Error( 'json_cannot_create', __( 'Sorry, you are not allowed to post on this site.' ), array( 'status' => 400 ) );
}
// If a user is trying to attach to a post make sure they have permissions. Bail early if post_id is not being passed
if ( $post_id !== 0 && ! current_user_can( $post_parent_type->cap->edit_post, $post_id ) ) {
return new WP_Error( 'json_cannot_edit', __( 'Sorry, you are not allowed to edit this post.' ), array( 'status' => 401 ) );
}
// Get the file via $_FILES or raw data
if ( empty( $_files ) ) {
$file = $this->upload_from_data( $_files, $_headers );
} else {
$file = $this->upload_from_file( $_files, $_headers );
}
if ( is_wp_error( $file ) ) {
return $file;
}
$name = basename( $file['file'] );
$name_parts = pathinfo( $name );
$name = trim( substr( $name, 0, -(1 + strlen($name_parts['extension'])) ) );
$url = $file['url'];
$type = $file['type'];
$file = $file['file'];
$title = $name;
$content = '';
// use image exif/iptc data for title and caption defaults if possible
if ( $image_meta = @wp_read_image_metadata($file) ) {
if ( trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) {
$title = $image_meta['title'];
}
if ( trim( $image_meta['caption'] ) ) {
$content = $image_meta['caption'];
}
}
// Construct the attachment array
$post_data = array();
$attachment = array(
'post_mime_type' => $type,
'guid' => $url,
'post_parent' => $post_id,
'post_title' => $title,
'post_content' => $content,
);
// This should never be set as it would then overwrite an existing attachment.
if ( isset( $attachment['ID'] ) ) {
unset( $attachment['ID'] );
}
// Save the data
$id = wp_insert_attachment($attachment, $file, $post_id );
if ( !is_wp_error($id) ) {
wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) );
}
$headers = array( 'Location' => json_url( '/media/' . $id ) );
return new WP_JSON_Response( $this->get_post( $id, 'edit' ), 201, $headers );
}
/**
* Handle an upload via raw POST data
*
* @param array $_files Data from $_FILES. Unused.
* @param array $_headers HTTP headers from the request
* @return array|WP_Error Data from {@see wp_handle_sideload()}
*/
protected function upload_from_data( $_files, $_headers ) {
$data = $this->server->get_raw_data();
if ( empty( $data ) ) {
return new WP_Error( 'json_upload_no_data', __( 'No data supplied' ), array( 'status' => 400 ) );
}
if ( empty( $_headers['CONTENT_TYPE'] ) ) {
return new WP_Error( 'json_upload_no_type', __( 'No Content-Type supplied' ), array( 'status' => 400 ) );
}
if ( empty( $_headers['CONTENT_DISPOSITION'] ) ) {
return new WP_Error( 'json_upload_no_disposition', __( 'No Content-Disposition supplied' ), array( 'status' => 400 ) );
}
// Get the filename
$disposition_parts = explode( ';', $_headers['CONTENT_DISPOSITION'] );
$filename = null;
foreach ( $disposition_parts as $part ) {
$part = trim( $part );
if ( strpos( $part, 'filename' ) !== 0 ) {
continue;
}
$filenameparts = explode( '=', $part );
$filename = trim( $filenameparts[1] );
}
if ( empty( $filename ) ) {
return new WP_Error( 'json_upload_invalid_disposition', __( 'Invalid Content-Disposition supplied' ), array( 'status' => 400 ) );
}
if ( ! empty( $_headers['CONTENT_MD5'] ) ) {
$expected = trim( $_headers['CONTENT_MD5'] );
$actual = md5( $data );
if ( $expected !== $actual ) {
return new WP_Error( 'json_upload_hash_mismatch', __( 'Content hash did not match expected' ), array( 'status' => 412 ) );
}
}
// Get the content-type
$type = $_headers['CONTENT_TYPE'];
// Save the file
$tmpfname = wp_tempnam( $filename );
$fp = fopen( $tmpfname, 'w+' );
if ( ! $fp ) {
return new WP_Error( 'json_upload_file_error', __( 'Could not open file handle' ), array( 'status' => 500 ) );
}
fwrite( $fp, $data );
fclose( $fp );
// Now, sideload it in
$file_data = array(
'error' => null,
'tmp_name' => $tmpfname,
'name' => $filename,
'type' => $type,
);
$overrides = array(
'test_form' => false,
);
$sideloaded = wp_handle_sideload( $file_data, $overrides );
if ( isset( $sideloaded['error'] ) ) {
@unlink( $tmpfname );
return new WP_Error( 'json_upload_sideload_error', $sideloaded['error'], array( 'status' => 500 ) );
}
return $sideloaded;
}
/**
* Handle an upload via multipart/form-data ($_FILES)
*
* @param array $_files Data from $_FILES
* @param array $_headers HTTP headers from the request
* @return array|WP_Error Data from {@see wp_handle_upload()}
*/
protected function upload_from_file( $_files, $_headers ) {
if ( empty( $_files['file'] ) )
return new WP_Error( 'json_upload_no_data', __( 'No data supplied' ), array( 'status' => 400 ) );
// Verify hash, if given
if ( ! empty( $_headers['CONTENT_MD5'] ) ) {
$expected = trim( $_headers['CONTENT_MD5'] );
$actual = md5_file( $_files['file']['tmp_name'] );
if ( $expected !== $actual ) {
return new WP_Error( 'json_upload_hash_mismatch', __( 'Content hash did not match expected' ), array( 'status' => 412 ) );
}
}
// Pass off to WP to handle the actual upload
$overrides = array(
'test_form' => false,
);
$file = wp_handle_upload( $_files['file'], $overrides );
if ( isset( $file['error'] ) ) {
return new WP_Error( 'json_upload_unknown_error', $file['error'], array( 'status' => 500 ) );
}
return $file;
}
/**
* Check featured image data before creating/updating post
*
* @param bool|WP_Error $status Previous status
* @param array $post Post data to be inserted
* @param array $data Supplied post data
* @return bool|WP_Error Success or error object
*/
public function preinsert_check( $status, $post, $data ) {
if ( is_wp_error( $status ) ) {
return $status;
}
// Featured image (pre-verification)
if ( ! empty( $data['featured_image'] ) ) {
$thumbnail = $this->get_post( (int) $data['featured_image'], 'child' );
if ( is_wp_error( $thumbnail ) ) {
return new WP_Error( 'json_invalid_featured_image', __( 'Invalid featured image.' ), array( 'status' => 400 ) );
}
}
return $status;
}
/**
* Set the featured image on post update
*
* @param array $post Post data
* @param array $data Supplied post data
* @param boolean $update Is this an update?
*/
public function attach_thumbnail( $post, $data, $update ) {
if ( ! $update ) {
return;
}
if ( ! empty( $data['featured_image'] ) ) {
// Already verified in preinsert_check()
$thumbnail = $this->get_post( $data['featured_image'], 'child' );
set_post_thumbnail( $post['ID'], $thumbnail['ID'] );
}
}
/**
* Add the featured image data to the post data
*
* @param array $data Post data
* @param array $post Raw post data from the database
* @param string $context Display context
* @return array Filtered post data
*/
public function add_thumbnail_data( $data, $post, $context ) {
if ( ! post_type_supports( $post['post_type'], 'thumbnail' ) ) {
return $data;
}
// Don't embed too deeply
if ( $context !== 'view' && $context !== 'edit' ) {
$data['featured_image'] = null;
$thumbnail_id = get_post_thumbnail_id( $post['ID'] );
if ( $thumbnail_id ) {
$data['featured_image'] = absint( $thumbnail_id );
}
return $data;
}
// Thumbnail
$data['featured_image'] = null;
$thumbnail_id = get_post_thumbnail_id( $post['ID'] );
if ( $thumbnail_id ) {
$data['featured_image'] = $this->get_post( $thumbnail_id, 'child' );
}
return $data;
}
/**
* Filter the post type archive link
*
* @param array $data Post type data
* @param stdClass $type Internal post type data
* @return array Filtered data
*/
public function type_archive_link( $data, $type ) {
if ( $type->name !== 'attachment' ) {
return $data;
}
$data['meta']['links']['archives'] = json_url( '/media' );
return $data;
}
}