292 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			292 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * Structured Data Object.
 | |
|  *
 | |
|  * @package     EDD
 | |
|  * @subpackage  StructuredData
 | |
|  * @copyright   Copyright (c) 2018, Easy Digital Downloads, LLC
 | |
|  * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
 | |
|  * @since       3.0
 | |
|  */
 | |
| namespace EDD;
 | |
| 
 | |
| // Exit if accessed directly
 | |
| defined( 'ABSPATH' ) || exit;
 | |
| 
 | |
| /**
 | |
|  * EDD_Structured_Data Class.
 | |
|  *
 | |
|  * @since 3.0
 | |
|  */
 | |
| class Structured_Data {
 | |
| 
 | |
| 	/**
 | |
| 	 * Structured data.
 | |
| 	 *
 | |
| 	 * @var array
 | |
| 	 */
 | |
| 	private $data = array();
 | |
| 
 | |
| 	/**
 | |
| 	 * Constructor.
 | |
| 	 *
 | |
| 	 * @since 3.0
 | |
| 	 */
 | |
| 	public function __construct() {
 | |
| 		$this->hooks();
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Register the hooks.
 | |
| 	 *
 | |
| 	 * @since 3.0
 | |
| 	 */
 | |
| 	private function hooks() {
 | |
| 		add_action( 'wp_footer', array( $this, 'output_structured_data' ) );
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Get raw data. This data is not formatted in any way.
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @since 3.0
 | |
| 	 *
 | |
| 	 * @return array Raw data.
 | |
| 	 */
 | |
| 	public function get_data() {
 | |
| 		return $this->data;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Set structured data. This is then output in `wp_footer`.
 | |
| 	 *
 | |
| 	 * @access private
 | |
| 	 * @since 3.0
 | |
| 	 *
 | |
| 	 * @param array $data JSON-LD structured data.
 | |
| 	 *
 | |
| 	 * @return bool True if data was set, false otherwise.
 | |
| 	 */
 | |
| 	private function set_data( $data = null ) {
 | |
| 		if ( is_null( $data ) || empty( $data ) || ! is_array( $data ) ) {
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		// Ensure the type exists and matches the format expected.
 | |
| 		if ( ! isset( $data['@type'] ) || ! preg_match( '|^[a-zA-Z]{1,20}$|', $data['@type'] ) ) {
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		$this->data[] = $data;
 | |
| 
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Generate the structured data for a given context.
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @since 3.0
 | |
| 	 *
 | |
| 	 * @param mixed string|false $context Default empty as the class figures out what the context is automatically.
 | |
| 	 * @param mixed $args Arguments that can be passed to the generators.
 | |
| 	 *
 | |
| 	 * @return string
 | |
| 	 */
 | |
| 	public function generate_structured_data( $context = false, $args = null ) {
 | |
| 		if ( is_singular( 'download' ) || 'download' === $context ) {
 | |
| 			$this->generate_download_data( $args );
 | |
| 		}
 | |
| 
 | |
| 		/**
 | |
| 		 * Allow actions to fire here to allow for different types of structured data.
 | |
| 		 *
 | |
| 		 * @since 3.0
 | |
| 		 *
 | |
| 		 * @param EDD_Structured_Data Instance of the object.
 | |
| 		 * @param mixed string|bool $context Context.
 | |
| 		 */
 | |
| 		do_action( 'edd_generate_structured_data', $this, $context );
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Generate structured data for a download.
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @since 3.0
 | |
| 	 *
 | |
| 	 * @param mixed int|EDD_Download Download ID or EDD_Download object to generate data for.
 | |
| 	 *
 | |
| 	 * @return bool True if data generated successfully, false otherwise.
 | |
| 	 */
 | |
| 	public function generate_download_data( $download = false ) {
 | |
| 		if ( false === $download || is_null( $download ) ) {
 | |
| 			global $post;
 | |
| 			$download = edd_get_download( $post->ID );
 | |
| 		} elseif ( is_int( $download ) ) {
 | |
| 			$download = edd_get_download( $download );
 | |
| 		} else {
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		// Return false if a download object could not be retrieved.
 | |
| 		if ( ! $download instanceof \EDD_Download ) {
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		$data = array(
 | |
| 			'@type'       => 'Product',
 | |
| 			'name'        => $download->post_title,
 | |
| 			'url'         => get_permalink( $download->ID ),
 | |
| 			'brand'       => array(
 | |
| 				'@type' => 'Thing',
 | |
| 				'name'  => get_bloginfo( 'name' ),
 | |
| 			),
 | |
| 			'sku'         => '-' === $download->get_sku()
 | |
| 				? $download->ID
 | |
| 				: $download->get_sku(),
 | |
| 		);
 | |
| 
 | |
| 		// Add image if it exists.
 | |
| 		$image_url = wp_get_attachment_image_url( get_post_thumbnail_id( $download->ID ) );
 | |
| 
 | |
| 		if ( false !== $image_url ) {
 | |
| 			$data['image'] = esc_url( $image_url );
 | |
| 		}
 | |
| 
 | |
| 		// Add description if it is not blank.
 | |
| 		if ( '' !== $download->post_excerpt ) {
 | |
| 			$data['description'] = $download->post_excerpt;
 | |
| 		}
 | |
| 
 | |
| 		if ( $download->has_variable_prices() ) {
 | |
| 			$variable_prices = $download->get_prices();
 | |
| 
 | |
| 			$offers = array();
 | |
| 
 | |
| 			foreach ( $variable_prices as $price ) {
 | |
| 				$offers[] = array(
 | |
| 					'@type'           => 'Offer',
 | |
| 					'price'           => $price['amount'],
 | |
| 					'priceCurrency'   => edd_get_currency(),
 | |
| 					'priceValidUntil' => date( 'c', time() + YEAR_IN_SECONDS ),
 | |
| 					'itemOffered'     => $data['name'] . ' - ' . $price['name'],
 | |
| 					'url'             => $data['url'],
 | |
| 					'availability'    => 'http://schema.org/InStock',
 | |
| 					'seller'          => array(
 | |
| 						'@type' => 'Organization',
 | |
| 						'name'  => get_bloginfo( 'name' ),
 | |
| 					),
 | |
| 				);
 | |
| 			}
 | |
| 
 | |
| 			$data['offers'] = $offers;
 | |
| 		} else {
 | |
| 			$data['offers'] = array(
 | |
| 				'@type'           => 'Offer',
 | |
| 				'price'           => $download->get_price(),
 | |
| 				'priceCurrency'   => edd_get_currency(),
 | |
| 				'priceValidUntil' => null,
 | |
| 				'url'             => $data['url'],
 | |
| 				'availability'    => 'http://schema.org/InStock',
 | |
| 				'seller'          => array(
 | |
| 					'@type' => 'Organization',
 | |
| 					'name'  => get_bloginfo( 'name' ),
 | |
| 				),
 | |
| 			);
 | |
| 		}
 | |
| 
 | |
| 		$download_categories = wp_get_post_terms( $download->ID, 'download_category' );
 | |
| 		if ( is_array( $download_categories ) && ! empty( $download_categories ) ) {
 | |
| 			$download_categories = wp_list_pluck( $download_categories, 'name' );
 | |
| 			$data['category']    = implode( ', ', $download_categories );
 | |
| 		}
 | |
| 
 | |
| 		/**
 | |
| 		 * Filter the structured data for a download.
 | |
| 		 *
 | |
| 		 * @since 3.0
 | |
| 		 *
 | |
| 		 * @param array $data Structured data for a download.
 | |
| 		 */
 | |
| 		$data = apply_filters( 'edd_generate_download_structured_data', $data );
 | |
| 
 | |
| 		$this->set_data( $data );
 | |
| 
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Sanitize the structured data.
 | |
| 	 *
 | |
| 	 * @access private
 | |
| 	 * @since 3.0
 | |
| 	 *
 | |
| 	 * @param array $data Data to be sanitized.
 | |
| 	 *
 | |
| 	 * @return array Sanitized data.
 | |
| 	 */
 | |
| 	private function sanitize_data( $data ) {
 | |
| 		$sanitized = array();
 | |
| 
 | |
| 		// Bail with an empty array if data does not exist.
 | |
| 		if ( ! $data || ! is_array( $data ) ) {
 | |
| 			return $sanitized;
 | |
| 		}
 | |
| 
 | |
| 		foreach ( $data as $key => $value ) {
 | |
| 			$key               = sanitize_text_field( $key );
 | |
| 			$sanitized[ $key ] = is_array( $value )
 | |
| 				? $this->sanitize_data( $value )
 | |
| 				: sanitize_text_field( $value );
 | |
| 		}
 | |
| 
 | |
| 		return $sanitized;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Encode the data, ready for output.
 | |
| 	 *
 | |
| 	 * @access private
 | |
| 	 * @since 3.0
 | |
| 	 */
 | |
| 	private function encoded_data() {
 | |
| 		$this->generate_structured_data();
 | |
| 
 | |
| 		// Bail if no data was generated.
 | |
| 		if ( empty( $this->data ) ) {
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		$structured_data = $this->get_data();
 | |
| 
 | |
| 		foreach ( $structured_data as $k => $v ) {
 | |
| 			$structured_data[ $k ]['@context'] = 'http://schema.org/';
 | |
| 		}
 | |
| 
 | |
| 		return wp_json_encode( $structured_data );
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Output the structured data.
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @since 3.0
 | |
| 	 *
 | |
| 	 * @return bool True by default, false if structured data does not exist.
 | |
| 	 */
 | |
| 	public function output_structured_data() {
 | |
| 		$this->data = $this->sanitize_data( $this->data );
 | |
| 
 | |
| 		$output_data = $this->encoded_data();
 | |
| 
 | |
| 		if ( empty( $output_data ) ) {
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		echo '<script type="application/ld+json">' . $output_data . '</script>';
 | |
| 
 | |
| 		return true;
 | |
| 	}
 | |
| }
 |