Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
277 changes: 102 additions & 175 deletions includes/classes/legacy/class-schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
/**
* Schema Class
*
* Entry point for Tour Operator structured data. Loads the shared helper class
* and the three P1 graph pieces (Trip, Accommodation, TouristDestination).
*
* When Yoast SEO is active the pieces are registered via the Yoast graph API.
* When Yoast is inactive a standalone JSON-LD block is printed in <head> using
* the same piece classes via `output_standalone_schema()`.
*
* @package Tour Operator
* @author LightSpeed
* @license GPL3
Expand All @@ -13,7 +20,7 @@
namespace lsx\legacy;

/**
* Main plugin class.
* Main Schema orchestrator class.
*
* @package Schema
* @author LightSpeed
Expand All @@ -30,16 +37,25 @@ class Schema

/**
* Constructor
*
* Loads new graph-piece classes and registers them either with Yoast SEO
* (when the WPSEO_Graph_Piece interface is present) or as a standalone
* wp_head output (when Yoast is inactive).
*/
public function __construct()
{
// Always load shared helpers and piece classes.
require_once LSX_TO_PATH . 'includes/classes/schema/class-lsx-to-schema-helpers.php';
require_once LSX_TO_PATH . 'includes/classes/schema/pieces/class-lsx-to-schema-trip.php';
require_once LSX_TO_PATH . 'includes/classes/schema/pieces/class-lsx-to-schema-accommodation.php';
require_once LSX_TO_PATH . 'includes/classes/schema/pieces/class-lsx-to-schema-destination.php';

if (interface_exists('WPSEO_Graph_Piece')) {
require_once LSX_TO_PATH . 'includes/classes/legacy/schema/class-schema-utils.php';
require_once LSX_TO_PATH . 'includes/classes/legacy/schema/class-lsx-to-schema-graph-piece.php';
require_once LSX_TO_PATH . 'includes/classes/legacy/schema/class-lsx-to-tour-schema.php';
require_once LSX_TO_PATH . 'includes/classes/legacy/schema/class-lsx-to-accommodation-schema.php';
require_once LSX_TO_PATH . 'includes/classes/legacy/schema/class-lsx-to-destination-schema.php';
// Yoast SEO is active: register new pieces via its graph API.
add_filter('wpseo_schema_graph_pieces', array($this, 'add_graph_pieces'), 11, 2);
} else {
// No Yoast: output a standalone JSON-LD graph in <head>.
add_action('wp_head', array($this, 'output_standalone_schema'), 5);
}
}

Expand All @@ -59,199 +75,110 @@ public static function get_instance()
}

/**
* Adds Schema pieces to our output.
* Adds new graph pieces to the Yoast SEO schema graph.
*
* @param array $pieces Graph pieces to output.
* @param \WPSEO_Schema_Context $context Object with context variables.
* Each piece is wrapped in a LSX_TO_Schema_Piece_Adapter so that it
* satisfies the WPSEO_Graph_Piece interface without the piece classes
* themselves depending on Yoast.
*
* @return array $pieces Graph pieces to output.
* @param array $pieces Existing graph pieces.
* @param \WPSEO_Schema_Context $context Yoast context object.
* @return array Updated graph pieces.
*/
public function add_graph_pieces($pieces, $context)
{
$pieces[] = new \LSX_TO_Tour_Schema($context);
$pieces[] = new \LSX_TO_Accommodation_Schema($context);
$pieces[] = new \LSX_TO_Destination_Schema($context);
$pieces[] = new LSX_TO_Schema_Piece_Adapter(new \lsx\schema\pieces\Trip($context));
$pieces[] = new LSX_TO_Schema_Piece_Adapter(new \lsx\schema\pieces\Accommodation($context));
$pieces[] = new LSX_TO_Schema_Piece_Adapter(new \lsx\schema\pieces\Destination($context));
return $pieces;
}

/**
* Creates the schema for the tour post type
* Prints a standalone JSON-LD schema graph when Yoast SEO is not active.
*
* @since 1.0.0
*/
public function tour_single_schema()
{
if (is_singular('tour')) {
$start_val = get_post_meta(get_the_ID(), 'booking_validity_start', false);
$end_val = get_post_meta(get_the_ID(), 'booking_validity_end', false);

if (! empty($des_list)) {
foreach ($des_list as $single_destination) {
++$i;
$url_option = get_the_permalink() . '#destination-' . $i;
$destination_name = get_the_title($single_destination);
$schema_day = array(
'@type' => 'PostalAddress',
'addressLocality' => $destination_name,
);
$des_schema[] = $schema_day;
}
}
$meta = array(
array(
'address' => $des_schema,
'telephone' => '0216713090',
),
);
$output = wp_json_encode($meta, JSON_UNESCAPED_SLASHES);
?>
<script type="application/ld+json">
<?php echo wp_kses_post($output); ?>
</script>
<?php
}
}


/**
* Creates the schema for the destination post type
* Only outputs data for the current single post when a matching piece
* reports is_needed() === true. The graph is printed as a single
* application/ld+json script with an @graph array.
*
* @since 1.0.0
* @return void
*/
public function destination_single_schema()
public function output_standalone_schema()
{
if (is_singular('destination')) {
$dest_travel_styles = get_the_terms(get_the_ID(), 'travel-style');
$destination_travel = array();
$destination_name = get_the_title();
$destination_url = get_the_permalink();
$destination_description = wp_strip_all_tags(get_the_content());
$address_accommodation = get_post_meta(get_the_ID(), 'location', true);
$street_address = $address_accommodation['address'];
$lat_address = $address_accommodation['latitude'];
$long_address = $address_accommodation['longitude'];

if (! empty($dest_travel_styles)) {
foreach ($dest_travel_styles as $single_travel_style) {
$destination_travel[] = $single_travel_style->name;
}
}
global $post;

$args = array(
'post_parent' => $post->ID,
'posts_per_page' => -1,
'post_type' => 'destination',
);

$the_query = new \WP_Query($args);
$the_regions = array();

if ($the_query->have_posts()) {
while ($the_query->have_posts()) {
$the_query->the_post();
$region_title = get_the_title();
$region_description = wp_strip_all_tags(get_the_content());

$region_list = array(
'@type' => 'TouristAttraction',
'name' => $region_title,
'description' => $region_description,
);
$the_regions[] = $region_list;
$pieces = array(
new \lsx\schema\pieces\Trip(),
new \lsx\schema\pieces\Accommodation(),
new \lsx\schema\pieces\Destination(),
);

foreach ($pieces as $piece) {
if ($piece->is_needed()) {
$graph = array(
'@context' => 'https://schema.org',
'@graph' => array($piece->generate()),
);
// JSON_HEX_TAG prevents </script> injection; JSON_HEX_AMP avoids
// HTML entity issues. JSON_UNESCAPED_UNICODE keeps readability.
$flags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP;
$json = wp_json_encode($graph, $flags);
if ($json) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<script type="application/ld+json">' . "\n" . $json . "\n</script>\n";
}
// Only one piece should match per page.
break;
}

wp_reset_postdata();

$meta = array(
'@context' => 'http://schema.org',
'@type' => 'TouristDestination',
'name' => $destination_name,
'address' => $street_address,
'description' => $destination_description,
'touristType' => $destination_travel,
'url' => $destination_url,
'geo' => array(
'@type' => 'GeoCoordinates',
'latitude' => $lat_address,
'longitude' => $long_address,
),
'containsPlace' => $the_regions,
);
$output = wp_json_encode($meta, JSON_UNESCAPED_SLASHES);
?>
<script type="application/ld+json">
<?php echo wp_kses_post($output); ?>
</script>
<?php
}
}
}

// ---------------------------------------------------------------------------
// Yoast adapter – only defined when the WPSEO_Graph_Piece interface exists.
// ---------------------------------------------------------------------------
if (interface_exists('WPSEO_Graph_Piece') && ! class_exists(__NAMESPACE__ . '\LSX_TO_Schema_Piece_Adapter')) {
/**
* Creates the schema for the accommodation post type
* Thin adapter that satisfies the WPSEO_Graph_Piece interface and
* delegates to an LSX Tour Operator schema piece class.
*
* @since 1.0.0
* This keeps the piece classes themselves free of Yoast dependencies so
* they can be instantiated and unit-tested without Yoast present.
*/
public function accommodation_single_schema()
class LSX_TO_Schema_Piece_Adapter implements \WPSEO_Graph_Piece
{
if (is_singular('accommodation')) {
$i = 0;
$spoken_languages = get_post_meta(get_the_ID(), 'spoken_languages', false);
$checkin_accommodation = get_post_meta(get_the_ID(), 'checkin_time', false);
$checkout_accommodation = get_post_meta(get_the_ID(), 'checkout_time', false);
$accommodation_expert_id = get_post_meta(get_the_ID(), 'team_to_accommodation', true);
$address_accommodation = get_post_meta(get_the_ID(), 'location', true);
$street_address = $address_accommodation['address'];
$accommodation_expert = get_the_title($accommodation_expert_id);
$title_accommodation = get_the_title();
$url_accommodation = get_the_permalink();
$description_accommodation = wp_strip_all_tags(get_the_content());
$image_accommodation = get_the_post_thumbnail_url(get_the_ID(), 'full');
$rating_accommodation = get_post_meta(get_the_ID(), 'rating', true);
$rooms_accommodation = get_post_meta(get_the_ID(), 'number_of_rooms', true);
$destinations_in_accommodation = get_post_meta(get_the_ID(), 'destination_to_accommodation', false);
$country = get_the_title($destinations_in_accommodation[0]);
$region_destinations = get_the_title($destinations_in_accommodation[1]);
$price_accommodation = get_post_meta(get_the_ID(), 'price', true);
$price_val = lsx_currencies()->base_currency;
/**
* The wrapped schema piece instance.
*
* @var object
*/
private $piece;

/**
* Constructor.
*
* @param object $piece Schema piece with is_needed() and generate() methods.
*/
public function __construct($piece)
{
$this->piece = $piece;
}

foreach ($spoken_languages as $language) {
foreach ($language as $morelanguage) {
++$i;
$url_option = get_the_permalink() . '#language-' . $i;
$language_list = array(
'@type' => 'language',
'@id' => $url_option,
'name' => $morelanguage,
);
$final_lang_list[] = $language_list;
}
}
/**
* Determines whether the piece should be added to the graph.
*
* @return bool
*/
public function is_needed()
{
return $this->piece->is_needed();
}

$meta = array(
'availableLanguage' => $final_lang_list,
'address' => array(
'addressCountry' => $country,
'addressRegion' => $region_destinations,
'streetAddress' => $street_address,
),
'checkinTime' => $checkin_accommodation,
'checkoutTime' => $checkout_accommodation,
'employee' => $accommodation_expert,
'image' => $image_accommodation,
'name' => $title_accommodation,
'numberOfRooms' => $rooms_accommodation,
'priceRange' => $price_val . $price_accommodation,
'url' => $url_accommodation,
'telephone' => '+18666434336',
);
$output = wp_json_encode($meta, JSON_UNESCAPED_SLASHES);
?>
<script type="application/ld+json">
<?php echo wp_kses_post($output); ?>
</script>
<?php
/**
* Generates the piece data.
*
* @return array
*/
public function generate()
{
return $this->piece->generate();
}
}
}
Loading