'', 'status' => 'error' ); } $existing_id = $this->find_existing( $blog_id ); // Auto-generated posts arrive as drafts; only publish when the member has // explicitly gone live in the AiM app (status === 'publish'). $desired_status = ( isset( $item['status'] ) && 'publish' === $item['status'] ) ? 'publish' : 'draft'; $postarr = array( 'post_title' => isset( $item['title'] ) ? sanitize_text_field( $item['title'] ) : '', 'post_content' => isset( $item['html'] ) ? wp_kses_post( $item['html'] ) : '', 'post_excerpt' => isset( $item['excerpt'] ) ? sanitize_textarea_field( $item['excerpt'] ) : '', 'post_status' => $desired_status, 'post_type' => 'post', ); // Runs from WP-Cron with no logged-in user. Assign a real author so // publishing behaves like a normal editorial publish (author 0 posts can // trip capability/ownership checks in some setups). $author_id = $this->default_author_id(); if ( $author_id ) { $postarr['post_author'] = $author_id; } if ( ! empty( $item['slug'] ) ) { $postarr['post_name'] = sanitize_title( $item['slug'] ); } if ( $existing_id ) { $postarr['ID'] = $existing_id; } $post_id = wp_insert_post( $postarr, true ); if ( is_wp_error( $post_id ) ) { // Surface the real reason so it can be seen server-side (ack). return array( 'blog_id' => $blog_id, 'status' => 'error', 'error' => $post_id->get_error_code() . ': ' . $post_id->get_error_message(), ); } if ( ! $post_id ) { return array( 'blog_id' => $blog_id, 'status' => 'error', 'error' => 'wp_insert_post returned 0' ); } update_post_meta( $post_id, '_aim_blog_id', $blog_id ); if ( ! empty( $item['image_url'] ) ) { $this->attach_featured_image( $post_id, esc_url_raw( $item['image_url'] ) ); } return array( 'blog_id' => $blog_id, 'post_id' => (string) $post_id, 'post_url' => get_permalink( $post_id ), 'status' => get_post_status( $post_id ), ); } /** * The user to attribute imported posts to — the first administrator. Cron has * no current user, so without this posts would be authored by user 0. * * @return int Author user ID, or 0 if none found. */ private function default_author_id() { $admins = get_users( array( 'role' => 'administrator', 'number' => 1, 'fields' => 'ID', 'orderby' => 'ID', 'order' => 'ASC', ) ); return ! empty( $admins ) ? (int) $admins[0] : 0; } /** * Find a post previously created from this AiM blog id. * * @param string $blog_id AiM blog id. * @return int Post ID, or 0. */ private function find_existing( $blog_id ) { $query = new WP_Query( array( 'post_type' => 'post', 'post_status' => 'any', // Idempotency lookup by our own post meta. Runs at most a few // times per sync, so the meta_key/meta_value query is fine. // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'meta_key' => '_aim_blog_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value 'meta_value' => $blog_id, 'fields' => 'ids', 'posts_per_page' => 1, 'no_found_rows' => true, ) ); return $query->have_posts() ? (int) $query->posts[0] : 0; } /** * Sideload a featured image from a URL using core media functions. * * @param int $post_id Target post. * @param string $image_url Image URL. * @return void */ private function attach_featured_image( $post_id, $image_url ) { if ( has_post_thumbnail( $post_id ) || empty( $image_url ) ) { return; } require_once ABSPATH . 'wp-admin/includes/media.php'; require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/image.php'; $attachment_id = media_sideload_image( $image_url, $post_id, null, 'id' ); if ( ! is_wp_error( $attachment_id ) ) { set_post_thumbnail( $post_id, $attachment_id ); } } }