(string) $filename,
'errors' => array(),
'warnings' => array(),
'notes' => array(),
'title' => '',
'slug' => '',
'status' => 'draft',
'excerpt' => '',
'categories' => array(),
'tags' => array(),
'seo_title' => '',
'meta_description' => '',
'focus_keyword' => '',
'featured_image' => '',
'image_alt' => '',
'content' => '',
'hash' => sha1( $raw ),
'duplicate' => 0,
);
$valid_utf8 = function_exists( 'wp_is_valid_utf8' ) ? wp_is_valid_utf8( $raw ) : seems_utf8( $raw ); // phpcs:ignore WordPress.WP.DeprecatedFunctions.seems_utf8Found -- only used on WordPress older than 6.9.
if ( ! $valid_utf8 ) {
$item['errors'][] = __( 'The file is not valid UTF-8.', 'draftdrop' );
return $item;
}
$parsed = Draftdrop_Frontmatter::parse( $raw );
$meta = $parsed['meta'];
if ( in_array( 'no_front_matter', $parsed['errors'], true ) ) {
$item['errors'][] = __( 'No front matter found. The file must start with a line of three dashes.', 'draftdrop' );
return $item;
}
if ( in_array( 'bad_line', $parsed['errors'], true ) ) {
$item['warnings'][] = __( 'Some front matter lines could not be read and were ignored.', 'draftdrop' );
}
$item['title'] = self::text( isset( $meta['title'] ) ? $meta['title'] : '' );
if ( '' === $item['title'] ) {
$item['errors'][] = __( 'The title is missing.', 'draftdrop' );
}
$slug_source = isset( $meta['slug'] ) ? self::text( $meta['slug'] ) : '';
$item['slug'] = sanitize_title( '' !== $slug_source ? $slug_source : $item['title'] );
if ( '' === $item['slug'] && '' !== $item['title'] ) {
$item['errors'][] = __( 'A valid slug could not be created.', 'draftdrop' );
}
$status = isset( $meta['status'] ) ? strtolower( self::text( $meta['status'] ) ) : 'draft';
if ( '' === $status ) {
$status = 'draft';
}
if ( ! in_array( $status, array( 'draft', 'pending' ), true ) ) {
/* translators: %s: status given in the file. */
$item['warnings'][] = sprintf( __( 'Status "%s" is not allowed in this version. Saved as draft.', 'draftdrop' ), $status );
$status = 'draft';
}
$item['status'] = $status;
$item['excerpt'] = isset( $meta['excerpt'] ) ? sanitize_textarea_field( is_array( $meta['excerpt'] ) ? implode( ' ', $meta['excerpt'] ) : (string) $meta['excerpt'] ) : '';
$cats = isset( $meta['categories'] ) ? $meta['categories'] : ( isset( $meta['category'] ) ? $meta['category'] : array() );
$item['categories'] = self::terms( $cats, false );
$item['tags'] = self::terms( isset( $meta['tags'] ) ? $meta['tags'] : array(), true );
$item['seo_title'] = self::text( isset( $meta['seo_title'] ) ? $meta['seo_title'] : '' );
$item['meta_description'] = self::text( isset( $meta['meta_description'] ) ? $meta['meta_description'] : '' );
$item['focus_keyword'] = self::text( isset( $meta['focus_keyword'] ) ? $meta['focus_keyword'] : '' );
$item['image_alt'] = self::text( isset( $meta['image_alt'] ) ? $meta['image_alt'] : '' );
if ( '' === $item['meta_description'] ) {
$item['warnings'][] = __( 'No meta description.', 'draftdrop' );
} elseif ( self::length( $item['meta_description'] ) > 160 ) {
/* translators: %d: number of characters. */
$item['warnings'][] = sprintf( __( 'Meta description is %d characters (about 160 recommended).', 'draftdrop' ), self::length( $item['meta_description'] ) );
}
if ( '' !== $item['seo_title'] && self::length( $item['seo_title'] ) > 60 ) {
/* translators: %d: number of characters. */
$item['warnings'][] = sprintf( __( 'SEO title is %d characters (about 60 recommended).', 'draftdrop' ), self::length( $item['seo_title'] ) );
}
if ( '' === $item['focus_keyword'] ) {
$item['warnings'][] = __( 'No focus keyphrase.', 'draftdrop' );
}
$image = isset( $meta['featured_image'] ) ? esc_url_raw( self::text( $meta['featured_image'] ) ) : '';
if ( '' !== $image ) {
if ( wp_http_validate_url( $image ) ) {
$item['featured_image'] = $image;
} else {
$item['warnings'][] = __( 'The featured image URL is not valid and will be skipped.', 'draftdrop' );
}
}
// Body: Markdown to HTML, sanitize, then to blocks.
$markdown = ltrim( $parsed['body'], "\n" );
$parsedown = new Draftdrop_Parsedown();
$html = $parsedown->text( $markdown );
$cleaned = Draftdrop_Sanitizer::clean( $html );
if ( ! empty( $cleaned['removed'] ) ) {
/* translators: %s: list of HTML tags, for example "script, iframe". */
$item['warnings'][] = sprintf( __( 'Unsafe HTML was removed: %s.', 'draftdrop' ), implode( ', ', $cleaned['removed'] ) );
}
$html = wp_kses_post( $cleaned['html'] );
if ( preg_match( '#^\s*
]*>.*?
\s*#is', $html ) ) {
$html = preg_replace( '#^\s*]*>.*?
\s*#is', '', $html, 1 );
$item['notes'][] = __( 'Leading H1 removed (the title comes from front matter).', 'draftdrop' );
}
if ( '' === trim( wp_strip_all_tags( $html ) ) && false === stripos( $html, '
0,
'notes' => array(),
'error' => '',
);
if ( ! current_user_can( 'edit_posts' ) ) {
$result['error'] = __( 'You are not allowed to create posts.', 'draftdrop' );
return $result;
}
$meta = array( self::HASH_META => $item['hash'] );
// Written at insert time so the SEO plugin indexes the values immediately.
if ( '' !== $item['seo_title'] ) {
$meta['_yoast_wpseo_title'] = $item['seo_title'];
}
if ( '' !== $item['meta_description'] ) {
$meta['_yoast_wpseo_metadesc'] = $item['meta_description'];
}
if ( '' !== $item['focus_keyword'] ) {
$meta['_yoast_wpseo_focuskw'] = $item['focus_keyword'];
}
$postarr = array(
'post_type' => 'post',
'post_status' => $item['status'],
'post_title' => $item['title'],
'post_name' => $item['slug'],
'post_excerpt' => $item['excerpt'],
'post_content' => $item['content'],
'post_author' => get_current_user_id(),
'meta_input' => $meta,
);
$cat_ids = self::resolve_categories( $item['categories'], $result['notes'] );
if ( ! empty( $cat_ids ) ) {
$postarr['post_category'] = $cat_ids;
}
if ( ! empty( $item['tags'] ) ) {
$postarr['tags_input'] = $item['tags'];
}
$post_id = wp_insert_post( wp_slash( $postarr ), true );
if ( is_wp_error( $post_id ) ) {
$result['error'] = $post_id->get_error_message();
return $result;
}
$result['post_id'] = (int) $post_id;
if ( '' !== $item['featured_image'] ) {
$image = self::sideload_featured_image( $post_id, $item['featured_image'], $item['image_alt'], $item['title'] );
if ( is_wp_error( $image ) ) {
/* translators: %s: error message. */
$result['notes'][] = sprintf( __( 'Featured image skipped: %s', 'draftdrop' ), $image->get_error_message() );
}
}
return $result;
}
/**
* Look up an existing post with the same content hash or slug.
*
* @param string $slug Post slug.
* @param string $hash Content hash.
* @return int Post ID or 0.
*/
private static function find_duplicate( $slug, $hash ) {
$by_hash = get_posts(
array(
'post_type' => 'post',
'post_status' => 'any',
'meta_key' => self::HASH_META, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
'meta_value' => $hash, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
'posts_per_page' => 1,
'fields' => 'ids',
'no_found_rows' => true,
)
);
if ( ! empty( $by_hash ) ) {
return (int) $by_hash[0];
}
$by_slug = get_posts(
array(
'post_type' => 'post',
'post_status' => 'any',
'name' => $slug,
'posts_per_page' => 1,
'fields' => 'ids',
'no_found_rows' => true,
)
);
return empty( $by_slug ) ? 0 : (int) $by_slug[0];
}
/**
* Resolve category names to IDs, creating missing ones when allowed.
*
* @param string[] $names Category names or slugs.
* @param string[] $notes Notes (by reference).
* @return int[]
*/
private static function resolve_categories( array $names, array &$notes ) {
$ids = array();
foreach ( $names as $name ) {
$term = get_term_by( 'name', $name, 'category' );
if ( ! $term ) {
$term = get_term_by( 'slug', sanitize_title( $name ), 'category' );
}
if ( $term ) {
$ids[] = (int) $term->term_id;
continue;
}
if ( current_user_can( 'manage_categories' ) ) {
$created = wp_insert_term( $name, 'category' );
if ( ! is_wp_error( $created ) ) {
$ids[] = (int) $created['term_id'];
/* translators: %s: category name. */
$notes[] = sprintf( __( 'Category "%s" was created.', 'draftdrop' ), $name );
continue;
}
}
/* translators: %s: category name. */
$notes[] = sprintf( __( 'Category "%s" does not exist and was not assigned.', 'draftdrop' ), $name );
}
return array_values( array_unique( $ids ) );
}
/**
* Download an image and set it as the featured image.
*
* @param int $post_id Post ID.
* @param string $url Image URL (http/https, validated).
* @param string $alt Alt text.
* @param string $title Attachment title.
* @return true|WP_Error
*/
private static function sideload_featured_image( $post_id, $url, $alt, $title ) {
if ( ! current_user_can( 'upload_files' ) ) {
return new WP_Error( 'draftdrop_cap', __( 'You are not allowed to upload files.', 'draftdrop' ) );
}
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/image.php';
$tmp = download_url( $url, 30 ); // Uses wp_safe_remote_get(): private and loopback hosts are refused.
if ( is_wp_error( $tmp ) ) {
return $tmp;
}
$size = filesize( $tmp );
if ( false === $size || $size > self::MAX_IMAGE_BYTES ) {
wp_delete_file( $tmp );
return new WP_Error( 'draftdrop_size', __( 'The image is larger than 8 MB.', 'draftdrop' ) );
}
$info = wp_getimagesize( $tmp );
$extensions = array(
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif',
'image/webp' => 'webp',
);
if ( ! is_array( $info ) || empty( $info['mime'] ) || ! isset( $extensions[ $info['mime'] ] ) ) {
wp_delete_file( $tmp );
return new WP_Error( 'draftdrop_type', __( 'Only JPEG, PNG, GIF and WebP images are accepted.', 'draftdrop' ) );
}
$base = sanitize_file_name( pathinfo( (string) wp_parse_url( $url, PHP_URL_PATH ), PATHINFO_FILENAME ) );
if ( '' === $base ) {
$base = 'draftdrop-image';
}
$file_array = array(
'name' => $base . '.' . $extensions[ $info['mime'] ],
'tmp_name' => $tmp,
);
$attachment_id = media_handle_sideload( $file_array, $post_id, $title );
if ( is_wp_error( $attachment_id ) ) {
wp_delete_file( $tmp );
return $attachment_id;
}
if ( '' !== $alt ) {
update_post_meta( $attachment_id, '_wp_attachment_image_alt', $alt );
}
set_post_thumbnail( $post_id, $attachment_id );
return true;
}
/**
* Sanitize a scalar (or join a list) into plain text.
*
* @param mixed $value Value.
* @return string
*/
private static function text( $value ) {
if ( is_array( $value ) ) {
$value = implode( ', ', $value );
}
return sanitize_text_field( (string) $value );
}
/**
* Normalise a list of terms.
*
* @param mixed $value String or list.
* @param bool $split_commas Split a plain string on commas.
* @return string[]
*/
private static function terms( $value, $split_commas ) {
if ( is_string( $value ) ) {
$value = $split_commas ? explode( ',', $value ) : array( $value );
}
if ( ! is_array( $value ) ) {
return array();
}
$out = array();
foreach ( $value as $term ) {
$term = sanitize_text_field( (string) $term );
if ( '' !== $term ) {
$out[] = $term;
}
}
return array_values( array_unique( $out ) );
}
/**
* Character length that is safe for multibyte text.
*
* @param string $s String.
* @return int
*/
private static function length( $s ) {
return function_exists( 'mb_strlen' ) ? mb_strlen( $s, 'UTF-8' ) : strlen( $s );
}
}