url = $url; $this->response = $response; $this->body = wp_remote_retrieve_body( $response ); $this->header = wp_remote_retrieve_headers( $response ); $this->response_code = wp_remote_retrieve_response_code( $response ); $this->content_type = wp_remote_retrieve_header( $response, 'content-type' ); } /** * Magic method for getter/setter. * * Provides a way to get or set properties of the Response object. * * @param string $method The name of the method being called. * @param array $params The parameters passed to the method. * * @return mixed If a getter is called, returns the value of the property. If a setter is called, sets the value of the property. */ public function __call( $method, $params ) { $var = strtolower( substr( $method, 4 ) ); if ( strncasecmp( $method, 'get', 3 ) === 0 ) { return $this->$var; } if ( strncasecmp( $method, 'set', 3 ) === 0 ) { $this->$var = $params[0]; } } /** * Check if response is a supported content type * * This function checks if the provided content type is supported. * * @return WP_Error|true Returns true if the content type is supported; otherwise, returns a WP_Error object. */ protected function check_content_type() { // not an (x)html, sgml, or xml page, no use going further if ( preg_match( '#(image|audio|video|model)/#is', $this->get_content_type() ) ) { return new WP_Error( 'unsupported_content_type', __( 'Content Type is not supported', 'webmention' ), array( 'status' => 400, ) ); } return true; } /** * Get content type of response. * * @return string|false Return either false or the stripped string */ protected function get_content_type() { $content_type = $this->content_type; // Strip any character set off the content type $content_type = explode( ';', $content_type ); if ( is_array( $content_type ) ) { $content_type = array_shift( $content_type ); } return trim( $content_type ); } /** * Takes the body and generates a DOMDocument. * * @param bool $validate_content_type Validate content type header * * @return WP_Error|DOMDocument An Error object or the DOMDocument. */ public function get_dom_document( $validate_content_type = true ) { if ( $this->dom_document instanceof DOMDocument ) { return $this->dom_document; } if ( $validate_content_type && ( ! in_array( $this->get_content_type(), array( 'text/html', 'text/xml', 'application/xhtml+xml' ), true ) ) ) { return new WP_Error( 'wrong_content_type', __( 'Cannot generate DOMDocument', 'webmention' ), array( $this->get_content_type() ) ); } $body = $this->body; if ( ! $body ) { return new WP_Error( 'empty_body', __( 'Request body has no data', 'webmention' ) ); } if ( ! is_html( $body ) ) { return new WP_Error( 'invalid_html', __( 'Request body is not valid HTML', 'webmention' ) ); } libxml_use_internal_errors( true ); if ( function_exists( 'mb_convert_encoding' ) ) { $body = mb_convert_encoding( $body, 'HTML-ENTITIES', mb_detect_encoding( $body ) ); } $dom_document = new DOMDocument(); $dom_document->loadHTML( $body ); libxml_use_internal_errors( false ); $this->dom_document = $dom_document; return $dom_document; } /** * Parses the Link Header * * @return array */ public function get_header_links() { if ( $this->header_links ) { return $this->header_links; } $links = wp_remote_retrieve_header( $this->response, 'link' ); if ( ! $links ) { return array(); } if ( ! is_array( $links ) ) { $links = explode( ',', $links ); } $items = array(); if ( is_array( $links ) && 1 <= count( $links ) ) { foreach ( $links as $link ) { $item = array(); $pieces = explode( ';', $link ); $uri = array_shift( $pieces ); foreach ( $pieces as $p ) { $elements = explode( '=', $p ); if ( is_array( $elements ) && ! empty( $elements[0] ) && ! empty( $elements[1] ) ) { $item[ trim( $elements[0] ) ] = trim( $elements[1], "\"' \n\r\t\v\x00" ); } continue; } $item['uri'] = trim( $uri, "<> \n\r\t\v\x00" ); if ( isset( $item['rel'] ) ) { $rels = explode( ' ', $item['rel'] ); foreach ( $rels as $rel ) { $item['rel'] = $rel; $items[] = $item; } } else { $items[] = $item; } } } $this->header_links = $items; return $items; } /** * Parses HTML links * * @return array An array of parsed HTML links. */ public function get_html_links() { $dom = $this->get_dom_document(); if ( ! $dom instanceof DOMDocument ) { return array(); } $xpath = new DOMXPath( $dom ); $items = array(); // check and elements foreach ( $xpath->query( '(//link|//a)[@rel and @href]' ) as $link ) { $rels = explode( ' ', $link->getAttribute( 'rel' ) ); foreach ( $rels as $rel ) { $item = array(); $item['rel'] = trim( $rel ); $item['uri'] = trim( $link->getAttribute( 'href' ) ); $item['type'] = trim( $link->getAttribute( 'type' ) ); $items[] = $item; } } return $items; } /** * Get link headers by a filter * * @param array $filter Filter link headers * for example `array( 'rel' => 'alternate', 'type' => 'application/json' )` * * @return array */ public function get_header_links_by( $filter ) { $links = $this->get_header_links(); if ( is_wp_error( $links ) ) { return array(); } $items = array(); foreach ( $links as $link ) { if ( array_intersect( $filter, $link ) === $filter ) { $items[] = $link; } } return $items; } /** * Get html link headers by a filter * * @param array $filter Filter link headers * for example `array( 'rel' => 'alternate', 'type' => 'application/json' )` * * @return array Array of links */ public function get_html_links_by( $filter ) { $links = $this->get_html_links(); if ( is_wp_error( $links ) ) { return array(); } $items = array(); foreach ( $links as $link ) { if ( array_intersect( $filter, $link ) === $filter ) { $items[] = $link; } } return $items; } /** * Get head and html links by a filter * * @param array $filter Filter links * for example `array( 'rel' => 'alternate', 'type' => 'application/json' )` * * @return array Array of links */ public function get_links_by( $filter ) { $links = array_merge( $this->get_header_links(), $this->get_html_links() ); if ( ! $links ) { return $links; } $items = array(); foreach ( $links as $link ) { if ( array_intersect( $filter, $link ) === $filter ) { $items[] = $link; } } return $items; } /** * Check if request returns an HTTP Error Code * * @return boolean */ public function is_error() { $code = $this->get_response_code(); return $code >= 400 && $code < 600; } /** * Get the HTTP Error if there is one * * @return WP_Error Returns a WP_Error object if the request fails; otherwise, returns false. */ public function get_error() { if ( ! $this->is_error() ) { return false; } return new WP_Error( 'http_error', wp_remote_retrieve_response_message( $this->get_response() ), array( 'status' => $this->get_response_code(), ) ); } }