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

Yoast / wordpress-seo / dd6e866a9e6d253114633104d9e3858d807178ba

19 Jun 2024 10:03AM UTC coverage: 48.628% (-4.3%) from 52.936%
dd6e866a9e6d253114633104d9e3858d807178ba

push

github

web-flow
Merge pull request #21431 from Yoast/21429-update-copy-in-the-introduction-and-consent-modals

Updates the copy for the introduction and consent modals

7441 of 13454 branches covered (55.31%)

Branch coverage included in aggregate %.

0 of 3 new or added lines in 2 files covered. (0.0%)

3718 existing lines in 107 files now uncovered.

25100 of 53464 relevant lines covered (46.95%)

62392.47 hits per line

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

0.0
/inc/class-wpseo-replace-vars.php
1
<?php
2
/**
3
 * WPSEO plugin file.
4
 *
5
 * @package WPSEO\Internals
6
 * @since   1.5.4
7
 */
8

9
// Avoid direct calls to this file.
UNCOV
10
if ( ! defined( 'WPSEO_VERSION' ) ) {
×
UNCOV
11
        header( 'Status: 403 Forbidden' );
×
UNCOV
12
        header( 'HTTP/1.1 403 Forbidden' );
×
UNCOV
13
        exit();
×
14
}
15

16
/**
17
 * Class: WPSEO_Replace_Vars.
18
 *
19
 * This class implements the replacing of `%%variable_placeholders%%` with their real value based on the current
20
 * requested page/post/cpt/etc in text strings.
21
 */
22
class WPSEO_Replace_Vars {
23

24
        /**
25
         * Default post/page/cpt information.
26
         *
27
         * @var array
28
         */
29
        protected $defaults = [
30
                'ID'            => '',
31
                'name'          => '',
32
                'post_author'   => '',
33
                'post_content'  => '',
34
                'post_date'     => '',
35
                'post_excerpt'  => '',
36
                'post_modified' => '',
37
                'post_title'    => '',
38
                'taxonomy'      => '',
39
                'term_id'       => '',
40
                'term404'       => '',
41
        ];
42

43
        /**
44
         * Current post/page/cpt information.
45
         *
46
         * @var stdClass
47
         */
48
        protected $args;
49

50
        /**
51
         * Help texts for use in WPSEO -> Search appearance tabs.
52
         *
53
         * @var array
54
         */
55
        protected static $help_texts = [];
56

57
        /**
58
         * Register of additional variable replacements registered by other plugins/themes.
59
         *
60
         * @var array
61
         */
62
        protected static $external_replacements = [];
63

64
        /**
65
         * Setup the help texts and external replacements as statics so they will be available to all instances.
66
         *
67
         * @return void
68
         */
69
        public static function setup_statics_once() {
×
70
                if ( self::$help_texts === [] ) {
×
71
                        self::set_basic_help_texts();
×
72
                        self::set_advanced_help_texts();
×
73
                }
74

75
                if ( self::$external_replacements === [] ) {
×
76
                        /**
77
                         * Action: 'wpseo_register_extra_replacements' - Allows for registration of additional
78
                         * variables to replace.
79
                         */
80
                        do_action( 'wpseo_register_extra_replacements' );
×
81
                }
82
        }
83

84
        /**
85
         * Register new replacement %%variables%%.
86
         * For use by other plugins/themes to register extra variables.
87
         *
88
         * @see wpseo_register_var_replacement() for a usage example.
89
         *
90
         * @param string $var_to_replace   The name of the variable to replace, i.e. '%%var%%'.
91
         *                                 Note: the surrounding %% are optional.
92
         * @param mixed  $replace_function Function or method to call to retrieve the replacement value for the variable.
93
         *                                 Uses the same format as add_filter/add_action function parameter and
94
         *                                 should *return* the replacement value. DON'T echo it.
95
         * @param string $type             Type of variable: 'basic' or 'advanced', defaults to 'advanced'.
96
         * @param string $help_text        Help text to be added to the help tab for this variable.
97
         *
98
         * @return bool Whether the replacement function was succesfully registered.
99
         */
100
        public static function register_replacement( $var_to_replace, $replace_function, $type = 'advanced', $help_text = '' ) {
×
101
                $success = false;
×
102

103
                if ( is_string( $var_to_replace ) && $var_to_replace !== '' ) {
×
104
                        $var_to_replace = self::remove_var_delimiter( $var_to_replace );
×
105

106
                        if ( preg_match( '`^[A-Z0-9_-]+$`i', $var_to_replace ) === false ) {
×
107
                                trigger_error( esc_html__( 'A replacement variable can only contain alphanumeric characters, an underscore or a dash. Try renaming your variable.', 'wordpress-seo' ), E_USER_WARNING );
×
108
                        }
109
                        elseif ( strpos( $var_to_replace, 'cf_' ) === 0 || strpos( $var_to_replace, 'ct_' ) === 0 ) {
×
110
                                trigger_error( esc_html__( 'A replacement variable can not start with "%%cf_" or "%%ct_" as these are reserved for the WPSEO standard variable variables for custom fields and custom taxonomies. Try making your variable name unique.', 'wordpress-seo' ), E_USER_WARNING );
×
111
                        }
112
                        elseif ( ! method_exists( self::class, 'retrieve_' . $var_to_replace ) ) {
×
113
                                if ( $var_to_replace !== '' && ! isset( self::$external_replacements[ $var_to_replace ] ) ) {
×
114
                                        self::$external_replacements[ $var_to_replace ] = $replace_function;
×
115
                                        $replacement_variable                           = new WPSEO_Replacement_Variable( $var_to_replace, $var_to_replace, $help_text );
×
116
                                        self::register_help_text( $type, $replacement_variable );
×
117
                                        $success = true;
×
118
                                }
119
                                else {
120
                                        trigger_error( esc_html__( 'A replacement variable with the same name has already been registered. Try making your variable name unique.', 'wordpress-seo' ), E_USER_WARNING );
×
121
                                }
122
                        }
123
                        else {
124
                                trigger_error( esc_html__( 'You cannot overrule a WPSEO standard variable replacement by registering a variable with the same name. Use the "wpseo_replacements" filter instead to adjust the replacement value.', 'wordpress-seo' ), E_USER_WARNING );
×
125
                        }
126
                }
127

128
                return $success;
×
129
        }
130

131
        /**
132
         * Replace `%%variable_placeholders%%` with their real value based on the current requested page/post/cpt/etc.
133
         *
134
         * @param string $text The string to replace the variables in.
135
         * @param array  $args The object some of the replacement values might come from,
136
         *                     could be a post, taxonomy or term.
137
         * @param array  $omit Variables that should not be replaced by this function.
138
         *
139
         * @return string
140
         */
141
        public function replace( $text, $args, $omit = [] ) {
×
142

143
                $text = wp_strip_all_tags( $text );
×
144

145
                // Let's see if we can bail super early.
146
                if ( strpos( $text, '%%' ) === false ) {
×
147
                        return YoastSEO()->helpers->string->standardize_whitespace( $text );
×
148
                }
149

150
                $args = (array) $args;
×
151
                if ( isset( $args['post_content'] ) && ! empty( $args['post_content'] ) ) {
×
152
                        $args['post_content'] = YoastSEO()->helpers->string->strip_shortcode( $args['post_content'] );
×
153
                }
154
                if ( isset( $args['post_excerpt'] ) && ! empty( $args['post_excerpt'] ) ) {
×
155
                        $args['post_excerpt'] = YoastSEO()->helpers->string->strip_shortcode( $args['post_excerpt'] );
×
156
                }
157
                $this->args = (object) wp_parse_args( $args, $this->defaults );
×
158

159
                // Clean $omit array.
160
                if ( is_array( $omit ) && $omit !== [] ) {
×
161
                        $omit = array_map( [ self::class, 'remove_var_delimiter' ], $omit );
×
162
                }
163

164
                $replacements = [];
×
165
                if ( preg_match_all( '`%%([^%]+(%%single)?)%%?`iu', $text, $matches ) ) {
×
166
                        $replacements = $this->set_up_replacements( $matches, $omit );
×
167
                }
168

169
                /**
170
                 * Filter: 'wpseo_replacements' - Allow customization of the replacements before they are applied.
171
                 *
172
                 * @param array $replacements The replacements.
173
                 * @param array $args         The object some of the replacement values might come from,
174
                 *                            could be a post, taxonomy or term.
175
                 */
176
                $replacements = apply_filters( 'wpseo_replacements', $replacements, $this->args );
×
177

178
                // Do the actual replacements.
179
                if ( is_array( $replacements ) && $replacements !== [] ) {
×
180
                        $text = str_replace(
×
181
                                array_keys( $replacements ),
×
182
                                // Make sure to exclude replacement values that are arrays e.g. coming from a custom field serialized value.
183
                                array_filter( array_values( $replacements ), 'is_scalar' ),
×
184
                                $text
×
185
                        );
186
                }
187

188
                /**
189
                 * Filter: 'wpseo_replacements_final' - Allow overruling of whether or not to remove placeholders
190
                 * which didn't yield a replacement.
191
                 *
192
                 * @example <code>add_filter( 'wpseo_replacements_final', '__return_false' );</code>
193
                 *
194
                 * @param bool $final
195
                 */
196
                if ( apply_filters( 'wpseo_replacements_final', true ) === true && ( isset( $matches[1] ) && is_array( $matches[1] ) ) ) {
×
197
                        // Remove non-replaced variables.
198
                        $remove = array_diff( $matches[1], $omit ); // Make sure the $omit variables do not get removed.
×
199
                        $remove = array_map( [ self::class, 'add_var_delimiter' ], $remove );
×
200
                        $text   = str_replace( $remove, '', $text );
×
201
                }
202

203
                // Undouble separators which have nothing between them, i.e. where a non-replaced variable was removed.
204
                if ( isset( $replacements['%%sep%%'] ) && ( is_string( $replacements['%%sep%%'] ) && $replacements['%%sep%%'] !== '' ) ) {
×
205
                        $q_sep = preg_quote( $replacements['%%sep%%'], '`' );
×
206
                        $text  = preg_replace( '`' . $q_sep . '(?:\s*' . $q_sep . ')*`u', $replacements['%%sep%%'], $text );
×
207
                }
208

209
                // Remove superfluous whitespace.
210
                $text = YoastSEO()->helpers->string->standardize_whitespace( $text );
×
211

212
                return $text;
×
213
        }
214

215
        /**
216
         * Register a new replacement variable if it has not been registered already.
217
         *
218
         * @param string $var_to_replace   The name of the variable to replace, i.e. '%%var%%'.
219
         *                                 Note: the surrounding %% are optional.
220
         * @param mixed  $replace_function Function or method to call to retrieve the replacement value for the variable.
221
         *                                 Uses the same format as add_filter/add_action function parameter and
222
         *                                 should *return* the replacement value. DON'T echo it.
223
         * @param string $type             Type of variable: 'basic' or 'advanced', defaults to 'advanced'.
224
         * @param string $help_text        Help text to be added to the help tab for this variable.
225
         *
226
         * @return bool `true` if the replace var has been registered, `false` if not.
227
         */
228
        public function safe_register_replacement( $var_to_replace, $replace_function, $type = 'advanced', $help_text = '' ) {
×
229
                if ( ! $this->has_been_registered( $var_to_replace ) ) {
×
230
                        return self::register_replacement( $var_to_replace, $replace_function, $type, $help_text );
×
231
                }
232
                return false;
×
233
        }
234

235
        /**
236
         * Checks whether the given replacement variable has already been registered or not.
237
         *
238
         * @param string $replacement_variable The replacement variable to check, including the variable delimiter (e.g. `%%var%%`).
239
         *
240
         * @return bool `true` if the replacement variable has already been registered.
241
         */
242
        public function has_been_registered( $replacement_variable ) {
×
243
                $replacement_variable = self::remove_var_delimiter( $replacement_variable );
×
244

245
                return isset( self::$external_replacements[ $replacement_variable ] );
×
246
        }
247

248
        /**
249
         * Returns the list of hidden replace vars.
250
         *
251
         * E.g. the replace vars that should work, but are not advertised.
252
         *
253
         * @return string[] The list of hidden replace vars.
254
         */
255
        public function get_hidden_replace_vars() {
×
256
                return [
257
                        'currentdate',
×
258
                        'currentyear',
259
                        'currentmonth',
260
                        'currentday',
261
                        'post_year',
262
                        'post_month',
263
                        'post_day',
264
                        'author_first_name',
265
                        'author_last_name',
266
                        'permalink',
267
                        'post_content',
268
                        'category_title',
269
                ];
270
        }
271

272
        /**
273
         * Retrieve the replacements for the variables found.
274
         *
275
         * @param array $matches Variables found in the original string - regex result.
276
         * @param array $omit    Variables that should not be replaced by this function.
277
         *
278
         * @return array Retrieved replacements - this might be a smaller array as some variables
279
         *               may not yield a replacement in certain contexts.
280
         */
281
        private function set_up_replacements( $matches, $omit ) {
×
282

283
                $replacements = [];
×
284

285
                // @todo Figure out a way to deal with external functions starting with cf_/ct_.
286
                foreach ( $matches[1] as $k => $var ) {
×
287

288
                        // Don't set up replacements which should be omitted.
289
                        if ( in_array( $var, $omit, true ) ) {
×
290
                                continue;
×
291
                        }
292

293
                        // Deal with variable variable names first.
294
                        if ( strpos( $var, 'cf_' ) === 0 ) {
×
295
                                $replacement = $this->retrieve_cf_custom_field_name( $var );
×
296
                        }
297
                        elseif ( strpos( $var, 'ct_desc_' ) === 0 ) {
×
298
                                $replacement = $this->retrieve_ct_desc_custom_tax_name( $var );
×
299
                        }
300
                        elseif ( strpos( $var, 'ct_' ) === 0 ) {
×
301
                                $single      = ( isset( $matches[2][ $k ] ) && $matches[2][ $k ] !== '' );
×
302
                                $replacement = $this->retrieve_ct_custom_tax_name( $var, $single );
×
303
                        }
304
                        // Deal with non-variable variable names.
305
                        elseif ( method_exists( $this, 'retrieve_' . $var ) ) {
×
306
                                $method_name = 'retrieve_' . $var;
×
307
                                $replacement = $this->$method_name();
×
308
                        }
309
                        // Deal with externally defined variable names.
310
                        elseif ( isset( self::$external_replacements[ $var ] ) && ! is_null( self::$external_replacements[ $var ] ) ) {
×
311
                                $replacement = call_user_func( self::$external_replacements[ $var ], $var, $this->args );
×
312
                        }
313

314
                        // Replacement retrievals can return null if no replacement can be determined, root those outs.
315
                        if ( isset( $replacement ) ) {
×
316
                                $var                  = self::add_var_delimiter( $var );
×
317
                                $replacements[ $var ] = $replacement;
×
318
                        }
319
                        unset( $replacement, $single, $method_name );
×
320
                }
321

322
                return $replacements;
×
323
        }
324

325
        /* *********************** BASIC VARIABLES ************************** */
326

327
        /**
328
         * Retrieve the post/cpt categories (comma separated) for use as replacement string.
329
         *
330
         * @return string|null
331
         */
332
        private function retrieve_category() {
×
333
                $replacement = null;
×
334

335
                if ( ! empty( $this->args->ID ) ) {
×
336
                        $cat = $this->get_terms( $this->args->ID, 'category' );
×
337
                        if ( $cat !== '' ) {
×
338
                                return $cat;
×
339
                        }
340
                }
341

342
                if ( isset( $this->args->cat_name ) && ! empty( $this->args->cat_name ) ) {
×
343
                        $replacement = $this->args->cat_name;
×
344
                }
345

346
                return $replacement;
×
347
        }
348

349
        /**
350
         * Retrieve the category description for use as replacement string.
351
         *
352
         * @return string|null
353
         */
354
        private function retrieve_category_description() {
×
355
                return $this->retrieve_term_description();
×
356
        }
357

358
        /**
359
         * Retrieve the date of the post/page/cpt for use as replacement string.
360
         *
361
         * @return string|null
362
         */
363
        private function retrieve_date() {
×
364
                $replacement = null;
×
365

366
                if ( $this->args->post_date !== '' ) {
×
367
                        // Returns a string.
368
                        $replacement = YoastSEO()->helpers->date->format_translated( $this->args->post_date, get_option( 'date_format' ) );
×
369
                }
370
                elseif ( get_query_var( 'day' ) && get_query_var( 'day' ) !== '' ) {
×
371
                        // Returns a string.
372
                        $replacement = get_the_date();
×
373
                }
374
                elseif ( single_month_title( ' ', false ) && single_month_title( ' ', false ) !== '' ) {
×
375
                        // Returns a string.
376
                        $replacement = single_month_title( ' ', false );
×
377
                }
378
                elseif ( get_query_var( 'year' ) !== '' ) {
×
379
                        // Returns an integer, let's cast to string.
380
                        $replacement = (string) get_query_var( 'year' );
×
381
                }
382

383
                return $replacement;
×
384
        }
385

386
        /**
387
         * Retrieve the post/page/cpt excerpt for use as replacement string.
388
         * The excerpt will be auto-generated if it does not exist.
389
         *
390
         * @return string|null
391
         */
UNCOV
392
        private function retrieve_excerpt() {
×
UNCOV
393
                $replacement = null;
×
UNCOV
394
                $locale      = get_locale();
×
395

396
                // Japanese doesn't have a jp_JP variant in WP.
UNCOV
397
                $limit = ( $locale === 'ja' ) ? 80 : 156;
×
398

399
                // The check `post_password_required` is because excerpt must be hidden for a post with a password.
UNCOV
400
                if ( ! empty( $this->args->ID ) && ! post_password_required( $this->args->ID ) ) {
×
UNCOV
401
                        if ( $this->args->post_excerpt !== '' ) {
×
UNCOV
402
                                $replacement = wp_strip_all_tags( $this->args->post_excerpt );
×
403
                        }
UNCOV
404
                        elseif ( $this->args->post_content !== '' ) {
×
UNCOV
405
                                $content = strip_shortcodes( $this->args->post_content );
×
UNCOV
406
                                $content = wp_strip_all_tags( $content );
×
407

UNCOV
408
                                if ( mb_strlen( $content ) <= $limit ) {
×
UNCOV
409
                                        return $content;
×
410
                                }
411

UNCOV
412
                                $replacement = wp_html_excerpt( $content, $limit );
×
413

414
                                // Check if the description has space and trim the auto-generated string to a word boundary.
UNCOV
415
                                if ( strrpos( $replacement, ' ' ) ) {
×
UNCOV
416
                                        $replacement = substr( $replacement, 0, strrpos( $replacement, ' ' ) );
×
417
                                }
418
                        }
419
                }
420

UNCOV
421
                return $replacement;
×
422
        }
423

424
        /**
425
         * Retrieve the post/page/cpt excerpt for use as replacement string (without auto-generation).
426
         *
427
         * @return string|null
428
         */
429
        private function retrieve_excerpt_only() {
×
430
                $replacement = null;
×
431

432
                // The check `post_password_required` is because excerpt must be hidden for a post with a password.
433
                if ( ! empty( $this->args->ID ) && $this->args->post_excerpt !== '' && ! post_password_required( $this->args->ID ) ) {
×
434
                        $replacement = wp_strip_all_tags( $this->args->post_excerpt );
×
435
                }
436

437
                return $replacement;
×
438
        }
439

440
        /**
441
         * Retrieve the title of the parent page of the current page/cpt for use as replacement string.
442
         * Only applicable for hierarchical post types.
443
         *
444
         * @todo Check: shouldn't this use $this->args as well ?
445
         *
446
         * @return string|null
447
         */
448
        private function retrieve_parent_title() {
×
449
                $replacement = null;
×
450

451
                if ( ! empty( $this->args->ID ) ) {
×
452
                        $parent_id = wp_get_post_parent_id( $this->args->ID );
×
453
                        if ( $parent_id ) {
×
454
                                $replacement = get_the_title( $parent_id );
×
455
                        }
456
                }
457

458
                return $replacement;
×
459
        }
460

461
        /**
462
         * Retrieve the current search phrase for use as replacement string.
463
         *
464
         * @return string|null
465
         */
466
        private function retrieve_searchphrase() {
×
467
                $replacement = null;
×
468

469
                $search = get_query_var( 's' );
×
470
                if ( $search !== '' ) {
×
471
                        $replacement = esc_html( $search );
×
472
                }
473

474
                return $replacement;
×
475
        }
476

477
        /**
478
         * Retrieve the separator for use as replacement string.
479
         *
480
         * @return string Retrieves the title separator.
481
         */
482
        private function retrieve_sep() {
×
483
                return YoastSEO()->helpers->options->get_title_separator();
×
484
        }
485

486
        /**
487
         * Retrieve the site's tag line / description for use as replacement string.
488
         *
489
         * The `$replacement` variable is static because it doesn't change depending
490
         * on the context. See https://github.com/Yoast/wordpress-seo/pull/1172#issuecomment-46019482.
491
         *
492
         * @return string|null
493
         */
494
        private function retrieve_sitedesc() {
×
495
                static $replacement;
×
496

497
                if ( ! isset( $replacement ) ) {
×
498
                        $description = wp_strip_all_tags( get_bloginfo( 'description' ) );
×
499
                        if ( $description !== '' ) {
×
500
                                $replacement = $description;
×
501
                        }
502
                }
503

504
                return $replacement;
×
505
        }
506

507
        /**
508
         * Retrieve the site's name for use as replacement string.
509
         *
510
         * The `$replacement` variable is static because it doesn't change depending
511
         * on the context. See https://github.com/Yoast/wordpress-seo/pull/1172#issuecomment-46019482.
512
         *
513
         * @return string|null
514
         */
515
        private function retrieve_sitename() {
×
516
                static $replacement;
×
517

518
                if ( ! isset( $replacement ) ) {
×
519
                        $sitename = YoastSEO()->helpers->site->get_site_name();
×
520
                        if ( $sitename !== '' ) {
×
521
                                $replacement = $sitename;
×
522
                        }
523
                }
524

525
                return $replacement;
×
526
        }
527

528
        /**
529
         * Retrieve the current tag/tags for use as replacement string.
530
         *
531
         * @return string|null
532
         */
533
        private function retrieve_tag() {
×
534
                $replacement = null;
×
535

536
                if ( ! empty( $this->args->ID ) ) {
×
537
                        $tags = $this->get_terms( $this->args->ID, 'post_tag' );
×
538
                        if ( $tags !== '' ) {
×
539
                                $replacement = $tags;
×
540
                        }
541
                }
542

543
                return $replacement;
×
544
        }
545

546
        /**
547
         * Retrieve the tag description for use as replacement string.
548
         *
549
         * @return string|null
550
         */
551
        private function retrieve_tag_description() {
×
552
                return $this->retrieve_term_description();
×
553
        }
554

555
        /**
556
         * Retrieve the term description for use as replacement string.
557
         *
558
         * @return string|null
559
         */
560
        private function retrieve_term_description() {
×
561
                $replacement = null;
×
562

563
                if ( ! empty( $this->args->term_id ) && ! empty( $this->args->taxonomy ) ) {
×
564
                        $term_desc = get_term_field( 'description', $this->args->term_id, $this->args->taxonomy );
×
565
                        if ( $term_desc !== '' ) {
×
566
                                $replacement = wp_strip_all_tags( $term_desc );
×
567
                        }
568
                }
569

570
                return $replacement;
×
571
        }
572

573
        /**
574
         * Retrieve the term name for use as replacement string.
575
         *
576
         * @return string|null
577
         */
578
        private function retrieve_term_title() {
×
579
                $replacement = null;
×
580

581
                if ( ! empty( $this->args->taxonomy ) && ! empty( $this->args->name ) ) {
×
582
                        $replacement = $this->args->name;
×
583
                }
584

585
                return $replacement;
×
586
        }
587

588
        /**
589
         * Retrieve the title of the post/page/cpt for use as replacement string.
590
         *
591
         * @return string|null
592
         */
593
        private function retrieve_title() {
×
594
                $replacement = null;
×
595

596
                if ( is_string( $this->args->post_title ) && $this->args->post_title !== '' ) {
×
597
                        $replacement = $this->args->post_title;
×
598
                }
599

600
                return $replacement;
×
601
        }
602

603
        /**
604
         * Retrieve primary category for use as replacement string.
605
         *
606
         * @return bool|int|null
607
         */
608
        private function retrieve_primary_category() {
×
609
                $primary_category = null;
×
610

611
                if ( ! empty( $this->args->ID ) ) {
×
612
                        $wpseo_primary_category = new WPSEO_Primary_Term( 'category', $this->args->ID );
×
613

614
                        $term_id = $wpseo_primary_category->get_primary_term();
×
615
                        $term    = get_term( $term_id );
×
616

617
                        if ( ! is_wp_error( $term ) && ! empty( $term ) ) {
×
618
                                $primary_category = $term->name;
×
619
                        }
620
                }
621

622
                return $primary_category;
×
623
        }
624

625
        /**
626
         * Retrieve the string generated by get_the_archive_title().
627
         *
628
         * @return string|null
629
         */
630
        private function retrieve_archive_title() {
×
631
                return get_the_archive_title();
×
632
        }
633

634
        /* *********************** ADVANCED VARIABLES ************************** */
635

636
        /**
637
         * Determine the page numbering of the current post/page/cpt.
638
         *
639
         * @param string $request Either 'nr'|'max' - whether to return the page number or the max number of pages.
640
         *
641
         * @return int|null
642
         */
643
        private function determine_pagenumbering( $request = 'nr' ) {
×
644
                global $wp_query, $post;
×
645
                $max_num_pages = null;
×
646
                $page_number   = null;
×
647

648
                $max_num_pages = 1;
×
649

650
                if ( ! is_singular() ) {
×
651
                        $page_number = get_query_var( 'paged' );
×
652
                        if ( $page_number === 0 || $page_number === '' ) {
×
653
                                $page_number = 1;
×
654
                        }
655

656
                        if ( ! empty( $wp_query->max_num_pages ) ) {
×
657
                                $max_num_pages = $wp_query->max_num_pages;
×
658
                        }
659
                }
660
                else {
661
                        $page_number = get_query_var( 'page' );
×
662
                        if ( $page_number === 0 || $page_number === '' ) {
×
663
                                $page_number = 1;
×
664
                        }
665

666
                        if ( isset( $post->post_content ) ) {
×
667
                                $max_num_pages = ( substr_count( $post->post_content, '<!--nextpage-->' ) + 1 );
×
668
                        }
669
                }
670

671
                $return = null;
×
672

673
                switch ( $request ) {
674
                        case 'nr':
×
675
                                $return = $page_number;
×
676
                                break;
×
677
                        case 'max':
×
678
                                $return = $max_num_pages;
×
679
                                break;
×
680
                }
681

682
                return $return;
×
683
        }
684

685
        /**
686
         * Determine the post type names for the current post/page/cpt.
687
         *
688
         * @param string $request Either 'single'|'plural' - whether to return the single or plural form.
689
         *
690
         * @return string|null
691
         */
692
        private function determine_pt_names( $request = 'single' ) {
×
693
                global $wp_query;
×
694
                $pt_single = null;
×
695
                $pt_plural = null;
×
696
                $post_type = '';
×
697

698
                if ( isset( $wp_query->query_vars['post_type'] ) && ( ( is_string( $wp_query->query_vars['post_type'] ) && $wp_query->query_vars['post_type'] !== '' ) || ( is_array( $wp_query->query_vars['post_type'] ) && $wp_query->query_vars['post_type'] !== [] ) ) ) {
×
699
                        $post_type = $wp_query->query_vars['post_type'];
×
700
                }
701
                elseif ( isset( $this->args->post_type ) && ( is_string( $this->args->post_type ) && $this->args->post_type !== '' ) ) {
×
702
                        $post_type = $this->args->post_type;
×
703
                }
704
                else {
705
                        // Make it work in preview mode.
706
                        $post = $wp_query->get_queried_object();
×
707
                        if ( $post instanceof WP_Post ) {
×
708
                                $post_type = $post->post_type;
×
709
                        }
710
                }
711

712
                if ( is_array( $post_type ) ) {
×
713
                        $post_type = reset( $post_type );
×
714
                }
715

716
                if ( $post_type !== '' ) {
×
717
                        $pt        = get_post_type_object( $post_type );
×
718
                        $pt_single = $pt->name;
×
719
                        $pt_plural = $pt->name;
×
720
                        if ( isset( $pt->labels->singular_name ) ) {
×
721
                                $pt_single = $pt->labels->singular_name;
×
722
                        }
723
                        if ( isset( $pt->labels->name ) ) {
×
724
                                $pt_plural = $pt->labels->name;
×
725
                        }
726
                }
727

728
                $return = null;
×
729

730
                switch ( $request ) {
731
                        case 'single':
×
732
                                $return = $pt_single;
×
733
                                break;
×
734
                        case 'plural':
×
735
                                $return = $pt_plural;
×
736
                                break;
×
737
                }
738

739
                return $return;
×
740
        }
741

742
        /**
743
         * Retrieve the attachment caption for use as replacement string.
744
         *
745
         * @return string|null
746
         */
747
        private function retrieve_caption() {
×
748
                return $this->retrieve_excerpt_only();
×
749
        }
750

751
        /**
752
         * Retrieve a post/page/cpt's custom field value for use as replacement string.
753
         *
754
         * @param string $var_to_replace The complete variable to replace which includes the name of
755
         *                               the custom field which value is to be retrieved.
756
         *
757
         * @return string|null
758
         */
759
        private function retrieve_cf_custom_field_name( $var_to_replace ) {
×
760
                $replacement = null;
×
761

762
                if ( is_string( $var_to_replace ) && $var_to_replace !== '' ) {
×
763
                        $field = substr( $var_to_replace, 3 );
×
764
                        if ( ! empty( $this->args->ID ) ) {
×
765
                                // Post meta can be arrays and in this case we need to exclude them.
766
                                $name = get_post_meta( $this->args->ID, $field, true );
×
767
                                if ( $name !== '' && ! is_array( $name ) ) {
×
768
                                        $replacement = $name;
×
769
                                }
770
                        }
771
                        elseif ( ! empty( $this->args->term_id ) ) {
×
772
                                $name = get_term_meta( $this->args->term_id, $field, true );
×
773
                                if ( $name !== '' ) {
×
774
                                        $replacement = $name;
×
775
                                }
776
                        }
777
                }
778

779
                return $replacement;
×
780
        }
781

782
        /**
783
         * Retrieve a post/page/cpt's custom taxonomies for use as replacement string.
784
         *
785
         * @param string $var_to_replace The complete variable to replace which includes the name of
786
         *                               the custom taxonomy which value(s) is to be retrieved.
787
         * @param bool   $single         Whether to retrieve only the first or all values for the taxonomy.
788
         *
789
         * @return string|null
790
         */
791
        private function retrieve_ct_custom_tax_name( $var_to_replace, $single = false ) {
×
792
                $replacement = null;
×
793

794
                if ( ( is_string( $var_to_replace ) && $var_to_replace !== '' ) && ! empty( $this->args->ID ) ) {
×
795
                        $tax  = substr( $var_to_replace, 3 );
×
796
                        $name = $this->get_terms( $this->args->ID, $tax, $single );
×
797
                        if ( $name !== '' ) {
×
798
                                $replacement = $name;
×
799
                        }
800
                }
801

802
                return $replacement;
×
803
        }
804

805
        /**
806
         * Retrieve a post/page/cpt's custom taxonomies description for use as replacement string.
807
         *
808
         * @param string $var_to_replace The complete variable to replace which includes the name of
809
         *                               the custom taxonomy which description is to be retrieved.
810
         *
811
         * @return string|null
812
         */
813
        private function retrieve_ct_desc_custom_tax_name( $var_to_replace ) {
×
814
                $replacement = null;
×
815

816
                if ( is_string( $var_to_replace ) && $var_to_replace !== '' ) {
×
817
                        $tax = substr( $var_to_replace, 8 );
×
818
                        if ( ! empty( $this->args->ID ) ) {
×
819
                                $terms = get_the_terms( $this->args->ID, $tax );
×
820
                                if ( is_array( $terms ) && $terms !== [] ) {
×
821
                                        $term      = current( $terms );
×
822
                                        $term_desc = get_term_field( 'description', $term->term_id, $tax );
×
823
                                        if ( $term_desc !== '' ) {
×
824
                                                $replacement = wp_strip_all_tags( $term_desc );
×
825
                                        }
826
                                }
827
                        }
828
                }
829

830
                return $replacement;
×
831
        }
832

833
        /**
834
         * Retrieve the current date for use as replacement string.
835
         *
836
         * The `$replacement` variable is static because it doesn't change depending
837
         * on the context. See https://github.com/Yoast/wordpress-seo/pull/1172#issuecomment-46019482.
838
         *
839
         * @return string The formatted current date.
840
         */
841
        private function retrieve_currentdate() {
×
842
                static $replacement;
×
843

844
                if ( ! isset( $replacement ) ) {
×
845
                        $replacement = date_i18n( get_option( 'date_format' ) );
×
846
                }
847

848
                return $replacement;
×
849
        }
850

851
        /**
852
         * Retrieve the current day for use as replacement string.
853
         *
854
         * The `$replacement` variable is static because it doesn't change depending
855
         * on the context. See https://github.com/Yoast/wordpress-seo/pull/1172#issuecomment-46019482.
856
         *
857
         * @return string The current day.
858
         */
859
        private function retrieve_currentday() {
×
860
                static $replacement;
×
861

862
                if ( ! isset( $replacement ) ) {
×
863
                        $replacement = date_i18n( 'j' );
×
864
                }
865

866
                return $replacement;
×
867
        }
868

869
        /**
870
         * Retrieve the current month for use as replacement string.
871
         *
872
         * The `$replacement` variable is static because it doesn't change depending
873
         * on the context. See https://github.com/Yoast/wordpress-seo/pull/1172#issuecomment-46019482.
874
         *
875
         * @return string The current month.
876
         */
877
        private function retrieve_currentmonth() {
×
878
                static $replacement;
×
879

880
                if ( ! isset( $replacement ) ) {
×
881
                        $replacement = date_i18n( 'F' );
×
882
                }
883

884
                return $replacement;
×
885
        }
886

887
        /**
888
         * Retrieve the current time for use as replacement string.
889
         *
890
         * The `$replacement` variable is static because it doesn't change depending
891
         * on the context. See https://github.com/Yoast/wordpress-seo/pull/1172#issuecomment-46019482.
892
         *
893
         * @return string The formatted current time.
894
         */
895
        private function retrieve_currenttime() {
×
896
                static $replacement;
×
897

898
                if ( ! isset( $replacement ) ) {
×
899
                        $replacement = date_i18n( get_option( 'time_format' ) );
×
900
                }
901

902
                return $replacement;
×
903
        }
904

905
        /**
906
         * Retrieve the current year for use as replacement string.
907
         *
908
         * The `$replacement` variable is static because it doesn't change depending
909
         * on the context. See https://github.com/Yoast/wordpress-seo/pull/1172#issuecomment-46019482.
910
         *
911
         * @return string The current year.
912
         */
913
        private function retrieve_currentyear() {
×
914
                static $replacement;
×
915

916
                if ( ! isset( $replacement ) ) {
×
917
                        $replacement = date_i18n( 'Y' );
×
918
                }
919

920
                return $replacement;
×
921
        }
922

923
        /**
924
         * Retrieve the post/page/cpt's focus keyword for use as replacement string.
925
         *
926
         * @return string|null
927
         */
928
        private function retrieve_focuskw() {
×
929
                // Retrieve focuskw from a Post.
930
                if ( ! empty( $this->args->ID ) ) {
×
931
                        $focus_kw = WPSEO_Meta::get_value( 'focuskw', $this->args->ID );
×
932
                        if ( $focus_kw !== '' ) {
×
933
                                return $focus_kw;
×
934
                        }
935

936
                        return null;
×
937
                }
938

939
                // Retrieve focuskw from a Term.
940
                if ( ! empty( $this->args->term_id ) ) {
×
941
                        $focus_kw = WPSEO_Taxonomy_Meta::get_term_meta( $this->args->term_id, $this->args->taxonomy, 'focuskw' );
×
942
                        if ( $focus_kw !== '' ) {
×
943
                                return $focus_kw;
×
944
                        }
945
                }
946

947
                return null;
×
948
        }
949

950
        /**
951
         * Retrieve the post/page/cpt ID for use as replacement string.
952
         *
953
         * @return string|null
954
         */
955
        private function retrieve_id() {
×
956
                $replacement = null;
×
957

958
                if ( ! empty( $this->args->ID ) ) {
×
959
                        // The post/page/cpt ID is an integer, let's cast to string.
960
                        $replacement = (string) $this->args->ID;
×
961
                }
962

963
                return $replacement;
×
964
        }
965

966
        /**
967
         * Retrieve the post/page/cpt modified time for use as replacement string.
968
         *
969
         * @return string|null
970
         */
971
        private function retrieve_modified() {
×
972
                $replacement = null;
×
973

974
                if ( ! empty( $this->args->post_modified ) ) {
×
975
                        $replacement = YoastSEO()->helpers->date->format_translated( $this->args->post_modified, get_option( 'date_format' ) );
×
976
                }
977

978
                return $replacement;
×
979
        }
980

981
        /**
982
         * Retrieve the post/page/cpt author's "nice name" for use as replacement string.
983
         *
984
         * @return string|null
985
         */
986
        private function retrieve_name() {
×
987
                $replacement = null;
×
988

989
                $user_id = (int) $this->retrieve_userid();
×
990
                $name    = get_the_author_meta( 'display_name', $user_id );
×
991
                if ( $name !== '' ) {
×
992
                        $replacement = $name;
×
993
                }
994

995
                return $replacement;
×
996
        }
997

998
        /**
999
         * Retrieve the post/page/cpt author's users description for use as a replacement string.
1000
         *
1001
         * @return string|null
1002
         */
1003
        private function retrieve_user_description() {
×
1004
                $replacement = null;
×
1005

1006
                $user_id     = (int) $this->retrieve_userid();
×
1007
                $description = get_the_author_meta( 'description', $user_id );
×
1008
                if ( $description !== '' ) {
×
1009
                        $replacement = $description;
×
1010
                }
1011

1012
                return $replacement;
×
1013
        }
1014

1015
        /**
1016
         * Retrieve the current page number with context (i.e. 'page 2 of 4') for use as replacement string.
1017
         *
1018
         * @return string
1019
         */
1020
        private function retrieve_page() {
×
1021
                $replacement = null;
×
1022

1023
                $max = $this->determine_pagenumbering( 'max' );
×
1024
                $nr  = $this->determine_pagenumbering( 'nr' );
×
1025
                $sep = $this->retrieve_sep();
×
1026

1027
                if ( $max > 1 && $nr > 1 ) {
×
1028
                        /* translators: 1: current page number, 2: total number of pages. */
1029
                        $replacement = sprintf( $sep . ' ' . __( 'Page %1$d of %2$d', 'wordpress-seo' ), $nr, $max );
×
1030
                }
1031

1032
                return $replacement;
×
1033
        }
1034

1035
        /**
1036
         * Retrieve the current page number for use as replacement string.
1037
         *
1038
         * @return string|null
1039
         */
1040
        private function retrieve_pagenumber() {
×
1041
                $replacement = null;
×
1042

1043
                $nr = $this->determine_pagenumbering( 'nr' );
×
1044
                if ( isset( $nr ) && $nr > 0 ) {
×
1045
                        $replacement = (string) $nr;
×
1046
                }
1047

1048
                return $replacement;
×
1049
        }
1050

1051
        /**
1052
         * Retrieve the current page total for use as replacement string.
1053
         *
1054
         * @return string|null
1055
         */
1056
        private function retrieve_pagetotal() {
×
1057
                $replacement = null;
×
1058

1059
                $max = $this->determine_pagenumbering( 'max' );
×
1060
                if ( isset( $max ) && $max > 0 ) {
×
1061
                        $replacement = (string) $max;
×
1062
                }
1063

1064
                return $replacement;
×
1065
        }
1066

1067
        /**
1068
         * Retrieve the post type plural label for use as replacement string.
1069
         *
1070
         * @return string|null
1071
         */
1072
        private function retrieve_pt_plural() {
×
1073
                $replacement = null;
×
1074

1075
                $name = $this->determine_pt_names( 'plural' );
×
1076
                if ( isset( $name ) && $name !== '' ) {
×
1077
                        $replacement = $name;
×
1078
                }
1079

1080
                return $replacement;
×
1081
        }
1082

1083
        /**
1084
         * Retrieve the post type single label for use as replacement string.
1085
         *
1086
         * @return string|null
1087
         */
1088
        private function retrieve_pt_single() {
×
1089
                $replacement = null;
×
1090

1091
                $name = $this->determine_pt_names( 'single' );
×
1092
                if ( isset( $name ) && $name !== '' ) {
×
1093
                        $replacement = $name;
×
1094
                }
1095

1096
                return $replacement;
×
1097
        }
1098

1099
        /**
1100
         * Retrieve the slug which caused the 404 for use as replacement string.
1101
         *
1102
         * @return string|null
1103
         */
1104
        private function retrieve_term404() {
×
1105
                $replacement = null;
×
1106

1107
                if ( $this->args->term404 !== '' ) {
×
1108
                        $replacement = sanitize_text_field( str_replace( '-', ' ', $this->args->term404 ) );
×
1109
                }
1110
                else {
1111
                        $error_request = get_query_var( 'pagename' );
×
1112
                        if ( $error_request !== '' ) {
×
1113
                                $replacement = sanitize_text_field( str_replace( '-', ' ', $error_request ) );
×
1114
                        }
1115
                        else {
1116
                                $error_request = get_query_var( 'name' );
×
1117
                                if ( $error_request !== '' ) {
×
1118
                                        $replacement = sanitize_text_field( str_replace( '-', ' ', $error_request ) );
×
1119
                                }
1120
                        }
1121
                }
1122

1123
                return $replacement;
×
1124
        }
1125

1126
        /**
1127
         * Retrieve the post/page/cpt author's user id for use as replacement string.
1128
         *
1129
         * @return string
1130
         */
1131
        private function retrieve_userid() {
×
1132
                // The user ID is an integer, let's cast to string.
1133
                $replacement = ! empty( $this->args->post_author ) ? (string) $this->args->post_author : (string) get_query_var( 'author' );
×
1134

1135
                return $replacement;
×
1136
        }
1137

1138
        /**
1139
         * Retrieve the post/page/cpt's published year for use as replacement string.
1140
         *
1141
         * @return string|null
1142
         */
1143
        private function retrieve_post_year() {
×
1144
                if ( empty( $this->args->ID ) ) {
×
1145
                        return null;
×
1146
                }
1147

1148
                return get_the_date( 'Y', $this->args->ID );
×
1149
        }
1150

1151
        /**
1152
         * Retrieve the post/page/cpt's published month for use as replacement string.
1153
         *
1154
         * @return string|null
1155
         */
1156
        private function retrieve_post_month() {
×
1157
                if ( empty( $this->args->ID ) ) {
×
1158
                        return null;
×
1159
                }
1160

1161
                return get_the_date( 'F', $this->args->ID );
×
1162
        }
1163

1164
        /**
1165
         * Retrieve the post/page/cpt's published day for use as replacement string.
1166
         *
1167
         * @return string|null
1168
         */
1169
        private function retrieve_post_day() {
×
1170
                if ( empty( $this->args->ID ) ) {
×
1171
                        return null;
×
1172
                }
1173

1174
                return get_the_date( 'd', $this->args->ID );
×
1175
        }
1176

1177
        /**
1178
         * Retrieve the post/page/cpt author's first name for use as replacement string.
1179
         *
1180
         * @return string|null
1181
         */
1182
        private function retrieve_author_first_name() {
×
1183
                $replacement = null;
×
1184

1185
                $user_id = (int) $this->retrieve_userid();
×
1186
                $name    = get_the_author_meta( 'first_name', $user_id );
×
1187
                if ( $name !== '' ) {
×
1188
                        $replacement = $name;
×
1189
                }
1190

1191
                return $replacement;
×
1192
        }
1193

1194
        /**
1195
         * Retrieve the post/page/cpt author's last name for use as replacement string.
1196
         *
1197
         * @return string|null
1198
         */
1199
        private function retrieve_author_last_name() {
×
1200
                $replacement = null;
×
1201

1202
                $user_id = (int) $this->retrieve_userid();
×
1203
                $name    = get_the_author_meta( 'last_name', $user_id );
×
1204
                if ( $name !== '' ) {
×
1205
                        $replacement = $name;
×
1206
                }
1207

1208
                return $replacement;
×
1209
        }
1210

1211
        /**
1212
         * Retrieve the post/page/cpt permalink for use as replacement string.
1213
         *
1214
         * @return string|null
1215
         */
1216
        private function retrieve_permalink() {
×
1217
                if ( empty( $this->args->ID ) ) {
×
1218
                        return null;
×
1219
                }
1220

1221
                return get_permalink( $this->args->ID );
×
1222
        }
1223

1224
        /**
1225
         * Retrieve the post/page/cpt content for use as replacement string.
1226
         *
1227
         * @return string|null
1228
         */
1229
        private function retrieve_post_content() {
×
1230
                $replacement = null;
×
1231

1232
                // The check `post_password_required` is because content must be hidden for a post with a password.
1233
                if ( ! empty( $this->args->ID ) && $this->args->post_content !== '' && ! post_password_required( $this->args->ID ) ) {
×
1234
                        $content     = strip_shortcodes( $this->args->post_content );
×
1235
                        $replacement = wp_strip_all_tags( $content );
×
1236
                }
1237

1238
                return $replacement;
×
1239
        }
1240

1241
        /**
1242
         * Retrieve the current or first category title. To be used for import data from AIOSEO.
1243
         * The code derives from AIOSEO's way of dealing with that var, so we can ensure 100% seamless transition.
1244
         *
1245
         * @return string|null
1246
         */
1247
        private function retrieve_category_title() {
×
1248
                if ( empty( $this->args ) || empty( $this->args->ID ) ) {
×
1249
                        return null;
×
1250
                }
1251
                $post_id = $this->args->ID;
×
1252

1253
                $post       = get_post( $post_id );
×
1254
                $taxonomies = get_object_taxonomies( $post, 'objects' );
×
1255

1256
                foreach ( $taxonomies as $taxonomy_slug => $taxonomy ) {
×
1257
                        if ( ! $taxonomy->hierarchical ) {
×
1258
                                continue;
×
1259
                        }
1260
                        $post_terms = get_the_terms( $post_id, $taxonomy_slug );
×
1261
                        if ( is_array( $post_terms ) && count( $post_terms ) > 0 ) {
×
1262
                                // AiOSEO takes the name of whatever the first hierarchical taxonomy is.
1263
                                $term = $post_terms[0];
×
1264
                                if ( $term ) {
×
1265
                                        return $term->name;
×
1266
                                }
1267
                        }
1268
                }
1269

1270
                return null;
×
1271
        }
1272

1273
        /* *********************** HELP TEXT RELATED ************************** */
1274

1275
        /**
1276
         * Set the help text for a user/plugin/theme defined extra variable.
1277
         *
1278
         * @param string                     $type                 Type of variable: 'basic' or 'advanced'.
1279
         * @param WPSEO_Replacement_Variable $replacement_variable The replacement variable to register.
1280
         *
1281
         * @return void
1282
         */
1283
        private static function register_help_text( $type, WPSEO_Replacement_Variable $replacement_variable ) {
×
1284
                $identifier = $replacement_variable->get_variable();
×
1285

1286
                if ( ( is_string( $type ) && in_array( $type, [ 'basic', 'advanced' ], true ) )
×
1287
                        && ( $identifier !== '' && ! isset( self::$help_texts[ $type ][ $identifier ] ) )
×
1288
                ) {
1289
                        self::$help_texts[ $type ][ $identifier ] = $replacement_variable;
×
1290
                }
1291
        }
1292

1293
        /**
1294
         * Generates a list of replacement variables based on the help texts.
1295
         *
1296
         * @return array List of replace vars.
1297
         */
1298
        public function get_replacement_variables_with_labels() {
×
1299
                self::setup_statics_once();
×
1300

1301
                $custom_variables = [];
×
1302
                foreach ( array_merge( WPSEO_Custom_Fields::get_custom_fields(), WPSEO_Custom_Taxonomies::get_custom_taxonomies() ) as $custom_variable ) {
×
1303
                        $custom_variables[ $custom_variable ] = new WPSEO_Replacement_Variable( $custom_variable, $this->get_label( $custom_variable ), '' );
×
1304
                }
1305

1306
                $replacement_variables = array_filter(
×
1307
                        array_merge( self::$help_texts['basic'], self::$help_texts['advanced'] ),
×
1308
                        [ $this, 'is_not_prefixed' ],
×
1309
                        ARRAY_FILTER_USE_KEY
1310
                );
1311

1312
                $hidden = $this->get_hidden_replace_vars();
1313

1314
                return array_values(
1315
                        array_map(
1316
                                static function ( WPSEO_Replacement_Variable $replacement_variable ) use ( $hidden ) {
1317
                                        $name = $replacement_variable->get_variable();
1318

UNCOV
1319
                                        return [
×
1320
                                                'name'   => $name,
×
1321
                                                'value'  => '',
×
1322
                                                'label'  => $replacement_variable->get_label(),
1323
                                                'hidden' => in_array( $name, $hidden, true ),
×
UNCOV
1324
                                        ];
×
1325
                                },
1326
                                array_merge( $replacement_variables, $custom_variables )
1327
                        )
1328
                );
1329
        }
1330

1331
        /**
1332
         * Generates a list of replacement variables based on the help texts.
1333
         *
1334
         * @return array List of replace vars.
1335
         */
1336
        public function get_replacement_variables_list() {
×
1337
                self::setup_statics_once();
×
1338

1339
                $replacement_variables = array_merge(
×
1340
                        $this->get_replacement_variables(),
×
1341
                        WPSEO_Custom_Fields::get_custom_fields(),
1342
                        WPSEO_Custom_Taxonomies::get_custom_taxonomies()
UNCOV
1343
                );
×
1344

1345
                return array_map( [ $this, 'format_replacement_variable' ], $replacement_variables );
1346
        }
1347

1348
        /**
1349
         * Creates a merged associative array of both the basic and advanced help texts.
1350
         *
1351
         * @return array Array with the replacement variables.
1352
         */
1353
        private function get_replacement_variables() {
×
1354
                $help_texts = array_merge( self::$help_texts['basic'], self::$help_texts['advanced'] );
×
1355

1356
                return array_filter( array_keys( $help_texts ), [ $this, 'is_not_prefixed' ] );
1357
        }
1358

1359
        /**
1360
         * Checks whether the replacement variable contains a `ct_` or `cf_` prefix, because they follow different logic.
1361
         *
1362
         * @param string $replacement_variable The replacement variable.
1363
         *
1364
         * @return bool True when the replacement variable is not prefixed.
1365
         */
1366
        private function is_not_prefixed( $replacement_variable ) {
×
1367
                $prefixes = [ 'cf_', 'ct_' ];
1368
                $prefix   = $this->get_prefix( $replacement_variable );
×
1369

1370
                return ! in_array( $prefix, $prefixes, true );
1371
        }
1372

1373
        /**
1374
         * Strip the prefix from a replacement variable name.
1375
         *
1376
         * @param string $replacement_variable The replacement variable.
1377
         *
1378
         * @return string The replacement variable name without the prefix.
1379
         */
1380
        private function strip_prefix( $replacement_variable ) {
1381
                return substr( $replacement_variable, 3 );
1382
        }
1383

1384
        /**
1385
         * Gets the prefix from a replacement variable name.
1386
         *
1387
         * @param string $replacement_variable The replacement variable.
1388
         *
1389
         * @return string The prefix of the replacement variable.
1390
         */
1391
        private function get_prefix( $replacement_variable ) {
1392
                return substr( $replacement_variable, 0, 3 );
1393
        }
1394

1395
        /**
1396
         * Strips 'desc_' if present, and appends ' description' at the end.
1397
         *
1398
         * @param string $label The replacement variable.
1399
         *
1400
         * @return string The altered replacement variable name.
1401
         */
1402
        private function handle_description( $label ) {
1403
                if ( strpos( $label, 'desc_' ) === 0 ) {
1404
                        return substr( $label, 5 ) . ' description';
1405
                }
1406

1407
                return $label;
1408
        }
1409

1410
        /**
1411
         * Creates a label for prefixed replacement variables that matches the format in the editors.
1412
         *
1413
         * @param string $replacement_variable The replacement variable.
1414
         *
1415
         * @return string The replacement variable label.
1416
         */
1417
        private function get_label( $replacement_variable ) {
×
1418
                $prefix = $this->get_prefix( $replacement_variable );
×
1419
                if ( $prefix === 'cf_' ) {
1420
                        return $this->strip_prefix( $replacement_variable ) . ' (custom field)';
1421
                }
1422

1423
                if ( $prefix === 'ct_' ) {
×
1424
                        $label = $this->strip_prefix( $replacement_variable );
×
1425
                        $label = $this->handle_description( $label );
1426
                        return ucfirst( $label . ' (custom taxonomy)' );
1427
                }
1428

1429
                if ( $prefix === 'pt_' ) {
×
1430
                        if ( $replacement_variable === 'pt_single' ) {
1431
                                return 'Post type (singular)';
1432
                        }
1433

1434
                        return 'Post type (plural)';
1435
                }
1436

1437
                return '';
1438
        }
1439

1440
        /**
1441
         * Formats the replacement variables.
1442
         *
1443
         * @param string $replacement_variable The replacement variable to format.
1444
         *
1445
         * @return array The formatted replacement variable.
1446
         */
1447
        private function format_replacement_variable( $replacement_variable ) {
1448
                return [
1449
                        'name'  => $replacement_variable,
1450
                        'value' => '',
1451
                        'label' => $this->get_label( $replacement_variable ),
1452
                ];
1453
        }
1454

1455
        /**
1456
         * Set/translate the help texts for the WPSEO standard basic variables.
1457
         *
1458
         * @return void
1459
         */
1460
        private static function set_basic_help_texts() {
×
1461
                /* translators: %s: wp_title() function. */
1462
                $separator_description = __( 'The separator defined in your theme\'s %s tag.', 'wordpress-seo' );
×
1463
                $separator_description = sprintf(
1464
                        $separator_description,
×
1465
                        // '<code>wp_title()</code>'
1466
                        'wp_title()'
1467
                );
1468

UNCOV
1469
                $replacement_variables = [
×
1470
                        new WPSEO_Replacement_Variable( 'date', __( 'Date', 'wordpress-seo' ), __( 'Replaced with the date of the post/page', 'wordpress-seo' ) ),
×
1471
                        new WPSEO_Replacement_Variable( 'title', __( 'Title', 'wordpress-seo' ), __( 'Replaced with the title of the post/page', 'wordpress-seo' ) ),
×
1472
                        new WPSEO_Replacement_Variable( 'parent_title', __( 'Parent title', 'wordpress-seo' ), __( 'Replaced with the title of the parent page of the current page', 'wordpress-seo' ) ),
×
1473
                        new WPSEO_Replacement_Variable( 'archive_title', __( 'Archive title', 'wordpress-seo' ), __( 'Replaced with the normal title for an archive generated by WordPress', 'wordpress-seo' ) ),
×
1474
                        new WPSEO_Replacement_Variable( 'sitename', __( 'Site title', 'wordpress-seo' ), __( 'The site\'s name', 'wordpress-seo' ) ),
×
1475
                        new WPSEO_Replacement_Variable( 'sitedesc', __( 'Tagline', 'wordpress-seo' ), __( 'The site\'s tagline', 'wordpress-seo' ) ),
×
1476
                        new WPSEO_Replacement_Variable( 'excerpt', __( 'Excerpt', 'wordpress-seo' ), __( 'Replaced with the post/page excerpt (or auto-generated if it does not exist)', 'wordpress-seo' ) ),
×
1477
                        new WPSEO_Replacement_Variable( 'excerpt_only', __( 'Excerpt only', 'wordpress-seo' ), __( 'Replaced with the post/page excerpt (without auto-generation)', 'wordpress-seo' ) ),
×
1478
                        new WPSEO_Replacement_Variable( 'tag', __( 'Tag', 'wordpress-seo' ), __( 'Replaced with the current tag/tags', 'wordpress-seo' ) ),
×
1479
                        new WPSEO_Replacement_Variable( 'category', __( 'Category', 'wordpress-seo' ), __( 'Replaced with the post categories (comma separated)', 'wordpress-seo' ) ),
×
1480
                        new WPSEO_Replacement_Variable( 'primary_category', __( 'Primary category', 'wordpress-seo' ), __( 'Replaced with the primary category of the post/page', 'wordpress-seo' ) ),
×
1481
                        new WPSEO_Replacement_Variable( 'category_description', __( 'Category description', 'wordpress-seo' ), __( 'Replaced with the category description', 'wordpress-seo' ) ),
×
1482
                        new WPSEO_Replacement_Variable( 'tag_description', __( 'Tag description', 'wordpress-seo' ), __( 'Replaced with the tag description', 'wordpress-seo' ) ),
×
1483
                        new WPSEO_Replacement_Variable( 'term_description', __( 'Term description', 'wordpress-seo' ), __( 'Replaced with the term description', 'wordpress-seo' ) ),
×
1484
                        new WPSEO_Replacement_Variable( 'term_title', __( 'Term title', 'wordpress-seo' ), __( 'Replaced with the term name', 'wordpress-seo' ) ),
×
1485
                        new WPSEO_Replacement_Variable( 'searchphrase', __( 'Search phrase', 'wordpress-seo' ), __( 'Replaced with the current search phrase', 'wordpress-seo' ) ),
×
1486
                        new WPSEO_Replacement_Variable( 'term_hierarchy', __( 'Term hierarchy', 'wordpress-seo' ), __( 'Replaced with the term ancestors hierarchy', 'wordpress-seo' ) ),
×
1487
                        new WPSEO_Replacement_Variable( 'sep', __( 'Separator', 'wordpress-seo' ), $separator_description ),
×
1488
                        new WPSEO_Replacement_Variable( 'currentdate', __( 'Current date', 'wordpress-seo' ), __( 'Replaced with the current date', 'wordpress-seo' ) ),
×
1489
                        new WPSEO_Replacement_Variable( 'currentyear', __( 'Current year', 'wordpress-seo' ), __( 'Replaced with the current year', 'wordpress-seo' ) ),
×
1490
                        new WPSEO_Replacement_Variable( 'currentmonth', __( 'Current month', 'wordpress-seo' ), __( 'Replaced with the current month', 'wordpress-seo' ) ),
×
1491
                        new WPSEO_Replacement_Variable( 'currentday', __( 'Current day', 'wordpress-seo' ), __( 'Replaced with the current day', 'wordpress-seo' ) ),
×
1492
                        new WPSEO_Replacement_Variable( 'post_year', __( 'Post year', 'wordpress-seo' ), __( 'Replaced with the year the post was published', 'wordpress-seo' ) ),
×
1493
                        new WPSEO_Replacement_Variable( 'post_month', __( 'Post month', 'wordpress-seo' ), __( 'Replaced with the month the post was published', 'wordpress-seo' ) ),
×
1494
                        new WPSEO_Replacement_Variable( 'post_day', __( 'Post day', 'wordpress-seo' ), __( 'Replaced with the day the post was published', 'wordpress-seo' ) ),
×
1495
                        new WPSEO_Replacement_Variable( 'author_first_name', __( 'Author first name', 'wordpress-seo' ), __( 'Replaced with the first name of the author', 'wordpress-seo' ) ),
×
1496
                        new WPSEO_Replacement_Variable( 'author_last_name', __( 'Author last name', 'wordpress-seo' ), __( 'Replaced with the last name of the author', 'wordpress-seo' ) ),
×
1497
                        new WPSEO_Replacement_Variable( 'permalink', __( 'Permalink', 'wordpress-seo' ), __( 'Replaced with the permalink', 'wordpress-seo' ) ),
×
1498
                        new WPSEO_Replacement_Variable( 'post_content', __( 'Post Content', 'wordpress-seo' ), __( 'Replaced with the post content', 'wordpress-seo' ) ),
1499
                        new WPSEO_Replacement_Variable( 'category_title', __( 'Category Title', 'wordpress-seo' ), __( 'Current or first category title', 'wordpress-seo' ) ),
UNCOV
1500
                ];
×
1501

1502
                foreach ( $replacement_variables as $replacement_variable ) {
1503
                        self::register_help_text( 'basic', $replacement_variable );
×
1504
                }
1505
        }
1506

1507
        /**
1508
         * Set/translate the help texts for the WPSEO standard advanced variables.
1509
         *
1510
         * @return void
1511
         */
1512
        private static function set_advanced_help_texts() {
×
UNCOV
1513
                $replacement_variables = [
×
1514
                        new WPSEO_Replacement_Variable( 'pt_single', __( 'Post type (singular)', 'wordpress-seo' ), __( 'Replaced with the content type single label', 'wordpress-seo' ) ),
×
1515
                        new WPSEO_Replacement_Variable( 'pt_plural', __( 'Post type (plural)', 'wordpress-seo' ), __( 'Replaced with the content type plural label', 'wordpress-seo' ) ),
×
1516
                        new WPSEO_Replacement_Variable( 'modified', __( 'Modified', 'wordpress-seo' ), __( 'Replaced with the post/page modified time', 'wordpress-seo' ) ),
×
1517
                        new WPSEO_Replacement_Variable( 'id', __( 'ID', 'wordpress-seo' ), __( 'Replaced with the post/page ID', 'wordpress-seo' ) ),
×
1518
                        new WPSEO_Replacement_Variable( 'name', __( 'Name', 'wordpress-seo' ), __( 'Replaced with the post/page author\'s \'nicename\'', 'wordpress-seo' ) ),
×
1519
                        new WPSEO_Replacement_Variable( 'user_description', __( 'User description', 'wordpress-seo' ), __( 'Replaced with the post/page author\'s \'Biographical Info\'', 'wordpress-seo' ) ),
×
1520
                        new WPSEO_Replacement_Variable( 'page', __( 'Page', 'wordpress-seo' ), __( 'Replaced with the current page number with context (i.e. page 2 of 4)', 'wordpress-seo' ) ),
×
1521
                        new WPSEO_Replacement_Variable( 'pagetotal', __( 'Pagetotal', 'wordpress-seo' ), __( 'Replaced with the current page total', 'wordpress-seo' ) ),
×
1522
                        new WPSEO_Replacement_Variable( 'pagenumber', __( 'Pagenumber', 'wordpress-seo' ), __( 'Replaced with the current page number', 'wordpress-seo' ) ),
×
1523
                        new WPSEO_Replacement_Variable( 'caption', __( 'Caption', 'wordpress-seo' ), __( 'Attachment caption', 'wordpress-seo' ) ),
×
1524
                        new WPSEO_Replacement_Variable( 'focuskw', __( 'Focus keyword', 'wordpress-seo' ), __( 'Replaced with the posts focus keyphrase', 'wordpress-seo' ) ),
×
1525
                        new WPSEO_Replacement_Variable( 'term404', __( 'Term404', 'wordpress-seo' ), __( 'Replaced with the slug which caused the 404', 'wordpress-seo' ) ),
×
1526
                        new WPSEO_Replacement_Variable( 'cf_<custom-field-name>', '<custom-field-name> ' . __( '(custom field)', 'wordpress-seo' ), __( 'Replaced with a posts custom field value', 'wordpress-seo' ) ),
×
1527
                        new WPSEO_Replacement_Variable( 'ct_<custom-tax-name>', '<custom-tax-name> ' . __( '(custom taxonomy)', 'wordpress-seo' ), __( 'Replaced with a posts custom taxonomies, comma separated.', 'wordpress-seo' ) ),
1528
                        new WPSEO_Replacement_Variable( 'ct_desc_<custom-tax-name>', '<custom-tax-name> ' . __( 'description (custom taxonomy)', 'wordpress-seo' ), __( 'Replaced with a custom taxonomies description', 'wordpress-seo' ) ),
UNCOV
1529
                ];
×
1530

1531
                foreach ( $replacement_variables as $replacement_variable ) {
1532
                        self::register_help_text( 'advanced', $replacement_variable );
×
1533
                }
1534
        }
1535

1536
        /* *********************** GENERAL HELPER METHODS ************************** */
1537

1538
        /**
1539
         * Remove the '%%' delimiters from a variable string.
1540
         *
1541
         * @param string $text Variable string to be cleaned.
1542
         *
1543
         * @return string
1544
         */
1545
        private static function remove_var_delimiter( $text ) {
1546
                return trim( $text, '%' );
1547
        }
1548

1549
        /**
1550
         * Add the '%%' delimiters to a variable string.
1551
         *
1552
         * @param string $text Variable string to be delimited.
1553
         *
1554
         * @return string
1555
         */
1556
        private static function add_var_delimiter( $text ) {
1557
                return '%%' . $text . '%%';
1558
        }
1559

1560
        /**
1561
         * Retrieve a post's terms, comma delimited.
1562
         *
1563
         * @param int    $id            ID of the post to get the terms for.
1564
         * @param string $taxonomy      The taxonomy to get the terms for this post from.
1565
         * @param bool   $return_single If true, return the first term.
1566
         *
1567
         * @return string Either a single term or a comma delimited string of terms.
1568
         */
1569
        public function get_terms( $id, $taxonomy, $return_single = false ) {
×
1570
                $output = '';
1571

1572
                // If we're on a specific tag, category or taxonomy page, use that.
1573
                if ( ! empty( $this->args->term_id ) ) {
1574
                        $output = $this->args->name;
×
1575
                }
1576
                elseif ( ! empty( $id ) && ! empty( $taxonomy ) ) {
×
1577
                        $terms = get_the_terms( $id, $taxonomy );
×
1578
                        if ( is_array( $terms ) && $terms !== [] ) {
×
1579
                                foreach ( $terms as $term ) {
×
1580
                                        if ( $return_single ) {
×
1581
                                                $output = $term->name;
1582
                                                break;
1583
                                        }
1584
                                        else {
1585
                                                $output .= $term->name . ', ';
1586
                                        }
1587
                                }
1588
                                $output = rtrim( trim( $output ), ',' );
1589
                        }
1590
                }
1591
                unset( $terms, $term );
1592

1593
                /**
1594
                 * Allows filtering of the terms list used to replace %%category%%, %%tag%%
1595
                 * and %%ct_<custom-tax-name>%% variables.
1596
                 *
1597
                 * @param string $output   Comma-delimited string containing the terms.
1598
                 * @param string $taxonomy The taxonomy of the terms.
1599
                 */
1600
                return apply_filters( 'wpseo_terms', $output, $taxonomy );
1601
        }
1602

1603
        /**
1604
         * Gets a taxonomy term hierarchy including the term to get the parents for.
1605
         *
1606
         * @return string
1607
         */
1608
        private function get_term_hierarchy() {
×
1609
                if ( ! is_taxonomy_hierarchical( $this->args->taxonomy ) ) {
1610
                        return '';
1611
                }
1612

1613
                $separator = ' ' . $this->retrieve_sep() . ' ';
1614

UNCOV
1615
                $args = [
×
1616
                        'format'    => 'name',
1617
                        'separator' => $separator,
1618
                        'link'      => false,
1619
                        'inclusive' => true,
UNCOV
1620
                ];
×
1621

1622
                return rtrim(
1623
                        get_term_parents_list( $this->args->term_id, $this->args->taxonomy, $args ),
1624
                        $separator
1625
                );
1626
        }
1627

1628
        /**
1629
         * Retrieves the term ancestors hierarchy.
1630
         *
1631
         * @return string|null The term ancestors hierarchy.
1632
         */
1633
        private function retrieve_term_hierarchy() {
×
1634
                $replacement = null;
×
1635

1636
                if ( ! empty( $this->args->term_id ) && ! empty( $this->args->taxonomy ) ) {
1637
                        $hierarchy = $this->get_term_hierarchy();
×
1638

1639
                        if ( $hierarchy !== '' ) {
1640
                                $replacement = esc_html( $hierarchy );
1641
                        }
1642
                }
1643

1644
                return $replacement;
×
1645
        }
1646
}
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