Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
48 / 48
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
Organization_Schema_Node
100.00% covered (success)
100.00%
48 / 48
100.00% covered (success)
100.00%
3 / 3
18
100.00% covered (success)
100.00%
1 / 1
 build
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
9
 logo
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
7
 text
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * Site-level Organization Schema.org node builder.
4 *
5 * Builds the Organization JSON-LD node that represents the site as a publishing
6 * entity. Most properties come straight from existing site identity — Site Title,
7 * site URL, Site Logo / Site Icon, and Tagline — so the node is useful with zero
8 * configuration. Properties with no WordPress source (social profiles `sameAs`,
9 * `email`) come from the schema settings, passed in via `$settings`; they are
10 * simply omitted until configured, so an unconfigured site still emits a valid
11 * node.
12 *
13 * The `$settings` argument carries the effective values from
14 * {@see Schema_Settings::get_organization()}: admin overrides for site-identity
15 * fields, plus the `sameAs` / `email` values WordPress can't supply.
16 *
17 * @package automattic/jetpack-seo-package
18 */
19
20namespace Automattic\Jetpack\SEO;
21
22/**
23 * Builds the site-level Organization node.
24 */
25class Organization_Schema_Node {
26
27    /**
28     * Build the Organization node, or null when the site has no name to identify
29     * it (an Organization entity with no name is not useful, so we emit nothing
30     * rather than something invalid).
31     *
32     * @param array $settings Optional schema settings: `name`, `description`,
33     *                        `sameAs` (array of URLs), `email`. Empty values fall
34     *                        back to site identity (or are omitted entirely).
35     * @return array|null
36     */
37    public static function build( array $settings = array() ) {
38        $name = self::text( $settings['name'] ?? '' );
39        if ( '' === $name ) {
40            $name = self::text( get_bloginfo( 'name' ) );
41        }
42        if ( '' === $name ) {
43            return null;
44        }
45
46        $node = array(
47            '@type' => 'Organization',
48            '@id'   => Schema_Node_Ids::organization(),
49            'name'  => $name,
50            'url'   => home_url( '/' ),
51        );
52
53        $description = self::text( $settings['description'] ?? '' );
54        if ( '' === $description ) {
55            $description = self::text( get_bloginfo( 'description' ) );
56        }
57        if ( '' !== $description ) {
58            $node['description'] = $description;
59        }
60
61        $logo = self::logo();
62        if ( null !== $logo ) {
63            $node['logo'] = $logo;
64        }
65
66        $same_as = Schema_Settings::sanitize_url_list( $settings['sameAs'] ?? array() );
67        if ( ! empty( $same_as ) ) {
68            $node['sameAs'] = $same_as;
69        }
70
71        $email = isset( $settings['email'] ) ? sanitize_email( (string) $settings['email'] ) : '';
72        if ( '' !== $email ) {
73            // Only from explicit settings — never auto-filled from admin_email.
74            $node['email'] = $email;
75        }
76
77        return $node;
78    }
79
80    /**
81     * The site logo as an ImageObject: the Site Logo (Customizer) when set,
82     * otherwise the Site Icon. Null when the site has neither.
83     *
84     * @return array|null
85     */
86    private static function logo() {
87        $custom_logo_id = get_theme_mod( 'custom_logo' );
88        if ( $custom_logo_id ) {
89            $src = wp_get_attachment_image_src( $custom_logo_id, 'full' );
90            if ( is_array( $src ) && ! empty( $src[0] ) ) {
91                $image = array(
92                    '@type' => 'ImageObject',
93                    'url'   => $src[0],
94                );
95                if ( ! empty( $src[1] ) && ! empty( $src[2] ) ) {
96                    $image['width']  = (int) $src[1];
97                    $image['height'] = (int) $src[2];
98                }
99                return $image;
100            }
101        }
102
103        $icon_url = get_site_icon_url();
104        if ( $icon_url ) {
105            return array(
106                '@type' => 'ImageObject',
107                'url'   => $icon_url,
108            );
109        }
110
111        return null;
112    }
113
114    /**
115     * Normalize a scalar setting/site value to trimmed plain text.
116     *
117     * @param mixed $value Raw value.
118     * @return string
119     */
120    private static function text( $value ) {
121        if ( ! is_string( $value ) ) {
122            return '';
123        }
124        return trim( wp_strip_all_tags( $value ) );
125    }
126}