PluginProbe
ZIP AI – AI Website Builder & AI Agent / trunk
ZIP AI – AI Website Builder & AI Agent vtrunk
0.0.13 0.0.12 0.0.11 0.0.10 0.0.9 trunk 0.0.4 0.0.5 0.0.6 0.0.7 0.0.8
zip-ai / inc / core / json-ld.php

json-ld.php in ZIP AI – AI Website Builder & AI Agent trunk, at inc/core/json-ld.php

111 lines 3.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Json Ld — the site's schema.org structured data, printed in `<head>`.
4 *
5 * The website build writes the site's JSON-LD blocks (Organization, plus
6 * LocalBusiness when the brief states an address) as one JSON string to
7 * `zip_ai_jsonld` over `/wp/v2/settings`; every front-end page prints it once
8 * as `<script type="application/ld+json">`. The option holds only JSON-LD:
9 * any other value is stored as '' and prints nothing.
10 *
11 * @since 0.0.11
12 * @package zip-ai
13 */
14
15 namespace ZipAI\MCP\Classes\Core;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit;
19 }
20
21 /**
22 * Registers and prints the JSON-LD option — see the file header.
23 */
24 class Json_Ld {
25
26 /**
27 * The JSON-LD blocks, as the JSON string the build wrote.
28 *
29 * @var string
30 */
31 const OPTION_KEY = 'zip_ai_jsonld';
32
33 /**
34 * Hook registration.
35 */
36 public static function init(): void {
37 add_action( 'init', array( __CLASS__, 'register_contract' ) );
38 add_action( 'wp_head', array( __CLASS__, 'print_script' ) );
39 }
40
41 /**
42 * Register the option for REST writes (`/wp/v2/settings`, manage_options).
43 *
44 * The group is the plugin's own, which no admin form posts: wp-admin/options.php
45 * writes null to every option of the posted group the form did not send, so
46 * under 'general' a Settings > General save would erase the build's JSON-LD.
47 */
48 public static function register_contract(): void {
49 register_setting(
50 'zip_ai',
51 self::OPTION_KEY,
52 array(
53 'type' => 'string',
54 'show_in_rest' => true,
55 'default' => '',
56 'sanitize_callback' => array( __CLASS__, 'sanitize' ),
57 )
58 );
59 }
60
61 /**
62 * Keep the value only when it is JSON-LD.
63 *
64 * @param mixed $value The submitted value.
65 * @return string The value unchanged, or '' when it is not JSON-LD.
66 */
67 public static function sanitize( $value ): string {
68 return is_string( $value ) && null !== self::decode( $value ) ? $value : '';
69 }
70
71 /**
72 * `wp_head`: print the stored blocks, re-encoded from their decoded value.
73 */
74 public static function print_script(): void {
75 $data = self::decode( get_option( self::OPTION_KEY, '' ) );
76 if ( null === $data ) {
77 return;
78 }
79 $json = wp_json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP );
80 if ( false === $json ) {
81 return;
82 }
83 wp_print_inline_script_tag( $json, array( 'type' => 'application/ld+json' ) );
84 }
85
86 /**
87 * Decode a JSON-LD document: one object carrying `@context`, or a non-empty
88 * list of such objects.
89 *
90 * @param mixed $value The stored or submitted value.
91 * @return \stdClass|array<int,\stdClass>|null The decoded document, or null when it is not JSON-LD.
92 */
93 private static function decode( $value ) {
94 if ( ! is_string( $value ) || '' === $value ) {
95 return null;
96 }
97 $data = json_decode( $value );
98 $blocks = array();
99 foreach ( is_array( $data ) ? $data : array( $data ) as $block ) {
100 if ( ! $block instanceof \stdClass || ! property_exists( $block, '@context' ) ) {
101 return null;
102 }
103 $blocks[] = $block;
104 }
105 if ( array() === $blocks ) {
106 return null;
107 }
108 return $data instanceof \stdClass ? $data : $blocks;
109 }
110 }
111