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( '%s', + get_the_permalink(), + __( 'Read more', 'oembed-api' ) + ) + ); +} + +/** + * Display the post excerpt for the embed template. + * + * @since 4.4.0 + */ +function the_excerpt_embed() { + $output = get_the_excerpt(); + + /** + * Filter the post excerpt for the embed template. + * + * @param string $output The current post excerpt. + */ + echo apply_filters( 'the_excerpt_embed', $output ); +} + +/** + * Filters the post excerpt for the embed template. + * + * Shows players for video and audio attachments. + * + * @since 4.4.0 + * + * @param string $content The current post excerpt. + * @return string The modified post excerpt. + */ +function wp_oembed_excerpt_attachment( $content ) { + if ( is_attachment() ) { + return prepend_attachment( '' ); + } + + return $content; +} + +/** + * Print the CSS in the embed iframe header. + * + * @since 4.4.0 + */ +function print_oembed_embed_styles() { + ?> + + + + + +> + + <?php wp_title( '-', true, 'right' ); ?> + + tag. + * + * @since 4.4.0 + */ + do_action( 'oembed_head' ); + ?> + +> + $data ) { + if ( $data['width'] / $data['height'] > $aspect_ratio ) { + $aspect_ratio = $data['width'] / $data['height']; + $measurements = array( $data['width'], $data['height'] ); + $image_size = $size; + } + } + } + + /** + * Filter the thumbnail image size for use in the embed template. + * + * @param string $image_size Thumbnail image size. + */ + $image_size = apply_filters( 'oembed_thumbnail_image_size', $image_size ); + + $shape = $measurements[0] / $measurements[1] >= 1.75 ? 'rectangular' : 'square'; + + /** + * Filter the thumbnail shape for use in the embed template. + * + * Rectangular images are shown above the title + * while square images are shown next to the content. + * + * @since 4.4.0 + * + * @param string $shape Thumbnail image shape. Either 'rectangular' or 'square'. + */ + $shape = apply_filters( 'oembed_thumbnail_image_shape', $shape ); + } + ?> +
> + + + + +

+ + + +

+ + + + + +
+ + + + + +
+ +
+

+ +
+

+
+ + +
+ + + diff --git a/src/wp-includes/js/tinymce/plugins/wpoembed/plugin.js b/src/wp-includes/js/tinymce/plugins/wpoembed/plugin.js new file mode 100644 index 0000000000..3572426fff --- /dev/null +++ b/src/wp-includes/js/tinymce/plugins/wpoembed/plugin.js @@ -0,0 +1,17 @@ +(function ( tinymce ) { + 'use strict'; + + tinymce.PluginManager.add( 'wpoembed', function ( editor, url ) { + editor.on( 'init', function () { + var scriptId = editor.dom.uniqueId(); + + var scriptElm = editor.dom.create( 'script', { + id: scriptId, + type: 'text/javascript', + src: url + '/../../../wp-oembed.js' + } ); + + editor.getDoc().getElementsByTagName( 'head' )[ 0 ].appendChild( scriptElm ); + } ); + } ); +})( window.tinymce ); diff --git a/src/wp-includes/js/wp-oembed-embed.js b/src/wp-includes/js/wp-oembed-embed.js new file mode 100644 index 0000000000..847ebcfd95 --- /dev/null +++ b/src/wp-includes/js/wp-oembed-embed.js @@ -0,0 +1,162 @@ +(function ( window, document ) { + 'use strict'; + + var secret = window.location.hash.replace( /.*secret=([\d\w]{10}).*/, '$1' ), + resizing; + + function sendEmbedMessage( message, value ) { + window.parent.postMessage( { + message: message, + value: value, + secret: secret + }, '*' ); + } + + function onLoad() { + var share_dialog = document.querySelector( '.wp-embed-share-dialog' ), + share_dialog_open = document.querySelector( '.wp-embed-share-dialog-open' ), + share_dialog_close = document.querySelector( '.wp-embed-share-dialog-close' ), + share_input = document.querySelectorAll( '.wp-embed-share-input' ), + share_dialog_tabs = document.querySelectorAll( '.wp-embed-share-tab-button button' ), + links = document.getElementsByTagName( 'a' ), + i; + + if ( share_input ) { + for ( i = 0; i < share_input.length; i++ ) { + share_input[ i ].addEventListener( 'click', function ( e ) { + e.target.select(); + } ); + } + } + + function openSharingDialog() { + share_dialog.className = share_dialog.className.replace( 'hidden', '' ); + share_input[ 0 ].select(); + } + + function closeSharingDialog() { + share_dialog.className += ' hidden'; + document.querySelector( '.wp-embed-share-dialog-open' ).focus(); + } + + if ( share_dialog_open ) { + share_dialog_open.addEventListener( 'click', function ( e ) { + openSharingDialog(); + e.preventDefault(); + } ); + } + + if ( share_dialog_close ) { + share_dialog_close.addEventListener( 'click', function ( e ) { + closeSharingDialog(); + e.preventDefault(); + } ); + } + + function shareClickHandler( e ) { + var currentTab = document.querySelector( '.wp-embed-share-tab-button [aria-selected="true"]' ); + currentTab.setAttribute( 'aria-selected', 'false' ); + document.querySelector( '#' + currentTab.getAttribute( 'aria-controls' ) ).setAttribute( 'aria-hidden', 'true' ); + + e.target.setAttribute( 'aria-selected', 'true' ); + document.querySelector( '#' + e.target.getAttribute( 'aria-controls' ) ).setAttribute( 'aria-hidden', 'false' ); + } + + function shareKeyHandler( e ) { + var target = e.target, + previousSibling = target.parentElement.previousElementSibling, + nextSibling = target.parentElement.nextElementSibling, + newTab, newTabChild; + + if ( 37 === e.keyCode ) { + newTab = previousSibling; + } else if ( 39 === e.keyCode ) { + newTab = nextSibling; + } else { + return false; + } + + if ( 'rtl' === document.documentElement.getAttribute( 'dir' ) ) { + newTab = ( newTab === previousSibling ) ? nextSibling : previousSibling; + } + + if ( newTab ) { + newTabChild = newTab.firstElementChild; + + target.setAttribute( 'tabindex', '-1' ); + target.setAttribute( 'aria-selected', false ); + document.querySelector( '#' + target.getAttribute( 'aria-controls' ) ).setAttribute( 'aria-hidden', 'true' ); + + newTabChild.setAttribute( 'tabindex', '0' ); + newTabChild.setAttribute( 'aria-selected', 'true' ); + newTabChild.focus(); + document.querySelector( '#' + newTabChild.getAttribute( 'aria-controls' ) ).setAttribute( 'aria-hidden', 'false' ); + } + } + + if ( share_dialog_tabs ) { + for ( i = 0; i < share_dialog_tabs.length; i++ ) { + share_dialog_tabs[ i ].addEventListener( 'click', shareClickHandler ); + + share_dialog_tabs[ i ].addEventListener( 'keydown', shareKeyHandler ); + } + } + + document.addEventListener( 'keydown', function ( e ) { + if ( e.keyCode === 27 && -1 === share_dialog.className.indexOf( 'hidden' ) ) { + closeSharingDialog(); + } + }, false ); + + if ( window.self === window.top ) { + return; + } + + /** + * Send this document's height to the parent (embedding) site. + */ + sendEmbedMessage( 'height', Math.ceil( document.body.getBoundingClientRect().height ) ); + + /** + * Detect clicks to external (_top) links. + */ + function linkClickHandler( e ) { + var target = e.target, + href; + if ( target.hasAttribute( 'href' ) ) { + href = target.getAttribute( 'href' ); + } else { + href = target.parentElement.getAttribute( 'href' ); + } + + /** + * Send link target to the parent (embedding) site. + */ + sendEmbedMessage( 'link', href ); + e.preventDefault(); + } + + for ( i = 0; i < links.length; i++ ) { + links[ i ].addEventListener( 'click', linkClickHandler ); + } + } + + document.addEventListener( 'DOMContentLoaded', onLoad, false ); + + /** + * Iframe resize handler. + */ + function onResize() { + if ( window.self === window.top ) { + return; + } + + clearTimeout( resizing ); + + resizing = setTimeout( function () { + sendEmbedMessage( 'height', Math.ceil( document.body.getBoundingClientRect().height ) ); + }, 100 ); + } + + window.addEventListener( 'resize', onResize, false ); +})( window, document ); diff --git a/src/wp-includes/js/wp-oembed.js b/src/wp-includes/js/wp-oembed.js new file mode 100644 index 0000000000..dd03bc0eba --- /dev/null +++ b/src/wp-includes/js/wp-oembed.js @@ -0,0 +1,67 @@ +(function ( window, document ) { + 'use strict'; + + window.wp = window.wp || {}; + + if ( !! window.wp.receiveEmbedMessage ) { + return; + } + + window.wp.receiveEmbedMessage = function( e ) { + var data = e.data; + if ( ! ( data.secret || data.message || data.value ) ) { + return; + } + + var iframes = document.querySelectorAll( '.wp-embedded-content[data-secret="' + data.secret + '"]' ); + + for ( var i = 0; i < iframes.length; i++ ) { + var source = iframes[ i ]; + + /* Resize the iframe on request. */ + if ( 'height' === data.message ) { + var height = data.value; + if ( height > 1000 ) { + height = 1000; + } else if ( height < 200 ) { + height = 200; + } + + source.height = (height) + 'px'; + } + + /* Link to a specific URL on request. */ + if ( 'link' === data.message ) { + var sourceURL = document.createElement( 'a' ), targetURL = document.createElement( 'a' ); + sourceURL.href = source.getAttribute( 'src' ); + targetURL.href = data.value; + + /* Only continue if link hostname matches iframe's hostname. */ + if ( targetURL.host === sourceURL.host && document.activeElement === source ) { + window.top.location.href = data.value; + } + } + } + }; + + window.addEventListener( 'message', window.wp.receiveEmbedMessage, false ); + + function onLoad() { + var isIE10 = -1 !== navigator.appVersion.indexOf( 'MSIE 10' ), + isIE11 = !!navigator.userAgent.match( /Trident.*rv\:11\./ ); + + /* Remove security attribute from iframes in IE10 and IE11. */ + if ( isIE10 || isIE11 ) { + var iframes = document.querySelectorAll( '.wp-embedded-content[security]' ), iframeClone; + + for ( var i = 0; i < iframes.length; i++ ) { + iframeClone = iframes[ i ].cloneNode( true ); + iframeClone.removeAttribute( 'security' ); + iframes[ i ].parentNode.insertBefore( iframeClone, iframes[ i ].nextSibling ); + iframes[ i ].parentNode.removeChild( iframes[ i ] ); + } + } + } + + document.addEventListener( 'DOMContentLoaded', onLoad, false ); +})( window, document ); diff --git a/src/wp-includes/query.php b/src/wp-includes/query.php index 69310345d5..94e48391d9 100644 --- a/src/wp-includes/query.php +++ b/src/wp-includes/query.php @@ -718,6 +718,26 @@ function is_404() { return $wp_query->is_404(); } +/** + * Is the query for an embedded post? + * + * @since 4.4.0 + * + * @global WP_Query $wp_query Global WP_Query instance. + * + * @return bool Whether we're in an embedded post or not. + */ +function is_embed() { + global $wp_query; + + if ( ! isset( $wp_query ) ) { + _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1' ); + return false; + } + + return $wp_query->is_embed(); +} + /** * Is the query the main query? * @@ -1200,6 +1220,15 @@ class WP_Query { */ public $is_404 = false; + /** + * Set if query is embed. + * + * @since 4.4.0 + * @access public + * @var bool + */ + public $is_embed = false; + /** * Set if query is within comments popup window. * @@ -1845,6 +1874,8 @@ class WP_Query { if ( '404' == $qv['error'] ) $this->set_404(); + $this->is_embed = isset( $qv['embed'] ) && ( $this->is_singular || $this->is_404 ); + $this->query_vars_hash = md5( serialize( $this->query_vars ) ); $this->query_vars_changed = false; @@ -4634,6 +4665,17 @@ class WP_Query { return (bool) $this->is_404; } + /** + * Is the query for an embedded post? + * + * @since 3.1.0 + * + * @return bool + */ + public function is_embed() { + return (bool) $this->is_embed; + } + /** * Is the query the main query? * @@ -4935,6 +4977,8 @@ function wp_old_slug_redirect() { $link = user_trailingslashit( trailingslashit( $link ) . 'feed' ); } elseif ( isset( $GLOBALS['wp_query']->query_vars['paged'] ) && $GLOBALS['wp_query']->query_vars['paged'] > 1 ) { $link = user_trailingslashit( trailingslashit( $link ) . 'page/' . $GLOBALS['wp_query']->query_vars['paged'] ); + } elseif( is_embed() ) { + $link = user_trailingslashit( trailingslashit( $link ) . 'embed' ); } elseif ( is_404() ) { // Add rewrite endpoints if necessary. foreach ( $wp_rewrite->endpoints as $endpoint ) { diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 4c802f36ee..4035525fc8 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -466,6 +466,8 @@ function wp_default_scripts( &$scripts ) { ), ) ); + $scripts->add( 'wp-oembed', "/wp-includes/js/wp-oembed$suffix.js" ); + // To enqueue media-views or media-editor, call wp_enqueue_media(). // Both rely on numerous settings, styles, and templates to operate correctly. $scripts->add( 'media-views', "/wp-includes/js/media-views$suffix.js", array( 'utils', 'media-models', 'wp-plupload', 'jquery-ui-sortable', 'wp-mediaelement' ), false, 1 ); diff --git a/src/wp-includes/template-loader.php b/src/wp-includes/template-loader.php index 95216fe5e0..d3eebe93ed 100644 --- a/src/wp-includes/template-loader.php +++ b/src/wp-includes/template-loader.php @@ -39,6 +39,20 @@ elseif ( is_feed() ) : elseif ( is_trackback() ) : include( ABSPATH . 'wp-trackback.php' ); return; +elseif ( is_embed() ) : + $template = ABSPATH . WPINC . '/embed-template.php'; + + /** + * Filter the template used for embedded posts. + * + * @since 4.4.0 + * + * @param string $template Path to the template file. + */ + $template = apply_filters( 'embed_template', $template ); + + include ( $template ); + return; endif; if ( defined('WP_USE_THEMES') && WP_USE_THEMES ) : diff --git a/src/wp-settings.php b/src/wp-settings.php index d2c8b0173a..26ef9885c2 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -147,6 +147,7 @@ require( ABSPATH . WPINC . '/canonical.php' ); require( ABSPATH . WPINC . '/shortcodes.php' ); require( ABSPATH . WPINC . '/class-wp-embed.php' ); require( ABSPATH . WPINC . '/embed-functions.php' ); +require( ABSPATH . WPINC . '/class-wp-oembed-controller.php' ); require( ABSPATH . WPINC . '/media.php' ); require( ABSPATH . WPINC . '/http.php' ); require( ABSPATH . WPINC . '/widgets.php' ); diff --git a/tests/phpunit/multisite.xml b/tests/phpunit/multisite.xml index 63b5f7696c..9712a3d0a6 100644 --- a/tests/phpunit/multisite.xml +++ b/tests/phpunit/multisite.xml @@ -25,6 +25,7 @@ ajax ms-files external-http + oembed-headers diff --git a/tests/phpunit/tests/oembed/controller.php b/tests/phpunit/tests/oembed/controller.php new file mode 100644 index 0000000000..4752421ffa --- /dev/null +++ b/tests/phpunit/tests/oembed/controller.php @@ -0,0 +1,273 @@ + '', + 'format' => 'json', + 'maxwidth' => 600, + ); + + $legacy_controller = new WP_oEmbed_Controller(); + + $this->assertEquals( 'Invalid URL.', $legacy_controller->dispatch( $request ) ); + } + + function test_request_json() { + $user = $this->factory->user->create_and_get( array( + 'display_name' => 'John Doe', + ) ); + $post = $this->factory->post->create_and_get( array( + 'post_author' => $user->ID, + 'post_title' => 'Hello World', + ) ); + + // WP_Query arguments. + $request = array( + 'url' => get_permalink( $post->ID ), + 'format' => 'json', + 'maxwidth' => 400, + 'callback' => '', + 'oembed' => true, + ); + + $legacy_controller = new WP_oEmbed_Controller(); + + $data = json_decode( $legacy_controller->dispatch( $request ), true ); + + $this->assertTrue( is_array( $data ) ); + + $this->assertArrayHasKey( 'version', $data ); + $this->assertArrayHasKey( 'provider_name', $data ); + $this->assertArrayHasKey( 'provider_url', $data ); + $this->assertArrayHasKey( 'author_name', $data ); + $this->assertArrayHasKey( 'author_url', $data ); + $this->assertArrayHasKey( 'title', $data ); + $this->assertArrayHasKey( 'type', $data ); + $this->assertArrayHasKey( 'width', $data ); + + $this->assertEquals( '1.0', $data['version'] ); + $this->assertEquals( get_bloginfo( 'name' ), $data['provider_name'] ); + $this->assertEquals( get_home_url(), $data['provider_url'] ); + $this->assertEquals( $user->display_name, $data['author_name'] ); + $this->assertEquals( get_author_posts_url( $user->ID, $user->user_nicename ), $data['author_url'] ); + $this->assertEquals( $post->post_title, $data['title'] ); + $this->assertEquals( 'rich', $data['type'] ); + $this->assertTrue( $data['width'] <= $request['maxwidth'] ); + } + + function test_request_jsonp() { + $user = $this->factory->user->create_and_get( array( + 'display_name' => 'John Doe', + ) ); + $post = $this->factory->post->create_and_get( array( + 'post_author' => $user->ID, + 'post_title' => 'Hello World', + ) ); + + $request = array( + 'url' => get_permalink( $post->ID ), + 'format' => 'json', + 'maxwidth' => 600, + 'callback' => 'mycallback', + ); + + $legacy_controller = new WP_oEmbed_Controller(); + + $data = $legacy_controller->dispatch( $request ); + + $this->assertEquals( 0, strpos( $data, '/**/mycallback(' ) ); + } + + function test_request_jsonp_invalid_callback() { + $user = $this->factory->user->create_and_get( array( + 'display_name' => 'John Doe', + ) ); + $post = $this->factory->post->create_and_get( array( + 'post_author' => $user->ID, + 'post_title' => 'Hello World', + ) ); + + $request = array( + 'url' => get_permalink( $post->ID ), + 'format' => 'json', + 'maxwidth' => 600, + 'callback' => array( 'foo', 'bar' ), + ); + + $legacy_controller = new WP_oEmbed_Controller(); + + $data = $legacy_controller->dispatch( $request ); + + $this->assertFalse( strpos( $data, '/**/' ) ); + } + + function test_request_json_invalid_data() { + $request = array( + 'callback' => '', + ); + + $legacy_controller = new WP_oEmbed_Controller(); + + $this->assertEquals( 'Not implemented', $legacy_controller->json_response( null, $request ) ); + $this->assertEquals( 'Not implemented', $legacy_controller->json_response( 123, $request ) ); + $this->assertEquals( 'Not implemented', $legacy_controller->json_response( array(), $request ) ); + } + + function test_request_xml() { + $user = $this->factory->user->create_and_get( array( + 'display_name' => 'John Doe', + ) ); + $post = $this->factory->post->create_and_get( array( + 'post_author' => $user->ID, + 'post_title' => 'Hello World', + ) ); + + $request = array( + 'url' => get_permalink( $post->ID ), + 'format' => 'xml', + 'maxwidth' => 400, + 'callback' => '', + ); + + $legacy_controller = new WP_oEmbed_Controller(); + + $data = $legacy_controller->dispatch( $request ); + + $data = simplexml_load_string( $data ); + $this->assertInstanceOf( 'SimpleXMLElement', $data ); + + $data = (array) $data; + + $this->assertArrayHasKey( 'version', $data ); + $this->assertArrayHasKey( 'provider_name', $data ); + $this->assertArrayHasKey( 'provider_url', $data ); + $this->assertArrayHasKey( 'author_name', $data ); + $this->assertArrayHasKey( 'author_url', $data ); + $this->assertArrayHasKey( 'title', $data ); + $this->assertArrayHasKey( 'type', $data ); + $this->assertArrayHasKey( 'width', $data ); + + $this->assertEquals( '1.0', $data['version'] ); + $this->assertEquals( get_bloginfo( 'name' ), $data['provider_name'] ); + $this->assertEquals( get_home_url(), $data['provider_url'] ); + $this->assertEquals( $user->display_name, $data['author_name'] ); + $this->assertEquals( get_author_posts_url( $user->ID, $user->user_nicename ), $data['author_url'] ); + $this->assertEquals( $post->post_title, $data['title'] ); + $this->assertEquals( 'rich', $data['type'] ); + $this->assertTrue( $data['width'] <= $request['maxwidth'] ); + } + + function test_request_xml_invalid_data() { + $legacy_controller = new WP_oEmbed_Controller(); + + $this->assertEquals( 'Not implemented', $legacy_controller->xml_response( null ) ); + $this->assertEquals( 'Not implemented', $legacy_controller->xml_response( 123 ) ); + $this->assertEquals( 'Not implemented', $legacy_controller->xml_response( array() ) ); + } + + /** + * @group multisite + */ + function test_request_ms_child_in_root_blog() { + if ( ! is_multisite() ) { + $this->markTestSkipped( __METHOD__ . ' is a multisite-only test.' ); + } + + $child = $this->factory->blog->create(); + + switch_to_blog( $child ); + + $post = $this->factory->post->create_and_get( array( + 'post_title' => 'Hello Child Blog', + ) ); + + $request = array( + 'url' => get_permalink( $post->ID ), + 'format' => 'json', + 'maxwidth' => 600, + 'callback' => '', + ); + + $legacy_controller = new WP_oEmbed_Controller(); + + $data = json_decode( $legacy_controller->dispatch( $request ), true ); + + $this->assertInternalType( 'array', $data ); + $this->assertNotEmpty( $data ); + + restore_current_blog(); + } + + function test_get_oembed_endpoint_url() { + $this->assertEquals( home_url() . '/?oembed=true', get_oembed_endpoint_url() ); + $this->assertEquals( home_url() . '/?oembed=true', get_oembed_endpoint_url( '', 'json' ) ); + $this->assertEquals( home_url() . '/?oembed=true', get_oembed_endpoint_url( '', 'xml' ) ); + + $post_id = $this->factory->post->create(); + $url = get_permalink( $post_id ); + + $this->assertEquals( home_url() . '/?oembed=true&url=' . $url, get_oembed_endpoint_url( $url ) ); + $this->assertEquals( home_url() . '/?oembed=true&url=' . $url . '&format=xml', get_oembed_endpoint_url( $url, 'xml' ) ); + } + + function test_wp_oembed_ensure_format() { + $this->assertEquals( 'json', wp_oembed_ensure_format( 'json' ) ); + $this->assertEquals( 'xml', wp_oembed_ensure_format( 'xml' ) ); + $this->assertEquals( 'json', wp_oembed_ensure_format( 123 ) ); + $this->assertEquals( 'json', wp_oembed_ensure_format( 'random' ) ); + $this->assertEquals( 'json', wp_oembed_ensure_format( array() ) ); + } + + function test_oembed_create_xml() { + $actual = _oembed_create_xml( array( + 'foo' => 'bar', + 'bar' => 'baz', + 'ping' => 'pong', + ) ); + + $expected = 'barbazpong'; + + $this->assertStringEndsWith( $expected, trim( $actual ) ); + + $actual = _oembed_create_xml( array( + 'foo' => array( + 'bar' => 'baz', + ), + 'ping' => 'pong', + ) ); + + $expected = 'bazpong'; + + $this->assertStringEndsWith( $expected, trim( $actual ) ); + + $actual = _oembed_create_xml( array( + 'foo' => array( + 'bar' => array( + 'ping' => 'pong', + ), + ), + 'hello' => 'world', + ) ); + + $expected = 'pongworld'; + + $this->assertStringEndsWith( $expected, trim( $actual ) ); + + $actual = _oembed_create_xml( array( + array( + 'foo' => array( + 'bar', + ), + ), + 'helloworld', + ) ); + + $expected = 'barhelloworld'; + + $this->assertStringEndsWith( $expected, trim( $actual ) ); + } +} diff --git a/tests/phpunit/tests/oembed/discovery.php b/tests/phpunit/tests/oembed/discovery.php new file mode 100644 index 0000000000..cad47cf813 --- /dev/null +++ b/tests/phpunit/tests/oembed/discovery.php @@ -0,0 +1,68 @@ +assertSame( '', $actual ); + } + + function test_add_oembed_discovery_links_to_post() { + $post_id = $this->factory->post->create(); + $this->go_to( get_permalink( $post_id ) ); + + $this->assertQueryTrue( 'is_single', 'is_singular' ); + + ob_start(); + wp_oembed_add_discovery_links(); + $actual = ob_get_clean(); + + $expected = '' . "\n"; + $expected .= '' . "\n"; + + $this->assertEquals( $expected, $actual ); + } + + function test_add_oembed_discovery_links_to_page() { + $post_id = $this->factory->post->create( array( + 'post_type' => 'page' + )); + $this->go_to( get_permalink( $post_id ) ); + + $this->assertQueryTrue( 'is_page', 'is_singular' ); + + ob_start(); + wp_oembed_add_discovery_links(); + $actual = ob_get_clean(); + + $expected = '' . "\n"; + $expected .= '' . "\n"; + + $this->assertEquals( $expected, $actual ); + } + + function test_add_oembed_discovery_links_to_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_permalink( $attachment_id ) ); + + $this->assertQueryTrue( 'is_attachment', 'is_singular', 'is_single' ); + + ob_start(); + wp_oembed_add_discovery_links(); + $actual = ob_get_clean(); + + $expected = '' . "\n"; + $expected .= '' . "\n"; + + $this->assertEquals( $expected, $actual ); + } +} diff --git a/tests/phpunit/tests/oembed/filterResult.php b/tests/phpunit/tests/oembed/filterResult.php new file mode 100644 index 0000000000..cfe5c98e3e --- /dev/null +++ b/tests/phpunit/tests/oembed/filterResult.php @@ -0,0 +1,81 @@ +

'; + + $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 = 'Hello

World

'; + $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' ) ); + } +}