diff --git a/Gruntfile.js b/Gruntfile.js
index a3c1a5d244..168037d792 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -232,7 +232,8 @@ module.exports = function(grunt) {
'wp-includes/css/*.css',
// Exceptions
- '!wp-includes/css/dashicons.css'
+ '!wp-includes/css/dashicons.css',
+ '!wp-includes/css/wp-oembed-embed.css'
]
},
colors: {
@@ -528,6 +529,10 @@ module.exports = function(grunt) {
emoji: {
src: BUILD_DIR + 'wp-includes/formatting.php',
dest: '.'
+ },
+ oembed: {
+ src: BUILD_DIR + 'wp-includes/oembed-functions.php',
+ dest: '.'
}
},
_watch: {
@@ -637,6 +642,7 @@ module.exports = function(grunt) {
'clean:tinymce',
'concat:emoji',
'includes:emoji',
+ 'includes:oembed',
'jsvalidate:build'
] );
diff --git a/src/wp-includes/admin-bar.php b/src/wp-includes/admin-bar.php
index a12e9c9e71..696c369978 100644
--- a/src/wp-includes/admin-bar.php
+++ b/src/wp-includes/admin-bar.php
@@ -898,6 +898,10 @@ function is_admin_bar_showing() {
if ( defined('XMLRPC_REQUEST') || defined('DOING_AJAX') || defined('IFRAME_REQUEST') )
return false;
+ if ( is_embed() ) {
+ return false;
+ }
+
// Integrated into the admin.
if ( is_admin() )
return true;
diff --git a/src/wp-includes/class-wp-editor.php b/src/wp-includes/class-wp-editor.php
index 911aaab540..8a6dc3bd02 100644
--- a/src/wp-includes/class-wp-editor.php
+++ b/src/wp-includes/class-wp-editor.php
@@ -401,7 +401,8 @@ final class _WP_Editors {
'wplink',
'wpdialogs',
'wptextpattern',
- 'wpview'
+ 'wpview',
+ 'wpoembed',
);
if ( ! self::$has_medialib ) {
diff --git a/src/wp-includes/class-wp-embed.php b/src/wp-includes/class-wp-embed.php
index d4b1ded4f0..730fc4fc91 100644
--- a/src/wp-includes/class-wp-embed.php
+++ b/src/wp-includes/class-wp-embed.php
@@ -233,12 +233,13 @@ class WP_Embed {
* Filter whether to inspect the given URL for discoverable link tags.
*
* @since 2.9.0
+ * @since 4.4.0 The default value changed to true.
*
* @see WP_oEmbed::discover()
*
- * @param bool $enable Whether to enable `` tag discovery. Default false.
+ * @param bool $enable Whether to enable `` tag discovery. Default true.
*/
- $attr['discover'] = ( apply_filters( 'embed_oembed_discover', false ) && author_can( $post_ID, 'unfiltered_html' ) );
+ $attr['discover'] = ( apply_filters( 'embed_oembed_discover', true ) );
// Use oEmbed to get the HTML
$html = wp_oembed_get( $url, $attr );
diff --git a/src/wp-includes/class-wp-oembed-controller.php b/src/wp-includes/class-wp-oembed-controller.php
new file mode 100644
index 0000000000..0fc25ba817
--- /dev/null
+++ b/src/wp-includes/class-wp-oembed-controller.php
@@ -0,0 +1,159 @@
+get( 'oembed', false ) ) {
+ return;
+ }
+
+ if ( false === $wp_query->get( 'url', false ) ) {
+ status_header( 400 );
+ echo 'URL parameter missing';
+ exit;
+ }
+
+ $url = esc_url_raw( get_query_var( 'url' ) );
+
+ $format = wp_oembed_ensure_format( get_query_var( 'format' ) );
+
+ /**
+ * Filter the maxwidth oEmbed parameter.
+ *
+ * @since 4.4.0
+ *
+ * @param int $maxwidth Maximum allowed width. Default 600.
+ */
+ $maxwidth = apply_filters( 'oembed_default_width', 600 );
+ $maxwidth = absint( get_query_var( 'maxwidth', $maxwidth ) );
+
+ $callback = get_query_var( '_jsonp', false );
+
+ $request = array(
+ 'url' => $url,
+ 'format' => $format,
+ 'maxwidth' => $maxwidth,
+ 'callback' => $callback,
+ );
+
+ echo $this->dispatch( $request );
+ exit;
+ }
+
+ /**
+ * Handle the whole request and print the response.
+ *
+ * @since 4.4.0
+ *
+ * @param array $request The request arguments.
+ * @return string The oEmbed API response.
+ */
+ public function dispatch( $request ) {
+ $post_id = url_to_postid( $request['url'] );
+
+ /**
+ * Filter the determined post id.
+ *
+ * @since 4.4.0
+ *
+ * @param int $post_id The post ID.
+ * @param string $url The requestd URL.
+ */
+ $post_id = apply_filters( 'oembed_request_post_id', $post_id, $request['url'] );
+
+ $data = get_oembed_response_data( $post_id, $request['maxwidth'] );
+
+ if ( false === $data ) {
+ status_header( 404 );
+ return __( 'Invalid URL.', 'oembed-api' );
+ }
+
+ if ( 'json' === $request['format'] ) {
+ return $this->json_response( $data, $request );
+ }
+
+ return $this->xml_response( $data );
+ }
+
+ /**
+ * Print the oEmbed JSON response.
+ *
+ * @since 4.4.0
+ *
+ * @param array $data The oEmbed response data.
+ * @param array $request The request arguments.
+ * @return string The JSON response data.
+ */
+ public function json_response( $data, $request ) {
+ if ( ! is_string( $request['callback'] ) || preg_match( '/[^\w\.]/', $request['callback'] ) ) {
+ $request['callback'] = false;
+ }
+
+ $result = wp_json_encode( $data );
+
+ // Bail if the result couldn't be JSON encoded.
+ if ( ! $result || ! is_array( $data ) || empty( $data ) ) {
+ status_header( 501 );
+ return 'Not implemented';
+ }
+
+ if ( ! headers_sent() ) {
+ $content_type = $request['callback'] ? 'application/javascript' : 'application/json';
+ header( 'Content-Type: ' . $content_type . '; charset=' . get_option( 'blog_charset' ) );
+ header( 'X-Content-Type-Options: nosniff' );
+ }
+
+ if ( $request['callback'] ) {
+ return '/**/' . $request['callback'] . '(' . $result . ')';
+ }
+
+ return $result;
+ }
+
+ /**
+ * Print the oEmbed XML response.
+ *
+ * @since 4.4.0
+ *
+ * @param array $data The oEmbed response data.
+ * @return string The XML response data.
+ */
+ public function xml_response( $data ) {
+ $result = _oembed_create_xml( $data );
+
+ // Bail if there's no XML.
+ if ( ! $result ) {
+ status_header( 501 );
+ return 'Not implemented';
+ }
+
+ if ( ! headers_sent() ) {
+ header( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ) );
+ }
+
+ return $result;
+ }
+}
diff --git a/src/wp-includes/class-wp-rewrite.php b/src/wp-includes/class-wp-rewrite.php
index 84a57c8107..b7b3753dd4 100644
--- a/src/wp-includes/class-wp-rewrite.php
+++ b/src/wp-includes/class-wp-rewrite.php
@@ -861,6 +861,7 @@ class WP_Rewrite {
$trackbackregex = 'trackback/?$';
$pageregex = $this->pagination_base . '/?([0-9]{1,})/?$';
$commentregex = $this->comments_pagination_base . '-([0-9]{1,})/?$';
+ $embedregex = 'embed/?$';
//build up an array of endpoint regexes to append => queries to append
if ( $endpoints ) {
@@ -884,6 +885,8 @@ class WP_Rewrite {
$index = $this->index; //probably 'index.php'
$feedindex = $index;
$trackbackindex = $index;
+ $embedindex = $index;
+
//build a list from the rewritecode and queryreplace arrays, that will look something like
//tagname=$matches[i] where i is the current $i
$queries = array();
@@ -1029,8 +1032,14 @@ class WP_Rewrite {
//create query and regex for trackback
$trackbackmatch = $match . $trackbackregex;
$trackbackquery = $trackbackindex . '?' . $query . '&tb=1';
+
+ // Create query and regex for embeds.
+ $embedmatch = $match . $embedregex;
+ $embedquery = $embedindex . '?' . $query . '&embed=true';
+
//trim slashes from the end of the regex for this dir
$match = rtrim($match, '/');
+
//get rid of brackets
$submatchbase = str_replace( array('(', ')'), '', $match);
@@ -1040,6 +1049,7 @@ class WP_Rewrite {
$sub1feed = $sub1 . $feedregex; //and /feed/(atom|...)
$sub1feed2 = $sub1 . $feedregex2; //and /(feed|atom...)
$sub1comment = $sub1 . $commentregex; //and /comment-page-xx
+ $sub1embed = $sub1 . $embedregex; //and /embed/...
//add another rule to match attachments in the explicit form:
///attachment/some-text
@@ -1048,12 +1058,14 @@ class WP_Rewrite {
$sub2feed = $sub2 . $feedregex; //feeds, /attachment/feed/(atom|...)
$sub2feed2 = $sub2 . $feedregex2; //and feeds again on to this /attachment/(feed|atom...)
$sub2comment = $sub2 . $commentregex; //and /comment-page-xx
+ $sub2embed = $sub2 . $embedregex; //and /embed/...
//create queries for these extra tag-ons we've just dealt with
$subquery = $index . '?attachment=' . $this->preg_index(1);
$subtbquery = $subquery . '&tb=1';
$subfeedquery = $subquery . '&feed=' . $this->preg_index(2);
$subcommentquery = $subquery . '&cpage=' . $this->preg_index(2);
+ $subembedquery = $subquery . '&embed=true';
//do endpoints for attachments
if ( !empty($endpoints) ) {
@@ -1092,10 +1104,16 @@ class WP_Rewrite {
//add trackback
$rewrite = array_merge(array($trackbackmatch => $trackbackquery), $rewrite);
+ // add embed
+ $rewrite = array_merge( array( $embedmatch => $embedquery ), $rewrite );
+
//add regexes/queries for attachments, attachment trackbacks and so on
- if ( ! $page ) //require /attachment/stuff form for pages because of confusion with subpages
- $rewrite = array_merge($rewrite, array($sub1 => $subquery, $sub1tb => $subtbquery, $sub1feed => $subfeedquery, $sub1feed2 => $subfeedquery, $sub1comment => $subcommentquery));
- $rewrite = array_merge(array($sub2 => $subquery, $sub2tb => $subtbquery, $sub2feed => $subfeedquery, $sub2feed2 => $subfeedquery, $sub2comment => $subcommentquery), $rewrite);
+ if ( ! $page ) {
+ //require /attachment/stuff form for pages because of confusion with subpages
+ $rewrite = array_merge( $rewrite, array($sub1 => $subquery, $sub1tb => $subtbquery, $sub1feed => $subfeedquery, $sub1feed2 => $subfeedquery, $sub1comment => $subcommentquery, $sub1embed => $subembedquery ) );
+ }
+
+ $rewrite = array_merge( array( $sub2 => $subquery, $sub2tb => $subtbquery, $sub2feed => $subfeedquery, $sub2feed2 => $subfeedquery, $sub2comment => $subcommentquery, $sub2embed => $subembedquery ), $rewrite );
}
} //if($num_toks)
//add the rules for this dir to the accumulating $post_rewrite
diff --git a/src/wp-includes/class-wp.php b/src/wp-includes/class-wp.php
index 4fe275742d..bba47ffb71 100644
--- a/src/wp-includes/class-wp.php
+++ b/src/wp-includes/class-wp.php
@@ -15,7 +15,7 @@ class WP {
* @access public
* @var array
*/
- public $public_query_vars = array('m', 'p', 'posts', 'w', 'cat', 'withcomments', 'withoutcomments', 's', 'search', 'exact', 'sentence', 'calendar', 'page', 'paged', 'more', 'tb', 'pb', 'author', 'order', 'orderby', 'year', 'monthnum', 'day', 'hour', 'minute', 'second', 'name', 'category_name', 'tag', 'feed', 'author_name', 'static', 'pagename', 'page_id', 'error', 'comments_popup', 'attachment', 'attachment_id', 'subpost', 'subpost_id', 'preview', 'robots', 'taxonomy', 'term', 'cpage', 'post_type', 'title');
+ public $public_query_vars = array('m', 'p', 'posts', 'w', 'cat', 'withcomments', 'withoutcomments', 's', 'search', 'exact', 'sentence', 'calendar', 'page', 'paged', 'more', 'tb', 'pb', 'author', 'order', 'orderby', 'year', 'monthnum', 'day', 'hour', 'minute', 'second', 'name', 'category_name', 'tag', 'feed', 'author_name', 'static', 'pagename', 'page_id', 'error', 'comments_popup', 'attachment', 'attachment_id', 'subpost', 'subpost_id', 'preview', 'robots', 'taxonomy', 'term', 'cpage', 'post_type', 'title', 'embed', 'oembed', 'format', 'url', '_jsonp', 'maxwidth' );
/**
* Private query variables.
diff --git a/src/wp-includes/css/wp-oembed-embed.css b/src/wp-includes/css/wp-oembed-embed.css
new file mode 100644
index 0000000000..7c1be9b897
--- /dev/null
+++ b/src/wp-includes/css/wp-oembed-embed.css
@@ -0,0 +1,359 @@
+html, body {
+ padding: 0;
+ margin: 0;
+}
+
+body {
+ font-family: sans-serif;
+}
+
+/* Text meant only for screen readers */
+.screen-reader-text {
+ clip: rect(1px, 1px, 1px, 1px);
+ height: 1px;
+ overflow: hidden;
+ position: absolute !important;
+ width: 1px;
+}
+
+/* Dashicons */
+.dashicons {
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+ background-color: transparent;
+ background-repeat: no-repeat;
+ -webkit-background-size: 20px 20px;
+ background-size: 20px;
+ background-position: center;
+ -webkit-transition: background .1s ease-in;
+ transition: background .1s ease-in;
+ position: relative;
+ top: 5px;
+}
+
+.dashicons-no {
+ background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Cpath%20d%3D%27M15.55%2013.7l-2.19%202.06-3.42-3.65-3.64%203.43-2.06-2.18%203.64-3.43-3.42-3.64%202.18-2.06%203.43%203.64%203.64-3.42%202.05%202.18-3.64%203.43z%27%20fill%3D%27%23fff%27%2F%3E%3C%2Fsvg%3E");
+}
+
+.dashicons-admin-comments {
+ background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Cpath%20d%3D%27M5%202h9q.82%200%201.41.59T16%204v7q0%20.82-.59%201.41T14%2013h-2l-5%205v-5H5q-.82%200-1.41-.59T3%2011V4q0-.82.59-1.41T5%202z%27%20fill%3D%27%2382878c%27%2F%3E%3C%2Fsvg%3E");
+}
+
+.wp-embed-comments a:hover .dashicons-admin-comments {
+ background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Cpath%20d%3D%27M5%202h9q.82%200%201.41.59T16%204v7q0%20.82-.59%201.41T14%2013h-2l-5%205v-5H5q-.82%200-1.41-.59T3%2011V4q0-.82.59-1.41T5%202z%27%20fill%3D%27%230073aa%27%2F%3E%3C%2Fsvg%3E");
+}
+
+.dashicons-share {
+ background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Cpath%20d%3D%27M14.5%2012q1.24%200%202.12.88T17.5%2015t-.88%202.12-2.12.88-2.12-.88T11.5%2015q0-.34.09-.69l-4.38-2.3Q6.32%2013%205%2013q-1.24%200-2.12-.88T2%2010t.88-2.12T5%207q1.3%200%202.21.99l4.38-2.3q-.09-.35-.09-.69%200-1.24.88-2.12T14.5%202t2.12.88T17.5%205t-.88%202.12T14.5%208q-1.3%200-2.21-.99l-4.38%202.3Q8%209.66%208%2010t-.09.69l4.38%202.3q.89-.99%202.21-.99z%27%20fill%3D%27%2382878c%27%2F%3E%3C%2Fsvg%3E");
+}
+
+.wp-embed-share-dialog-open:hover .dashicons-share {
+ background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Cpath%20d%3D%27M14.5%2012q1.24%200%202.12.88T17.5%2015t-.88%202.12-2.12.88-2.12-.88T11.5%2015q0-.34.09-.69l-4.38-2.3Q6.32%2013%205%2013q-1.24%200-2.12-.88T2%2010t.88-2.12T5%207q1.3%200%202.21.99l4.38-2.3q-.09-.35-.09-.69%200-1.24.88-2.12T14.5%202t2.12.88T17.5%205t-.88%202.12T14.5%208q-1.3%200-2.21-.99l-4.38%202.3Q8%209.66%208%2010t-.09.69l4.38%202.3q.89-.99%202.21-.99z%27%20fill%3D%27%230073aa%27%2F%3E%3C%2Fsvg%3E");
+}
+
+.wp-embed {
+ width: 100%;
+ padding: 25px;
+ font: 400 14px/1.5 'Open Sans', sans-serif;
+ color: #82878c;
+ background: white;
+ border: 1px solid #e5e5e5;
+ -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
+ box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ /* Clearfix */
+ overflow: auto;
+ zoom: 1;
+}
+
+.wp-embed a {
+ color: #82878c;
+ text-decoration: none;
+}
+
+.wp-embed a:hover {
+ text-decoration: underline;
+}
+
+.wp-embed-featured-image {
+ margin-bottom: 20px;
+}
+
+.wp-embed-featured-image img {
+ width: 100%;
+ height: auto;
+ border: none;
+}
+
+.wp-embed-featured-image.square {
+ float: left;
+ max-width: 160px;
+ margin-right: 20px;
+}
+
+.wp-embed p {
+ margin: 0;
+}
+
+p.wp-embed-heading {
+ margin: 0 0 15px;
+ font-weight: bold;
+ font-size: 22px;
+ line-height: 1.3;
+}
+
+.wp-embed-heading a {
+ color: #32373c;
+}
+
+.wp-embed .wp-embed-more {
+ color: #b4b9be;
+}
+
+.wp-embed-footer {
+ display: table;
+ width: 100%;
+ margin-top: 30px;
+}
+
+.wp-embed-site-icon {
+ position: absolute;
+ top: 50%;
+ left: 0;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%);
+ height: 25px;
+ width: 25px;
+ border: 0;
+}
+
+.wp-embed-site-title {
+ font-weight: bold;
+ line-height: 25px;
+}
+
+.wp-embed-site-title a {
+ position: relative;
+ display: inline-block;
+ padding-left: 35px;
+}
+
+.wp-embed-site-title,
+.wp-embed-meta {
+ display: table-cell;
+}
+
+.wp-embed-meta {
+ text-align: right;
+ white-space: nowrap;
+ vertical-align: middle;
+}
+
+.wp-embed-comments,
+.wp-embed-share {
+ display: inline;
+}
+
+.wp-embed-meta a:hover {
+ text-decoration: none;
+ color: #0073aa;
+}
+
+.wp-embed-comments a {
+ line-height: 25px;
+ display: inline-block;
+}
+
+.wp-embed-comments + .wp-embed-share {
+ margin-left: 10px;
+}
+
+.wp-embed-share-dialog {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(10, 10, 10, 0.9);
+ color: #fff;
+ opacity: 1;
+ transition: opacity .25s ease-in-out;
+ -moz-transition: opacity .25s ease-in-out;
+ -webkit-transition: opacity .25s ease-in-out;
+}
+
+.wp-embed-share-dialog.hidden {
+ opacity: 0;
+ visibility: hidden;
+}
+
+.wp-embed-share-dialog-open,
+.wp-embed-share-dialog-close {
+ margin: -8px 0 0;
+ padding: 0;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ outline: none;
+}
+
+.wp-embed-share-dialog-open .dashicons,
+.wp-embed-share-dialog-close .dashicons {
+ padding: 4px;
+}
+
+.wp-embed-share-dialog-open .dashicons {
+ top: 8px;
+}
+
+.wp-embed-share-dialog-open:focus .dashicons,
+.wp-embed-share-dialog-close:focus .dashicons {
+ -webkit-box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8);
+ box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8);
+ -webkit-border-radius: 100%;
+ border-radius: 100%;
+}
+
+.wp-embed-share-dialog-close {
+ position: absolute;
+ top: 20px;
+ right: 20px;
+ font-size: 22px;
+}
+
+.wp-embed-share-dialog-close:hover {
+ text-decoration: none;
+}
+
+.wp-embed-share-dialog-close .dashicons {
+ height: 24px;
+ width: 24px;
+ -webkit-background-size: 24px 24px;
+ background-size: 24px;
+}
+
+.wp-embed-share-dialog-content {
+ height: 100%;
+ -webkit-transform-style: preserve-3d;
+ transform-style: preserve-3d;
+ overflow: hidden;
+}
+
+.wp-embed-share-dialog-text {
+ margin-top: 25px;
+ padding: 20px;
+}
+
+.wp-embed-share-tabs {
+ margin: 0 0 20px;
+ padding: 0;
+ list-style: none;
+}
+
+.wp-embed-share-tab-button {
+ display: inline;
+}
+
+.wp-embed-share-tab-button button {
+ margin: 0;
+ padding: 0;
+ border: none;
+ background: transparent;
+ font-size: 16px;
+ line-height: 1.3;
+ color: #aaa;
+ cursor: pointer;
+ -webkit-transition: color .1s ease-in;
+ transition: color .1s ease-in;
+}
+
+.wp-embed-share-tab-button [aria-selected="true"] {
+ color: #fff;
+}
+
+.wp-embed-share-tab-button button:hover {
+ color: #fff;
+}
+
+.wp-embed-share-tab-button + .wp-embed-share-tab-button {
+ margin: 0 0 0 10px;
+ padding: 0 0 0 11px;
+ border-left: 1px solid #aaa;
+}
+
+.wp-embed-share-tab[aria-hidden="true"] {
+ display: none;
+}
+
+p.wp-embed-share-description {
+ margin: 0;
+ font-size: 14px;
+ line-height: 1;
+ font-style: italic;
+ color: #aaa;
+}
+
+.wp-embed-share-input {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ width: 100%;
+ border: none;
+ height: 28px;
+ margin: 0 0 10px 0;
+ padding: 0 5px;
+ font: 400 14px/1.5 'Open Sans', sans-serif;
+ resize: none;
+ cursor: text;
+}
+
+textarea.wp-embed-share-input {
+ height: 72px;
+}
+
+html[dir="rtl"] .wp-embed-featured-image.square {
+ float: right;
+ margin-right: 0;
+
+ margin-left: 20px;
+}
+
+html[dir="rtl"] .wp-embed-site-title a {
+ padding-left: 0;
+ padding-right: 35px;
+}
+
+html[dir="rtl"] .wp-embed-site-icon {
+ margin-right: 0;
+ margin-left: 10px;
+ left: auto;
+ right: 0;
+}
+
+html[dir="rtl"] .wp-embed-meta {
+ text-align: left;
+}
+
+html[dir="rtl"] .wp-embed-footer {
+}
+
+html[dir="rtl"] .wp-embed-share {
+ margin-left: 0;
+ margin-right: 10px;
+}
+
+html[dir="rtl"] .wp-embed-share-dialog-close {
+ right: auto;
+ left: 20px;
+}
+
+html[dir="rtl"] .wp-embed-share-tab-button + .wp-embed-share-tab-button {
+ margin: 0 10px 0 0;
+ padding: 0 11px 0 0;
+ border-left: none;
+ border-right: 1px solid #aaa;
+}
diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php
index c9eb06b133..20dbaeab91 100644
--- a/src/wp-includes/default-filters.php
+++ b/src/wp-includes/default-filters.php
@@ -422,4 +422,33 @@ add_action( 'media_buttons', 'media_buttons' );
add_filter( 'image_send_to_editor', 'image_add_caption', 20, 8 );
add_filter( 'media_send_to_editor', 'image_media_send_to_editor', 10, 3 );
+// Embeds
+
+add_action( 'parse_query', 'wp_oembed_parse_query' );
+
+add_action( 'wp_head', 'wp_oembed_add_discovery_links' );
+add_action( 'wp_head', 'wp_oembed_add_host_js' );
+
+add_action( 'oembed_head', 'print_emoji_detection_script' );
+add_action( 'oembed_head', 'print_emoji_styles' );
+add_action( 'oembed_head', 'print_oembed_embed_styles' );
+add_action( 'oembed_head', 'print_oembed_embed_scripts' );
+add_action( 'oembed_head', 'wp_print_head_scripts', 20 );
+add_action( 'oembed_head', 'wp_print_styles', 20 );
+add_action( 'oembed_head', 'wp_no_robots' );
+add_action( 'oembed_head', 'rel_canonical' );
+add_action( 'oembed_head', 'locale_stylesheet' );
+
+add_action( 'oembed_footer', 'wp_print_footer_scripts', 20 );
+
+add_filter( 'excerpt_more', 'wp_oembed_excerpt_more', 20 );
+add_filter( 'the_excerpt_embed', 'wptexturize' );
+add_filter( 'the_excerpt_embed', 'convert_chars' );
+add_filter( 'the_excerpt_embed', 'wpautop' );
+add_filter( 'the_excerpt_embed', 'shortcode_unautop' );
+add_filter( 'the_excerpt_embed', 'wp_oembed_excerpt_attachment' );
+
+add_filter( 'oembed_dataparse', 'wp_filter_oembed_result', 10, 3 );
+add_filter( 'oembed_response_data', 'get_oembed_response_data_rich', 10, 4 );
+
unset( $filter, $action );
diff --git a/src/wp-includes/embed-functions.php b/src/wp-includes/embed-functions.php
index 2fc255549b..ad5bfd711e 100644
--- a/src/wp-includes/embed-functions.php
+++ b/src/wp-includes/embed-functions.php
@@ -326,3 +326,529 @@ function wp_embed_handler_video( $matches, $attr, $url, $rawattr ) {
*/
return apply_filters( 'wp_embed_handler_video', $video, $attr, $url, $rawattr );
}
+
+/**
+ * Parse an oEmbed API query.
+ *
+ * @since 4.4.0
+ */
+function wp_oembed_parse_query( $wp_query ) {
+ $controller = new WP_oEmbed_Controller;
+ $controller->parse_query( $wp_query );
+}
+
+/**
+ * Adds oEmbed discovery links in the website .
+ *
+ * @since 4.4.0
+ */
+function wp_oembed_add_discovery_links() {
+ $output = '';
+
+ if ( is_singular() ) {
+ $output .= '' . "\n";
+ $output .= '' . "\n";
+ }
+
+ /**
+ * Filter the oEmbed discovery links.
+ *
+ * @since 4.4.0
+ *
+ * @param string $output HTML of the discovery links.
+ */
+ echo apply_filters( 'oembed_discovery_links', $output );
+}
+
+/**
+ * Add the necessary JavaScript to communicate with the embedded iframes.
+ *
+ * @since 4.4.0
+ */
+function wp_oembed_add_host_js() {
+ wp_enqueue_script( 'wp-oembed' );
+}
+
+
+/**
+ * Get the URL to embed a specific post in an iframe.
+ *
+ * @since 4.4.0
+ *
+ * @param int|WP_Post $post Optional. Post ID or object. Defaults to the current post.
+ * @return string|false The post embed URL on success, false if the post doesn't exist.
+ */
+function get_post_embed_url( $post = null ) {
+ $post = get_post( $post );
+
+ if ( ! $post ) {
+ return false;
+ }
+
+ if ( get_option( 'permalink_structure' ) ) {
+ $embed_url = trailingslashit( get_permalink( $post ) ) . user_trailingslashit( 'embed' );
+ } else {
+ $embed_url = add_query_arg( array( 'embed' => 'true' ), get_permalink( $post ) );
+ }
+
+ /**
+ * Filter the URL to embed a specific post.
+ *
+ * @since 4.4.0
+ *
+ * @param string $embed_url The post embed URL.
+ * @param WP_Post $post The corresponding post object.
+ */
+ return esc_url_raw( apply_filters( 'post_embed_url', $embed_url, $post ) );
+}
+
+/**
+ * Get the oEmbed endpoint URL for a given permalink.
+ *
+ * Pass an empty string as the first argument
+ * to get the endpoint base URL.
+ *
+ * @since 4.4.0
+ *
+ * @param string $permalink Optional. The permalink used for the `url` query arg. Default empty.
+ * @param string $format Optional. The requested response format. Default 'json'.
+ * @return string The oEmbed endpoint URL.
+ */
+function get_oembed_endpoint_url( $permalink = '', $format = 'json' ) {
+ $url = add_query_arg( array( 'oembed' => 'true' ), home_url( '/' ) );
+
+ if ( 'json' === $format ) {
+ $format = false;
+ }
+
+ if ( '' !== $permalink ) {
+ $url = add_query_arg( array(
+ 'url' => $permalink,
+ 'format' => $format,
+ ), $url );
+ }
+
+ /**
+ * Filter the oEmbed endpoint URL.
+ *
+ * @since 4.4.0
+ *
+ * @param string $url The URL to the oEmbed endpoint.
+ * @param string $permalink The permalink used for the `url` query arg.
+ * @param string $format The requested response format.
+ */
+ return apply_filters( 'oembed_endpoint_url', $url, $permalink, $format );
+}
+
+/**
+ * Get the embed code for a specific post.
+ *
+ * @since 4.4.0
+ *
+ * @param int|WP_Post $post Optional. Post ID or object. Default is global `$post`.
+ * @param int $width The width for the response.
+ * @param int $height The height for the response.
+ * @return string|false Embed code on success, false if post doesn't exist.
+ */
+function get_post_embed_html( $post = null, $width, $height ) {
+ $post = get_post( $post );
+
+ if ( ! $post ) {
+ return false;
+ }
+
+ $embed_url = get_post_embed_url( $post );
+
+ $output = "";
+
+ $output .= sprintf(
+ '',
+ esc_url( $embed_url ),
+ absint( $width ),
+ absint( $height ),
+ esc_attr__( 'Embedded WordPress Post', 'oembed-api' )
+ );
+
+ /**
+ * Filters the oEmbed HTML output.
+ *
+ * @since 4.4.0
+ *
+ * @param string $output The default HTML.
+ * @param WP_Post $post Current post object.
+ * @param int $width Width of the response.
+ * @param int $height Height of the response.
+ */
+ return apply_filters( 'oembed_html', $output, $post, $width, $height );
+}
+
+/**
+ * Get the oEmbed response data for a given post.
+ *
+ * @since 4.4.0
+ *
+ * @param WP_Post|int $post Optional. Post object or ID. Default is global `$post`.
+ * @param int $width The requested width.
+ * @return array|false Response data on success, false if post doesn't exist.
+ */
+function get_oembed_response_data( $post = null, $width ) {
+ $post = get_post( $post );
+
+ if ( ! $post ) {
+ return false;
+ }
+
+ if ( 'publish' !== get_post_status( $post ) ) {
+ return false;
+ }
+
+ /**
+ * Filter the allowed minimum width for the oEmbed response.
+ *
+ * @param int $width The minimum width. Defaults to 200.
+ */
+ $minwidth = apply_filters( 'oembed_minwidth', 200 );
+
+ /**
+ * Filter the allowed maximum width for the oEmbed response.
+ *
+ * @param int $width The maximum width. Defaults to 600.
+ */
+ $maxwidth = apply_filters( 'oembed_maxwidth', 600 );
+
+ if ( $width < $minwidth ) {
+ $width = $minwidth;
+ } else if ( $width > $maxwidth ) {
+ $width = $maxwidth;
+ }
+
+ $height = ceil( $width / 16 * 9 );
+
+ if ( 200 > $height ) {
+ $height = 200;
+ }
+
+ $data = array(
+ 'version' => '1.0',
+ 'provider_name' => get_bloginfo( 'name' ),
+ 'provider_url' => get_home_url(),
+ 'author_name' => get_bloginfo( 'name' ),
+ 'author_url' => get_home_url(),
+ 'title' => $post->post_title,
+ 'type' => 'link',
+ );
+
+ $author = get_userdata( $post->post_author );
+
+ if ( $author ) {
+ $data['author_name'] = $author->display_name;
+ $data['author_url'] = get_author_posts_url( $author->ID );
+ }
+
+ /**
+ * Filter the oEmbed response data.
+ *
+ * @since 4.4.0
+ *
+ * @param array $data The response data.
+ * @param WP_Post $post The post object.
+ * @param int $width The requested width.
+ * @param int $height The calculated height.
+ */
+ return apply_filters( 'oembed_response_data', $data, $post, $width, $height );
+}
+
+/**
+ * Filters the oEmbed response data to return an iframe embed code.
+ *
+ * @since 4.4.0
+ *
+ * @param array $data The response data.
+ * @param WP_Post $post The post object.
+ * @param int $width The requested width.
+ * @param int $height The calculated height.
+ * @return array The modified response data.
+ */
+function get_oembed_response_data_rich( $data, $post, $width, $height ) {
+ $data['width'] = absint( $width );
+ $data['height'] = absint( $height );
+ $data['type'] = 'rich';
+ $data['html'] = get_post_embed_html( $post, $width, $height );
+
+ // Add post thumbnail to response if available.
+ $thumbnail_id = false;
+
+ if ( has_post_thumbnail( $post->ID ) ) {
+ $thumbnail_id = get_post_thumbnail_id( $post->ID );
+ }
+
+ if ( 'attachment' === get_post_type( $post ) ) {
+ if ( wp_attachment_is_image( $post ) ) {
+ $thumbnail_id = $post->ID;
+ } else if ( wp_attachment_is( 'video', $post ) ) {
+ $thumbnail_id = get_post_thumbnail_id( $post );
+ $data['type'] = 'video';
+ }
+ }
+
+ if ( $thumbnail_id ) {
+ list( $thumbnail_url, $thumbnail_width, $thumbnail_height ) = wp_get_attachment_image_src( $thumbnail_id, array( $width, 99999 ) );
+ $data['thumbnail_url'] = $thumbnail_url;
+ $data['thumbnail_width'] = $thumbnail_width;
+ $data['thumbnail_height'] = $thumbnail_height;
+ }
+
+ return $data;
+}
+
+/**
+ * Ensures that the specified format is either 'json' or 'xml'.
+ *
+ * @since 4.4.0
+ *
+ * @param string $format The oEmbed response format. Accepts 'json', 'xml'.
+ * @return string The format, either 'xml' or 'json'. Default 'json'.
+ */
+function wp_oembed_ensure_format( $format ) {
+ if ( ! in_array( $format, array( 'json', 'xml' ), true ) ) {
+ return 'json';
+ }
+
+ return $format;
+}
+
+/**
+ * Creates an XML string from a given array.
+ *
+ * @since 4.4.0
+ * @access private
+ *
+ * @param array $data The original oEmbed response data.
+ * @param SimpleXMLElement $node Optional. XML node to append the result to recursively.
+ * @return string|false XML string on success, false on error.
+ */
+function _oembed_create_xml( $data, $node = null ) {
+ if ( ! is_array( $data ) || empty( $data ) ) {
+ return false;
+ }
+
+ if ( null === $node ) {
+ $node = new SimpleXMLElement( '' );
+ }
+
+ foreach ( $data as $key => $value ) {
+ if ( is_numeric( $key ) ) {
+ $key = 'oembed';
+ }
+
+ if ( is_array( $value ) ) {
+ $item = $node->addChild( $key );
+ _oembed_create_xml( $value, $item );
+ } else {
+ $node->addChild( $key, esc_html( $value ) );
+ }
+ }
+
+ return $node->asXML();
+}
+
+/**
+ * Filters the returned oEmbed HTML.
+ *
+ * If the $url isn't on the trusted providers list,
+ * we need to filter the HTML heavily for security.
+ *
+ * Only filters 'rich' and 'html' response types.
+ *
+ * @since 4.4.0
+ *
+ * @param string $return The returned oEmbed HTML.
+ * @param object $data A data object result from an oEmbed provider.
+ * @param string $url The URL of the content to be embedded.
+ * @return string The filtered and sanitized oEmbed result.
+ */
+function wp_filter_oembed_result( $return, $data, $url ) {
+ if ( false === $return || ! in_array( $data->type, array( 'rich', 'video' ) ) ) {
+ return $return;
+ }
+
+ require_once( ABSPATH . WPINC . '/class-oembed.php' );
+ $wp_oembed = _wp_oembed_get_object();
+
+ // Don't modify the HTML for trusted providers.
+ if ( false !== $wp_oembed->get_provider( $url, array( 'discover' => false ) ) ) {
+ return $return;
+ }
+
+ $allowed_html = array(
+ 'iframe' => array(
+ 'src' => true,
+ 'width' => true,
+ 'height' => true,
+ 'frameborder' => true,
+ 'marginwidth' => true,
+ 'marginheight' => true,
+ 'scrolling' => true,
+ 'title' => true,
+ 'class' => true,
+ ),
+ );
+
+ $html = wp_kses( $return, $allowed_html );
+ preg_match( '|^.*().*$|m', $html, $iframes );
+
+ if ( empty( $iframes ) ) {
+ return false;
+ }
+
+ $html = str_replace( '
';
+
+ $actual = wp_filter_oembed_result( $html, (object) array( 'type' => 'rich' ), 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' );
+
+ $this->assertEquals( $html, $actual );
+ }
+
+ function test_filter_oembed_result_with_untrusted_provider() {
+ $html = '';
+ $actual = wp_filter_oembed_result( $html, (object) array( 'type' => 'rich' ), 'http://example.com/sample-page/' );
+
+ $matches = array();
+ preg_match( '|src=".*#\?secret=([\w\d]+)" data-secret="([\w\d]+)"|', $actual, $matches );
+
+ $this->assertTrue( isset( $matches[1] ) );
+ $this->assertTrue( isset( $matches[2] ) );
+ $this->assertEquals( $matches[1], $matches[2] );
+ }
+
+ function test_filter_oembed_result_only_one_iframe_is_allowed() {
+ $html = '';
+ $actual = wp_filter_oembed_result( $html, (object) array( 'type' => 'rich' ), '' );
+
+ $this->assertEquals( '', $actual );
+ }
+
+ function test_filter_oembed_result_with_newlines() {
+ $html = <<var = 1;
+
+
+
+EOD;
+
+ $actual = wp_filter_oembed_result( $html, (object) array( 'type' => 'rich' ), '' );
+
+ $this->assertEquals( '', $actual );
+ }
+
+ function test_filter_oembed_result_without_iframe() {
+ $html = 'HelloWorld
';
+ $actual = wp_filter_oembed_result( $html, (object) array( 'type' => 'rich' ), '' );
+
+ $this->assertFalse( $actual );
+
+ $html = '';
+ $actual = wp_filter_oembed_result( $html, (object) array( 'type' => 'rich' ), '' );
+
+ $this->assertFalse( $actual );
+ }
+
+ function test_filter_oembed_result_secret_param_available() {
+ $html = '';
+ $actual = wp_filter_oembed_result( $html, (object) array( 'type' => 'rich' ), '' );
+
+ $matches = array();
+ preg_match( '|src="https://wordpress.org#\?secret=([\w\d]+)" data-secret="([\w\d]+)"|', $actual, $matches );
+
+ $this->assertTrue( isset( $matches[1] ) );
+ $this->assertTrue( isset( $matches[2] ) );
+ $this->assertEquals( $matches[1], $matches[2] );
+ }
+
+ function test_filter_oembed_result_wrong_type_provided() {
+ $actual = wp_filter_oembed_result( 'some string', (object) array( 'type' => 'link' ), '' );
+
+ $this->assertEquals( 'some string', $actual );
+ }
+
+ function test_filter_oembed_result_invalid_result() {
+ $this->assertFalse( wp_filter_oembed_result( false, (object) array( 'type' => 'rich' ), '' ) );
+ $this->assertFalse( wp_filter_oembed_result( '', (object) array( 'type' => 'rich' ), '' ) );
+ }
+}
diff --git a/tests/phpunit/tests/oembed/getResponseData.php b/tests/phpunit/tests/oembed/getResponseData.php
new file mode 100644
index 0000000000..431707fe40
--- /dev/null
+++ b/tests/phpunit/tests/oembed/getResponseData.php
@@ -0,0 +1,170 @@
+assertFalse( get_oembed_response_data( 0, 100 ) );
+ }
+
+ function test_get_oembed_response_data() {
+ $post = $this->factory->post->create_and_get( array(
+ 'post_title' => 'Some Post',
+ ) );
+
+ $data = get_oembed_response_data( $post, 400 );
+
+ $this->assertEqualSets( array(
+ 'version' => '1.0',
+ 'provider_name' => get_bloginfo( 'name' ),
+ 'provider_url' => get_home_url( '/' ),
+ 'author_name' => get_bloginfo( 'name' ),
+ 'author_url' => get_home_url( '/' ),
+ 'title' => 'Some Post',
+ 'type' => 'rich',
+ 'width' => 400,
+ 'height' => 225,
+ 'html' => get_post_embed_html( $post, 400, 225 ),
+ ), $data );
+ }
+
+ /**
+ * Test get_oembed_response_data with an author.
+ */
+ function test_get_oembed_response_data_author() {
+ $user_id = $this->factory->user->create( array(
+ 'display_name' => 'John Doe',
+ ) );
+
+ $post = $this->factory->post->create_and_get( array(
+ 'post_title' => 'Some Post',
+ 'post_author' => $user_id,
+ ) );
+
+ $data = get_oembed_response_data( $post, 400 );
+
+ $this->assertEqualSets( array(
+ 'version' => '1.0',
+ 'provider_name' => get_bloginfo( 'name' ),
+ 'provider_url' => get_home_url( '/' ),
+ 'author_name' => 'John Doe',
+ 'author_url' => get_author_posts_url( $user_id ),
+ 'title' => 'Some Post',
+ 'type' => 'rich',
+ 'width' => 400,
+ 'height' => 225,
+ 'html' => get_post_embed_html( $post, 400, 225 ),
+ ), $data );
+ }
+
+ function test_get_oembed_response_link() {
+ remove_filter( 'oembed_response_data', 'get_oembed_response_data_rich' );
+
+ $post = $this->factory->post->create_and_get( array(
+ 'post_title' => 'Some Post',
+ ) );
+
+ $data = get_oembed_response_data( $post, 600 );
+
+ $this->assertEqualSets( array(
+ 'version' => '1.0',
+ 'provider_name' => get_bloginfo( 'name' ),
+ 'provider_url' => get_home_url( '/' ),
+ 'author_name' => get_bloginfo( 'name' ),
+ 'author_url' => get_home_url( '/' ),
+ 'title' => 'Some Post',
+ 'type' => 'link',
+ ), $data );
+
+ add_filter( 'oembed_response_data', 'get_oembed_response_data_rich', 10, 4 );
+ }
+
+ function test_get_oembed_response_data_with_draft_post() {
+ $post = $this->factory->post->create_and_get( array(
+ 'post_status' => 'draft',
+ ) );
+
+ $this->assertFalse( get_oembed_response_data( $post, 100 ) );
+ }
+
+ function test_get_oembed_response_data_with_scheduled_post() {
+ $post = $this->factory->post->create_and_get( array(
+ 'post_status' => 'future',
+ 'post_date' => strftime( '%Y-%m-%d %H:%M:%S', strtotime( '+1 day' ) ),
+ ) );
+
+ $this->assertFalse( get_oembed_response_data( $post, 100 ) );
+ }
+
+ function test_get_oembed_response_data_with_private_post() {
+ $post = $this->factory->post->create_and_get( array(
+ 'post_status' => 'private',
+ ) );
+
+ $this->assertFalse( get_oembed_response_data( $post, 100 ) );
+ }
+
+ function test_get_oembed_response_data_maxwidth_too_high() {
+ $post = $this->factory->post->create_and_get();
+
+ $data = get_oembed_response_data( $post, 1000 );
+
+ $this->assertEquals( 600, $data['width'] );
+ $this->assertEquals( 338, $data['height'] );
+ }
+
+ function test_get_oembed_response_data_maxwidth_too_low() {
+ $post = $this->factory->post->create_and_get();
+
+ $data = get_oembed_response_data( $post, 100 );
+
+ $this->assertEquals( 200, $data['width'] );
+ $this->assertEquals( 200, $data['height'] );
+ }
+
+ function test_get_oembed_response_data_maxwidth_invalid() {
+ $post = $this->factory->post->create_and_get();
+
+ $data = get_oembed_response_data( $post, '400;" DROP TABLES' );
+
+ $this->assertEquals( 400, $data['width'] );
+ $this->assertEquals( 225, $data['height'] );
+
+ $data = get_oembed_response_data( $post, "lol this isn't even a number?!?!?" );
+
+ $this->assertEquals( 200, $data['width'] );
+ $this->assertEquals( 200, $data['height'] );
+ }
+
+ function test_get_oembed_response_data_with_thumbnail() {
+ $post = $this->factory->post->create_and_get();
+ $file = DIR_TESTDATA . '/images/canola.jpg';
+ $attachment_id = $this->factory->attachment->create_object( $file, $post->ID, array(
+ 'post_mime_type' => 'image/jpeg',
+ ) );
+ set_post_thumbnail( $post, $attachment_id );
+
+ $data = get_oembed_response_data( $post, 400 );
+
+ $this->assertArrayHasKey( 'thumbnail_url', $data );
+ $this->assertArrayHasKey( 'thumbnail_width', $data );
+ $this->assertArrayHasKey( 'thumbnail_height', $data );
+ $this->assertTrue( 400 >= $data['thumbnail_width'] );
+ }
+
+ function test_get_oembed_response_data_for_attachment() {
+ $parent = $this->factory->post->create();
+ $file = DIR_TESTDATA . '/images/canola.jpg';
+ $post = $this->factory->attachment->create_object( $file, $parent, array(
+ 'post_mime_type' => 'image/jpeg',
+ ) );
+
+ $data = get_oembed_response_data( $post, 400 );
+
+ $this->assertArrayHasKey( 'thumbnail_url', $data );
+ $this->assertArrayHasKey( 'thumbnail_width', $data );
+ $this->assertArrayHasKey( 'thumbnail_height', $data );
+ $this->assertTrue( 400 >= $data['thumbnail_width'] );
+ }
+}
diff --git a/tests/phpunit/tests/oembed/headers.php b/tests/phpunit/tests/oembed/headers.php
new file mode 100644
index 0000000000..2ee7a4f18b
--- /dev/null
+++ b/tests/phpunit/tests/oembed/headers.php
@@ -0,0 +1,66 @@
+markTestSkipped( 'xdebug is required for this test' );
+ }
+
+ $post = $this->factory->post->create_and_get( array(
+ 'post_title' => 'Hello World',
+ ) );
+
+ $request = array(
+ 'url' => get_permalink( $post->ID ),
+ 'format' => 'json',
+ 'maxwidth' => 600,
+ 'callback' => '',
+ );
+
+ $legacy_controller = new WP_oEmbed_Controller();
+ $legacy_controller->dispatch( $request );
+
+ $headers = xdebug_get_headers();
+
+ $this->assertTrue( in_array( 'Content-Type: application/json; charset=' . get_option( 'blog_charset' ), $headers ) );
+ $this->assertTrue( in_array( 'X-Content-Type-Options: nosniff', $headers ) );
+
+ $request['callback'] = 'foobar';
+
+ $legacy_controller->dispatch( $request );
+
+ $headers = xdebug_get_headers();
+
+ $this->assertTrue( in_array( 'Content-Type: application/javascript; charset=' . get_option( 'blog_charset' ), $headers ) );
+ $this->assertTrue( in_array( 'X-Content-Type-Options: nosniff', $headers ) );
+ }
+
+ function test_request_xml_response_headers() {
+ if ( ! function_exists( 'xdebug_get_headers' ) ) {
+ $this->markTestSkipped( 'xdebug is required for this test' );
+ }
+
+ $post = $this->factory->post->create_and_get( array(
+ 'post_title' => 'Hello World',
+ ) );
+
+ $request = array(
+ 'url' => get_permalink( $post->ID ),
+ 'format' => 'xml',
+ 'maxwidth' => 600,
+ );
+
+ $legacy_controller = new WP_oEmbed_Controller();
+ $legacy_controller->dispatch( $request );
+
+ $headers = xdebug_get_headers();
+
+ $this->assertTrue( in_array( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ), $headers ) );
+ }
+}
diff --git a/tests/phpunit/tests/oembed/postEmbedUrl.php b/tests/phpunit/tests/oembed/postEmbedUrl.php
new file mode 100644
index 0000000000..26e57ce433
--- /dev/null
+++ b/tests/phpunit/tests/oembed/postEmbedUrl.php
@@ -0,0 +1,31 @@
+assertFalse( $embed_url );
+ }
+
+ function test_get_post_embed_url_with_pretty_permalinks() {
+ update_option( 'permalink_structure', '/%postname%' );
+
+ $post_id = $this->factory->post->create();
+ $permalink = get_permalink( $post_id );
+ $embed_url = get_post_embed_url( $post_id );
+
+ $this->assertEquals( user_trailingslashit( trailingslashit( $permalink ) . 'embed' ), $embed_url );
+
+ update_option( 'permalink_structure', '' );
+ }
+
+ function test_get_post_embed_url_with_ugly_permalinks() {
+ $post_id = $this->factory->post->create();
+ $permalink = get_permalink( $post_id );
+ $embed_url = get_post_embed_url( $post_id );
+
+ $this->assertEquals( $permalink . '&embed=true', $embed_url );
+ }
+}
diff --git a/tests/phpunit/tests/oembed/template.php b/tests/phpunit/tests/oembed/template.php
new file mode 100644
index 0000000000..2c97d2cfaf
--- /dev/null
+++ b/tests/phpunit/tests/oembed/template.php
@@ -0,0 +1,257 @@
+factory->user->create_and_get( array(
+ 'display_name' => 'John Doe',
+ ) );
+
+ $post_id = $this->factory->post->create( array(
+ 'post_author' => $user->ID,
+ 'post_title' => 'Hello World',
+ 'post_content' => 'Foo Bar',
+ 'post_excerpt' => 'Bar Baz',
+ ) );
+ $this->go_to( get_post_embed_url( $post_id ) );
+
+ $this->assertQueryTrue( 'is_single', 'is_singular' );
+
+ ob_start();
+ include( ABSPATH . WPINC . '/embed-template.php' );
+ $actual = ob_get_clean();
+
+ $doc = new DOMDocument();
+ $this->assertTrue( $doc->loadHTML( $actual ) );
+ $this->assertTrue( false === strpos( $actual, 'Page not found' ) );
+ $this->assertTrue( false !== strpos( $actual, 'Hello World' ) );
+ }
+
+ function test_oembed_output_post_with_thumbnail() {
+ $post_id = $this->factory->post->create( array(
+ 'post_title' => 'Hello World',
+ 'post_content' => 'Foo Bar',
+ 'post_excerpt' => 'Bar Baz',
+ ) );
+ $file = DIR_TESTDATA . '/images/canola.jpg';
+ $attachment_id = $this->factory->attachment->create_object( $file, $post_id, array(
+ 'post_mime_type' => 'image/jpeg',
+ ) );
+ set_post_thumbnail( $post_id, $attachment_id );
+
+ $this->go_to( get_post_embed_url( $post_id ) );
+
+ $this->assertQueryTrue( 'is_single', 'is_singular' );
+
+ ob_start();
+ include( ABSPATH . WPINC . '/embed-template.php' );
+ $actual = ob_get_clean();
+
+ $doc = new DOMDocument();
+ $this->assertTrue( $doc->loadHTML( $actual ) );
+ $this->assertFalse( strpos( $actual, 'Page not found' ) );
+ $this->assertTrue( false !== strpos( $actual, 'Hello World' ) );
+ $this->assertTrue( false !== strpos( $actual, 'canola.jpg' ) );
+ }
+
+ function test_oembed_output_404() {
+ $this->go_to( home_url( '/?p=123&embed=true' ) );
+ $GLOBALS['wp_query']->query_vars['embed'] = true;
+
+ $this->assertQueryTrue( 'is_404' );
+
+ ob_start();
+ include( ABSPATH . WPINC . '/embed-template.php' );
+ $actual = ob_get_clean();
+
+ $doc = new DOMDocument();
+ $this->assertTrue( $doc->loadHTML( $actual ) );
+ $this->assertTrue( false !== strpos( $actual, 'Page not found' ) );
+ }
+
+ function test_oembed_output_attachment() {
+ $post = $this->factory->post->create_and_get();
+ $file = DIR_TESTDATA . '/images/canola.jpg';
+ $attachment_id = $this->factory->attachment->create_object( $file, $post->ID, array(
+ 'post_mime_type' => 'image/jpeg',
+ 'post_title' => 'Hello World',
+ 'post_content' => 'Foo Bar',
+ 'post_excerpt' => 'Bar Baz',
+ ) );
+
+ $this->go_to( get_post_embed_url( $attachment_id ) );
+
+ $this->assertQueryTrue( 'is_single', 'is_singular', 'is_attachment' );
+
+ ob_start();
+ include( ABSPATH . WPINC . '/embed-template.php' );
+ $actual = ob_get_clean();
+
+ $doc = new DOMDocument();
+ $this->assertTrue( $doc->loadHTML( $actual ) );
+ $this->assertFalse( strpos( $actual, 'Page not found' ) );
+ $this->assertTrue( false !== strpos( $actual, 'Hello World' ) );
+ $this->assertTrue( false !== strpos( $actual, 'canola.jpg' ) );
+ }
+
+ function test_oembed_output_draft_post() {
+ $post_id = $this->factory->post->create( array(
+ 'post_title' => 'Hello World',
+ 'post_content' => 'Foo Bar',
+ 'post_excerpt' => 'Bar Baz',
+ 'post_status' => 'draft',
+ ) );
+
+ $this->go_to( get_post_embed_url( $post_id ) );
+
+ $this->assertQueryTrue( 'is_404' );
+
+ ob_start();
+ include( ABSPATH . WPINC . '/embed-template.php' );
+ $actual = ob_get_clean();
+
+ $doc = new DOMDocument();
+ $this->assertTrue( $doc->loadHTML( $actual ) );
+ $this->assertTrue( false !== strpos( $actual, 'Page not found' ) );
+ }
+
+ function test_oembed_output_scheduled_post() {
+ $post_id = $this->factory->post->create( array(
+ 'post_title' => 'Hello World',
+ 'post_content' => 'Foo Bar',
+ 'post_excerpt' => 'Bar Baz',
+ 'post_status' => 'future',
+ 'post_date' => strftime( '%Y-%m-%d %H:%M:%S', strtotime( '+1 day' ) ),
+ ) );
+
+ $this->go_to( get_post_embed_url( $post_id ) );
+
+ $this->assertQueryTrue( 'is_404' );
+
+ ob_start();
+ include( ABSPATH . WPINC . '/embed-template.php' );
+ $actual = ob_get_clean();
+
+ $doc = new DOMDocument();
+ $this->assertTrue( $doc->loadHTML( $actual ) );
+ $this->assertTrue( false !== strpos( $actual, 'Page not found' ) );
+ }
+
+ function test_oembed_output_private_post() {
+ $post_id = $this->factory->post->create( array(
+ 'post_title' => 'Hello World',
+ 'post_content' => 'Foo Bar',
+ 'post_excerpt' => 'Bar Baz',
+ 'post_status' => 'private',
+ ) );
+
+ $this->go_to( get_post_embed_url( $post_id ) );
+
+ $this->assertQueryTrue( 'is_404' );
+
+ ob_start();
+ include( ABSPATH . WPINC . '/embed-template.php' );
+ $actual = ob_get_clean();
+
+ $doc = new DOMDocument();
+ $this->assertTrue( $doc->loadHTML( $actual ) );
+ $this->assertTrue( false !== strpos( $actual, 'Page not found' ) );
+ }
+
+ function test_oembed_output_private_post_with_permissions() {
+ $user_id = $this->factory->user->create( array( 'role' => 'editor' ) );
+ wp_set_current_user( $user_id );
+
+ $post_id = $this->factory->post->create( array(
+ 'post_title' => 'Hello World',
+ 'post_content' => 'Foo Bar',
+ 'post_excerpt' => 'Bar Baz',
+ 'post_status' => 'private',
+ 'post_author' => $user_id,
+ ) );
+
+ $this->go_to( get_post_embed_url( $post_id ) );
+
+ $this->assertQueryTrue( 'is_single', 'is_singular' );
+
+ ob_start();
+ include( ABSPATH . WPINC . '/embed-template.php' );
+ $actual = ob_get_clean();
+
+ $doc = new DOMDocument();
+ $this->assertTrue( $doc->loadHTML( $actual ) );
+ $this->assertTrue( false === strpos( $actual, 'Page not found' ) );
+ $this->assertTrue( false !== strpos( $actual, 'Hello World' ) );
+ }
+
+ function test_wp_oembed_excerpt_more_no_embed() {
+ $GLOBALS['wp_query'] = new WP_Query();
+
+ $this->assertEquals( 'foo bar', wp_oembed_excerpt_more( 'foo bar' ) );
+ }
+
+ function test_wp_oembed_excerpt_more() {
+ $post_id = $this->factory->post->create( array(
+ 'post_content' => 'Foo Bar',
+ ) );
+
+ $this->assertEquals( '', wp_oembed_excerpt_more( '' ) );
+
+ $this->go_to( get_post_embed_url( $post_id ) );
+
+ $actual = wp_oembed_excerpt_more( '' );
+
+ $expected = sprintf(
+ '… Read more',
+ get_the_permalink()
+ );
+
+ $this->assertEquals( $expected, $actual );
+ }
+
+ function test_is_embed_post() {
+ $this->assertFalse( is_embed() );
+
+ $post_id = $this->factory->post->create();
+ $this->go_to( get_post_embed_url( $post_id ) );
+ $this->assertTrue( is_embed() );
+ }
+
+ function test_is_embed_attachment() {
+ $post_id = $this->factory->post->create();
+ $file = DIR_TESTDATA . '/images/canola.jpg';
+ $attachment_id = $this->factory->attachment->create_object( $file, $post_id, array(
+ 'post_mime_type' => 'image/jpeg',
+ ) );
+ $this->go_to( get_post_embed_url( $attachment_id ) );
+ $this->assertTrue( is_embed() );
+ }
+
+ function test_is_embed_404() {
+ $this->go_to( home_url( '/?p=12345&embed=true' ) );
+ $this->assertTrue( is_embed() );
+ }
+
+ function test_get_post_embed_html_non_existent_post() {
+ $this->assertFalse( get_post_embed_html( 0, 200, 200 ) );
+ $this->assertFalse( get_post_embed_html( null, 200, 200 ) );
+ }
+
+ function test_get_post_embed_html() {
+ $post_id = $this->factory->post->create();
+
+ $expected = '';
+
+ $this->assertStringEndsWith( $expected, get_post_embed_html( $post_id, 200, 200 ) );
+ }
+
+ function test_add_host_js() {
+ ob_start();
+ wp_oembed_add_host_js();
+ ob_end_clean();
+
+ $this->assertTrue( wp_script_is( 'wp-oembed' ) );
+ }
+}