<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[48273] trunk: REST API: Link to the REST route for the currently queried resource.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/48273">48273</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/48273","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>TimothyBlynJacobs</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2020-07-02 05:55:04 +0000 (Thu, 02 Jul 2020)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>REST API: Link to the REST route for the currently queried resource.

This allows for programatically determining the REST version of the current page. The links also aid human discovery of the REST API in general.

Props dshanske, tfrommen, TimothyBlynJacobs.
Fixes <a href="https://core.trac.wordpress.org/ticket/49116">#49116</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesclasswptaxonomyphp">trunk/src/wp-includes/class-wp-taxonomy.php</a></li>
<li><a href="#trunksrcwpincludesrestapiphp">trunk/src/wp-includes/rest-api.php</a></li>
<li><a href="#trunktestsphpunittestsrestapiresttaxonomiescontrollerphp">trunk/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php</a></li>
<li><a href="#trunktestsphpunittestsrestapiphp">trunk/tests/phpunit/tests/rest-api.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesclasswptaxonomyphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-taxonomy.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-taxonomy.php       2020-07-02 02:01:28 UTC (rev 48272)
+++ trunk/src/wp-includes/class-wp-taxonomy.php 2020-07-02 05:55:04 UTC (rev 48273)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -210,6 +210,16 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public $rest_controller_class;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * The controller instance for this taxonomy's REST API endpoints.
+        *
+        * Lazily computed. Should be accessed using {@see WP_Taxonomy::get_rest_controller()}.
+        *
+        * @since 5.5.0
+        * @var WP_REST_Controller $rest_controller
+        */
+       public $rest_controller;
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Whether it is a built-in taxonomy.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 4.7.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -452,4 +462,40 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public function remove_hooks() {
</span><span class="cx" style="display: block; padding: 0 10px">                remove_filter( 'wp_ajax_add-' . $this->name, '_wp_ajax_add_hierarchical_term' );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * Gets the REST API controller for this taxonomy.
+        *
+        * Will only instantiate the controller class once per request.
+        *
+        * @since 5.5.0
+        *
+        * @return WP_REST_Controller|null The controller instance, or null if the taxonomy
+        *                                 is set not to show in rest.
+        */
+       public function get_rest_controller() {
+               if ( ! $this->show_in_rest ) {
+                       return null;
+               }
+
+               $class = $this->rest_controller_class ? $this->rest_controller_class : WP_REST_Terms_Controller::class;
+
+               if ( ! class_exists( $class ) ) {
+                       return null;
+               }
+
+               if ( ! is_subclass_of( $class, WP_REST_Controller::class ) ) {
+                       return null;
+               }
+
+               if ( ! $this->rest_controller ) {
+                       $this->rest_controller = new $class( $this->name );
+               }
+
+               if ( ! ( $this->rest_controller instanceof $class ) ) {
+                       return null;
+               }
+
+               return $this->rest_controller;
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunksrcwpincludesrestapiphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/rest-api.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/rest-api.php        2020-07-02 02:01:28 UTC (rev 48272)
+++ trunk/src/wp-includes/rest-api.php  2020-07-02 05:55:04 UTC (rev 48273)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -234,15 +234,11 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        // Terms.
</span><span class="cx" style="display: block; padding: 0 10px">        foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $class = ! empty( $taxonomy->rest_controller_class ) ? $taxonomy->rest_controller_class : 'WP_REST_Terms_Controller';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $controller = $taxonomy->get_rest_controller();
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! class_exists( $class ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! $controller ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         continue;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $controller = new $class( $taxonomy->name );
-               if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) {
-                       continue;
-               }
</del><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $controller->register_routes();
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -873,7 +869,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">                return;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        echo "<link rel='https://api.w.org/' href='" . esc_url( $api_root ) . "' />\n";
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ printf( '<link rel="https://api.w.org/" href="%s" />', esc_url( $api_root ) );
+
+       $resource = rest_get_queried_resource_route();
+
+       if ( $resource ) {
+               printf( '<link rel="alternate" type="application/json" href="%s" />', esc_url( rest_url( $resource ) ) );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -892,7 +894,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">                return;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        header( 'Link: <' . esc_url_raw( $api_root ) . '>; rel="https://api.w.org/"', false );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ header( sprintf( 'Link: <%s>; rel="https://api.w.org/"', esc_url_raw( $api_root ) ), false );
+
+       $resource = rest_get_queried_resource_route();
+
+       if ( $resource ) {
+               header( sprintf( 'Link: <%s>; rel="alternate"; type="application/json"', esc_url_raw( rest_url( $resource ) ) ), false );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1823,3 +1831,121 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        return $schema;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/**
+ * Gets the REST API route for a post.
+ *
+ * @since 5.5.0
+ *
+ * @param int|WP_Post $post Post ID or post object.
+ * @return string The route path with a leading slash for the given post, or an empty string if there is not a route.
+ */
+function rest_get_route_for_post( $post ) {
+       $post = get_post( $post );
+
+       if ( ! $post instanceof WP_Post ) {
+               return '';
+       }
+
+       $post_type = get_post_type_object( $post->post_type );
+       if ( ! $post_type ) {
+               return '';
+       }
+
+       $controller = $post_type->get_rest_controller();
+       if ( ! $controller ) {
+               return '';
+       }
+
+       $route = '';
+
+       // The only two controllers that we can detect are the Attachments and Posts controllers.
+       if ( in_array( get_class( $controller ), array( 'WP_REST_Attachments_Controller', 'WP_REST_Posts_Controller' ), true ) ) {
+               $namespace = 'wp/v2';
+               $rest_base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;
+               $route     = sprintf( '/%s/%s/%d', $namespace, $rest_base, $post->ID );
+       }
+
+       /**
+        * Filters the REST API route for a post.
+        *
+        * @since 5.5.0
+        *
+        * @param string  $route The route path.
+        * @param WP_Post $post  The post object.
+        */
+       return apply_filters( 'rest_route_for_post', $route, $post );
+}
+
+/**
+ * Gets the REST API route for a term.
+ *
+ * @since 5.5.0
+ *
+ * @param int|WP_Term $term Term ID or term object.
+ * @return string The route path with a leading slash for the given term, or an empty string if there is not a route.
+ */
+function rest_get_route_for_term( $term ) {
+       $term = get_term( $term );
+
+       if ( ! $term instanceof WP_Term ) {
+               return '';
+       }
+
+       $taxonomy = get_taxonomy( $term->taxonomy );
+       if ( ! $taxonomy ) {
+               return '';
+       }
+
+       $controller = $taxonomy->get_rest_controller();
+       if ( ! $controller ) {
+               return '';
+       }
+
+       $route = '';
+
+       // The only controller that works is the Terms controller.
+       if ( 'WP_REST_Terms_Controller' === get_class( $controller ) ) {
+               $namespace = 'wp/v2';
+               $rest_base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
+               $route     = sprintf( '/%s/%s/%d', $namespace, $rest_base, $term->term_id );
+       }
+
+       /**
+        * Filters the REST API route for a term.
+        *
+        * @since 5.5.0
+        *
+        * @param string  $route The route path.
+        * @param WP_Term $term  The term object.
+        */
+       return apply_filters( 'rest_route_for_term', $route, $term );
+}
+
+/**
+ * Gets the REST route for the currently queried object.
+ *
+ * @since 5.5.0
+ *
+ * @return string The REST route of the resource, or an empty string if no resource identified.
+ */
+function rest_get_queried_resource_route() {
+       if ( is_singular() ) {
+               $route = rest_get_route_for_post( get_queried_object() );
+       } elseif ( is_category() || is_tag() || is_tax() ) {
+               $route = rest_get_route_for_term( get_queried_object() );
+       } elseif ( is_author() ) {
+               $route = '/wp/v2/users/' . get_queried_object_id();
+       } else {
+               $route = '';
+       }
+
+       /**
+        * Filters the REST route for the currently queried object.
+        *
+        * @since 5.5.0
+        *
+        * @param string $link The route with a leading slash, or an empty string.
+        */
+       return apply_filters( 'rest_queried_resource_route', $route );
+}
</ins></span></pre></div>
<a id="trunktestsphpunittestsrestapiresttaxonomiescontrollerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php 2020-07-02 02:01:28 UTC (rev 48272)
+++ trunk/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php   2020-07-02 05:55:04 UTC (rev 48273)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -291,4 +291,32 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertEquals( count( $taxonomies ), count( $data ) );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        /**
+        * @ticket 49116
+        */
+       public function test_get_for_taxonomy_reuses_same_instance() {
+               $this->assertSame(
+                       get_taxonomy( 'category' )->get_rest_controller(),
+                       get_taxonomy( 'category' )->get_rest_controller()
+               );
+       }
+
+       /**
+        * @ticket 49116
+        */
+       public function test_get_for_taxonomy_returns_terms_controller_if_custom_class_not_specified() {
+               register_taxonomy(
+                       'test',
+                       'post',
+                       array(
+                               'show_in_rest' => true,
+                       )
+               );
+
+               $this->assertInstanceOf(
+                       WP_REST_Terms_Controller::class,
+                       get_taxonomy( 'test' )->get_rest_controller()
+               );
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapiphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api.php    2020-07-02 02:01:28 UTC (rev 48272)
+++ trunk/tests/phpunit/tests/rest-api.php      2020-07-02 05:55:04 UTC (rev 48273)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1349,4 +1349,112 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        array( new WP_REST_Response( 'rest' ), 'rest' ),
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * @ticket 49116
+        */
+       public function test_rest_get_route_for_post_non_post() {
+               $this->assertEquals( '', rest_get_route_for_post( 'garbage' ) );
+       }
+
+       /**
+        * @ticket 49116
+        */
+       public function test_rest_get_route_for_post_invalid_post_type() {
+               register_post_type( 'invalid' );
+               $post = self::factory()->post->create_and_get( array( 'post_type' => 'invalid' ) );
+               unregister_post_type( 'invalid' );
+
+               $this->assertEquals( '', rest_get_route_for_post( $post ) );
+       }
+
+       /**
+        * @ticket 49116
+        */
+       public function test_rest_get_route_for_post_non_rest() {
+               $post = self::factory()->post->create_and_get( array( 'post_type' => 'custom_css' ) );
+               $this->assertEquals( '', rest_get_route_for_post( $post ) );
+       }
+
+       /**
+        * @ticket 49116
+        */
+       public function test_rest_get_route_for_post_custom_controller() {
+               $post = self::factory()->post->create_and_get( array( 'post_type' => 'wp_block' ) );
+               $this->assertEquals( '', rest_get_route_for_post( $post ) );
+       }
+
+       /**
+        * @ticket 49116
+        */
+       public function test_rest_get_route_for_post() {
+               $post = self::factory()->post->create_and_get();
+               $this->assertEquals( '/wp/v2/posts/' . $post->ID, rest_get_route_for_post( $post ) );
+       }
+
+       /**
+        * @ticket 49116
+        */
+       public function test_rest_get_route_for_media() {
+               $post = self::factory()->attachment->create_and_get();
+               $this->assertEquals( '/wp/v2/media/' . $post->ID, rest_get_route_for_post( $post ) );
+       }
+
+       /**
+        * @ticket 49116
+        */
+       public function test_rest_get_route_for_post_id() {
+               $post = self::factory()->post->create_and_get();
+               $this->assertEquals( '/wp/v2/posts/' . $post->ID, rest_get_route_for_post( $post->ID ) );
+       }
+
+       /**
+        * @ticket 49116
+        */
+       public function test_rest_get_route_for_term_non_term() {
+               $this->assertEquals( '', rest_get_route_for_term( 'garbage' ) );
+       }
+
+       /**
+        * @ticket 49116
+        */
+       public function test_rest_get_route_for_term_invalid_term_type() {
+               register_taxonomy( 'invalid', 'post' );
+               $term = self::factory()->term->create_and_get( array( 'taxonomy' => 'invalid' ) );
+               unregister_taxonomy( 'invalid' );
+
+               $this->assertEquals( '', rest_get_route_for_term( $term ) );
+       }
+
+       /**
+        * @ticket 49116
+        */
+       public function test_rest_get_route_for_term_non_rest() {
+               $term = self::factory()->term->create_and_get( array( 'taxonomy' => 'post_format' ) );
+               $this->assertEquals( '', rest_get_route_for_term( $term ) );
+       }
+
+       /**
+        * @ticket 49116
+        */
+       public function test_rest_get_route_for_term() {
+               $term = self::factory()->term->create_and_get();
+               $this->assertEquals( '/wp/v2/tags/' . $term->term_id, rest_get_route_for_term( $term ) );
+       }
+
+       /**
+        * @ticket 49116
+        */
+       public function test_rest_get_route_for_category() {
+               $term = self::factory()->category->create_and_get();
+               $this->assertEquals( '/wp/v2/categories/' . $term->term_id, rest_get_route_for_term( $term ) );
+       }
+
+       /**
+        * @ticket 49116
+        */
+       public function test_rest_get_route_for_term_id() {
+               $term = self::factory()->term->create_and_get();
+               $this->assertEquals( '/wp/v2/tags/' . $term->term_id, rest_get_route_for_term( $term->term_id ) );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre>
</div>
</div>

</body>
</html>