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;
|
||
|
}
|
||
|
}
|