• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

AxeWP / wp-graphql-rank-math / 8121672810

02 Mar 2024 08:51AM UTC coverage: 92.294% (+0.1%) from 92.185%
8121672810

push

github

web-flow
chore: pin WPBrowser to <3.5 (#71)

* chore: pin WPBrowser to <3.5

* chore: add changelog

2539 of 2751 relevant lines covered (92.29%)

11.25 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.06
/src/Model/Seo.php
1
<?php
2
/**
3
 * The abstract SEO model.
4
 *
5
 * @package \WPGraphQL\RankMath\Model
6
 */
7

8
namespace WPGraphQL\RankMath\Model;
9

10
use RankMath\Frontend\Breadcrumbs as RMBreadcrumbs;
11
use RankMath\Helper as RMHelper;
12
use RankMath\Paper\Paper;
13
use WPGraphQL\Model\Model;
14

15
/**
16
 * Class - Seo
17
 *
18
 * @property string $type The type of object.
19
 */
20
abstract class Seo extends Model {
21
        /**
22
         * Stores the incoming post data
23
         *
24
         * @var \WP_Post|\WP_Term|\WP_User|\WP_Post_Type $data
25
         */
26
        protected $data;
27

28
        /**
29
         * The database id for the current object.
30
         *
31
         * @var int
32
         */
33
        protected int $database_id;
34

35
        /**
36
         * The current RankMath paper helper.
37
         *
38
         * @var \RankMath\Paper\Paper
39
         */
40
        protected $helper;
41

42
        /**
43
         * The settings prefix
44
         *
45
         * @var string
46
         */
47
        protected string $prefix;
48

49
        /**
50
         * The head markup.
51
         *
52
         * It's stored here to avoid having to query it multiple times.
53
         *
54
         * A `false` value is used to determine whether an attempt has already been made to fetch it.
55
         *
56
         * @var string|false|null
57
         */
58
        protected $full_head;
59

60
        /**
61
         * The Global Post at time of Model generation
62
         *
63
         * @var \WP_Post
64
         */
65
        protected $global_post;
66

67
        /**
68
         * Constructor.
69
         *
70
         * @param \WP_User|\WP_Term|\WP_Post|\WP_Post_Type $wp_object .
71
         * @param string                                   $capability .
72
         * @param string[]                                 $allowed_fields .
73
         */
74
        public function __construct( $wp_object, $capability = '', $allowed_fields = [] ) {
75
                $this->full_head = false;
6✔
76
                $this->data      = $wp_object;
6✔
77

78
                $allowed_fields = array_merge(
6✔
79
                        [
6✔
80
                                'breadcrumbs',
6✔
81
                                'title',
6✔
82
                                'description',
6✔
83
                                'robots',
6✔
84
                                'fullHead',
6✔
85
                                'jsonLd',
6✔
86
                                'openGraph',
6✔
87
                                'type',
6✔
88
                        ],
6✔
89
                        $allowed_fields
6✔
90
                );
6✔
91

92
                parent::__construct( $capability, $allowed_fields );
6✔
93

94
                // Seat up RM Globals.
95
                $url = $this->get_object_url();
6✔
96

97
                $this->setup_post_head( $url );
6✔
98
        }
99

100
        /**
101
         * {@inheritDoc}
102
         */
103
        public function setup(): void {
104
                Paper::reset();
6✔
105
                /** @var \RankMath\Paper\Paper $paper */
106
                $paper        = Paper::get();
6✔
107
                $this->helper = $paper;
6✔
108
        }
109

110
        /**
111
         * {@inheritDoc}
112
         */
113
        protected function init() {
114
                if ( empty( $this->fields ) ) {
6✔
115
                        $this->fields = [
6✔
116
                                'breadcrumbs'   => fn (): ?array => $this->get_breadcrumbs(),
6✔
117
                                'title'         => function (): ?string {
6✔
118
                                        $title = $this->helper->get_title();
6✔
119

120
                                        return ! empty( $title ) ? html_entity_decode( $title, ENT_QUOTES ) : null;
6✔
121
                                },
6✔
122
                                'description'   => function (): ?string {
6✔
123
                                        $description = $this->helper->get_description();
6✔
124

125
                                        return ! empty( $description ) ? html_entity_decode( $description, ENT_QUOTES ) : null;
6✔
126
                                },
6✔
127
                                'robots'        => function (): ?array {
6✔
128
                                        return $this->helper->get_robots() ?: null;
6✔
129
                                },
6✔
130
                                'canonicalUrl'  => function (): ?string {
6✔
131
                                        return $this->helper->get_canonical() ?: null;
5✔
132
                                },
6✔
133
                                'focusKeywords' => function (): ?array {
6✔
134
                                        $keywords = $this->helper->get_keywords();
5✔
135

136
                                        return ! empty( $keywords ) ? explode( ',', $keywords ) : null;
5✔
137
                                },
6✔
138
                                'fullHead'      => function (): ?string {
6✔
139
                                        $head = $this->get_head();
×
140
                                        return $head ?: null;
×
141
                                },
6✔
142
                                'jsonLd'        => static function () {
6✔
143
                                        $json = new \RankMath\Schema\JsonLD();
6✔
144
                                        $json->setup();
6✔
145
                                        ob_start();
6✔
146
                                        $json->json_ld();
6✔
147
                                        $output = ob_get_clean();
6✔
148

149
                                        return [ 'raw' => $output ?: null ];
6✔
150
                                },
6✔
151
                                'openGraph'     => function () {
6✔
152
                                        $head = $this->get_head();
2✔
153

154
                                        return ! empty( $head ) ? $this->parse_og_tags( $head ) : null;
2✔
155
                                },
6✔
156
                                'type'          => function (): string {
6✔
157
                                        return $this->get_object_type();
×
158
                                },
6✔
159
                        ];
6✔
160
                }
161
        }
162

163
        /**
164
         * Gets and parses the breadcrumbs for the object.
165
         *
166
         * @return ?array<string, string|mixed>[] The breadcrumbs.
167
         */
168
        protected function get_breadcrumbs(): ?array {
169
                // Get the crumbs and shape them.
170
                $crumbs      = RMBreadcrumbs::get()->get_crumbs();
5✔
171
                $breadcrumbs = array_map(
5✔
172
                        static function ( $crumb ) {
5✔
173
                                return [
5✔
174
                                        'text'     => $crumb[0] ?? null,
5✔
175
                                        'url'      => $crumb[1] ?? null,
5✔
176
                                        'isHidden' => ! empty( $crumb['hide_in_schema'] ),
5✔
177
                                ];
5✔
178
                        },
5✔
179
                        $crumbs
5✔
180
                );
5✔
181

182
                // Pop the current item's title.
183
                $remove_title = ( is_single( $this->database_id ) || is_page( $this->database_id ) ) && RMHelper::get_settings( 'general.breadcrumbs_remove_post_title' );
5✔
184
                if ( $remove_title ) {
5✔
185
                        array_pop( $breadcrumbs );
×
186
                }
187

188
                return ! empty( $breadcrumbs ) ? $breadcrumbs : null;
5✔
189
        }
190

191
        /**
192
         * Gets the hydrated meta, falling back to default settings.
193
         *
194
         * @param string $key The local meta key.
195
         * @param string $fallback Optional. The settings meta key.
196
         * @param string $default_value Optional. The default value.
197
         *
198
         * @return mixed|null
199
         */
200
        protected function get_meta( string $key, string $fallback = '', string $default_value = '' ) {
201
                $value = null;
6✔
202
                if ( $this->data instanceof \WP_Post ) {
6✔
203
                        $value = RMHelper::get_post_meta( $key, $this->database_id );
3✔
204
                } elseif ( $this->data instanceof \WP_Term ) {
3✔
205
                        $value = RMHelper::get_term_meta( $key, $this->database_id );
2✔
206
                } elseif ( $this->data instanceof \WP_User ) {
1✔
207
                        $value = RMHelper::get_user_meta( $key, $this->database_id );
1✔
208
                }
209

210
                if ( empty( $value ) && ! empty( $fallback ) ) {
6✔
211
                        $value = RMHelper::get_settings( "titles.{$fallback}", $default_value );
×
212
                        if ( ! empty( $value ) ) {
×
213
                                $value = RMHelper::replace_vars( $value, $this->data );
×
214
                        }
215
                }
216

217
                return ( ! empty( $value ) ? $value : $default_value ) ?: null;
6✔
218
        }
219

220
        /**
221
         * Gets the object type used to determine how the GraphQL interface should resolve.
222
         */
223
        abstract public function get_object_type(): string;
224

225
        /**
226
         * Gets the object-specific url to use for generating the RankMath <head>.
227
         */
228
        abstract protected function get_object_url(): string;
229

230
        /**
231
         * Gets the object-specific url to use for generating the RankMath <head>.
232
         *
233
         * @deprecated 0.0.8
234
         */
235
        protected function get_rest_url_param(): string {
236
                _deprecated_function( __FUNCTION__, '0.0.8', __NAMESPACE__ . '::get_object_url()' );
×
237
                return $this->get_object_url();
×
238
        }
239

240
        /**
241
         * Gets all the tags that go in the <head>.
242
         *
243
         * Shims the `RankMath\Rest\Headless::get_html_head() private method to avoid a REST Call.
244
         *
245
         * @throws \GraphQL\Error\Error When the REST request is invalid.
246
         * @throws \GraphQL\Error\UserError When REST response fails.
247
         */
248
        protected function get_head(): ?string {
249
                if ( false !== $this->full_head ) {
2✔
250
                        return $this->full_head;
×
251
                }
252

253
                ob_start();
2✔
254
                do_action( 'wp' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
2✔
255
                do_action( 'rank_math/head' );
2✔
256

257
                $head = ob_get_clean();
2✔
258

259
                $this->full_head = $head ?: null;
2✔
260

261
                return $this->full_head;
2✔
262
        }
263

264
        /**
265
         * Parses the Open Graph tags from the head.
266
         *
267
         * @param string $head The head.
268
         *
269
         * @return ?array<string, mixed> The tags.
270
         */
271
        protected function parse_og_tags( string $head ): ?array {
272
                $tags = [];
2✔
273

274
                if ( preg_match_all( '/<meta (property|name)="([^"]+)" content="([^"]+)" \/>/', $head, $matches ) ) {
2✔
275
                        $this->save_tags_from_matches( $matches, $tags );
2✔
276
                }
277

278
                return $tags ?: null;
2✔
279
        }
280

281
        /**
282
         * Saves the tags from the matches.
283
         *
284
         * @param string[][]           $matches The matches.
285
         * @param array<string, mixed> $tags The tags array reference.
286
         */
287
        private function save_tags_from_matches( array $matches, array &$tags ): void {
288
                // $matches[2] contains the OpenGraph prefix (og, article, twitter, etc ).
289
                foreach ( $matches[2] as $key => $match ) {
2✔
290
                        // Each `:` is a new level.
291
                        $parts   = explode( ':', $match );
2✔
292
                        $pointer = &$tags;
2✔
293

294
                        // Loop through each part and build the array.
295
                        foreach ( $parts as $part ) {
2✔
296
                                if ( ! isset( $pointer[ $part ] ) ) {
2✔
297
                                        $pointer[ $part ] = [];
2✔
298
                                }
299

300
                                $pointer = &$pointer[ $part ];
2✔
301
                        }
302

303
                        // Save the value.
304
                        $pointer = $matches[3][ $key ];
2✔
305
                }
306
        }
307

308
        /**
309
         * Prepare head output for a URL.
310
         *
311
         * Shims the RankMath\Rest\Headless::setup_post_head() private method to avoid a REST call.
312
         *
313
         * @param string $url The URL.
314
         */
315
        private function setup_post_head( string $url ): void {
316
                $headless = new \RankMath\Rest\Headless();
6✔
317
                // Setup WordPress.
318
                $_SERVER['REQUEST_URI'] = esc_url_raw( $headless->generate_request_uri( $url ) );
6✔
319
                remove_all_actions( 'wp' );
6✔
320
                remove_all_actions( 'parse_request' );
6✔
321
                remove_all_actions( 'rank_math/head' );
6✔
322
                remove_all_actions( 'rank_math/json_ld' );
6✔
323
                remove_all_actions( 'rank_math/opengraph/facebook' );
6✔
324
                remove_all_actions( 'rank_math/opengraph/twitter' );
6✔
325
                remove_all_actions( 'rank_math/opengraph/slack' );
6✔
326

327
                if ( $headless->is_home ) {
6✔
328
                        $GLOBALS['wp_query']->is_home = true;
1✔
329
                }
330

331
                remove_filter( 'option_rewrite_rules', [ $headless, 'fix_query_notice' ] );
6✔
332

333
                // Setup Rank Math.
334
                rank_math()->variables->setup();
6✔
335
                rank_math()->manager->load_modules();
6✔
336
                new \RankMath\Frontend\Frontend();
6✔
337
        }
338
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc