diff --git a/classes/NPRAPIWordpress.php b/classes/NPRAPIWordpress.php index 3a0f328..d391e76 100644 --- a/classes/NPRAPIWordpress.php +++ b/classes/NPRAPIWordpress.php @@ -102,6 +102,7 @@ function update_posts_from_stories( $publish = TRUE, $qnum = false ) { if ( empty( $pull_post_type ) ) { $pull_post_type = 'post'; } + $use_npr_layout = !empty(get_option( 'dp_npr_query_use_layout' )) ? TRUE : FALSE; $post_id = null; @@ -138,7 +139,18 @@ function update_posts_from_stories( $publish = TRUE, $qnum = false ) { } else { $existing = $existing_status = null; } - + + $npr_has_layout = FALSE; + $npr_has_video = FALSE; + if ($use_npr_layout) { + // get the "NPR layout" version if available and the "use rich layout" option checked in settings + $npr_layout = $this->get_body_with_layout($story); + if (!empty($npr_layout['body'])) { + $story->body = $npr_layout['body']; + $npr_has_layout = TRUE; + $npr_has_video = $npr_layout['has_video']; + } + } //add the transcript $story->body .= $this->get_transcript_body($story); @@ -226,6 +238,8 @@ function update_posts_from_stories( $publish = TRUE, $qnum = false ) { NPR_PUB_DATE_META_KEY => $story->pubDate->value, NPR_STORY_DATE_MEATA_KEY => $story->storyDate->value, NPR_LAST_MODIFIED_DATE_KEY => $story->lastModifiedDate->value, + NPR_STORY_HAS_LAYOUT_META_KEY => $npr_has_layout, + NPR_STORY_HAS_VIDEO_META_KEY => $npr_has_video, ); //get audio if ( isset($story->audio) ) { @@ -244,6 +258,8 @@ function update_posts_from_stories( $publish = TRUE, $qnum = false ) { $metas[NPR_AUDIO_META_KEY] = implode( ',', $mp3_array ); $metas[NPR_AUDIO_M3U_META_KEY] = implode( ',', $m3u_array ); } + + if ( $existing ) { $created = false; $args[ 'ID' ] = $existing->ID; @@ -263,10 +279,21 @@ function update_posts_from_stories( $publish = TRUE, $qnum = false ) { * @param NPRMLEntity $story Story object created during import * @param bool $created true if not pre-existing, false otherwise */ + + if ($npr_has_layout) { + // keep WP from stripping content from NPR posts + kses_remove_filters(); + } + $args = apply_filters( 'npr_pre_insert_post', $args, $post_id, $story, $created ); $post_id = wp_insert_post( $args ); + if ($npr_has_layout) { + // re-enable the built-in content stripping + kses_init_filters(); + } + //now that we have an id, we can add images //this is the way WP seems to do it, but we couldn't call media_sideload_image or media_ because that returned only the URL //for the attachment, and we want to be able to set the primary image, so we had to use this method to get the attachment ID. @@ -285,6 +312,12 @@ function update_posts_from_stories( $publish = TRUE, $qnum = false ) { $attached_images = get_children( $image_args ); } foreach ( (array) $story->image as $image ) { + + // only sideload the primary image if using the npr layout + if ( ($image->type != 'primary') && $npr_has_layout ) { + continue; + } + $image_url = ''; //check the and then the crops, in this order "enlargement", "standard" if they don't exist, just get the image->src if ( ! empty( $image->enlargement ) ) { @@ -440,9 +473,20 @@ function update_posts_from_stories( $publish = TRUE, $qnum = false ) { * @param int $post_id Post ID or NULL if no post ID. * @param NPRMLEntity $story Story object created during import */ + + if ($npr_has_layout) { + // keep WP from stripping content from NPR posts + kses_remove_filters(); + } + $args = apply_filters( 'npr_pre_update_post', $args, $post_id, $story ); $post_id = wp_insert_post( $args ); + + if ($npr_has_layout) { + // re-enable content stripping + kses_init_filters(); + } } //set categories for story @@ -664,4 +708,232 @@ function get_transcript_body( $story ) { return $transcript_body; } + + + /** + * + * This function will check a story to see if it has a layout object, if there is + * we'll format the body with any images, externalAssets, or htmlAssets inserted in the order they are in the layout + * and return an array of the transformed body and flags for what sort of elements are returned + * + * @param NPRMLEntity $story Story object created during import + * @return array with reconstructed body and flags describing returned elements + */ + function get_body_with_layout( $story ) { + $returnary = array('body' => FALSE, 'has_layout' => FALSE, 'has_image' => FALSE, 'has_video' => FALSE, 'has_external' => FALSE); + $body_with_layout = ""; + if ( ! empty( $story->layout ) ) { + // simplify the arrangement of the storytext object + $layoutarry = array(); + foreach($story->layout->storytext as $type => $elements) { + if (!is_array($elements)) { + $elements = array($elements); + } + foreach ($elements as $element) { + $num = $element->num; + $reference = $element->refId; + if ($type == 'text') { + // only paragraphs don't have a refId, they use num instead + $reference = $element->paragraphNum; + } + $layoutarry[(int)$num] = array('type'=>$type, 'reference' => $reference); + } + } + ksort($layoutarry); + $returnary['has_layout'] = TRUE; + + $paragraphs = array(); + $num = 1; + foreach ($story->textWithHtml->paragraphs as $paragraph) { + $partext = (string) $paragraph->value; + $paragraphs[$num] = $partext; + $num++; + } + + $storyimages = array(); + if (isset($story->image) ) { + $storyimages_array = array(); + if (isset($story->image->id)) { + $storyimages_array[] = $story->image; + } else { + // sometimes there are multiple objects + foreach ( (array) $story->image as $stryimage ) { + if (isset($stryimage->id)) { + $storyimages_array[] = $stryimage; + } + } + } + foreach ($storyimages_array as $image) { + $image_url = FALSE; + $is_portrait = FALSE; + if ( ! empty( $image->enlargement ) ) { + $image_url = $image->enlargement->src; + } + if ( ! empty( $image->crop )) { + if (!is_array( $image->crop ) ) { + $cropobj = $image->crop; + unset($image->crop); + $image->crop = array($cropobj); + } + foreach ( $image->crop as $crop ) { + if (empty($crop->primary)) { + continue; + } + $image_url = $crop->src; + if ($crop->type == 'custom' && ((int)$crop->height > (int)$crop->width)) { + $is_portrait = TRUE; + } + break; + } + } + if ( empty( $image_url ) && ! empty( $image->src ) ) { + $image_url = $image->src; + } + // add resizing to any npr-hosted image + if (strpos($image_url, 'media.npr.org')) { + // remove any existing querystring + if (strpos($image_url, '?')) { + $image_url = substr($image_url, 0, strpos($image_url, '?')); + } + $image_url .= !$is_portrait ? '?s=6' : '?s=12'; + } + $storyimages[$image->id] = (array) $image; + $storyimages[$image->id]['image_url'] = $image_url; + $storyimages[$image->id]['is_portrait'] = $is_portrait; + } + } + + + + $externalAssets = array(); + if (isset($story->externalAsset) ) { + $externals_array = array(); + if (isset($story->externalAsset->type)) { + $externals_array[] = $story->externalAsset; + } else { + // sometimes there are multiple objects + foreach ( (array) $story->externalAsset as $extasset ) { + if (isset($extasset->type)) { + $externals_array[] = $extasset; + } + } + } + foreach ($externals_array as $embed) { + $externalAssets[$embed->id] = (array) $embed; + } + } + $htmlAssets = array(); + if (isset($story->htmlAsset) ) { + if (isset($story->htmlAsset->id)) { + $htmlAssets[$story->htmlAsset->id] = $story->htmlAsset->value; + } else { + // sometimes there are multiple objects + foreach ( (array) $story->htmlAsset as $hasset ) { + if (isset($hasset->id)) { + $htmlAssets[$hasset->id] = $hasset->value; + } + } + } + } + + foreach ($layoutarry as $ordernum => $element) { + $reference = $element['reference']; + switch ($element['type']) { + case 'text': + if (!empty($paragraphs[$reference])) { + $body_with_layout .= "

" . $paragraphs[$reference] . "

\n"; + } + break; + case 'staticHtml': + if (!empty($htmlAssets[$reference])) { + $body_with_layout .= $htmlAssets[$reference] . "\n\n"; + $returnary['has_external'] = TRUE; + if (strpos($htmlAssets[$reference], 'jwplayer.com')) { + $returnary['has_video'] = TRUE; + } + } + break; + case 'externalAsset': + if (!empty($externalAssets[$reference])) { + $figclass = "wp-block-embed"; + if (!empty( (string)$externalAssets[$reference]['type']) && strtolower((string)$externalAssets[$reference]['type']) == 'youtube') { + $returnary['has_video'] = TRUE; + $figclass .= " is-type-video"; + } + $fightml = "
"; + $fightml .= "\n" . $externalAssets[$reference]['url'] . "\n"; + $figcaption = ''; + if (!empty( (string)$externalAssets[$reference]['credit']) || !empty( (string)$externalAssets[$reference]['caption'] ) ) { + if (!empty( trim((string)$externalAssets[$reference]['credit']))) { + $figcaption .= "" . trim((string) $externalAssets[$reference]['credit']) . ""; + } + if (!empty( (string)$externalAssets[$reference]['caption'])) { + $figcaption .= trim((string) $externalAssets[$reference]['caption']); + } + $figcaption = !empty($figcaption) ? "
$figcaption
" : ""; + } + $fightml .= "
$figcaption
\n"; + $body_with_layout .= $fightml; + } + break; + default: + // handles both 'list' and 'image' since it will reset the type and then assign the reference + if ($element['type'] == 'list') { + foreach ($storyimages as $image) { + if ($image['type'] != 'primary') { + continue; + } + $reference = $image['id']; + $element['type'] = 'image'; + break; + } + } + if ($element['type'] != 'image') { + break; + } + if (!empty($storyimages[$reference])) { + $figclass = "wp-block-image size-large"; + $thisimg = $storyimages[$reference]; + $fightml = !empty( (string)$thisimg['image_url']) ? '' . strip_tags($thiscaption) . '' : ''; + $figcaption = (!empty($fightml) && !empty( $thiscaption)) ? $thiscaption : ''; + $cites = ''; + foreach (array('producer', 'provider', 'copyright') as $item) { + $thisitem = trim( (string)$thisimg[$item] ); + if (!empty($thisitem)) { + $cites .= !empty($cites) ? ' | ' . $thisitem : $thisitem; + } + } + $cites = !empty($cites) ? "$cites" : ''; + $thiscaption .= $cites; + $figcaption = (!empty($fightml) && !empty( $thiscaption)) ? "
$thiscaption
" : ''; + $fightml .= (!empty($fightml) && !empty($figcaption)) ? $figcaption : ''; + $body_with_layout .= (!empty($fightml)) ? "
$fightml
\n\n" : ''; + // make sure it doesn't get reused; + unset($storyimages[$reference]); + } + break; + } + } + + } + $returnary['body']= $body_with_layout; + + return $returnary; + } + + + + + + + + + } diff --git a/ds-npr-api.php b/ds-npr-api.php index a2a4282..45053f8 100644 --- a/ds-npr-api.php +++ b/ds-npr-api.php @@ -32,6 +32,7 @@ define( 'NPR_BYLINE_LINK_META_KEY', 'npr_byline_link' ); define( 'NPR_MULTI_BYLINE_META_KEY', 'npr_multi_byline' ); define( 'NPR_IMAGE_GALLERY_META_KEY', 'npr_image_gallery'); +define( 'NPR_HTML_ASSETS_META_KEY', 'npr_html_assets'); define( 'NPR_AUDIO_META_KEY', 'npr_audio'); define( 'NPR_AUDIO_M3U_META_KEY', 'npr_audio_m3u'); define( 'NPR_PUB_DATE_META_KEY', 'npr_pub_date'); @@ -43,6 +44,9 @@ define( 'NPR_IMAGE_AGENCY_META_KEY', 'npr_image_agency'); define( 'NPR_IMAGE_CAPTION_META_KEY', 'npr_image_caption'); +define( 'NPR_STORY_HAS_LAYOUT_META_KEY', 'npr_has_layout'); +define( 'NPR_STORY_HAS_VIDEO_META_KEY', 'npr_has_video'); + define( 'NPR_PUSH_STORY_ERROR', 'npr_push_story_error'); define( 'NPR_MAX_QUERIES', 10 ); diff --git a/push_story.php b/push_story.php index a964698..6ecb6de 100644 --- a/push_story.php +++ b/push_story.php @@ -82,13 +82,6 @@ function nprstory_api_push ( $post_ID, $post ) { * @param unknown_type $post_ID */ function nprstory_api_delete ( $post_ID ) { - if ( ! current_user_can( 'delete_others_posts' ) ) { - wp_die( - __('You do not have permission to delete posts in the NPR API. Users that can delete other users\' posts have that ability: administrators and editors.'), - __('NPR Story API Error'), - 403 - ); - } $push_post_type = get_option( 'ds_npr_push_post_type' ); if ( empty( $push_post_type ) ) { @@ -106,6 +99,15 @@ function nprstory_api_delete ( $post_ID ) { //if the push url isn't set, don't even try to delete. $push_url = get_option( 'ds_npr_api_push_url' ); if ( $post->post_type == $push_post_type && ! empty( $push_url ) && ! empty( $api_id ) ) { + // don't let a non-admin/editor push a delete to the API + if ( ! current_user_can( 'delete_others_posts' ) ) { + wp_die( + __('You do not have permission to delete posts in the NPR API. Users that can delete other users\' posts have that ability: administrators and editors.'), + __('NPR Story API Error'), + 403 + ); + } + // For now, only submit regular posts, and only on publish. if ( $post->post_type != 'post' || $post->post_status != 'publish' ) { return; diff --git a/settings.php b/settings.php index 7778224..753ed7c 100644 --- a/settings.php +++ b/settings.php @@ -91,9 +91,15 @@ function nprstory_settings_init() { add_settings_field( 'dp_npr_query_run_multi', 'Run the queries on saving changes', 'nprstory_query_run_multi_callback', 'ds_npr_api_get_multi_settings', 'ds_npr_api_get_multi_settings' ); register_setting( 'ds_npr_api_get_multi_settings', 'dp_npr_query_run_multi' , 'nprstory_validation_callback_checkbox'); + add_settings_field( 'dp_npr_query_multi_cron_interval', 'Interval to run Get Multi cron', 'nprstory_query_multi_cron_interval_callback', 'ds_npr_api_get_multi_settings', 'ds_npr_api_get_multi_settings' ); register_setting( 'ds_npr_api_get_multi_settings', 'dp_npr_query_multi_cron_interval', 'intval' ); + + add_settings_field( 'dp_npr_query_use_layout', 'Use rich layout on pulled posts if available', 'nprstory_query_use_layout_callback', 'ds_npr_api_get_multi_settings', 'ds_npr_api_get_multi_settings' ); + register_setting( 'ds_npr_api_get_multi_settings', 'dp_npr_query_use_layout' , 'nprstory_validation_callback_checkbox'); + + add_settings_field( 'ds_npr_pull_post_type', 'NPR Pull Post Type', 'nprstory_pull_post_type_callback', 'ds_npr_api', 'ds_npr_api_settings' ); register_setting( 'ds_npr_api', 'ds_npr_pull_post_type', 'nprstory_validation_callback_select' ); @@ -151,6 +157,19 @@ function nprstory_query_run_multi_callback() { wp_nonce_field( 'nprstory_nonce_ds_npr_query_run_multi', 'nprstory_nonce_ds_npr_query_run_multi_name', true, true ); } +function nprstory_query_use_layout_callback() { + $use_layout = get_option('dp_npr_query_use_layout'); + $check_box_string = "If 'layout' is available in the NPR Story API output for your key, checking this box will import posts with more complex HTML to render any images, YouTube videos, Tweets, iframes, or JavaScript-based widgets within the post in the order they appeared on the NPR website. Only the 'primary' image for the story will be sideloaded into the Media Library, all other images will be linked from NPR. CAUTION: This disables the normal 'wp_kses' filtering for imported posts that prevents any JavaScript from being included in the post. We assume that NPR Story API posts will not have malicious scripts.

"; + wp_nonce_field( 'nprstory_nonce_ds_npr_query_use_layout', 'nprstory_nonce_ds_npr_query_use_layout_name', true, true ); +} + function nprstory_query_multi_cron_interval_callback() { $option = get_option( 'dp_npr_query_multi_cron_interval' ); echo "

How often, in minutes, should the Get Multi function run? (default = 60)";