Skip to content

Commit

Permalink
AN-17 Expanding media source support (#358)
Browse files Browse the repository at this point in the history
* Adds support for root-relative URLs for audio, images, and videos.
* Adds additional checks on image sources to ensure they are valid.
* Prevents images from being added with empty URLs.
* Adds test coverage for empty image sources, fragment sources, and root-relative sources for images.

Fixes #316.
Fixes #351.
  • Loading branch information
kevinfodness authored Apr 19, 2017
1 parent 8727eb0 commit be4424b
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 47 deletions.
20 changes: 13 additions & 7 deletions includes/apple-exporter/builders/class-metadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
require_once plugin_dir_path( __FILE__ ) . '../../../admin/class-admin-apple-news.php';

use \Admin_Apple_News;
use \Apple_Exporter\Workspace;
use \Apple_Exporter\Exporter_Content;

/**
* @since 0.4.0
Expand Down Expand Up @@ -64,16 +64,22 @@ protected function build() {
if ( preg_match_all( '/<video[^>]+poster="([^"]+)".*?>(.+?)<\/video>/s', $this->content_text(), $matches ) ) {

// Loop through matched video elements looking for MP4 files.
for ( $i = 0; $i < count( $matches[2] ); $i ++ ) {
$total = count( $matches[2] );
for ( $i = 0; $i < $total; $i ++ ) {

// Try to match an MP4 source URL.
if ( preg_match( '/src="([^\?"]+\.mp4[^"]*)"/', $matches[2][ $i ], $src ) ) {
$meta['thumbnailURL'] = $this->maybe_bundle_source(
$matches[1][ $i ]
);
$meta['videoURL'] = $src[1];

break;
// Include the thumbnail and video URL if the video URL is valid.
$url = Exporter_Content::format_src_url( $src[1] );
if ( ! empty( $url ) ) {
$meta['thumbnailURL'] = $this->maybe_bundle_source(
$matches[1][ $i ]
);
$meta['videoURL'] = esc_url_raw( $url );

break;
}
}
}
}
Expand Down
36 changes: 36 additions & 0 deletions includes/apple-exporter/class-exporter-content.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,42 @@ class Exporter_Content {
*/
private $settings;

/**
* Formats a URL from a `src` parameter to be compatible with remote sources.
*
* Will return a blank string if the URL is invalid.
*
* @param string $url The URL to format.
*
* @access protected
* @return string The formatted URL on success, or a blank string on failure.
*/
public static function format_src_url( $url ) {

// If this is a root-relative path, make absolute.
if ( 0 === strpos( $url, '/' ) ) {
$url = site_url( $url );
}

// Escape the URL and ensure it is valid.
$url = esc_url_raw( $url );
if ( empty( $url ) ) {
return '';
}

// Ensure the URL begins with http.
if ( 0 !== strpos( $url, 'http' ) ) {
return '';
}

// Ensure the URL passes filter_var checks.
if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
return '';
}

return $url;
}

/**
* Contstructor.
*
Expand Down
10 changes: 8 additions & 2 deletions includes/apple-exporter/components/class-audio.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php
namespace Apple_Exporter\Components;

use \Apple_Exporter\Exporter_Content;

/**
* An HTML audio tag.
*
Expand Down Expand Up @@ -52,12 +54,16 @@ protected function build( $text ) {
return null;
}

$url = $match[1];
// Ensure the URL is valid.
$url = Exporter_Content::format_src_url( $match[1] );
if ( empty( $url ) ) {
return;
}

$this->register_json(
'json',
array(
'#url#' => $url,
'#url#' => esc_url_raw( $url ),
)
);
}
Expand Down
71 changes: 47 additions & 24 deletions includes/apple-exporter/components/class-component.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

require_once __DIR__ . '/../class-markdown.php';

use Apple_Exporter\Parser;
use Apple_Exporter\Component_Spec;
use \Apple_Exporter\Component_Spec;
use \Apple_Exporter\Exporter_Content;
use \Apple_Exporter\Parser;

/**
* Base component class. All components must inherit from this class and
Expand Down Expand Up @@ -614,33 +615,55 @@ public function get_component_name() {
* @access protected
*/
protected static function remote_file_exists( $node ) {
$html = $node->ownerDocument->saveXML( $node );
preg_match( '/src="([^"]*?)"/im', $html, $matches );
$path = $matches[1];

// Is it a URL? Check the headers in case of 404
if ( false !== filter_var( $path, FILTER_VALIDATE_URL ) ) {
if ( defined( 'WPCOM_IS_VIP_ENV' ) && WPCOM_IS_VIP_ENV ) {
$result = vip_safe_wp_remote_get( $path );
} else {
$result = wp_safe_remote_get( $path );
}

if ( is_wp_error( $result ) || empty( $result['response']['code'] ) || 404 === $result['response']['code'] ) {
return false;
} else {
return true;
}
// Try to get a URL from the src attribute of the HTML.
$html = $node->ownerDocument->saveXML( $node );
$path = self::url_from_src( $html );
if ( empty( $path ) ) {
return false;
}

// This could be a local file path.
// Check that, except on WordPress VIP where this is not possible.
if ( ! defined( 'WPCOM_IS_VIP_ENV' ) || ! WPCOM_IS_VIP_ENV ) {
return file_exists( $path );
// Fork for method of retrieval if running on VIP.
if ( defined( 'WPCOM_IS_VIP_ENV' ) && WPCOM_IS_VIP_ENV ) {
$result = vip_safe_wp_remote_get( $path );
} else {
$result = wp_safe_remote_get( $path );
}

// Nothing was found or no further validation is possible.
return false;
// Check the headers in case of an error.
return ( ! is_wp_error( $result )
&& ! empty( $result['response']['code'] )
&& $result['response']['code'] < 400
);
}

/**
* Returns a full URL from the first `src` parameter in the provided HTML that
* has content.
*
* @param string $html The HTML to examine for `src` parameters.
*
* @return string A URL on success, or a blank string on failure.
*/
protected static function url_from_src( $html ) {

// Try to find src values in the provided HTML.
if ( ! preg_match_all( '/src=[\'"]([^\'"]+)[\'"]/im', $html, $matches ) ) {
return '';
}

// Loop through matches, returning the first valid URL found.
foreach ( $matches[1] as $url ) {

// Run the URL through the formatter.
$url = Exporter_Content::format_src_url( $url );

// If the URL passes validation, return it.
if ( ! empty( $url ) ) {
return $url;
}
}

return '';
}
}
9 changes: 8 additions & 1 deletion includes/apple-exporter/components/class-gallery.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Apple_Exporter\Components;

use \Apple_Exporter\Exporter_Content;
use \DOMDocument;
use \DOMElement;

Expand Down Expand Up @@ -98,9 +99,15 @@ protected function build( $text ) {
continue;
}

// Ensure the URL is valid.
$url = Exporter_Content::format_src_url( $matches[1] );
if ( empty( $url ) ) {
continue;
}

// Start building the item.
$content = array(
'URL' => $this->maybe_bundle_source( $matches[1] ),
'URL' => $this->maybe_bundle_source( esc_url_raw( $url ) ),
);

// Try to add the caption.
Expand Down
32 changes: 22 additions & 10 deletions includes/apple-exporter/components/class-image.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public function register_specs() {
'margin' => array(
'bottom' => 25,
'top' => 25,
)
),
)
);

Expand Down Expand Up @@ -122,14 +122,30 @@ public function register_specs() {
/**
* Build the component.
*
* @param string $text
* @param string $text The text to convert into a component.
*
* @access protected
*/
protected function build( $text ) {
preg_match( '/src="([^"]*?)"/im', $text, $matches );
$url = esc_url_raw( apply_filters( 'apple_news_build_image_src', $matches[1], $text ) );
$filename = preg_replace( '/\\?.*/', '', \Apple_News::get_filename( $url ) );

// Extract the URL from the text.
$url = self::url_from_src( $text );

/**
* Allows for an image src value to be filtered before being applied.
*
* @param string $url The URL to be filtered.
* @param string $text The raw text that was parsed for the URL.
*/
$url = esc_url_raw( apply_filters( 'apple_news_build_image_src', $url, $text ) );

// If we don't have a valid URL at this point, bail.
if ( empty( $url ) ) {
return;
}

// Add the URL as a parameter for replacement.
$filename = preg_replace( '/\\?.*/', '', \Apple_News::get_filename( $url ) );
$values = array(
'#url#' => $this->maybe_bundle_source( $url, $filename ),
);
Expand Down Expand Up @@ -262,14 +278,10 @@ private function group_component( $caption, $values ) {
'#caption_tracking#' => intval( $this->get_setting( 'caption_tracking' ) ) / 100,
'#caption_line_height#' => intval( $this->get_setting( 'caption_line_height' ) ),
'#caption_color#' => $this->get_setting( 'caption_color' ),
'#full_bleed_images#' => ( 'yes' === $this->get_setting( 'full_bleed_images' ) ),
)
);

// Add full bleed image option.
if ( 'yes' === $this->get_setting( 'full_bleed_images' ) ) {
$values['#full_bleed_images#'] = true;
}

return $values;
}
}
16 changes: 13 additions & 3 deletions includes/apple-exporter/components/class-video.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Apple_Exporter\Components;

use \Apple_Exporter\Exporter_Content;
use \DOMElement;

/**
Expand Down Expand Up @@ -69,14 +70,23 @@ protected function build( $html ) {
return;
}

// Set values
// Ensure the source URL is valid.
$url = Exporter_Content::format_src_url( $matches[1] );
if ( empty( $url ) ) {
return;
}

// Set values.
$values = array(
'#url#' => $matches[1],
'#url#' => esc_url_raw( $url ),
);

// Add poster frame, if defined.
if ( preg_match( '/poster="([^"]+)"/', $html, $poster ) ) {
$values['#still_url#'] = $this->maybe_bundle_source( $poster[1] );
$still_url = Exporter_Content::format_src_url( $poster[1] );
if ( ! empty( $still_url ) ) {
$values['#still_url#'] = $this->maybe_bundle_source( $poster[1] );
}
}

$this->register_json(
Expand Down
Loading

0 comments on commit be4424b

Please sign in to comment.