cap->edit_posts ) ) { return new WP_Error( 'acsagen_forbidden', 'page' === $post_type ? __( 'You are not allowed to create pages on this site.', 'acs-article-generator' ) : __( 'You are not allowed to create posts on this site.', 'acs-article-generator' ) ); } $title = sanitize_text_field( (string) $article['title'] ); $description = self::clean_description( (string) $article['meta_description'] ); $slug = sanitize_title( (string) $article['slug'] ); $content = self::clean_content( (string) $article['content_html'] ); if ( '' === $title ) { return new WP_Error( 'acsagen_empty_title', __( 'The reply did not contain a usable title. Please try again.', 'acs-article-generator' ) ); } $postarr = array( 'post_title' => $title, 'post_content' => $content, 'post_excerpt' => $description, 'post_name' => $slug, 'post_status' => 'draft', 'post_type' => $post_type, 'post_author' => get_current_user_id(), ); $notices = array(); if ( ! $skip_terms && ACSAGEN_Settings::get( 'draft_category', 1 ) ) { $unmatched = array(); $categories = self::resolve_terms( isset( $article['categories'] ) ? (array) $article['categories'] : array(), 'category', $unmatched ); if ( ! empty( $categories ) ) { $postarr['post_category'] = $categories; } if ( ! empty( $unmatched ) ) { $notices[] = empty( $categories ) ? sprintf( /* translators: %s: comma separated category names suggested by the model. */ __( 'No suggested category matched an existing category, so the draft was filed under the default category. Suggested: %s', 'acs-article-generator' ), implode( ', ', $unmatched ) ) : sprintf( /* translators: %s: comma separated category names suggested by the model. */ __( 'These suggested categories did not match an existing category and were not created: %s', 'acs-article-generator' ), implode( ', ', $unmatched ) ); } } /** * Filters the draft just before it is inserted. * * @param array $postarr Arguments for wp_insert_post(). * @param array $article The decoded article data. */ $postarr = apply_filters( 'acsagen_draft_postarr', $postarr, $article ); // Keep the status out of anyone else's hands. A draft is a promise the // plugin makes to the site owner, not a default. $postarr['post_status'] = 'draft'; $post_id = wp_insert_post( $postarr, true ); if ( is_wp_error( $post_id ) ) { return $post_id; } if ( ! $skip_terms && ACSAGEN_Settings::get( 'draft_category', 1 ) ) { $tags = self::clean_term_names( isset( $article['tags'] ) ? (array) $article['tags'] : array() ); if ( ! empty( $tags ) ) { wp_set_post_terms( $post_id, $tags, 'post_tag', false ); } } if ( ACSAGEN_Settings::get( 'sync_seo_meta', 1 ) && '' !== $description ) { self::sync_seo_meta( $post_id, $title, $description ); } return array( 'post_id' => (int) $post_id, 'title' => (string) get_the_title( $post_id ), 'slug' => (string) get_post_field( 'post_name', $post_id ), 'description' => $description, 'edit_url' => (string) get_edit_post_link( $post_id, 'raw' ), 'notices' => $notices, ); } /** * Hand the SEO title and description to whichever SEO plugin is active. * * The description is also kept in the post excerpt, which is where * WordPress itself looks when no SEO plugin is installed. * * @param int $post_id Draft id. * @param string $title SEO title. * @param string $description Meta description. */ private static function sync_seo_meta( $post_id, $title, $description ) { // ACS SEO Plus. if ( class_exists( 'ACSSEO_Meta' ) ) { update_post_meta( $post_id, '_acsseo_title', $title ); update_post_meta( $post_id, '_acsseo_description', $description ); } // Yoast SEO. if ( defined( 'WPSEO_VERSION' ) ) { update_post_meta( $post_id, '_yoast_wpseo_title', $title ); update_post_meta( $post_id, '_yoast_wpseo_metadesc', $description ); } } /** * Trim a meta description to a sensible length. * * @param string $text Raw description. * @return string */ private static function clean_description( $text ) { $text = sanitize_text_field( wp_strip_all_tags( $text ) ); return mb_substr( $text, 0, 200 ); } /** * Strip anything from the article body that has no business in a post. * * The body is written by a model, so it is treated as untrusted input and * run through the same filter WordPress applies to post content from a * contributor, which keeps block comments but removes scripts, styles, * iframes, event handlers and javascript URLs. * * @param string $html Raw article body. * @return string */ private static function clean_content( $html ) { $allowed = wp_kses_allowed_html( 'post' ); // Block editor markup lives in HTML comments, which wp_kses keeps. $clean = wp_kses( $html, $allowed ); return trim( $clean ); } /** * Turn a list of term names into term ids. * * Names are matched against existing terms loosely (see * ACSAGEN_Checks::match_term()). A name that matches nothing is only * created when the "create_categories" setting is on; otherwise it is * reported back through $unmatched. * * @param array $names Term names. * @param string $taxonomy Taxonomy name. * @param array $unmatched Receives the names that matched no term and were not created. * @return array Term ids. */ private static function resolve_terms( $names, $taxonomy, &$unmatched = array() ) { $ids = array(); $unmatched = array(); foreach ( self::clean_term_names( $names ) as $name ) { $term_id = ACSAGEN_Checks::match_term( $name, $taxonomy ); if ( $term_id ) { $ids[] = $term_id; continue; } if ( ! ACSAGEN_Settings::get( 'create_categories', 0 ) ) { $unmatched[] = $name; continue; } if ( ! current_user_can( 'manage_categories' ) ) { continue; } $created = wp_insert_term( $name, $taxonomy ); if ( ! is_wp_error( $created ) && isset( $created['term_id'] ) ) { $ids[] = (int) $created['term_id']; } } return array_values( array_unique( $ids ) ); } /** * Clean a list of term names. * * @param array $names Raw names. * @return array */ private static function clean_term_names( $names ) { $clean = array(); foreach ( $names as $name ) { if ( ! is_string( $name ) ) { continue; } $name = sanitize_text_field( $name ); if ( '' === $name ) { continue; } $clean[] = mb_substr( $name, 0, 60 ); if ( count( $clean ) >= 10 ) { break; } } return array_values( array_unique( $clean ) ); } }