The SEO Framework - Version 4.2.0

Version Description

  • Perfect =

Come with me, and you'll be in a world of pure imagination. - Gene Wilder, Anthony Newley, Leslie Bricusse

Release highlights

  • This update brings new post-type-archive settings. You can now edit their titles, descriptions, social and visibility settings, and even add redirects on the SEO Settings page.
  • Fancy a custom site title for SEO purposes only? Check out the new title settings.
  • You will find that the sitemap's stylesheet now has its URLs centered; it also supports mobile devices.
  • Developers can now enjoy using the new tsf() function -- an alias of the_seo_framework().
  • If you're a developer, you should also check out our perfectly tuned memo(). umemo(), and fmemo() functions, which help make TSF so performant.

Psst: Check out our Cyber Sale.

Perfect

TSF is finally what I (Sybre) envisioned it to become when I first named it "The SEO Framework": It's lightning-fast, hassle-free, and has all necessary options while giving you the best in class experience. I hope you enjoy what I believe is a perfect product!

To me, The SEO Framework is finished. Done. It's excellent. This doesn't mean its journey ends here. Now, it's time to add features you want. A restyled interface, migration support, custom title generation, more structured data fields, and many other community proposals are under consideration.

A perfect tip

This update comes with updated browser styles and scripts. Not all browsers get these for you; so, the interface might appear broken (and your "inputs" seem to be "removed"). Try a different browser, or clear your favorite browser's cache. That ought to do the trick.

Environment upgrade notes

WordPress 5.1 through 5.4 are no longer supported. Here's why:

  • Over 66% of all WordPress sites are using WordPress v5.5 or later.
  • Newer versions of WordPress are faster, more reliable, and easier to work with; for both you and us.
  • Supporting past versions takes time away that's better used implementing new features.

PHP 5.6 through 7.1 are no longer supported. Here's why:

  • Almost 80% of all WordPress sites are using PHP v7.2 or later.
  • Newer versions of PHP are faster and more secure. For us, they're also easier to work with.
  • Again, supporting past versions takes time away that's better used implementing new features.

Support the development

We hope you'll love this update as much as we do. Please consider supporting us by sharing a fantastic review, get a license, or do your friends and colleagues a favor by installing TSF for them.

Detailed log

Practice makes perfect. So does working 16 hours a day.

Download this release

Release Info

Developer Cybr
Plugin Icon 128x128 The SEO Framework
Version 4.2.0
Comparing to
See all releases

Code changes from version 4.1.5.1 to 4.2.0

Files changed (107) hide show
  1. autodescription.php +55 -50
  2. bootstrap/activation.php +2 -2
  3. bootstrap/deactivation.php +1 -1
  4. bootstrap/define.php +10 -2
  5. bootstrap/envtest.php +0 -157
  6. bootstrap/load.php +16 -11
  7. bootstrap/upgrade.php +89 -97
  8. inc/classes/admin-init.class.php +88 -89
  9. inc/classes/admin-pages.class.php +50 -158
  10. inc/classes/bridges/ajax.class.php +44 -54
  11. inc/classes/bridges/feed.class.php +13 -25
  12. inc/classes/bridges/index.php +3 -2
  13. inc/classes/bridges/listedit.class.php +22 -24
  14. inc/classes/bridges/listtable.class.php +15 -17
  15. inc/classes/bridges/ping.class.php +32 -15
  16. inc/classes/bridges/plugintable.class.php +6 -4
  17. inc/classes/bridges/postsettings.class.php +15 -14
  18. inc/classes/bridges/scripts.class.php +102 -216
  19. inc/classes/bridges/seobar.class.php +7 -7
  20. inc/classes/bridges/seosettings.class.php +173 -143
  21. inc/classes/bridges/sitemap.class.php +75 -81
  22. inc/classes/bridges/termsettings.class.php +1 -1
  23. inc/classes/bridges/usersettings.class.php +3 -3
  24. inc/classes/builders/coresitemaps/main.class.php +5 -7
  25. inc/classes/builders/coresitemaps/posts.class.php +23 -16
  26. inc/classes/builders/coresitemaps/taxonomies.class.php +38 -39
  27. inc/classes/builders/images.class.php +17 -15
  28. inc/classes/builders/robots/args.class.php +162 -0
  29. inc/classes/builders/robots/factory.class.php +160 -0
  30. inc/classes/builders/robots/index.php +9 -0
  31. inc/classes/builders/robots/main.class.php +220 -0
  32. inc/classes/builders/robots/query.class.php +260 -0
  33. inc/classes/builders/scripts.class.php +44 -60
  34. inc/classes/builders/seobar/index.php +6 -0
  35. inc/classes/builders/{seobar.class.php → seobar/main.class.php} +16 -21
  36. inc/classes/builders/{seobar-page.class.php → seobar/page.class.php} +85 -84
  37. inc/classes/builders/{seobar-term.class.php → seobar/term.class.php} +74 -69
  38. inc/classes/builders/sitemap.class.php +8 -251
  39. inc/classes/builders/{sitemap-base.class.php → sitemap/base.class.php} +83 -131
  40. inc/classes/builders/sitemap/index.php +0 -0
  41. inc/classes/builders/sitemap/main.class.php +280 -0
  42. inc/classes/cache.class.php +67 -71
  43. inc/classes/core.class.php +138 -235
  44. inc/classes/detect.class.php +426 -513
  45. inc/classes/generate-description.class.php +239 -189
  46. inc/classes/generate-image.class.php +44 -27
  47. inc/classes/generate-ldjson.class.php +43 -116
  48. inc/classes/generate-title.class.php +394 -344
  49. inc/classes/generate-url.class.php +255 -193
  50. inc/classes/generate.class.php +148 -457
  51. inc/classes/index.php +1 -11
  52. inc/classes/init.class.php +176 -126
  53. inc/classes/{debug.class.php → internal/debug.class.php} +44 -73
  54. inc/classes/{deprecated.class.php → internal/deprecated.class.php} +809 -269
  55. inc/classes/internal/index.php +7 -0
  56. inc/classes/{silencer.class.php → internal/silencer.class.php} +7 -6
  57. inc/classes/interpreters/form.class.php +48 -338
  58. inc/classes/interpreters/html.class.php +58 -38
  59. inc/classes/interpreters/markdown.class.php +6 -3
  60. inc/classes/interpreters/seobar.class.php +70 -55
  61. inc/classes/interpreters/settings-input.class.php +208 -0
  62. inc/classes/interpreters/sitemap-xsl.class.php +186 -0
  63. inc/classes/load.class.php +17 -10
  64. inc/classes/post-data.class.php +61 -153
  65. inc/classes/query.class.php +240 -517
  66. inc/classes/render.class.php +105 -109
  67. inc/classes/sanitize.class.php +127 -94
  68. inc/classes/site-options.class.php +385 -419
  69. inc/classes/term-data.class.php +51 -79
  70. inc/classes/user-data.class.php +34 -45
  71. inc/compat/plugin-bbpress.php +6 -6
  72. inc/compat/plugin-buddypress.php +2 -2
  73. inc/compat/plugin-edd.php +3 -3
  74. inc/compat/plugin-elementor.php +25 -0
  75. inc/compat/plugin-polylang.php +28 -18
  76. inc/compat/plugin-ultimatemember.php +30 -93
  77. inc/compat/plugin-woocommerce.php +65 -42
  78. inc/compat/plugin-wpforo.php +5 -5
  79. inc/compat/plugin-wpml.php +6 -4
  80. inc/compat/theme-genesis.php +1 -1
  81. inc/functions/api.php +209 -28
  82. inc/functions/upgrade-suggestion.php +15 -35
  83. inc/views/admin/wrap-content.php +0 -60
  84. inc/views/debug/output.php +19 -14
  85. inc/views/edit/seo-settings-singular-gutenberg-data.php +1 -1
  86. inc/views/edit/seo-settings-singular.php +93 -62
  87. inc/views/edit/seo-settings-tt.php +40 -16
  88. inc/views/edit/wrap-content.php +26 -13
  89. inc/views/edit/wrap-nav.php +19 -15
  90. inc/views/list/bulk-post.php +1 -1
  91. inc/views/list/quick-post.php +1 -1
  92. inc/views/list/quick-term.php +1 -1
  93. inc/views/notice/persistent.php +1 -1
  94. inc/views/profile/author.php +1 -1
  95. inc/views/{admin/seo-settings-columns.php → settings/columns.php} +1 -1
  96. inc/views/{admin → settings}/index.php +0 -0
  97. inc/views/{admin/metaboxes/description-metabox.php → settings/metaboxes/description.php} +8 -11
  98. inc/views/{admin/metaboxes/feed-metabox.php → settings/metaboxes/feed.php} +9 -12
  99. inc/views/{admin/metaboxes/general-metabox.php → settings/metaboxes/general.php} +65 -79
  100. inc/views/{admin/metaboxes/homepage-metabox.php → settings/metaboxes/homepage.php} +124 -118
  101. inc/views/{admin → settings}/metaboxes/index.php +0 -0
  102. inc/views/settings/metaboxes/post-type-archive.php +469 -0
  103. inc/views/{admin/metaboxes/robots-metabox.php → settings/metaboxes/robots.php} +64 -73
  104. inc/views/{admin/metaboxes/schema-metabox.php → settings/metaboxes/schema.php} +55 -57
  105. inc/views/{admin/metaboxes/sitemaps-metabox.php → settings/metaboxes/sitemaps.php} +73 -84
  106. inc/views/{admin/metaboxes/social-metabox.php → settings/metaboxes/social.php} +67 -69
  107. inc/views/{admin/metaboxes/title-metabox.php → settings/metaboxes/title.php} +24 -23
autodescription.php CHANGED
@@ -3,12 +3,14 @@
3
  * Plugin Name: The SEO Framework
4
  * Plugin URI: https://theseoframework.com/
5
  * Description: An automated, advanced, accessible, unbranded and extremely fast SEO solution for your WordPress website.
6
- * Version: 4.1.5.1
7
  * Author: The SEO Framework Team
8
  * Author URI: https://theseoframework.com/
9
  * License: GPLv3
10
  * Text Domain: autodescription
11
  * Domain Path: /language
 
 
12
  *
13
  * @package The_SEO_Framework\Bootstrap
14
  */
@@ -32,13 +34,6 @@ defined( 'ABSPATH' ) or die;
32
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
33
  */
34
 
35
- /**
36
- * @NOTE This file MUST be written according to WordPress's minimum PHP requirements.
37
- * Which is PHP 5.2.
38
- * When we only support WordPress 5.2+, it'll be PHP 5.6.
39
- * When we only support WordPress 5.9?+, it'll be PHP 7.1.
40
- */
41
-
42
  /**
43
  * The plugin version.
44
  *
@@ -46,7 +41,7 @@ defined( 'ABSPATH' ) or die;
46
  *
47
  * @since 2.3.5
48
  */
49
- define( 'THE_SEO_FRAMEWORK_VERSION', '4.1.5' );
50
 
51
  /**
52
  * The plugin Database version.
@@ -55,7 +50,7 @@ define( 'THE_SEO_FRAMEWORK_VERSION', '4.1.5' );
55
  *
56
  * @since 2.7.0
57
  */
58
- define( 'THE_SEO_FRAMEWORK_DB_VERSION', '4120' );
59
 
60
  /**
61
  * The plugin file, absolute unix path.
@@ -71,45 +66,55 @@ define( 'THE_SEO_FRAMEWORK_PLUGIN_BASE_FILE', __FILE__ );
71
  */
72
  define( 'THE_SEO_FRAMEWORK_BOOTSTRAP_PATH', dirname( THE_SEO_FRAMEWORK_PLUGIN_BASE_FILE ) . DIRECTORY_SEPARATOR . 'bootstrap' . DIRECTORY_SEPARATOR );
73
 
74
- /**
75
- * Checks whether to start plugin or test the server environment first.
76
- *
77
- * @since 2.8.0
78
- */
79
- if ( get_option( 'the_seo_framework_tested_upgrade_version' ) < THE_SEO_FRAMEWORK_DB_VERSION ) {
80
- require THE_SEO_FRAMEWORK_BOOTSTRAP_PATH . 'envtest.php';
81
-
82
- if ( get_option( 'the_seo_framework_tested_upgrade_version' ) >= THE_SEO_FRAMEWORK_DB_VERSION )
83
- the_seo_framework_boot();
84
- } else {
85
- the_seo_framework_boot();
86
- }
87
-
88
- /**
89
- * Starts the plugin, loads files outside of the global scope.
90
- *
91
- * @since 3.1.0
92
- * @since 4.1.4 Unloaded the functions deprecated.php file.
93
- * @access private
94
- */
95
- function the_seo_framework_boot() {
96
-
97
- // Defines environental constants.
98
- require THE_SEO_FRAMEWORK_BOOTSTRAP_PATH . 'define.php';
99
-
100
- // Load plugin API functions.
101
- require THE_SEO_FRAMEWORK_DIR_PATH_FUNCT . 'api.php';
102
-
103
- // Prepare plugin upgrader before the plugin loads. This may also downgrade (3103 or higher).
104
- the_seo_framework_db_version() !== THE_SEO_FRAMEWORK_DB_VERSION
105
- and require THE_SEO_FRAMEWORK_BOOTSTRAP_PATH . 'upgrade.php';
106
-
107
- // Load deprecated functions.
108
- // require THE_SEO_FRAMEWORK_DIR_PATH_FUNCT . 'deprecated.php';
109
-
110
- // Load plugin.
111
- require THE_SEO_FRAMEWORK_BOOTSTRAP_PATH . 'load.php';
112
- }
 
 
 
 
 
 
 
 
 
 
113
 
114
  // phpcs:disable, Squiz.Commenting.InlineComment, Squiz.PHP.CommentedOutCode
115
  //
@@ -126,8 +131,8 @@ function the_seo_framework_boot() {
126
  // ( $_GET['reset_tsf_upgrade'] ?? 0 ) and delete_option( 'the_seo_framework_upgraded_db_version' ) and delete_option( 'the_seo_framework_initial_db_version' );
127
  // ( $_GET['downgrade_tsf'] ?? 0 ) and update_option( 'the_seo_framework_upgraded_db_version', (string) (int) $_GET['downgrade_tsf'] );
128
  // ( $_GET['downgrade_tsf_initial'] ?? 0 ) and update_option( 'the_seo_framework_initial_db_version', (string) (int) $_GET['downgrade_tsf_initial'] );
129
- // ( $_GET['reset_tsf_tested'] ?? 0 ) and delete_option( 'the_seo_framework_tested_upgrade_version' );
130
  // ( $_GET['tsf_headless'] ?? 0 ) and define( 'THE_SEO_FRAMEWORK_HEADLESS', $_GET['tsf_headless'] === 'true' ?: $_GET['tsf_headless'] );
 
131
  // }
132
  // }},0);
133
  // phpcs:enable, Squiz.Commenting.InlineComment, Squiz.PHP.CommentedOutCode
3
  * Plugin Name: The SEO Framework
4
  * Plugin URI: https://theseoframework.com/
5
  * Description: An automated, advanced, accessible, unbranded and extremely fast SEO solution for your WordPress website.
6
+ * Version: 4.2.0
7
  * Author: The SEO Framework Team
8
  * Author URI: https://theseoframework.com/
9
  * License: GPLv3
10
  * Text Domain: autodescription
11
  * Domain Path: /language
12
+ * Requires at least: 5.5
13
+ * Requires PHP: 7.2.0
14
  *
15
  * @package The_SEO_Framework\Bootstrap
16
  */
34
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
35
  */
36
 
 
 
 
 
 
 
 
37
  /**
38
  * The plugin version.
39
  *
41
  *
42
  * @since 2.3.5
43
  */
44
+ define( 'THE_SEO_FRAMEWORK_VERSION', '4.2.0' );
45
 
46
  /**
47
  * The plugin Database version.
50
  *
51
  * @since 2.7.0
52
  */
53
+ define( 'THE_SEO_FRAMEWORK_DB_VERSION', '4200' );
54
 
55
  /**
56
  * The plugin file, absolute unix path.
66
  */
67
  define( 'THE_SEO_FRAMEWORK_BOOTSTRAP_PATH', dirname( THE_SEO_FRAMEWORK_PLUGIN_BASE_FILE ) . DIRECTORY_SEPARATOR . 'bootstrap' . DIRECTORY_SEPARATOR );
68
 
69
+ // Defines environental constants.
70
+ require THE_SEO_FRAMEWORK_BOOTSTRAP_PATH . 'define.php';
71
+
72
+ // Load plugin API functions.
73
+ require THE_SEO_FRAMEWORK_DIR_PATH_FUNCT . 'api.php';
74
+
75
+ // Prepare plugin upgrader before the plugin loads. This may also downgrade (3103 or higher).
76
+ the_seo_framework_db_version() !== THE_SEO_FRAMEWORK_DB_VERSION
77
+ and require THE_SEO_FRAMEWORK_BOOTSTRAP_PATH . 'upgrade.php';
78
+
79
+ // Load deprecated functions.
80
+ // require THE_SEO_FRAMEWORK_DIR_PATH_FUNCT . 'deprecated.php';
81
+
82
+ // Load plugin.
83
+ require THE_SEO_FRAMEWORK_BOOTSTRAP_PATH . 'load.php';
84
+
85
+ // # Zelda is here to protect your site from hackers.
86
+ // #
87
+ // # OLLLLLLLLL
88
+ // # GGOiiiiiiiilGGG
89
+ // # GGGOllttttttttttlllll
90
+ // # GG1111tt1;;;;;;lltttt..L
91
+ // # GttLLLLttiiiiiii;;tttl..,;
92
+ // # GGtLLLLLLLLLiiiiiiii;tttttt,,
93
+ // # GllLLLLLLLLLLLLiiiiiii;;tttt,,.
94
+ // # ,;OttLLLLLLtttLttiiii;;i;;:ttt::. ttttG
95
+ // # ;;Olll;;:::ittL::,;;;;;;ii;ttttt. i.iii..
96
+ // # ;,iiiii:LL:,,:iiiii;ii;ttttt. ;::iii0..
97
+ // # ,,...,,;;;,.....,ii;ii;...ll. :::OO,GG.
98
+ // # , L...........,::O::.00Gii;ii;ll.11. .,,G,,0..
99
+ // # , L..0000LL000LLL0::.00Gii,;;l..lll. ... ..,00,00.
100
+ // # , L..L;;;..1LLLLLLLL1iiiii,ll;ttt11t..:::. .,,0,,0..
101
+ // # , L..O;;i;;;;;LLLLLLiiiiii,;;L;;t11t..:ii.;;,0i:0;,
102
+ // # , L..Oiii;;;;;LLL1iiiii,::t,,t111::1:::00.,,,00L..
103
+ // # , L..Oiiiii:::t.........::tttt11tttttt:::.OOiii.
104
+ // # , L..Oiit00tttLlll11lttt;;tll1llllltttt..GGGO:::..
105
+ // # , L..L..;00tttOii...:tt;;;tll::::::lttttt.OO::::::.
106
+ // # , L..O;;L00:::011;..l::;tttll:.. ;::lll...:::0ii.
107
+ // # , L..L;;L00:::011;..llllttttt. :..:..LLL.......
108
+ // # , L..L::;GGLLL011;,,iGGGOOGGGG:: ;;;GG.....
109
+ // # , :LLiiiiiiiii.iiGOOOGGtttt.. ;;;::.
110
+ // # ,:LLiiiiiiiii.lltttttttttl.. :;
111
+ // # ::tiiiiiittllllllllllllll.
112
+ // # tt:ttiiit;;::::::::::::::::::
113
+ // # l::iii:..:OOO.. :::::0,,
114
+ // # 11;;;,;;:OO. ,::L;;:
115
+ // # 1..Lll;.. .ll1tt.
116
+ // # 11t11l11, .ll111l.. It's Link?! Not Zelda??
117
+ // # ll...... ........ - Sybre drew this by hand.
118
 
119
  // phpcs:disable, Squiz.Commenting.InlineComment, Squiz.PHP.CommentedOutCode
120
  //
131
  // ( $_GET['reset_tsf_upgrade'] ?? 0 ) and delete_option( 'the_seo_framework_upgraded_db_version' ) and delete_option( 'the_seo_framework_initial_db_version' );
132
  // ( $_GET['downgrade_tsf'] ?? 0 ) and update_option( 'the_seo_framework_upgraded_db_version', (string) (int) $_GET['downgrade_tsf'] );
133
  // ( $_GET['downgrade_tsf_initial'] ?? 0 ) and update_option( 'the_seo_framework_initial_db_version', (string) (int) $_GET['downgrade_tsf_initial'] );
 
134
  // ( $_GET['tsf_headless'] ?? 0 ) and define( 'THE_SEO_FRAMEWORK_HEADLESS', $_GET['tsf_headless'] === 'true' ?: $_GET['tsf_headless'] );
135
+ // add_action( 'admin_footer', function() { print( '<script>jQuery.migrateMute=true;</script>' ); } );
136
  // }
137
  // }},0);
138
  // phpcs:enable, Squiz.Commenting.InlineComment, Squiz.PHP.CommentedOutCode
bootstrap/activation.php CHANGED
@@ -44,7 +44,7 @@ _activation_set_plugin_check_caches();
44
  */
45
  function _activation_set_plugin_check_caches() {
46
 
47
- $tsf = \the_seo_framework();
48
 
49
  if ( $tsf->loaded ) {
50
  $tsf->set_plugin_check_caches();
@@ -60,7 +60,7 @@ function _activation_set_plugin_check_caches() {
60
  */
61
  function _activation_set_options_autoload() {
62
 
63
- $tsf = \the_seo_framework();
64
 
65
  if ( $tsf->loaded ) {
66
  $options = $tsf->get_all_options();
44
  */
45
  function _activation_set_plugin_check_caches() {
46
 
47
+ $tsf = \tsf();
48
 
49
  if ( $tsf->loaded ) {
50
  $tsf->set_plugin_check_caches();
60
  */
61
  function _activation_set_options_autoload() {
62
 
63
+ $tsf = \tsf();
64
 
65
  if ( $tsf->loaded ) {
66
  $options = $tsf->get_all_options();
bootstrap/deactivation.php CHANGED
@@ -41,7 +41,7 @@ _deactivation_unset_options_autoload();
41
  */
42
  function _deactivation_unset_options_autoload() {
43
 
44
- $the_seo_framework = \the_seo_framework();
45
 
46
  if ( $the_seo_framework->loaded ) {
47
  $options = $the_seo_framework->get_all_options();
41
  */
42
  function _deactivation_unset_options_autoload() {
43
 
44
+ $the_seo_framework = \tsf();
45
 
46
  if ( $the_seo_framework->loaded ) {
47
  $options = $the_seo_framework->get_all_options();
bootstrap/define.php CHANGED
@@ -163,7 +163,7 @@ namespace The_SEO_Framework;
163
  * @since 4.0.0
164
  * @see \The_SEO_Framework\Generate\robots_meta()
165
  */
166
- const ROBOTS_IGNORE_PROTECTION = 0b01;
167
 
168
  /**
169
  * Robots setting, ignore settings.
@@ -171,4 +171,12 @@ const ROBOTS_IGNORE_PROTECTION = 0b01;
171
  * @since 4.0.0
172
  * @see \The_SEO_Framework\Generate\robots_meta()
173
  */
174
- const ROBOTS_IGNORE_SETTINGS = 0b10;
 
 
 
 
 
 
 
 
163
  * @since 4.0.0
164
  * @see \The_SEO_Framework\Generate\robots_meta()
165
  */
166
+ const ROBOTS_IGNORE_PROTECTION = 0b001;
167
 
168
  /**
169
  * Robots setting, ignore settings.
171
  * @since 4.0.0
172
  * @see \The_SEO_Framework\Generate\robots_meta()
173
  */
174
+ const ROBOTS_IGNORE_SETTINGS = 0b010;
175
+
176
+ /**
177
+ * Robots setting, enable asserting.
178
+ *
179
+ * @since 4.2.0
180
+ * @see \The_SEO_Framework\Generate\robots_meta()
181
+ */
182
+ const ROBOTS_ASSERT = 0b100;
bootstrap/envtest.php DELETED
@@ -1,157 +0,0 @@
1
- <?php
2
- /**
3
- * @package The_SEO_Framework\Bootstrap\Install
4
- *
5
- * @NOTE This file MUST be written according to WordPress's minimum PHP requirements.
6
- * Which is PHP 5.2.
7
- * When we only support WordPress 5.2+, it'll be PHP 5.6.
8
- * When we only support WordPress 5.6?+, it'll be PHP 7.1.
9
- *
10
- * This file can be removed when we only support WordPress 5.2 or later. However, their
11
- * onboarding message isn't as useful, informative, or even as friendly.
12
- *
13
- * To use that, we need to add these plugin headers in the plugin's main PHP file:
14
- * Requires PHP: 5.6.5
15
- * Requires at least: 5.1
16
- */
17
-
18
- defined( 'THE_SEO_FRAMEWORK_DB_VERSION' ) or die;
19
-
20
- /**
21
- * The SEO Framework plugin
22
- * Copyright (C) 2018 - 2021 Sybre Waaijer, CyberWire B.V. (https://cyberwire.nl/)
23
- *
24
- * This program is free software: you can redistribute it and/or modify
25
- * it under the terms of the GNU General Public License version 3 as published
26
- * by the Free Software Foundation.
27
- *
28
- * This program is distributed in the hope that it will be useful,
29
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
30
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31
- * GNU General Public License for more details.
32
- *
33
- * You should have received a copy of the GNU General Public License
34
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
35
- */
36
-
37
- /**
38
- * This file holds functions for testing the plugin after upgrade.
39
- * This file will only be called ONCE if the required version option is lower
40
- * compared to The SEO Framework version constant.
41
- *
42
- * @since 3.1.0
43
- * @access private
44
- */
45
-
46
- the_seo_framework_pre_boot_test();
47
- /**
48
- * Tests plugin upgrade.
49
- *
50
- * @since 3.1.0
51
- * @since 4.0.5 No longer assumes the main blog (WP Multisite) has been tested, although that's very likely when updated via the interface.
52
- * @access private
53
- * @link http://php.net/eol.php
54
- * @link https://codex.wordpress.org/WordPress_Versions
55
- */
56
- function the_seo_framework_pre_boot_test() {
57
-
58
- $ms = is_multisite();
59
-
60
- if ( $ms && function_exists( 'get_network' ) ) {
61
- // Try bypassing testing and deactivation gaming when the main blog has already been tested.
62
-
63
- /**
64
- * @since 2.9.4
65
- * Delete old and redundant network option.
66
- */
67
- delete_site_option( 'the_seo_framework_tested_upgrade_version' );
68
-
69
- $nw = get_network();
70
- if ( $nw instanceof WP_Network ) {
71
- if ( get_blog_option( $nw->site_id, 'the_seo_framework_tested_upgrade_version' ) >= THE_SEO_FRAMEWORK_DB_VERSION ) {
72
- update_option( 'the_seo_framework_tested_upgrade_version', THE_SEO_FRAMEWORK_DB_VERSION );
73
- return;
74
- }
75
- }
76
- //= Free memory.
77
- unset( $nw );
78
- }
79
-
80
- $requirements = array(
81
- 'php' => 50600,
82
- 'wp' => '5.1-dev',
83
- );
84
-
85
- // phpcs:disable, Generic.Formatting.MultipleStatementAlignment, WordPress.WhiteSpace.PrecisionAlignment
86
- ! defined( 'PHP_VERSION_ID' ) || PHP_VERSION_ID < $requirements['php'] and $test = 1
87
- or version_compare( $GLOBALS['wp_version'], $requirements['wp'], '<' ) and $test = 2
88
- or $test = true;
89
- // phpcs:enable, Generic.Formatting.MultipleStatementAlignment, WordPress.WhiteSpace.PrecisionAlignment
90
-
91
- // All good.
92
- if ( true === $test ) {
93
- update_option( 'the_seo_framework_tested_upgrade_version', THE_SEO_FRAMEWORK_DB_VERSION );
94
- return;
95
- }
96
-
97
- if ( $ms ) {
98
- $_plugins = get_site_option( 'active_sitewide_plugins' );
99
- $network_mode = isset( $_plugins[ plugin_basename( THE_SEO_FRAMEWORK_PLUGIN_BASE_FILE ) ] );
100
- } else {
101
- $network_mode = false;
102
- }
103
-
104
- if ( ! function_exists( 'deactivate_plugins' ) )
105
- require_once ABSPATH . 'wp-admin/includes/plugin.php';
106
-
107
- $admin = is_admin();
108
- $silent = ! $admin;
109
-
110
- // Not good. Deactivate plugin.
111
- deactivate_plugins( plugin_basename( THE_SEO_FRAMEWORK_PLUGIN_BASE_FILE ), $silent, $network_mode );
112
-
113
- // Don't die on front-end. Live, my friend.
114
- if ( ! $admin )
115
- return;
116
-
117
- switch ( $test ) :
118
- case 1:
119
- // PHP requirements not met, always count up to encourage best standards.
120
- $requirement = 'PHP 5.6.0 or later';
121
- $issue = 'PHP version';
122
- $version = PHP_VERSION;
123
- $subtitle = 'Server Requirements';
124
- break;
125
-
126
- case 2:
127
- // WordPress requirements not met.
128
- $requirement = 'WordPress 5.1 or later';
129
- $issue = 'WordPress version';
130
- $version = $GLOBALS['wp_version'];
131
- $subtitle = 'WordPress Requirements';
132
- break;
133
-
134
- default:
135
- wp_die( 'oi' );
136
- break;
137
- endswitch;
138
-
139
- // network_admin_url() falls back to admin_url() on single. But networks can enable single too.
140
- $pluginspage = $network_mode ? network_admin_url( 'plugins.php' ) : admin_url( 'plugins.php' );
141
-
142
- // Let's have some fun with teapots.
143
- $response = floor( time() / DAY_IN_SECONDS ) === floor( strtotime( 'first day of April ' . gmdate( 'Y' ) ) / DAY_IN_SECONDS ) ? 418 : 500;
144
-
145
- wp_die(
146
- sprintf(
147
- '<p><strong>The SEO Framework</strong> requires <em>%s</em>. Sorry about that!<br>Your %s is: <code>%s</code></p>
148
- <p>Do you want to <strong><a onclick="window.history.back()" href="%s">go back</a></strong>?</p>',
149
- esc_html( $requirement ),
150
- esc_html( $issue ),
151
- esc_html( $version ),
152
- esc_url( $pluginspage )
153
- ),
154
- esc_attr( sprintf( 'The SEO Framework &laquo; %s', $subtitle ) ),
155
- array( 'response' => intval( $response ) )
156
- );
157
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bootstrap/load.php CHANGED
@@ -52,7 +52,8 @@ function _init_locale() {
52
  *
53
  * @since 3.1.0
54
  * @access private
55
- * @see function the_seo_framework().
 
56
  * @factory
57
  *
58
  * @return object|null The SEO Framework Facade class object. Null on failure.
@@ -94,13 +95,13 @@ function _init_tsf() {
94
  */
95
  \do_action( 'the_seo_framework_loaded' );
96
  } else {
97
- $tsf = new Silencer();
98
  $tsf->loaded = false;
99
  }
100
 
101
  // did_action() checks for current action too.
102
  if ( ! \did_action( 'plugins_loaded' ) )
103
- $tsf->_doing_it_wrong( 'the_seo_framework() or ' . __FUNCTION__, 'Use <code>the_seo_framework()</code> after action <code>plugins_loaded</code> priority 5.', '3.1' );
104
 
105
  return $tsf;
106
  }
@@ -111,11 +112,12 @@ spl_autoload_register( __NAMESPACE__ . '\\_autoload_classes', true, true );
111
  * the plugin classes.
112
  *
113
  * @since 2.8.0
114
- * @since 3.1.0 : 1. No longer maintains cache.
115
- * 2. Now always returns void.
116
- * @since 4.0.0 : 1. Streamlined folder lookup by more effectively using the namespace.
117
- * 2. Added timing functionality
118
- * 3. No longer loads interfaces automatically.
 
119
  * @uses THE_SEO_FRAMEWORK_DIR_PATH_CLASS
120
  * @access private
121
  *
@@ -127,7 +129,10 @@ spl_autoload_register( __NAMESPACE__ . '\\_autoload_classes', true, true );
127
  */
128
  function _autoload_classes( $class ) {
129
 
130
- if ( 0 !== strpos( $class, 'The_SEO_Framework\\', 0 ) ) return;
 
 
 
131
 
132
  static $_timenow = true;
133
  // Lock $_timenow to prevent stacking timers during class extending. This is released when the class stack loaded.
@@ -138,7 +143,7 @@ function _autoload_classes( $class ) {
138
  $_bootstrap_timer = 0;
139
  }
140
 
141
- $_chunks = explode( '\\', strtolower( $class ) );
142
  $_chunck_count = \count( $_chunks );
143
 
144
  if ( $_chunck_count > 2 ) {
@@ -152,7 +157,7 @@ function _autoload_classes( $class ) {
152
  $file = str_replace( '_', '-', end( $_chunks ) );
153
 
154
  // The extension is deemed to be ".class.php" always. We may wish to alter this for traits?
155
- require THE_SEO_FRAMEWORK_DIR_PATH_CLASS . $rel_dir . $file . '.class.php';
156
 
157
  if ( $_bootstrap_timer ) {
158
  _bootstrap_timer( microtime( true ) - $_bootstrap_timer );
52
  *
53
  * @since 3.1.0
54
  * @access private
55
+ * @see function tsf().
56
+ * @see function tsf().
57
  * @factory
58
  *
59
  * @return object|null The SEO Framework Facade class object. Null on failure.
95
  */
96
  \do_action( 'the_seo_framework_loaded' );
97
  } else {
98
+ $tsf = new Internal\Silencer();
99
  $tsf->loaded = false;
100
  }
101
 
102
  // did_action() checks for current action too.
103
  if ( ! \did_action( 'plugins_loaded' ) )
104
+ $tsf->_doing_it_wrong( 'tsf(), the_seo_framework(), or ' . __FUNCTION__, 'Use <code>tsf()</code> after action <code>plugins_loaded</code> priority 5.', '3.1' );
105
 
106
  return $tsf;
107
  }
112
  * the plugin classes.
113
  *
114
  * @since 2.8.0
115
+ * @since 3.1.0 1. No longer maintains cache.
116
+ * 2. Now always returns void.
117
+ * @since 4.0.0 1. Streamlined folder lookup by more effectively using the namespace.
118
+ * 2. Added timing functionality
119
+ * 3. No longer loads interfaces automatically.
120
+ * @since 4.2.0 Now supports mixed class case.
121
  * @uses THE_SEO_FRAMEWORK_DIR_PATH_CLASS
122
  * @access private
123
  *
129
  */
130
  function _autoload_classes( $class ) {
131
 
132
+ $class = strtolower( $class );
133
+
134
+ // It's The_SEO_Framework, not the_seo_framework! -- Sybre's a nightmare, honestly! No wonder he hasn't gotten any friends.
135
+ if ( 0 !== strpos( $class, 'the_seo_framework\\', 0 ) ) return;
136
 
137
  static $_timenow = true;
138
  // Lock $_timenow to prevent stacking timers during class extending. This is released when the class stack loaded.
143
  $_bootstrap_timer = 0;
144
  }
145
 
146
+ $_chunks = explode( '\\', $class );
147
  $_chunck_count = \count( $_chunks );
148
 
149
  if ( $_chunck_count > 2 ) {
157
  $file = str_replace( '_', '-', end( $_chunks ) );
158
 
159
  // The extension is deemed to be ".class.php" always. We may wish to alter this for traits?
160
+ require THE_SEO_FRAMEWORK_DIR_PATH_CLASS . "{$rel_dir}{$file}.class.php";
161
 
162
  if ( $_bootstrap_timer ) {
163
  _bootstrap_timer( microtime( true ) - $_bootstrap_timer );
bootstrap/upgrade.php CHANGED
@@ -30,12 +30,11 @@ namespace The_SEO_Framework\Bootstrap;
30
  * compared to The SEO Framework version constant.
31
  *
32
  * @since 2.7.0
 
33
  * @since 4.1.1 No longer memoizes the previous version early. This should help bypass the cache flush.
34
  * @access private
35
  * @TODO convert to class, see \TSF_Extension_Manager\Upgrader
36
  * It's a generator/iterator, so we must wait to PHP>5.5 support.
37
- *
38
- * @since 3.2.4 Applied namspacing to this file. All method names have changed.
39
  */
40
 
41
  // phpcs:disable, TSF.Performance.Opcodes.ShouldHaveNamespaceEscape
@@ -45,12 +44,6 @@ namespace The_SEO_Framework\Bootstrap;
45
  \add_action( 'the_seo_framework_upgraded', __NAMESPACE__ . '\\_prepare_upgrade_suggestion', 100, 2 );
46
  \add_action( 'the_seo_framework_downgraded', __NAMESPACE__ . '\\_prepare_downgrade_notice', 99, 2 );
47
 
48
- /**
49
- * @since 4.1.0 Deprecated. We can no longer rely on this from WP 5.5.
50
- * @deprecated Use persistent notifications, instead.
51
- */
52
- \add_action( 'admin_notices', __NAMESPACE__ . '\\_output_upgrade_notices' );
53
-
54
  /**
55
  * Returns the default site options.
56
  * Memoizes the return value.
@@ -60,8 +53,8 @@ namespace The_SEO_Framework\Bootstrap;
60
  * @return array The default site options.
61
  */
62
  function _upgrade_default_site_options() {
63
- static $cache;
64
- return isset( $cache ) ? $cache : $cache = \the_seo_framework()->get_default_site_options();
65
  }
66
 
67
  /**
@@ -73,8 +66,8 @@ function _upgrade_default_site_options() {
73
  * @return string The prior-to-upgrade TSF db version.
74
  */
75
  function _previous_db_version() {
76
- static $cache;
77
- return isset( $cache ) ? $cache : $cache = \get_option( 'the_seo_framework_upgraded_db_version', '0' );
78
  }
79
 
80
  /**
@@ -108,7 +101,7 @@ function _previous_db_version() {
108
  */
109
  function _do_upgrade() {
110
 
111
- $tsf = \the_seo_framework();
112
 
113
  if ( ! $tsf->loaded ) return;
114
  if ( \wp_doing_ajax() ) return;
@@ -216,12 +209,11 @@ function _upgrade( $previous_version ) {
216
  //? This means no data may be erased for at least 1 major version, or 1 year, whichever is later.
217
  //? We must manually delete settings that are no longer used; we merge them otherwise.
218
  //? When a user upgrades beyond this scope, they aren't expected to roll back.
219
- $versions = [ '1', '2701', '2802', '2900', '3001', '3103', '3300', '4051', '4103', '4110', '4120' ];
220
 
221
  foreach ( $versions as $_version ) {
222
  if ( $current_version < $_version ) {
223
- // ( __NAMESPACE__ . "\\_do_upgrade_{$_version}" )(); // This is an undocumented method for variable functions. So much for that.
224
- call_user_func( __NAMESPACE__ . "\\_do_upgrade_{$_version}" ); // PHP 5.6 compat
225
  $current_version = _set_version( $_version );
226
  }
227
  }
@@ -364,7 +356,7 @@ function _prepare_downgrade_notice( $previous_version, $current_version ) {
364
 
365
  // phpcs:ignore, WordPress.PHP.StrictComparisons.LooseComparison -- might be mixed types.
366
  if ( $previous_version && $previous_version != $current_version ) { // User successfully downgraded.
367
- $tsf = \the_seo_framework();
368
 
369
  $tsf->register_dismissible_persistent_notice(
370
  $tsf->convert_markdown(
@@ -401,6 +393,7 @@ function _prepare_downgrade_notice( $previous_version, $current_version ) {
401
  * @since 4.1.0 1. Moved admin notice user capability check here.
402
  * 2. Now registers persistent notice for the update version.
403
  * @since 4.1.2 No longer can accidentally show the install notice after stale upgrade.
 
404
  * @TODO Add browser cache flush notice? Or set a pragma/cache-control header?
405
  * Users that remove query strings (thanks to YSlow) are to blame, though.
406
  * The authors of the plugin that allowed this to happen are even more to blame.
@@ -413,7 +406,7 @@ function _prepare_upgrade_notice( $previous_version, $current_version ) {
413
 
414
  // phpcs:ignore, WordPress.PHP.StrictComparisons.LooseComparison -- might be mixed types.
415
  if ( $previous_version && $previous_version != $current_version ) { // User successfully upgraded.
416
- $tsf = \the_seo_framework();
417
 
418
  $tsf->register_dismissible_persistent_notice(
419
  $tsf->convert_markdown(
@@ -440,40 +433,43 @@ function _prepare_upgrade_notice( $previous_version, $current_version ) {
440
  ]
441
  );
442
  } elseif ( ! $previous_version && $current_version ) { // User successfully installed.
443
- if ( \current_user_can( 'update_plugins' ) ) {
444
- \add_action( 'admin_notices', __NAMESPACE__ . '\\_do_install_notice' );
445
- }
446
- }
447
- }
448
 
449
- /**
450
- * Outputs "your site has been installed" notification to applicable plugin users on upgrade.
451
- *
452
- * @since 4.1.0
453
- */
454
- function _do_install_notice() {
455
-
456
- $tsf = \the_seo_framework();
457
 
458
- // Make this persistent (2x count, 1 minute timeout)?
459
- $tsf->do_dismissible_notice(
460
- sprintf(
461
- '<p>%s</p><p>%s</p>',
462
- \esc_html__( 'The SEO Framework automatically optimizes your website for search engines and social media.', 'autodescription' ),
463
- $tsf->convert_markdown(
464
  sprintf(
465
- /* translators: %s = Link, markdown. */
466
- \esc_html__( 'To take full advantage of all SEO features, please follow our [5-minute setup guide](%s).', 'autodescription' ),
467
- 'https://theseoframework.com/docs/seo-plugin-setup/' // Use https://tsf.fyi/docs/setup ? Needless redirection...
 
 
 
 
 
 
 
 
468
  ),
469
- [ 'a' ],
470
- [ 'a_internal' => false ]
471
- )
472
- ),
473
- 'info',
474
- false,
475
- false
476
- );
 
 
 
 
 
 
 
 
 
477
  }
478
 
479
  /**
@@ -499,41 +495,30 @@ function _prepare_upgrade_suggestion( $previous_version, $current_version ) { //
499
  }
500
 
501
  /**
502
- * Memoize and returns upgrade notices to be outputted in admin.
503
  *
504
  * @since 2.9.0
505
  * @since 4.1.0 Deprecated. We can no longer rely on this from WP 5.5.
506
- * @deprecated Use persistent notifications, instead.
 
507
  *
508
- * @param string $notice The upgrade notice.
509
- * @param bool $get Whether to return the upgrade notices.
510
- * @return array|void The notices when $get is true.
511
  */
512
- function _add_upgrade_notice( $notice = '', $get = false ) {
513
-
514
- // Memoize the strings for a later $get
515
- static $cache = [];
516
-
517
- if ( $get )
518
- return $cache;
519
-
520
- $cache[] = $notice;
521
- }
522
-
523
- /**
524
- * Outputs available upgrade notices.
525
- *
526
- * @since 2.9.0
527
- * @since 3.0.0 Added prefix.
528
- * @since 4.1.0 Deprecated. We can no longer rely on this from WP 5.5.
529
- * @deprecated Use persistent notifications, instead.
530
- * @uses _add_upgrade_notice()
531
- */
532
- function _output_upgrade_notices() {
533
- foreach ( _add_upgrade_notice( '', true ) as $notice ) {
534
- // @TODO rtl?
535
- \the_seo_framework()->do_dismissible_notice( 'SEO: ' . $notice, 'info' );
536
- }
537
  }
538
 
539
  /**
@@ -542,7 +527,7 @@ function _output_upgrade_notices() {
542
  * @since 3.1.0
543
  */
544
  function _do_upgrade_1() {
545
- \the_seo_framework()->register_settings();
546
  }
547
 
548
  /**
@@ -561,7 +546,7 @@ function _do_upgrade_2701() {
561
  \add_term_meta( $term_id, THE_SEO_FRAMEWORK_TERM_OPTIONS, $meta, true );
562
  }
563
 
564
- //= Rudimentary test for remaining ~300 users of earlier versions passed, set initial version to 2600.
565
  \update_option( 'the_seo_framework_initial_db_version', '2600', 'no' );
566
  }
567
  }
@@ -587,13 +572,13 @@ function _do_upgrade_2802() {
587
  function _do_upgrade_2900() {
588
 
589
  if ( \get_option( 'the_seo_framework_initial_db_version' ) < '2900' ) {
590
- $tsf = \the_seo_framework();
591
 
592
  $card_type = trim( $tsf->get_option( 'twitter_card', false ) );
593
  if ( 'photo' === $card_type ) {
594
  $tsf->update_option( 'twitter_card', 'summary_large_image' );
595
  _add_upgrade_notice(
596
- \esc_html__( 'Twitter Photo Cards have been deprecated. Your site now uses Summary Cards when applicable.', 'autodescription' )
597
  );
598
  }
599
  }
@@ -611,14 +596,14 @@ function _do_upgrade_2900() {
611
  function _do_upgrade_3001() {
612
 
613
  if ( \get_option( 'the_seo_framework_initial_db_version' ) < '3001' ) {
614
- $tsf = \the_seo_framework();
615
 
616
- //= Only show notice if old option exists. Falls back to default upgrader otherwise.
617
  $sitemap_timestamps = $tsf->get_option( 'sitemap_timestamps', false );
618
  if ( '' !== $sitemap_timestamps ) {
619
  $tsf->update_option( 'timestamps_format', (string) (int) $sitemap_timestamps );
620
  _add_upgrade_notice(
621
- \esc_html__( 'The previous sitemap timestamp settings have been converted into new global timestamp settings.', 'autodescription' )
622
  );
623
  } else {
624
  $tsf->update_option( 'timestamps_format', '1' );
@@ -648,7 +633,7 @@ function _do_upgrade_3103() {
648
  \add_option( THE_SEO_FRAMEWORK_SITE_CACHE, [] );
649
 
650
  if ( \get_option( 'the_seo_framework_initial_db_version' ) < '3103' ) {
651
- $tsf = \the_seo_framework();
652
 
653
  // Transport title separator (option name typo).
654
  $tsf->update_option( 'title_separator', $tsf->get_option( 'title_seperator', false ) ?: 'hyphen' );
@@ -674,9 +659,6 @@ function _do_upgrade_3103() {
674
 
675
  // Add non-default HTML stripping option. Defaulting to previous behavior.
676
  $tsf->update_option( 'title_strip_tags', 0 ); // NOTE: Default is 1.
677
-
678
- // Adds non-default priority option.
679
- $tsf->update_option( 'sitemaps_priority', 1 ); // NOTE: Default is 0.
680
  }
681
  }
682
 
@@ -695,7 +677,7 @@ function _do_upgrade_3103() {
695
  function _do_upgrade_3300() {
696
 
697
  if ( \get_option( 'the_seo_framework_initial_db_version' ) < '3300' ) {
698
- $tsf = \the_seo_framework();
699
 
700
  // Remove old rewrite rules.
701
  unset(
@@ -713,7 +695,7 @@ function _do_upgrade_3300() {
713
 
714
  if ( $tsf->get_option( 'ping_google', false ) || $tsf->get_option( 'ping_bing', false ) ) {
715
  _add_upgrade_notice(
716
- \esc_html__( 'A cronjob is now used to ping search engines, and it alerts them to changes in your sitemap.', 'autodescription' )
717
  );
718
  }
719
 
@@ -726,7 +708,7 @@ function _do_upgrade_3300() {
726
  }
727
 
728
  _add_upgrade_notice(
729
- \esc_html__( 'The positions in the "Meta Title Additions Location" setting for the homepage have been reversed, left to right, but the output has not been changed. If you must downgrade for some reason, remember to switch the location back again.', 'autodescription' )
730
  );
731
  }
732
  }
@@ -744,7 +726,7 @@ function _do_upgrade_3300() {
744
  function _do_upgrade_4051() {
745
 
746
  if ( \get_option( 'the_seo_framework_initial_db_version' ) < '4051' ) {
747
- $tsf = \the_seo_framework();
748
 
749
  $tsf->update_option( 'advanced_query_protection', 0 );
750
  $tsf->update_option( 'index_the_feed', 0 );
@@ -767,7 +749,7 @@ function _do_upgrade_4051() {
767
  function _do_upgrade_4103() {
768
 
769
  if ( \get_option( 'the_seo_framework_initial_db_version' ) < '4103' ) {
770
- $tsf = \the_seo_framework();
771
 
772
  $tsf->update_option( 'disabled_taxonomies', [] );
773
  $tsf->update_option( 'sitemap_logo_url', '' );
@@ -809,7 +791,7 @@ function _do_upgrade_4103() {
809
  function _do_upgrade_4110() {
810
 
811
  if ( \get_option( 'the_seo_framework_initial_db_version' ) < '4110' ) {
812
- $tsf = \the_seo_framework();
813
 
814
  $tsf->update_option( 'oembed_use_og_title', 0 );
815
  $tsf->update_option( 'oembed_use_social_image', 0 ); // Defaults to 1 for new sites!
@@ -823,8 +805,18 @@ function _do_upgrade_4110() {
823
  */
824
  function _do_upgrade_4120() {
825
  if ( \get_option( 'the_seo_framework_initial_db_version' ) < '4120' ) {
826
- $tsf = \the_seo_framework();
827
-
828
  $tsf->update_option( 'ping_use_cron_prerender', 0 );
829
  }
830
  }
 
 
 
 
 
 
 
 
 
 
 
30
  * compared to The SEO Framework version constant.
31
  *
32
  * @since 2.7.0
33
+ * @since 3.2.4 Applied namspacing to this file. All method names have changed.
34
  * @since 4.1.1 No longer memoizes the previous version early. This should help bypass the cache flush.
35
  * @access private
36
  * @TODO convert to class, see \TSF_Extension_Manager\Upgrader
37
  * It's a generator/iterator, so we must wait to PHP>5.5 support.
 
 
38
  */
39
 
40
  // phpcs:disable, TSF.Performance.Opcodes.ShouldHaveNamespaceEscape
44
  \add_action( 'the_seo_framework_upgraded', __NAMESPACE__ . '\\_prepare_upgrade_suggestion', 100, 2 );
45
  \add_action( 'the_seo_framework_downgraded', __NAMESPACE__ . '\\_prepare_downgrade_notice', 99, 2 );
46
 
 
 
 
 
 
 
47
  /**
48
  * Returns the default site options.
49
  * Memoizes the return value.
53
  * @return array The default site options.
54
  */
55
  function _upgrade_default_site_options() {
56
+ static $memo;
57
+ return $memo ?? ( $memo = \tsf()->get_default_site_options() );
58
  }
59
 
60
  /**
66
  * @return string The prior-to-upgrade TSF db version.
67
  */
68
  function _previous_db_version() {
69
+ static $memo;
70
+ return $memo ?? ( $memo = \get_option( 'the_seo_framework_upgraded_db_version', '0' ) );
71
  }
72
 
73
  /**
101
  */
102
  function _do_upgrade() {
103
 
104
+ $tsf = \tsf();
105
 
106
  if ( ! $tsf->loaded ) return;
107
  if ( \wp_doing_ajax() ) return;
209
  //? This means no data may be erased for at least 1 major version, or 1 year, whichever is later.
210
  //? We must manually delete settings that are no longer used; we merge them otherwise.
211
  //? When a user upgrades beyond this scope, they aren't expected to roll back.
212
+ $versions = [ '1', '2701', '2802', '2900', '3001', '3103', '3300', '4051', '4103', '4110', '4120', '4200' ];
213
 
214
  foreach ( $versions as $_version ) {
215
  if ( $current_version < $_version ) {
216
+ ( __NAMESPACE__ . "\\_do_upgrade_{$_version}" )(); // This is an undocumented method for variable functions.
 
217
  $current_version = _set_version( $_version );
218
  }
219
  }
356
 
357
  // phpcs:ignore, WordPress.PHP.StrictComparisons.LooseComparison -- might be mixed types.
358
  if ( $previous_version && $previous_version != $current_version ) { // User successfully downgraded.
359
+ $tsf = \tsf();
360
 
361
  $tsf->register_dismissible_persistent_notice(
362
  $tsf->convert_markdown(
393
  * @since 4.1.0 1. Moved admin notice user capability check here.
394
  * 2. Now registers persistent notice for the update version.
395
  * @since 4.1.2 No longer can accidentally show the install notice after stale upgrade.
396
+ * @since 4.2.0 The installation notice is now persistent, shown twice, to users with activate_plugins capability, on the main site.
397
  * @TODO Add browser cache flush notice? Or set a pragma/cache-control header?
398
  * Users that remove query strings (thanks to YSlow) are to blame, though.
399
  * The authors of the plugin that allowed this to happen are even more to blame.
406
 
407
  // phpcs:ignore, WordPress.PHP.StrictComparisons.LooseComparison -- might be mixed types.
408
  if ( $previous_version && $previous_version != $current_version ) { // User successfully upgraded.
409
+ $tsf = \tsf();
410
 
411
  $tsf->register_dismissible_persistent_notice(
412
  $tsf->convert_markdown(
433
  ]
434
  );
435
  } elseif ( ! $previous_version && $current_version ) { // User successfully installed.
436
+ $network_mode = (bool) ( \get_site_option( 'active_sitewide_plugins' )[ THE_SEO_FRAMEWORK_PLUGIN_BASENAME ] ?? false );
 
 
 
 
437
 
438
+ // Only show notice when not in network mode, or on main site otherwise.
439
+ if ( ! $network_mode || \is_main_site() ) {
440
+ $tsf = \tsf();
 
 
 
 
 
441
 
442
+ $tsf->register_dismissible_persistent_notice(
 
 
 
 
 
443
  sprintf(
444
+ '<p>%s</p><p>%s</p>',
445
+ \esc_html__( 'The SEO Framework automatically optimizes your website for search engines and social media.', 'autodescription' ),
446
+ $tsf->convert_markdown(
447
+ sprintf(
448
+ /* translators: %s = Link, markdown. */
449
+ \esc_html__( 'To take full advantage of all SEO features, please follow our [5-minute setup guide](%s).', 'autodescription' ),
450
+ 'https://theseoframework.com/docs/seo-plugin-setup/' // Use https://tsf.fyi/docs/setup ? Needless redirection...
451
+ ),
452
+ [ 'a' ],
453
+ [ 'a_internal' => false ]
454
+ )
455
  ),
456
+ 'thank-you-installed',
457
+ [
458
+ 'type' => 'info',
459
+ 'icon' => false,
460
+ 'escape' => false,
461
+ ],
462
+ [
463
+ 'screens' => [],
464
+ 'excl_screens' => [ 'post', 'term', 'upload', 'media', 'plugin-editor', 'plugin-install', 'themes' ],
465
+ 'capability' => 'activate_plugins',
466
+ 'user' => 0,
467
+ 'count' => 1,
468
+ 'timeout' => 2 * MINUTE_IN_SECONDS,
469
+ ]
470
+ );
471
+ }
472
+ }
473
  }
474
 
475
  /**
495
  }
496
 
497
  /**
498
+ * Registers upgrade notices.
499
  *
500
  * @since 2.9.0
501
  * @since 4.1.0 Deprecated. We can no longer rely on this from WP 5.5.
502
+ * @since 4.2.0 1. Reinstated, and now forwards notices to the persistent-notice system.
503
+ * 2. No longer returns values. Removed pertaining second parameter.
504
  *
505
+ * @param string $notice The upgrade notice. Doesn't need to be escaped.
 
 
506
  */
507
+ function _add_upgrade_notice( $notice = '' ) {
508
+
509
+ $tsf = \tsf();
510
+
511
+ $tsf->register_dismissible_persistent_notice(
512
+ "SEO: $notice",
513
+ 'upgrade-' . ( hash( 'md5', $notice ) ?: uniqid( '', true ) ), // if md5 is unregistered, it'll return false
514
+ [
515
+ 'type' => 'info',
516
+ ],
517
+ [
518
+ 'excl_screens' => [ 'post', 'term', 'upload', 'media', 'plugin-editor', 'plugin-install', 'themes' ],
519
+ 'capability' => $tsf->get_settings_capability(),
520
+ ]
521
+ );
 
 
 
 
 
 
 
 
 
 
522
  }
523
 
524
  /**
527
  * @since 3.1.0
528
  */
529
  function _do_upgrade_1() {
530
+ \tsf()->register_settings();
531
  }
532
 
533
  /**
546
  \add_term_meta( $term_id, THE_SEO_FRAMEWORK_TERM_OPTIONS, $meta, true );
547
  }
548
 
549
+ // Rudimentary test for remaining ~300 users of earlier versions passed, set initial version to 2600.
550
  \update_option( 'the_seo_framework_initial_db_version', '2600', 'no' );
551
  }
552
  }
572
  function _do_upgrade_2900() {
573
 
574
  if ( \get_option( 'the_seo_framework_initial_db_version' ) < '2900' ) {
575
+ $tsf = \tsf();
576
 
577
  $card_type = trim( $tsf->get_option( 'twitter_card', false ) );
578
  if ( 'photo' === $card_type ) {
579
  $tsf->update_option( 'twitter_card', 'summary_large_image' );
580
  _add_upgrade_notice(
581
+ \__( 'Twitter Photo Cards have been deprecated. Your site now uses Summary Cards when applicable.', 'autodescription' )
582
  );
583
  }
584
  }
596
  function _do_upgrade_3001() {
597
 
598
  if ( \get_option( 'the_seo_framework_initial_db_version' ) < '3001' ) {
599
+ $tsf = \tsf();
600
 
601
+ // Only show notice if old option exists. Falls back to default upgrader otherwise.
602
  $sitemap_timestamps = $tsf->get_option( 'sitemap_timestamps', false );
603
  if ( '' !== $sitemap_timestamps ) {
604
  $tsf->update_option( 'timestamps_format', (string) (int) $sitemap_timestamps );
605
  _add_upgrade_notice(
606
+ \__( 'The previous sitemap timestamp settings have been converted into new global timestamp settings.', 'autodescription' )
607
  );
608
  } else {
609
  $tsf->update_option( 'timestamps_format', '1' );
633
  \add_option( THE_SEO_FRAMEWORK_SITE_CACHE, [] );
634
 
635
  if ( \get_option( 'the_seo_framework_initial_db_version' ) < '3103' ) {
636
+ $tsf = \tsf();
637
 
638
  // Transport title separator (option name typo).
639
  $tsf->update_option( 'title_separator', $tsf->get_option( 'title_seperator', false ) ?: 'hyphen' );
659
 
660
  // Add non-default HTML stripping option. Defaulting to previous behavior.
661
  $tsf->update_option( 'title_strip_tags', 0 ); // NOTE: Default is 1.
 
 
 
662
  }
663
  }
664
 
677
  function _do_upgrade_3300() {
678
 
679
  if ( \get_option( 'the_seo_framework_initial_db_version' ) < '3300' ) {
680
+ $tsf = \tsf();
681
 
682
  // Remove old rewrite rules.
683
  unset(
695
 
696
  if ( $tsf->get_option( 'ping_google', false ) || $tsf->get_option( 'ping_bing', false ) ) {
697
  _add_upgrade_notice(
698
+ \__( 'A cronjob is now used to ping search engines, and it alerts them to changes in your sitemap.', 'autodescription' )
699
  );
700
  }
701
 
708
  }
709
 
710
  _add_upgrade_notice(
711
+ \__( 'The positions in the "Meta Title Additions Location" setting for the homepage have been reversed, left to right, but the output has not been changed. If you must downgrade for some reason, remember to switch the location back again.', 'autodescription' )
712
  );
713
  }
714
  }
726
  function _do_upgrade_4051() {
727
 
728
  if ( \get_option( 'the_seo_framework_initial_db_version' ) < '4051' ) {
729
+ $tsf = \tsf();
730
 
731
  $tsf->update_option( 'advanced_query_protection', 0 );
732
  $tsf->update_option( 'index_the_feed', 0 );
749
  function _do_upgrade_4103() {
750
 
751
  if ( \get_option( 'the_seo_framework_initial_db_version' ) < '4103' ) {
752
+ $tsf = \tsf();
753
 
754
  $tsf->update_option( 'disabled_taxonomies', [] );
755
  $tsf->update_option( 'sitemap_logo_url', '' );
791
  function _do_upgrade_4110() {
792
 
793
  if ( \get_option( 'the_seo_framework_initial_db_version' ) < '4110' ) {
794
+ $tsf = \tsf();
795
 
796
  $tsf->update_option( 'oembed_use_og_title', 0 );
797
  $tsf->update_option( 'oembed_use_social_image', 0 ); // Defaults to 1 for new sites!
805
  */
806
  function _do_upgrade_4120() {
807
  if ( \get_option( 'the_seo_framework_initial_db_version' ) < '4120' ) {
808
+ $tsf = \tsf();
 
809
  $tsf->update_option( 'ping_use_cron_prerender', 0 );
810
  }
811
  }
812
+
813
+ /**
814
+ * Removes the global `the_seo_framework_tested_upgrade_version` option.
815
+ *
816
+ * @since 4.2.0
817
+ */
818
+ function _do_upgrade_4200() {
819
+ if ( \get_option( 'the_seo_framework_initial_db_version' ) < '4200' ) {
820
+ \delete_option( 'the_seo_framework_tested_upgrade_version' );
821
+ }
822
+ }
inc/classes/admin-init.class.php CHANGED
@@ -43,7 +43,7 @@ class Admin_Init extends Init {
43
  */
44
  public function _init_seo_bar_tables() {
45
  if ( $this->get_option( 'display_seo_bar_tables' ) )
46
- new Bridges\SeoBar;
47
  }
48
 
49
  /**
@@ -68,7 +68,7 @@ class Admin_Init extends Init {
68
  */
69
  public function _add_post_state( $states = [], $post = null ) {
70
 
71
- $post_id = isset( $post->ID ) ? $post->ID : false;
72
 
73
  if ( $post_id ) {
74
  $search_exclude = $this->get_option( 'alter_search_query' ) && $this->get_post_meta_item( 'exclude_local_search', $post_id );
@@ -177,15 +177,13 @@ class Admin_Init extends Init {
177
  */
178
  public function get_input_guidelines( $locale = null ) {
179
 
180
- static $guidelines = [];
181
-
182
  $locale = $locale ?: \get_locale();
183
 
184
  // Strip the "_formal" and other suffixes. 5 length: xx_YY
185
  $locale = substr( $locale, 0, 5 );
186
 
187
- if ( isset( $guidelines[ $locale ] ) )
188
- return $guidelines[ $locale ];
189
 
190
  // phpcs:disable, WordPress.WhiteSpace.OperatorSpacing.SpacingAfter
191
  $character_adjustments = [
@@ -204,7 +202,7 @@ class Admin_Init extends Init {
204
  ];
205
  // phpcs:enable, WordPress.WhiteSpace.OperatorSpacing.SpacingAfter
206
 
207
- $c_adjust = isset( $character_adjustments[ $locale ] ) ? $character_adjustments[ $locale ] : 1;
208
 
209
  $pixel_adjustments = [
210
  'ar' => 760 / 910, // Arabic (العربية)
@@ -215,7 +213,7 @@ class Admin_Init extends Init {
215
  'ckb' => 760 / 910, // Central Kurdish (كوردی)
216
  ];
217
 
218
- $p_adjust = isset( $pixel_adjustments[ $locale ] ) ? $pixel_adjustments[ $locale ] : 1;
219
 
220
  // phpcs:disable, WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned
221
  /**
@@ -223,78 +221,81 @@ class Admin_Init extends Init {
223
  * @param array $guidelines The title and description guidelines.
224
  * Don't alter the format. Only change the numeric values.
225
  */
226
- return $guidelines[ $locale ] = (array) \apply_filters(
227
- 'the_seo_framework_input_guidelines',
228
- [
229
- 'title' => [
230
- 'search' => [
231
- 'chars' => [
232
- 'lower' => (int) ( 25 * $c_adjust ),
233
- 'goodLower' => (int) ( 35 * $c_adjust ),
234
- 'goodUpper' => (int) ( 65 * $c_adjust ),
235
- 'upper' => (int) ( 75 * $c_adjust ),
 
 
 
 
 
 
 
 
236
  ],
237
- 'pixels' => [
238
- 'lower' => (int) ( 200 * $p_adjust ),
239
- 'goodLower' => (int) ( 280 * $p_adjust ),
240
- 'goodUpper' => (int) ( 520 * $p_adjust ),
241
- 'upper' => (int) ( 600 * $p_adjust ),
 
 
 
242
  ],
243
- ],
244
- 'opengraph' => [
245
- 'chars' => [
246
- 'lower' => 15,
247
- 'goodLower' => 25,
248
- 'goodUpper' => 88,
249
- 'upper' => 100,
 
250
  ],
251
- 'pixels' => [],
252
  ],
253
- 'twitter' => [
254
- 'chars' => [
255
- 'lower' => 15,
256
- 'goodLower' => 25,
257
- 'goodUpper' => 69,
258
- 'upper' => 70,
 
 
 
 
 
 
 
 
259
  ],
260
- 'pixels' => [],
261
- ],
262
- ],
263
- 'description' => [
264
- 'search' => [
265
- 'chars' => [
266
- 'lower' => (int) ( 45 * $c_adjust ),
267
- 'goodLower' => (int) ( 80 * $c_adjust ),
268
- 'goodUpper' => (int) ( 160 * $c_adjust ),
269
- 'upper' => (int) ( 320 * $c_adjust ),
270
  ],
271
- 'pixels' => [
272
- 'lower' => (int) ( 256 * $p_adjust ),
273
- 'goodLower' => (int) ( 455 * $p_adjust ),
274
- 'goodUpper' => (int) ( 910 * $p_adjust ),
275
- 'upper' => (int) ( 1820 * $p_adjust ),
 
 
 
276
  ],
277
  ],
278
- 'opengraph' => [
279
- 'chars' => [
280
- 'lower' => 45,
281
- 'goodLower' => 80,
282
- 'goodUpper' => 200,
283
- 'upper' => 300,
284
- ],
285
- 'pixels' => [],
286
- ],
287
- 'twitter' => [
288
- 'chars' => [
289
- 'lower' => 45,
290
- 'goodLower' => 80,
291
- 'goodUpper' => 200,
292
- 'upper' => 200,
293
- ],
294
- 'pixels' => [],
295
- ],
296
- ],
297
- ]
298
  );
299
  // phpcs:enable, WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned
300
  }
@@ -306,6 +307,7 @@ class Admin_Init extends Init {
306
  *
307
  * @since 3.1.0
308
  * @since 4.0.0 Now added a short leading-dot version for ARIA labels.
 
309
  *
310
  * @return array
311
  */
@@ -355,7 +357,7 @@ class Admin_Init extends Init {
355
  * valid and generated between 12-24 hours ago.
356
  */
357
  public function _check_tsf_ajax_referer( $capability ) {
358
- return \check_ajax_referer( 'tsf-ajax-' . $capability, 'nonce', true );
359
  }
360
 
361
  /**
@@ -364,15 +366,16 @@ class Admin_Init extends Init {
364
  *
365
  * @since 2.2.2
366
  * @since 2.9.2 Added user-friendly exception handling.
367
- * @since 2.9.3 : 1. Query arguments work again (regression 2.9.2).
368
- * 2. Now only accepts http and https protocols.
 
369
  *
370
  * @param string $page Menu slug. This slug must exist, or the redirect will loop back to the current page.
371
  * @param array $query_args Optional. Associative array of query string arguments
372
  * (key => value). Default is an empty array.
373
  * @return null Return early if first argument is false.
374
  */
375
- public function admin_redirect( $page, array $query_args = [] ) {
376
 
377
  if ( empty( $page ) ) return;
378
 
@@ -380,11 +383,7 @@ class Admin_Init extends Init {
380
  // Might cause security issues... we _must_ exit, always? Show warning?
381
  $url = html_entity_decode( \menu_page_url( $page, false ) );
382
 
383
- foreach ( $query_args as $key => $value )
384
- if ( empty( $key ) || empty( $value ) )
385
- unset( $query_args[ $key ] );
386
-
387
- $target = \add_query_arg( $query_args, $url );
388
  $target = \esc_url_raw( $target, [ 'https', 'http' ] );
389
 
390
  // Predict white screen:
@@ -395,7 +394,7 @@ class Admin_Init extends Init {
395
  * 1. Change 302 to 500 if you wish to test headers.
396
  * 2. Also force handle_admin_redirect_error() to run.
397
  */
398
- $success = \wp_safe_redirect( $target, 302 ); // phpcs:ignore, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
399
 
400
  // White screen of death for non-debugging users. Let's make it friendlier.
401
  if ( $headers_sent )
@@ -468,9 +467,10 @@ class Admin_Init extends Init {
468
  * Do not input non-integer values (such as `false`), for those might cause adverse events.
469
  * }
470
  */
471
- public function register_dismissible_persistent_notice( $message, $key, array $args = [], array $conditions = [] ) {
472
 
473
- // We made this mistake ourselves. Let's test against it. Can't wait for PHP 7.1+ support.
 
474
  if ( ! is_scalar( $key ) || ! \strlen( $key ) ) return;
475
 
476
  // Sanitize the key so that HTML, JS, and PHP can communicate easily via it.
@@ -595,18 +595,17 @@ class Admin_Init extends Init {
595
  public function _dismiss_notice() {
596
 
597
  // phpcs:ignore, WordPress.Security.NonceVerification.Missing -- We require the POST data to find locally stored nonces.
598
- $key = isset( $_POST['tsf-notice-submit'] ) ? $_POST['tsf-notice-submit'] : '';
 
599
  if ( ! $key ) return;
600
 
601
  $notices = $this->get_static_cache( 'persistent_notices', [] );
602
  // Notice was deleted already elsewhere, or key was faulty. Either way, ignore--should be self-resolving.
603
  if ( empty( $notices[ $key ]['conditions']['capability'] ) ) return;
604
 
605
- // phpcs:ignore, WordPress.Security.NonceVerification.Missing -- We require the POST data to find locally stored nonces.
606
- $nonce = isset( $_POST['tsf_notice_nonce'] ) ? $_POST['tsf_notice_nonce'] : '';
607
-
608
  if ( ! \current_user_can( $notices[ $key ]['conditions']['capability'] )
609
- || ! \wp_verify_nonce( $nonce, $this->_get_dismiss_notice_nonce_action( $key ) ) ) {
 
610
  \wp_die( -1, 403 );
611
  }
612
 
43
  */
44
  public function _init_seo_bar_tables() {
45
  if ( $this->get_option( 'display_seo_bar_tables' ) )
46
+ new Bridges\SEOBar;
47
  }
48
 
49
  /**
68
  */
69
  public function _add_post_state( $states = [], $post = null ) {
70
 
71
+ $post_id = $post->ID ?? false;
72
 
73
  if ( $post_id ) {
74
  $search_exclude = $this->get_option( 'alter_search_query' ) && $this->get_post_meta_item( 'exclude_local_search', $post_id );
177
  */
178
  public function get_input_guidelines( $locale = null ) {
179
 
 
 
180
  $locale = $locale ?: \get_locale();
181
 
182
  // Strip the "_formal" and other suffixes. 5 length: xx_YY
183
  $locale = substr( $locale, 0, 5 );
184
 
185
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
186
+ if ( null !== $memo = memo( null, $locale ) ) return $memo;
187
 
188
  // phpcs:disable, WordPress.WhiteSpace.OperatorSpacing.SpacingAfter
189
  $character_adjustments = [
202
  ];
203
  // phpcs:enable, WordPress.WhiteSpace.OperatorSpacing.SpacingAfter
204
 
205
+ $c_adjust = $character_adjustments[ $locale ] ?? 1;
206
 
207
  $pixel_adjustments = [
208
  'ar' => 760 / 910, // Arabic (العربية)
213
  'ckb' => 760 / 910, // Central Kurdish (كوردی)
214
  ];
215
 
216
+ $p_adjust = $pixel_adjustments[ $locale ] ?? 1;
217
 
218
  // phpcs:disable, WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned
219
  /**
221
  * @param array $guidelines The title and description guidelines.
222
  * Don't alter the format. Only change the numeric values.
223
  */
224
+ return memo(
225
+ (array) \apply_filters(
226
+ 'the_seo_framework_input_guidelines',
227
+ [
228
+ 'title' => [
229
+ 'search' => [
230
+ 'chars' => [
231
+ 'lower' => (int) ( 25 * $c_adjust ),
232
+ 'goodLower' => (int) ( 35 * $c_adjust ),
233
+ 'goodUpper' => (int) ( 65 * $c_adjust ),
234
+ 'upper' => (int) ( 75 * $c_adjust ),
235
+ ],
236
+ 'pixels' => [
237
+ 'lower' => (int) ( 200 * $p_adjust ),
238
+ 'goodLower' => (int) ( 280 * $p_adjust ),
239
+ 'goodUpper' => (int) ( 520 * $p_adjust ),
240
+ 'upper' => (int) ( 600 * $p_adjust ),
241
+ ],
242
  ],
243
+ 'opengraph' => [
244
+ 'chars' => [
245
+ 'lower' => 15,
246
+ 'goodLower' => 25,
247
+ 'goodUpper' => 88,
248
+ 'upper' => 100,
249
+ ],
250
+ 'pixels' => [],
251
  ],
252
+ 'twitter' => [
253
+ 'chars' => [
254
+ 'lower' => 15,
255
+ 'goodLower' => 25,
256
+ 'goodUpper' => 69,
257
+ 'upper' => 70,
258
+ ],
259
+ 'pixels' => [],
260
  ],
 
261
  ],
262
+ 'description' => [
263
+ 'search' => [
264
+ 'chars' => [
265
+ 'lower' => (int) ( 45 * $c_adjust ),
266
+ 'goodLower' => (int) ( 80 * $c_adjust ),
267
+ 'goodUpper' => (int) ( 160 * $c_adjust ),
268
+ 'upper' => (int) ( 320 * $c_adjust ),
269
+ ],
270
+ 'pixels' => [
271
+ 'lower' => (int) ( 256 * $p_adjust ),
272
+ 'goodLower' => (int) ( 455 * $p_adjust ),
273
+ 'goodUpper' => (int) ( 910 * $p_adjust ),
274
+ 'upper' => (int) ( 1820 * $p_adjust ),
275
+ ],
276
  ],
277
+ 'opengraph' => [
278
+ 'chars' => [
279
+ 'lower' => 45,
280
+ 'goodLower' => 80,
281
+ 'goodUpper' => 200,
282
+ 'upper' => 300,
283
+ ],
284
+ 'pixels' => [],
 
 
285
  ],
286
+ 'twitter' => [
287
+ 'chars' => [
288
+ 'lower' => 45,
289
+ 'goodLower' => 80,
290
+ 'goodUpper' => 200,
291
+ 'upper' => 200,
292
+ ],
293
+ 'pixels' => [],
294
  ],
295
  ],
296
+ ]
297
+ ),
298
+ $locale
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
  );
300
  // phpcs:enable, WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned
301
  }
307
  *
308
  * @since 3.1.0
309
  * @since 4.0.0 Now added a short leading-dot version for ARIA labels.
310
+ * @TODO move this to another object? -> i18n/guidelines
311
  *
312
  * @return array
313
  */
357
  * valid and generated between 12-24 hours ago.
358
  */
359
  public function _check_tsf_ajax_referer( $capability ) {
360
+ return \check_ajax_referer( "tsf-ajax-$capability", 'nonce', true );
361
  }
362
 
363
  /**
366
  *
367
  * @since 2.2.2
368
  * @since 2.9.2 Added user-friendly exception handling.
369
+ * @since 2.9.3 1. Query arguments work again (regression 2.9.2).
370
+ * 2. Now only accepts http and https protocols.
371
+ * @since 4.2.0 Now allows query arguments with value 0|'0'.
372
  *
373
  * @param string $page Menu slug. This slug must exist, or the redirect will loop back to the current page.
374
  * @param array $query_args Optional. Associative array of query string arguments
375
  * (key => value). Default is an empty array.
376
  * @return null Return early if first argument is false.
377
  */
378
+ public function admin_redirect( $page, $query_args = [] ) {
379
 
380
  if ( empty( $page ) ) return;
381
 
383
  // Might cause security issues... we _must_ exit, always? Show warning?
384
  $url = html_entity_decode( \menu_page_url( $page, false ) );
385
 
386
+ $target = \add_query_arg( array_filter( $query_args, 'strlen' ), $url );
 
 
 
 
387
  $target = \esc_url_raw( $target, [ 'https', 'http' ] );
388
 
389
  // Predict white screen:
394
  * 1. Change 302 to 500 if you wish to test headers.
395
  * 2. Also force handle_admin_redirect_error() to run.
396
  */
397
+ \wp_safe_redirect( $target, 302 );
398
 
399
  // White screen of death for non-debugging users. Let's make it friendlier.
400
  if ( $headers_sent )
467
  * Do not input non-integer values (such as `false`), for those might cause adverse events.
468
  * }
469
  */
470
+ public function register_dismissible_persistent_notice( $message, $key, $args = [], $conditions = [] ) {
471
 
472
+ // We made this mistake ourselves. Let's test against it.
473
+ // We can't type $key to scalar, for PHP is dumb with that type.
474
  if ( ! is_scalar( $key ) || ! \strlen( $key ) ) return;
475
 
476
  // Sanitize the key so that HTML, JS, and PHP can communicate easily via it.
595
  public function _dismiss_notice() {
596
 
597
  // phpcs:ignore, WordPress.Security.NonceVerification.Missing -- We require the POST data to find locally stored nonces.
598
+ $key = $_POST['tsf-notice-submit'] ?? '';
599
+
600
  if ( ! $key ) return;
601
 
602
  $notices = $this->get_static_cache( 'persistent_notices', [] );
603
  // Notice was deleted already elsewhere, or key was faulty. Either way, ignore--should be self-resolving.
604
  if ( empty( $notices[ $key ]['conditions']['capability'] ) ) return;
605
 
 
 
 
606
  if ( ! \current_user_can( $notices[ $key ]['conditions']['capability'] )
607
+ // phpcs:ignore, WordPress.Security.NonceVerification.Missing -- We require the POST data to find locally stored nonces.
608
+ || ! \wp_verify_nonce( $_POST['tsf_notice_nonce'] ?? '', $this->_get_dismiss_notice_nonce_action( $key ) ) ) {
609
  \wp_die( -1, 403 );
610
  }
611
 
inc/classes/admin-pages.class.php CHANGED
@@ -86,8 +86,8 @@ class Admin_Pages extends Generate_Ldjson {
86
  );
87
 
88
  // Enqueue scripts
89
- \add_action( 'admin_print_scripts-' . $this->seo_settings_page_hook, [ $this, '_init_admin_scripts' ], 11 );
90
- \add_action( 'load-' . $this->seo_settings_page_hook, [ $this, '_register_seo_settings_meta_boxes' ] );
91
  }
92
 
93
  /**
@@ -110,8 +110,8 @@ class Admin_Pages extends Generate_Ldjson {
110
  public function _output_settings_wrap() {
111
 
112
  \add_action(
113
- $this->seo_settings_page_hook . '_settings_page_boxes',
114
- Bridges\SeoSettings::class . '::_output_columns'
115
  );
116
 
117
  Bridges\SeoSettings::_output_wrap();
@@ -139,7 +139,7 @@ class Admin_Pages extends Generate_Ldjson {
139
  if ( $show_seobox )
140
  \add_action(
141
  'add_meta_boxes',
142
- Bridges\PostSettings::class . '::_prepare_meta_box'
143
  );
144
  }
145
 
@@ -164,8 +164,8 @@ class Admin_Pages extends Generate_Ldjson {
164
  $priority = (int) \apply_filters( 'the_seo_framework_term_metabox_priority', 0 );
165
 
166
  \add_action(
167
- $taxonomy . '_edit_form',
168
- Bridges\TermSettings::class . '::_prepare_setting_fields',
169
  $priority,
170
  2
171
  );
@@ -184,8 +184,8 @@ class Admin_Pages extends Generate_Ldjson {
184
  // WordPress made a mess of this. We can't reliably get a user future-proof. Load class for all users; check there.
185
  // if ( ! $user->has_cap( THE_SEO_FRAMEWORK_AUTHOR_INFO_CAP ) ) return;
186
 
187
- \add_action( 'show_user_profile', Bridges\UserSettings::class . '::_prepare_setting_fields', 0, 1 );
188
- \add_action( 'edit_user_profile', Bridges\UserSettings::class . '::_prepare_setting_fields', 0, 1 );
189
  }
190
 
191
  /**
@@ -340,7 +340,7 @@ class Admin_Pages extends Generate_Ldjson {
340
  * 'escape' => bool Optional. Whether to escape the $message. Default true.
341
  * }
342
  */
343
- protected function output_dismissible_persistent_notice( $message, $key, array $args ) { // phpcs:ignore,VariableAnalysis.CodeAnalysis
344
  $this->get_view( 'notice/persistent', get_defined_vars() );
345
  }
346
 
@@ -355,9 +355,8 @@ class Admin_Pages extends Generate_Ldjson {
355
  */
356
  protected function output_dismissible_persistent_notices() {
357
 
358
- $notices = $this->get_static_cache( 'persistent_notices', [] );
359
- $current_screen = \get_current_screen();
360
- $base = isset( $current_screen->base ) ? $current_screen->base : '';
361
 
362
  // Ideally, we don't want to output more than one on no-js. Alas, we can't anticipate the importance and order of the notices.
363
  foreach ( $notices as $key => $notice ) {
@@ -365,8 +364,8 @@ class Admin_Pages extends Generate_Ldjson {
365
 
366
  if ( ! \current_user_can( $cond['capability'] ) ) continue;
367
  if ( $cond['user'] && $cond['user'] !== $this->get_user_id() ) continue;
368
- if ( $cond['screens'] && ! \in_array( $base, $cond['screens'], true ) ) continue;
369
- if ( $cond['excl_screens'] && \in_array( $base, $cond['excl_screens'], true ) ) continue;
370
 
371
  if ( -1 !== $cond['timeout'] && $cond['timeout'] < time() ) {
372
  $this->clear_persistent_notice( $key );
@@ -384,7 +383,7 @@ class Admin_Pages extends Generate_Ldjson {
384
  * Returns the SEO Bar.
385
  *
386
  * @since 4.0.0
387
- * @uses \The_SEO_Framework\Interpreters\SeoBar::generate_bar();
388
  *
389
  * @param array $query : {
390
  * int $id : Required. The current post or term ID.
@@ -394,8 +393,8 @@ class Admin_Pages extends Generate_Ldjson {
394
  * }
395
  * @return string The generated SEO bar, in HTML.
396
  */
397
- public function get_generated_seo_bar( array $query ) {
398
- return Interpreters\SeoBar::generate_bar( $query );
399
  }
400
 
401
  /**
@@ -422,8 +421,8 @@ class Admin_Pages extends Generate_Ldjson {
422
  * @param string $id The input ID.
423
  * @param array $data The input data.
424
  */
425
- public function output_js_title_data( $id, array $data ) {
426
- printf(
427
  implode(
428
  '',
429
  [
@@ -435,9 +434,30 @@ class Admin_Pages extends Generate_Ldjson {
435
  '<span id="tsf-title-data_%1$s" class="hidden wp-exclude-emoji" data-for="%1$s" %2$s></span>',
436
  ]
437
  ),
438
- \esc_attr( $id ),
439
- // phpcs:ignore, WordPress.Security.EscapeOutput -- make_data_attributes escapes.
440
- Interpreters\HTML::make_data_attributes( $data )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441
  );
442
  }
443
 
@@ -464,8 +484,8 @@ class Admin_Pages extends Generate_Ldjson {
464
  * @param string $id The description input ID.
465
  * @param array $data The input data.
466
  */
467
- public function output_js_description_data( $id, array $data ) {
468
- printf(
469
  implode(
470
  '',
471
  [
@@ -473,139 +493,11 @@ class Admin_Pages extends Generate_Ldjson {
473
  '<span id="tsf-description-data_%1$s" class="hidden wp-exclude-emoji" data-for="%1$s" %2$s ></span>',
474
  ]
475
  ),
476
- \esc_attr( $id ),
477
- // phpcs:ignore, WordPress.Security.EscapeOutput -- make_data_attributes escapes.
478
- Interpreters\HTML::make_data_attributes( $data )
 
 
479
  );
480
  }
481
-
482
- /**
483
- * Calculates the social title and description placeholder values.
484
- * This is intricated, voluminous, and convoluted; but, there's no other way :(
485
- *
486
- * @since 4.0.0
487
- * @since 4.1.0 Now consistently applies escaping and transformation of the titles and descriptions.
488
- * This was not a security issue, since we always escape properly at output for sanity.
489
- * @access private
490
- * @todo deprecate--let JS handle this.
491
- *
492
- * @param array $args An array of 'id' and 'taxonomy' values.
493
- * @param string $for The screen it's for. Accepts 'edit' and 'settings'.
494
- * @return array An array social of titles and descriptions.
495
- */
496
- public function _get_social_placeholders( array $args, $for = 'edit' ) {
497
-
498
- $desc_from_custom_field = $this->get_description_from_custom_field( $args, false );
499
-
500
- if ( 'settings' === $for ) {
501
- $pm_edit_og_title = $args['id'] ? $this->get_post_meta_item( '_open_graph_title', $args['id'] ) : '';
502
- $pm_edit_og_desc = $args['id'] ? $this->get_post_meta_item( '_open_graph_description', $args['id'] ) : '';
503
- $pm_edit_tw_title = $args['id'] ? $this->get_post_meta_item( '_twitter_title', $args['id'] ) : '';
504
- $pm_edit_tw_desc = $args['id'] ? $this->get_post_meta_item( '_twitter_description', $args['id'] ) : '';
505
-
506
- // Gets custom fields from SEO settings.
507
- $home_og_title = $this->get_option( 'homepage_og_title' );
508
- $home_og_desc = $this->get_option( 'homepage_og_description' );
509
-
510
- //! OG title generator falls back to meta input. The description does not.
511
- $og_tit_placeholder = $pm_edit_og_title
512
- ?: $this->get_generated_open_graph_title( $args, false );
513
- $og_desc_placeholder = $pm_edit_og_desc
514
- ?: $desc_from_custom_field
515
- ?: $this->get_generated_open_graph_description( $args, false );
516
-
517
- //! TW title generator falls back to meta input. The description does not.
518
- $tw_tit_placeholder = $pm_edit_tw_title
519
- ?: $home_og_title
520
- ?: $pm_edit_og_title
521
- ?: $this->get_generated_twitter_title( $args, false );
522
- $tw_desc_placeholder = $pm_edit_tw_desc
523
- ?: $home_og_desc
524
- ?: $pm_edit_og_desc
525
- ?: $desc_from_custom_field
526
- ?: $this->get_generated_twitter_description( $args, false );
527
- } elseif ( 'edit' === $for ) {
528
- if ( ! $args['taxonomy'] ) {
529
- if ( $this->is_static_frontpage( $args['id'] ) ) {
530
- // Gets custom fields from SEO settings.
531
- $home_desc = $this->get_option( 'homepage_description' );
532
-
533
- $home_og_title = $this->get_option( 'homepage_og_title' );
534
- $home_og_desc = $this->get_option( 'homepage_og_description' );
535
- $home_tw_title = $this->get_option( 'homepage_twitter_title' );
536
- $home_tw_desc = $this->get_option( 'homepage_twitter_description' );
537
-
538
- // Gets custom fields from page.
539
- $custom_og_title = $this->get_post_meta_item( '_open_graph_title', $args['id'] );
540
- $custom_og_desc = $this->get_post_meta_item( '_open_graph_description', $args['id'] );
541
-
542
- //! OG title generator falls back to meta input. The description does not.
543
- $og_tit_placeholder = $home_og_title
544
- ?: $this->get_generated_open_graph_title( $args, false );
545
- $og_desc_placeholder = $home_og_desc
546
- ?: $home_desc
547
- ?: $desc_from_custom_field
548
- ?: $this->get_generated_open_graph_description( $args, false );
549
-
550
- //! TW title generator falls back to meta input. The description does not.
551
- $tw_tit_placeholder = $home_tw_title
552
- ?: $home_og_title
553
- ?: $custom_og_title
554
- ?: $this->get_generated_twitter_title( $args, false );
555
- $tw_desc_placeholder = $home_tw_desc
556
- ?: $home_og_desc
557
- ?: $custom_og_desc
558
- ?: $home_desc
559
- ?: $desc_from_custom_field
560
- ?: $this->get_generated_twitter_description( $args, false );
561
- } else {
562
- // Gets custom fields.
563
- $custom_og_title = $this->get_post_meta_item( '_open_graph_title', $args['id'] );
564
- $custom_og_desc = $this->get_post_meta_item( '_open_graph_description', $args['id'] );
565
-
566
- //! OG title generator falls back to meta input. The description does not.
567
- $og_tit_placeholder = $this->get_generated_open_graph_title( $args, false );
568
- $og_desc_placeholder = $desc_from_custom_field
569
- ?: $this->get_generated_open_graph_description( $args, false );
570
-
571
- //! TW title generator falls back to meta input. The description does not.
572
- $tw_tit_placeholder = $custom_og_title
573
- ?: $this->get_generated_twitter_title( $args, false );
574
- $tw_desc_placeholder = $custom_og_desc
575
- ?: $desc_from_custom_field
576
- ?: $this->get_generated_twitter_description( $args, false );
577
- }
578
- } else {
579
- $meta = $this->get_term_meta( $args['id'] );
580
-
581
- //! OG title generator falls back to meta input. The description does not.
582
- $og_tit_placeholder = $this->get_generated_open_graph_title( $args, false );
583
- $og_desc_placeholder = $desc_from_custom_field
584
- ?: $this->get_generated_open_graph_description( $args, false );
585
-
586
- //! TW title generator falls back to meta input. The description does not.
587
- $tw_tit_placeholder = $meta['og_title']
588
- ?: $og_tit_placeholder;
589
- $tw_desc_placeholder = $meta['og_description']
590
- ?: $desc_from_custom_field
591
- ?: $this->get_generated_twitter_description( $args, false );
592
- }
593
- } else {
594
- $og_tit_placeholder = '';
595
- $tw_tit_placeholder = '';
596
- $og_desc_placeholder = '';
597
- $tw_desc_placeholder = '';
598
- }
599
-
600
- return [
601
- 'title' => [
602
- 'og' => $this->escape_title( $og_tit_placeholder ?: '' ),
603
- 'twitter' => $this->escape_title( $tw_tit_placeholder ?: '' ),
604
- ],
605
- 'description' => [
606
- 'og' => $this->escape_description( $og_desc_placeholder ?: '' ),
607
- 'twitter' => $this->escape_description( $tw_desc_placeholder ?: '' ),
608
- ],
609
- ];
610
- }
611
  }
86
  );
87
 
88
  // Enqueue scripts
89
+ \add_action( "admin_print_scripts-{$this->seo_settings_page_hook}", [ $this, '_init_admin_scripts' ], 11 );
90
+ \add_action( "load-{$this->seo_settings_page_hook}", [ $this, '_register_seo_settings_meta_boxes' ] );
91
  }
92
 
93
  /**
110
  public function _output_settings_wrap() {
111
 
112
  \add_action(
113
+ "{$this->seo_settings_page_hook}_settings_page_boxes",
114
+ [ Bridges\SeoSettings::class, '_output_columns' ]
115
  );
116
 
117
  Bridges\SeoSettings::_output_wrap();
139
  if ( $show_seobox )
140
  \add_action(
141
  'add_meta_boxes',
142
+ [ Bridges\PostSettings::class, '_prepare_meta_box' ]
143
  );
144
  }
145
 
164
  $priority = (int) \apply_filters( 'the_seo_framework_term_metabox_priority', 0 );
165
 
166
  \add_action(
167
+ "{$taxonomy}_edit_form",
168
+ [ Bridges\TermSettings::class, '_prepare_setting_fields' ],
169
  $priority,
170
  2
171
  );
184
  // WordPress made a mess of this. We can't reliably get a user future-proof. Load class for all users; check there.
185
  // if ( ! $user->has_cap( THE_SEO_FRAMEWORK_AUTHOR_INFO_CAP ) ) return;
186
 
187
+ \add_action( 'show_user_profile', [ Bridges\UserSettings::class, '_prepare_setting_fields' ], 0, 1 );
188
+ \add_action( 'edit_user_profile', [ Bridges\UserSettings::class, '_prepare_setting_fields' ], 0, 1 );
189
  }
190
 
191
  /**
340
  * 'escape' => bool Optional. Whether to escape the $message. Default true.
341
  * }
342
  */
343
+ protected function output_dismissible_persistent_notice( $message, $key, $args ) { // phpcs:ignore,VariableAnalysis.CodeAnalysis
344
  $this->get_view( 'notice/persistent', get_defined_vars() );
345
  }
346
 
355
  */
356
  protected function output_dismissible_persistent_notices() {
357
 
358
+ $notices = $this->get_static_cache( 'persistent_notices', [] );
359
+ $screenbase = \get_current_screen()->base ?? '';
 
360
 
361
  // Ideally, we don't want to output more than one on no-js. Alas, we can't anticipate the importance and order of the notices.
362
  foreach ( $notices as $key => $notice ) {
364
 
365
  if ( ! \current_user_can( $cond['capability'] ) ) continue;
366
  if ( $cond['user'] && $cond['user'] !== $this->get_user_id() ) continue;
367
+ if ( $cond['screens'] && ! \in_array( $screenbase, $cond['screens'], true ) ) continue;
368
+ if ( $cond['excl_screens'] && \in_array( $screenbase, $cond['excl_screens'], true ) ) continue;
369
 
370
  if ( -1 !== $cond['timeout'] && $cond['timeout'] < time() ) {
371
  $this->clear_persistent_notice( $key );
383
  * Returns the SEO Bar.
384
  *
385
  * @since 4.0.0
386
+ * @uses \The_SEO_Framework\Interpreters\SEOBar::generate_bar();
387
  *
388
  * @param array $query : {
389
  * int $id : Required. The current post or term ID.
393
  * }
394
  * @return string The generated SEO bar, in HTML.
395
  */
396
+ public function get_generated_seo_bar( $query ) {
397
+ return Interpreters\SEOBar::generate_bar( $query );
398
  }
399
 
400
  /**
421
  * @param string $id The input ID.
422
  * @param array $data The input data.
423
  */
424
+ public function output_js_title_data( $id, $data ) {
425
+ vprintf(
426
  implode(
427
  '',
428
  [
434
  '<span id="tsf-title-data_%1$s" class="hidden wp-exclude-emoji" data-for="%1$s" %2$s></span>',
435
  ]
436
  ),
437
+ [
438
+ \esc_attr( $id ),
439
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- make_data_attributes escapes.
440
+ Interpreters\HTML::make_data_attributes( $data ),
441
+ ]
442
+ );
443
+ }
444
+
445
+ /**
446
+ * Outputs reference social HTML elements for JavaScript for a specific ID.
447
+ *
448
+ * @since 4.2.0
449
+ *
450
+ * @param string $group The social input group ID.
451
+ * @param array[og,tw] $settings The input settings data.
452
+ */
453
+ public function output_js_social_data( $group, $settings ) {
454
+ vprintf(
455
+ '<span id="tsf-social-data_%1$s" class="hidden wp-exclude-emoji" data-group="%1$s" %2$s></span>',
456
+ [
457
+ \esc_attr( $group ),
458
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- make_data_attributes escapes.
459
+ Interpreters\HTML::make_data_attributes( [ 'settings' => $settings ] ),
460
+ ]
461
  );
462
  }
463
 
484
  * @param string $id The description input ID.
485
  * @param array $data The input data.
486
  */
487
+ public function output_js_description_data( $id, $data ) {
488
+ vprintf(
489
  implode(
490
  '',
491
  [
493
  '<span id="tsf-description-data_%1$s" class="hidden wp-exclude-emoji" data-for="%1$s" %2$s ></span>',
494
  ]
495
  ),
496
+ [
497
+ \esc_attr( $id ),
498
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- make_data_attributes escapes.
499
+ Interpreters\HTML::make_data_attributes( $data ),
500
+ ]
501
  );
502
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
503
  }
inc/classes/bridges/ajax.class.php CHANGED
@@ -42,19 +42,21 @@ final class AJAX {
42
  *
43
  * @since 4.1.0
44
  * @since 4.1.4 Moved to \The_SEO_Framework\Bridges\AJAX and made static.
 
45
  * Security check OK.
46
  * @access private
47
  */
48
  public static function _wp_ajax_dismiss_notice() {
49
 
 
 
 
50
  // phpcs:ignore, WordPress.Security.NonceVerification.Missing -- We require the POST data to find locally stored nonces.
51
- $key = isset( $_POST['tsf_dismiss_key'] ) ? $_POST['tsf_dismiss_key'] : '';
52
 
53
  if ( ! $key )
54
  \wp_send_json_error( null, 400 );
55
 
56
- $tsf = \the_seo_framework();
57
-
58
  $notices = $tsf->get_static_cache( 'persistent_notices', [] );
59
  if ( empty( $notices[ $key ]['conditions']['capability'] ) ) {
60
  // Notice was deleted already elsewhere, or key was faulty. Either way, ignore--should be self-resolving.
@@ -74,27 +76,22 @@ final class AJAX {
74
  *
75
  * @since 3.1.0 Introduced in 2.6.0, but the name changed.
76
  * @since 4.1.4 Moved to \The_SEO_Framework\Bridges\AJAX and made static.
 
 
77
  * @securitycheck 3.0.0 OK.
78
  * @access private
79
  */
80
  public static function _wp_ajax_update_counter_type() {
81
 
82
- $tsf = \the_seo_framework();
 
83
 
84
  // phpcs:disable, WordPress.Security.NonceVerification -- _check_tsf_ajax_referer() does this.
85
  $tsf->_check_tsf_ajax_referer( 'edit_posts' );
86
 
87
- // Remove output buffer. TODO why don't we do this consistently??
88
- $tsf->clean_response_header();
89
-
90
  // If current user isn't allowed to edit posts, don't do anything and kill PHP.
91
- if ( ! \current_user_can( 'edit_posts' ) ) {
92
- // Encode and echo results. Requires JSON decode within JS.
93
- \wp_send_json( [
94
- 'type' => 'failure',
95
- 'value' => '',
96
- ] );
97
- }
98
 
99
  /**
100
  * Count up, reset to 0 if needed. We have 4 options: 0, 1, 2, 3
@@ -111,16 +108,13 @@ final class AJAX {
111
  $value = 0;
112
 
113
  // Update the option and get results of action.
114
- $type = $tsf->update_user_option( 0, 'counter_type', $value ) ? 'success' : 'error';
115
-
116
- $results = [
117
- 'type' => $type,
118
- 'value' => $value,
119
- ];
120
 
121
  // Encode and echo results. Requires JSON decode within JS.
122
- \wp_send_json( $results );
123
-
 
 
124
  // phpcs:enable, WordPress.Security.NonceVerification
125
  }
126
 
@@ -142,13 +136,17 @@ final class AJAX {
142
  *
143
  * @since 3.1.0 Introduced in 2.9.0, but the name changed.
144
  * @since 4.1.4 Moved to \The_SEO_Framework\Bridges\AJAX and made static.
 
145
  * @securitycheck 3.0.0 OK.
146
  * @access private
147
  */
148
  public static function _wp_ajax_crop_image() {
149
 
 
 
 
150
  // phpcs:disable, WordPress.Security.NonceVerification -- _check_tsf_ajax_referer does this.
151
- \the_seo_framework()->_check_tsf_ajax_referer( 'upload_files' );
152
 
153
  if ( ! \current_user_can( 'upload_files' ) || ! isset( $_POST['id'], $_POST['context'], $_POST['cropDetails'] ) )
154
  \wp_send_json_error();
@@ -156,7 +154,7 @@ final class AJAX {
156
  $attachment_id = \absint( $_POST['id'] );
157
 
158
  $context = str_replace( '_', '-', \sanitize_key( $_POST['context'] ) );
159
- $data = array_map( '\\absint', $_POST['cropDetails'] );
160
  $cropped = \wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] );
161
 
162
  if ( ! $cropped || \is_wp_error( $cropped ) )
@@ -235,26 +233,21 @@ final class AJAX {
235
  *
236
  * @since 4.0.0
237
  * @since 4.1.4 Moved to \The_SEO_Framework\Bridges\AJAX and made static.
 
238
  * @access private
239
  */
240
  public static function _wp_ajax_get_post_data() {
241
 
242
- $tsf = \the_seo_framework();
 
243
 
244
  // phpcs:disable, WordPress.Security.NonceVerification -- _check_tsf_ajax_referer() does this.
245
  $tsf->_check_tsf_ajax_referer( 'edit_posts' );
246
 
247
- // Clear output buffer.
248
- $tsf->clean_response_header();
249
-
250
  $post_id = \absint( $_POST['post_id'] );
251
 
252
- if ( ! $post_id || ! \current_user_can( 'edit_post', $post_id ) ) {
253
- \wp_send_json( [
254
- 'type' => 'failure',
255
- 'data' => [],
256
- ] );
257
- }
258
 
259
  $_get_defaults = [
260
  'seobar' => false,
@@ -270,17 +263,14 @@ final class AJAX {
270
  array_intersect_key(
271
  array_merge(
272
  $_get_defaults,
273
- (array) ( isset( $_POST['get'] ) ? $_POST['get'] : [] )
274
  ),
275
  $_get_defaults
276
  )
277
  )
278
  );
279
 
280
- $_generator_args = [
281
- 'id' => $post_id,
282
- 'taxonomy' => '',
283
- ];
284
 
285
  $data = [];
286
 
@@ -296,23 +286,27 @@ final class AJAX {
296
  switch ( $g ) {
297
  case 'metadescription':
298
  if ( $tsf->is_static_frontpage( $post_id ) ) {
299
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
300
  $data[ $g ] = $tsf->get_option( 'homepage_description' )
301
  ?: $tsf->get_generated_description( $_generator_args, false );
302
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
303
  } else {
304
  $data[ $g ] = $tsf->get_generated_description( $_generator_args, false );
305
  }
306
  break;
307
  case 'ogdescription':
308
- // phpcs:ignore, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- Smart loop.
309
- $_social_ph = isset( $_social_ph ) ? $_social_ph : $tsf->_get_social_placeholders( $_generator_args );
310
- $data[ $g ] = $_social_ph['description']['og'];
 
 
 
311
  break;
312
  case 'twdescription':
313
- // phpcs:ignore, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- Smart loop.
314
- $_social_ph = isset( $_social_ph ) ? $_social_ph : $tsf->_get_social_placeholders( $_generator_args );
315
- $data[ $g ] = $_social_ph['description']['twitter'];
 
 
 
316
  break;
317
  }
318
 
@@ -321,11 +315,9 @@ final class AJAX {
321
 
322
  case 'imageurl':
323
  if ( $tsf->is_static_frontpage( $post_id ) && $tsf->get_option( 'homepage_social_image_url' ) ) {
324
- $image_details = current( $tsf->get_image_details( $_generator_args, true, 'social', true ) );
325
- $data[ $g ] = isset( $image_details['url'] ) ? $image_details['url'] : '';
326
  } else {
327
- $image_details = current( $tsf->get_generated_image_details( $_generator_args, true, 'social', true ) );
328
- $data[ $g ] = isset( $image_details['url'] ) ? $image_details['url'] : '';
329
  }
330
  break;
331
 
@@ -334,12 +326,10 @@ final class AJAX {
334
  }
335
  endforeach;
336
 
337
- \wp_send_json( [
338
- 'type' => 'success',
339
  'data' => $data,
340
  'processed' => $get,
341
  ] );
342
-
343
  // phpcs:enable, WordPress.Security.NonceVerification
344
  }
345
  }
42
  *
43
  * @since 4.1.0
44
  * @since 4.1.4 Moved to \The_SEO_Framework\Bridges\AJAX and made static.
45
+ * @since 4.2.0 Now cleans response header.
46
  * Security check OK.
47
  * @access private
48
  */
49
  public static function _wp_ajax_dismiss_notice() {
50
 
51
+ $tsf = \tsf();
52
+ $tsf->clean_response_header();
53
+
54
  // phpcs:ignore, WordPress.Security.NonceVerification.Missing -- We require the POST data to find locally stored nonces.
55
+ $key = $_POST['tsf_dismiss_key'] ?? '';
56
 
57
  if ( ! $key )
58
  \wp_send_json_error( null, 400 );
59
 
 
 
60
  $notices = $tsf->get_static_cache( 'persistent_notices', [] );
61
  if ( empty( $notices[ $key ]['conditions']['capability'] ) ) {
62
  // Notice was deleted already elsewhere, or key was faulty. Either way, ignore--should be self-resolving.
76
  *
77
  * @since 3.1.0 Introduced in 2.6.0, but the name changed.
78
  * @since 4.1.4 Moved to \The_SEO_Framework\Bridges\AJAX and made static.
79
+ * @since 4.2.0 1. Now uses wp.ajax instead of $.ajax.
80
+ * 2. No longer tests if settings-saving was successful.
81
  * @securitycheck 3.0.0 OK.
82
  * @access private
83
  */
84
  public static function _wp_ajax_update_counter_type() {
85
 
86
+ $tsf = \tsf();
87
+ $tsf->clean_response_header();
88
 
89
  // phpcs:disable, WordPress.Security.NonceVerification -- _check_tsf_ajax_referer() does this.
90
  $tsf->_check_tsf_ajax_referer( 'edit_posts' );
91
 
 
 
 
92
  // If current user isn't allowed to edit posts, don't do anything and kill PHP.
93
+ if ( ! \current_user_can( 'edit_posts' ) )
94
+ \wp_send_json_error();
 
 
 
 
 
95
 
96
  /**
97
  * Count up, reset to 0 if needed. We have 4 options: 0, 1, 2, 3
108
  $value = 0;
109
 
110
  // Update the option and get results of action.
111
+ $tsf->update_single_user_meta_item( $tsf->get_user_id(), 'counter_type', $value );
 
 
 
 
 
112
 
113
  // Encode and echo results. Requires JSON decode within JS.
114
+ \wp_send_json_success( [
115
+ 'type' => 'success',
116
+ 'value' => $value,
117
+ ] );
118
  // phpcs:enable, WordPress.Security.NonceVerification
119
  }
120
 
136
  *
137
  * @since 3.1.0 Introduced in 2.9.0, but the name changed.
138
  * @since 4.1.4 Moved to \The_SEO_Framework\Bridges\AJAX and made static.
139
+ * @since 4.2.0 Now cleans response header.
140
  * @securitycheck 3.0.0 OK.
141
  * @access private
142
  */
143
  public static function _wp_ajax_crop_image() {
144
 
145
+ $tsf = \tsf();
146
+ $tsf->clean_response_header();
147
+
148
  // phpcs:disable, WordPress.Security.NonceVerification -- _check_tsf_ajax_referer does this.
149
+ $tsf->_check_tsf_ajax_referer( 'upload_files' );
150
 
151
  if ( ! \current_user_can( 'upload_files' ) || ! isset( $_POST['id'], $_POST['context'], $_POST['cropDetails'] ) )
152
  \wp_send_json_error();
154
  $attachment_id = \absint( $_POST['id'] );
155
 
156
  $context = str_replace( '_', '-', \sanitize_key( $_POST['context'] ) );
157
+ $data = array_map( 'absint', $_POST['cropDetails'] );
158
  $cropped = \wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] );
159
 
160
  if ( ! $cropped || \is_wp_error( $cropped ) )
233
  *
234
  * @since 4.0.0
235
  * @since 4.1.4 Moved to \The_SEO_Framework\Bridges\AJAX and made static.
236
+ * @since 4.2.0 Now uses wp.ajax, instead of $.ajax
237
  * @access private
238
  */
239
  public static function _wp_ajax_get_post_data() {
240
 
241
+ $tsf = \tsf();
242
+ $tsf->clean_response_header();
243
 
244
  // phpcs:disable, WordPress.Security.NonceVerification -- _check_tsf_ajax_referer() does this.
245
  $tsf->_check_tsf_ajax_referer( 'edit_posts' );
246
 
 
 
 
247
  $post_id = \absint( $_POST['post_id'] );
248
 
249
+ if ( ! $post_id || ! \current_user_can( 'edit_post', $post_id ) )
250
+ \wp_send_json_error();
 
 
 
 
251
 
252
  $_get_defaults = [
253
  'seobar' => false,
263
  array_intersect_key(
264
  array_merge(
265
  $_get_defaults,
266
+ (array) ( $_POST['get'] ?? [] )
267
  ),
268
  $_get_defaults
269
  )
270
  )
271
  );
272
 
273
+ $_generator_args = [ 'id' => $post_id ];
 
 
 
274
 
275
  $data = [];
276
 
286
  switch ( $g ) {
287
  case 'metadescription':
288
  if ( $tsf->is_static_frontpage( $post_id ) ) {
 
289
  $data[ $g ] = $tsf->get_option( 'homepage_description' )
290
  ?: $tsf->get_generated_description( $_generator_args, false );
 
291
  } else {
292
  $data[ $g ] = $tsf->get_generated_description( $_generator_args, false );
293
  }
294
  break;
295
  case 'ogdescription':
296
+ if ( $tsf->is_static_frontpage( $post_id ) ) {
297
+ $data[ $g ] = $tsf->get_option( 'homepage_description' )
298
+ ?: $tsf->get_generated_open_graph_description( $_generator_args, false );
299
+ } else {
300
+ $data[ $g ] = $tsf->get_generated_open_graph_description( $_generator_args, false );
301
+ }
302
  break;
303
  case 'twdescription':
304
+ if ( $tsf->is_static_frontpage( $post_id ) ) {
305
+ $data[ $g ] = $tsf->get_option( 'homepage_description' )
306
+ ?: $tsf->get_generated_twitter_description( $_generator_args, false );
307
+ } else {
308
+ $data[ $g ] = $tsf->get_generated_twitter_description( $_generator_args, false );
309
+ }
310
  break;
311
  }
312
 
315
 
316
  case 'imageurl':
317
  if ( $tsf->is_static_frontpage( $post_id ) && $tsf->get_option( 'homepage_social_image_url' ) ) {
318
+ $data[ $g ] = current( $tsf->get_image_details( $_generator_args, true, 'social', true ) )['url'] ?? '';
 
319
  } else {
320
+ $data[ $g ] = current( $tsf->get_generated_image_details( $_generator_args, true, 'social', true ) )['url'] ?? '';
 
321
  }
322
  break;
323
 
326
  }
327
  endforeach;
328
 
329
+ \wp_send_json_success( [
 
330
  'data' => $data,
331
  'processed' => $get,
332
  ] );
 
333
  // phpcs:enable, WordPress.Security.NonceVerification
334
  }
335
  }
inc/classes/bridges/feed.class.php CHANGED
@@ -25,18 +25,6 @@ namespace The_SEO_Framework\Bridges;
25
 
26
  \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
 
28
- /**
29
- * Sets up class loader as file is loaded.
30
- * This is done asynchronously, because static calls are handled prior and after.
31
- *
32
- * @see EOF. Because of the autoloader and (future) trait calling, we can't do it before the class is read.
33
- * @link https://bugs.php.net/bug.php?id=75771
34
- */
35
- $_load_feed_class = function() {
36
- // phpcs:ignore, TSF.Performance.Opcodes.ShouldHaveNamespaceEscape
37
- new Feed();
38
- };
39
-
40
  /**
41
  * Prepares feed mofifications.
42
  *
@@ -65,7 +53,7 @@ final class Feed {
65
  * @return \The_SEO_Framework\Bridges\Feed $instance
66
  */
67
  public static function get_instance() {
68
- return static::$instance;
69
  }
70
 
71
  /**
@@ -76,7 +64,9 @@ final class Feed {
76
  *
77
  * @since 4.1.0
78
  */
79
- public static function prepare() {}
 
 
80
 
81
  /**
82
  * The constructor. Can't be instantiated externally from this file.
@@ -93,8 +83,7 @@ final class Feed {
93
  static $count = 0;
94
  0 === $count++ or \wp_die( 'Don\'t instance <code>' . __CLASS__ . '</code>.' );
95
 
96
- static::$tsf = \the_seo_framework();
97
- static::$instance = &$this;
98
  }
99
 
100
  /**
@@ -157,19 +146,20 @@ final class Feed {
157
 
158
  if ( ! $content ) return '';
159
 
160
- // Strip all code and lines.
161
- $excerpt = static::$tsf->s_excerpt_raw( $content, false );
162
-
163
  /**
164
  * @since 2.5.2
165
  * @param int $max_len The maximum feed (multibyte) string length.
166
  */
167
  $max_len = (int) \apply_filters( 'the_seo_framework_max_content_feed_length', 400 );
168
 
169
- // Generate excerpt.
170
- $excerpt = static::$tsf->trim_excerpt( $excerpt, 0, $max_len );
 
 
 
 
171
 
172
- return '<p>' . $excerpt . '</p>';
173
  }
174
 
175
  /**
@@ -191,11 +181,9 @@ final class Feed {
191
  );
192
 
193
  return sprintf(
194
- '<p><a href="%s" rel="nofollow">%s</a></p>',
195
  \esc_url( \get_permalink() ),
196
  \esc_html( $source_i18n )
197
  );
198
  }
199
  }
200
-
201
- $_load_feed_class();
25
 
26
  \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  /**
29
  * Prepares feed mofifications.
30
  *
53
  * @return \The_SEO_Framework\Bridges\Feed $instance
54
  */
55
  public static function get_instance() {
56
+ return static::$instance ?? ( static::$instance = new static );
57
  }
58
 
59
  /**
64
  *
65
  * @since 4.1.0
66
  */
67
+ public static function prepare() {
68
+ static::get_instance();
69
+ }
70
 
71
  /**
72
  * The constructor. Can't be instantiated externally from this file.
83
  static $count = 0;
84
  0 === $count++ or \wp_die( 'Don\'t instance <code>' . __CLASS__ . '</code>.' );
85
 
86
+ static::$tsf = \tsf();
 
87
  }
88
 
89
  /**
146
 
147
  if ( ! $content ) return '';
148
 
 
 
 
149
  /**
150
  * @since 2.5.2
151
  * @param int $max_len The maximum feed (multibyte) string length.
152
  */
153
  $max_len = (int) \apply_filters( 'the_seo_framework_max_content_feed_length', 400 );
154
 
155
+ // Strip all code and lines, and AI-trim it.
156
+ $excerpt = static::$tsf->trim_excerpt(
157
+ static::$tsf->s_excerpt_raw( $content, false ),
158
+ 0,
159
+ $max_len
160
+ );
161
 
162
+ return "<p>$excerpt</p>";
163
  }
164
 
165
  /**
181
  );
182
 
183
  return sprintf(
184
+ '<p><a href="%s" rel="nofollow">%s</a></p>', // Keep XHTML
185
  \esc_url( \get_permalink() ),
186
  \esc_html( $source_i18n )
187
  );
188
  }
189
  }
 
 
inc/classes/bridges/index.php CHANGED
@@ -4,6 +4,7 @@
4
  *
5
  * - David Seltzer
6
  *
7
- * (probably, uncredited: it's based on "Charlie and the Chocolate Factory" by Roald Dahl, but it's verbatim to the book;
8
- * the screenwriting was edited so much that Roald ultimately decided to disowned the film)
 
9
  */
4
  *
5
  * - David Seltzer
6
  *
7
+ * (probably, uncredited: quote's based on "Charlie and the Chocolate Factory" by
8
+ * Roald Dahl, but it's verbatim to the book; the screenwriting was edited so
9
+ * much that Roald ultimately decided to disown the film)
10
  */
inc/classes/bridges/listedit.class.php CHANGED
@@ -65,9 +65,7 @@ final class ListEdit extends ListTable {
65
  */
66
  public function _prepare_edit_box( $screen ) {
67
 
68
- $taxonomy = isset( $screen->taxonomy ) ? $screen->taxonomy : '';
69
-
70
- if ( ! $taxonomy ) {
71
  // WordPress doesn't support this feature yet for taxonomies.
72
  // Exclude it for when the time may come and faulty fields are displayed.
73
  // Mind the "2".
@@ -125,7 +123,7 @@ final class ListEdit extends ListTable {
125
  if ( $taxonomy ) {
126
  // Not yet.
127
  } else {
128
- \the_seo_framework()->get_view( 'list/bulk-post', get_defined_vars() );
129
  }
130
  }
131
 
@@ -144,9 +142,9 @@ final class ListEdit extends ListTable {
144
  if ( $this->column_name !== $column_name ) return;
145
 
146
  if ( $taxonomy ) {
147
- \the_seo_framework()->get_view( 'list/quick-term', get_defined_vars() );
148
  } else {
149
- \the_seo_framework()->get_view( 'list/quick-post', get_defined_vars() );
150
  }
151
  }
152
 
@@ -165,16 +163,13 @@ final class ListEdit extends ListTable {
165
  if ( $this->column_name !== $column_name ) return;
166
  if ( ! \current_user_can( 'edit_post', $post_id ) ) return;
167
 
168
- $tsf = \the_seo_framework();
169
 
170
- $query = [
171
- 'id' => $post_id,
172
- 'taxonomy' => '',
173
- ];
174
 
175
  $r_defaults = $tsf->generate_robots_meta(
176
  $query,
177
- null,
178
  \The_SEO_Framework\ROBOTS_IGNORE_SETTINGS
179
  );
180
 
@@ -224,7 +219,7 @@ final class ListEdit extends ListTable {
224
  * @param string $default Optional. Only works when $isSelect is true. The default value to be set in select index 0.
225
  * }
226
  * }
227
- * @param array $query The query data. Contains 'id' and 'taxonomy'.
228
  */
229
  $data = \apply_filters_ref_array( 'the_seo_framework_list_table_data', [ $data, $query ] );
230
 
@@ -237,7 +232,6 @@ final class ListEdit extends ListTable {
237
  );
238
 
239
  if ( $tsf->is_static_frontpage( $query['id'] ) ) {
240
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
241
  // When the homepage title is set, we can safely get the custom field.
242
  $_has_home_title = (bool) $tsf->escape_title( $tsf->get_option( 'homepage_title' ) );
243
  $default_title = $_has_home_title
@@ -253,7 +247,6 @@ final class ListEdit extends ListTable {
253
  ? $tsf->get_description_from_custom_field( $query )
254
  : $tsf->get_generated_description( $query );
255
  $is_desc_ref_locked = $_has_home_desc;
256
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
257
  } else {
258
  $default_title = $tsf->get_filtered_raw_generated_title( $query );
259
  $addition = $tsf->get_blogname();
@@ -269,9 +262,9 @@ final class ListEdit extends ListTable {
269
  ];
270
  $title_data = [
271
  'refTitleLocked' => $is_title_ref_locked,
272
- 'defaultTitle' => $default_title,
273
  'addAdditions' => $tsf->use_title_branding( $query ),
274
- 'additionValue' => $tsf->s_title_raw( $addition ),
275
  'additionPlacement' => 'left' === $seplocation ? 'before' : 'after',
276
  ];
277
  $desc_data = [
@@ -309,6 +302,7 @@ final class ListEdit extends ListTable {
309
  * Returns the quick edit data for terms.
310
  *
311
  * @since 4.0.0
 
312
  * @access private
313
  * @abstract
314
  * @NOTE Unlike `_output_column_post_data()`, this is a filter callback.
@@ -325,7 +319,7 @@ final class ListEdit extends ListTable {
325
  if ( $this->column_name !== $column_name ) return $string;
326
  if ( ! \current_user_can( 'edit_term', $term_id ) ) return $string;
327
 
328
- $tsf = \the_seo_framework();
329
 
330
  $query = [
331
  'id' => $term_id,
@@ -334,7 +328,7 @@ final class ListEdit extends ListTable {
334
 
335
  $r_defaults = $tsf->generate_robots_meta(
336
  $query,
337
- null,
338
  \The_SEO_Framework\ROBOTS_IGNORE_SETTINGS
339
  );
340
 
@@ -397,15 +391,19 @@ final class ListEdit extends ListTable {
397
  HTML::make_data_attributes( [ 'le' => $data ] )
398
  );
399
 
400
- $term_prefix = $tsf->use_generated_archive_prefix( \get_taxonomy( $query['taxonomy'] ) )
401
- ? $tsf->prepend_tax_label_prefix( '', $query['taxonomy'] )
 
 
 
 
402
  : '';
403
 
404
  $title_data = [
405
  'refTitleLocked' => false,
406
- 'defaultTitle' => $tsf->get_filtered_raw_generated_title( $query ),
407
  'addAdditions' => $tsf->use_title_branding( $query ),
408
- 'additionValue' => $tsf->s_title_raw( $tsf->get_blogname() ),
409
  'additionPlacement' => 'left' === $tsf->get_title_seplocation() ? 'before' : 'after',
410
  'termPrefix' => $term_prefix,
411
  ];
@@ -430,6 +428,6 @@ final class ListEdit extends ListTable {
430
  if ( $this->doing_ajax )
431
  $container .= $this->get_ajax_dispatch_updated_event();
432
 
433
- return $string . $container;
434
  }
435
  }
65
  */
66
  public function _prepare_edit_box( $screen ) {
67
 
68
+ if ( empty( $screen->taxonomy ) ) {
 
 
69
  // WordPress doesn't support this feature yet for taxonomies.
70
  // Exclude it for when the time may come and faulty fields are displayed.
71
  // Mind the "2".
123
  if ( $taxonomy ) {
124
  // Not yet.
125
  } else {
126
+ \tsf()->get_view( 'list/bulk-post', get_defined_vars() );
127
  }
128
  }
129
 
142
  if ( $this->column_name !== $column_name ) return;
143
 
144
  if ( $taxonomy ) {
145
+ \tsf()->get_view( 'list/quick-term', get_defined_vars() );
146
  } else {
147
+ \tsf()->get_view( 'list/quick-post', get_defined_vars() );
148
  }
149
  }
150
 
163
  if ( $this->column_name !== $column_name ) return;
164
  if ( ! \current_user_can( 'edit_post', $post_id ) ) return;
165
 
166
+ $tsf = \tsf();
167
 
168
+ $query = [ 'id' => $post_id ];
 
 
 
169
 
170
  $r_defaults = $tsf->generate_robots_meta(
171
  $query,
172
+ [ 'noindex', 'nofollow', 'noarchive' ],
173
  \The_SEO_Framework\ROBOTS_IGNORE_SETTINGS
174
  );
175
 
219
  * @param string $default Optional. Only works when $isSelect is true. The default value to be set in select index 0.
220
  * }
221
  * }
222
+ * @param array $query The query data. Contains 'id' or 'taxonomy'.
223
  */
224
  $data = \apply_filters_ref_array( 'the_seo_framework_list_table_data', [ $data, $query ] );
225
 
232
  );
233
 
234
  if ( $tsf->is_static_frontpage( $query['id'] ) ) {
 
235
  // When the homepage title is set, we can safely get the custom field.
236
  $_has_home_title = (bool) $tsf->escape_title( $tsf->get_option( 'homepage_title' ) );
237
  $default_title = $_has_home_title
247
  ? $tsf->get_description_from_custom_field( $query )
248
  : $tsf->get_generated_description( $query );
249
  $is_desc_ref_locked = $_has_home_desc;
 
250
  } else {
251
  $default_title = $tsf->get_filtered_raw_generated_title( $query );
252
  $addition = $tsf->get_blogname();
262
  ];
263
  $title_data = [
264
  'refTitleLocked' => $is_title_ref_locked,
265
+ 'defaultTitle' => $tsf->s_title( $default_title ),
266
  'addAdditions' => $tsf->use_title_branding( $query ),
267
+ 'additionValue' => $tsf->s_title( $addition ),
268
  'additionPlacement' => 'left' === $seplocation ? 'before' : 'after',
269
  ];
270
  $desc_data = [
302
  * Returns the quick edit data for terms.
303
  *
304
  * @since 4.0.0
305
+ * @since 4.2.0 Now properly populates use_generated_archive_prefix() with a \WP_Term object.
306
  * @access private
307
  * @abstract
308
  * @NOTE Unlike `_output_column_post_data()`, this is a filter callback.
319
  if ( $this->column_name !== $column_name ) return $string;
320
  if ( ! \current_user_can( 'edit_term', $term_id ) ) return $string;
321
 
322
+ $tsf = \tsf();
323
 
324
  $query = [
325
  'id' => $term_id,
328
 
329
  $r_defaults = $tsf->generate_robots_meta(
330
  $query,
331
+ [ 'noindex', 'nofollow', 'noarchive' ],
332
  \The_SEO_Framework\ROBOTS_IGNORE_SETTINGS
333
  );
334
 
391
  HTML::make_data_attributes( [ 'le' => $data ] )
392
  );
393
 
394
+ $term_prefix = $tsf->use_generated_archive_prefix( \get_term( $query['id'], $query['taxonomy'] ) )
395
+ ? sprintf(
396
+ /* translators: %s: Taxonomy singular name. */
397
+ \_x( '%s:', 'taxonomy term archive title prefix', 'default' ),
398
+ $tsf->get_tax_type_label( $query['taxonomy'] )
399
+ )
400
  : '';
401
 
402
  $title_data = [
403
  'refTitleLocked' => false,
404
+ 'defaultTitle' => $tsf->s_title( $tsf->get_filtered_raw_generated_title( $query ) ),
405
  'addAdditions' => $tsf->use_title_branding( $query ),
406
+ 'additionValue' => $tsf->s_title( $tsf->get_blogname() ),
407
  'additionPlacement' => 'left' === $tsf->get_title_seplocation() ? 'before' : 'after',
408
  'termPrefix' => $term_prefix,
409
  ];
428
  if ( $this->doing_ajax )
429
  $container .= $this->get_ajax_dispatch_updated_event();
430
 
431
+ return "$string$container";
432
  }
433
  }
inc/classes/bridges/listtable.class.php CHANGED
@@ -125,7 +125,7 @@ abstract class ListTable {
125
  $pto = $post_type ? \get_post_type_object( $post_type ) : false;
126
 
127
  // TODO shouldn't we just use `edit_post`? See _output_column_contents_for_post && get_post_type_capabilities
128
- if ( $pto && \current_user_can( 'edit_' . $pto->capability_type, (int) $_POST['post_ID'] ) )
129
  $this->init_columns_ajax();
130
  }
131
 
@@ -142,9 +142,7 @@ abstract class ListTable {
142
  || empty( $_POST['tax_ID'] ) )
143
  return;
144
 
145
- $tax_id = (int) $_POST['tax_ID'];
146
-
147
- if ( \current_user_can( 'edit_term', $tax_id ) )
148
  $this->init_columns_ajax();
149
  }
150
 
@@ -157,18 +155,18 @@ abstract class ListTable {
157
  */
158
  private function init_columns( $screen ) {
159
 
160
- if ( ! \the_seo_framework()->is_wp_lists_edit()
161
  || empty( $screen->id ) )
162
  return;
163
 
164
- $post_type = isset( $screen->post_type ) ? $screen->post_type : '';
165
- $taxonomy = isset( $screen->taxonomy ) ? $screen->taxonomy : '';
166
 
167
  if ( $taxonomy ) {
168
- if ( ! \the_seo_framework()->is_taxonomy_supported( $taxonomy ) )
169
  return;
170
  } else {
171
- if ( ! \the_seo_framework()->is_post_type_supported( $post_type ) )
172
  return;
173
  }
174
 
@@ -176,9 +174,9 @@ abstract class ListTable {
176
  $this->taxonomy = $taxonomy;
177
 
178
  if ( $taxonomy )
179
- \add_filter( 'manage_' . $taxonomy . '_custom_column', [ $this, '_output_column_contents_for_term' ], 1, 3 );
180
 
181
- \add_filter( 'manage_' . $screen->id . '_columns', [ $this, '_add_column' ], 10, 1 );
182
  /**
183
  * Always load pages and posts.
184
  * Many CPT plugins rely on these.
@@ -207,10 +205,10 @@ abstract class ListTable {
207
  ?: ( isset( $_POST['tax_type'] ) ? stripslashes( $_POST['tax_type'] ) : '' );
208
 
209
  if ( $taxonomy ) {
210
- if ( ! \the_seo_framework()->is_taxonomy_supported( $taxonomy ) )
211
  return;
212
  } else {
213
- if ( ! \the_seo_framework()->is_post_type_supported( $post_type ) )
214
  return;
215
  }
216
 
@@ -222,11 +220,11 @@ abstract class ListTable {
222
 
223
  // Not elseif; either request.
224
  if ( $taxonomy )
225
- \add_filter( 'manage_' . $taxonomy . '_custom_column', [ $this, '_output_column_contents_for_term' ], 1, 3 );
226
 
227
  if ( $screen_id ) {
228
  // Everything but inline-save-tax action.
229
- \add_filter( 'manage_' . $screen_id . '_columns', [ $this, '_add_column' ], 10, 1 );
230
 
231
  /**
232
  * Always load pages and posts.
@@ -239,9 +237,9 @@ abstract class ListTable {
239
  * Action "inline-save-tax" does not POST 'screen'.
240
  *
241
  * @see WP Core wp_ajax_inline_save_tax():
242
- * `_get_list_table( 'WP_Terms_List_Table', array( 'screen' => 'edit-' . $taxonomy ) );`
243
  */
244
- \add_filter( 'manage_edit-' . $taxonomy . '_columns', [ $this, '_add_column' ], 1, 1 );
245
  }
246
  // phpcs:enable, WordPress.Security.NonceVerification
247
  }
125
  $pto = $post_type ? \get_post_type_object( $post_type ) : false;
126
 
127
  // TODO shouldn't we just use `edit_post`? See _output_column_contents_for_post && get_post_type_capabilities
128
+ if ( $pto && \current_user_can( "edit_{$pto->capability_type}", (int) $_POST['post_ID'] ) )
129
  $this->init_columns_ajax();
130
  }
131
 
142
  || empty( $_POST['tax_ID'] ) )
143
  return;
144
 
145
+ if ( \current_user_can( 'edit_term', (int) $_POST['tax_ID'] ) )
 
 
146
  $this->init_columns_ajax();
147
  }
148
 
155
  */
156
  private function init_columns( $screen ) {
157
 
158
+ if ( ! \tsf()->is_wp_lists_edit()
159
  || empty( $screen->id ) )
160
  return;
161
 
162
+ $post_type = $screen->post_type ?? '';
163
+ $taxonomy = $screen->taxonomy ?? '';
164
 
165
  if ( $taxonomy ) {
166
+ if ( ! \tsf()->is_taxonomy_supported( $taxonomy ) )
167
  return;
168
  } else {
169
+ if ( ! \tsf()->is_post_type_supported( $post_type ) )
170
  return;
171
  }
172
 
174
  $this->taxonomy = $taxonomy;
175
 
176
  if ( $taxonomy )
177
+ \add_filter( "manage_{$taxonomy}_custom_column", [ $this, '_output_column_contents_for_term' ], 1, 3 );
178
 
179
+ \add_filter( "manage_{$screen->id}_columns", [ $this, '_add_column' ], 10, 1 );
180
  /**
181
  * Always load pages and posts.
182
  * Many CPT plugins rely on these.
205
  ?: ( isset( $_POST['tax_type'] ) ? stripslashes( $_POST['tax_type'] ) : '' );
206
 
207
  if ( $taxonomy ) {
208
+ if ( ! \tsf()->is_taxonomy_supported( $taxonomy ) )
209
  return;
210
  } else {
211
+ if ( ! \tsf()->is_post_type_supported( $post_type ) )
212
  return;
213
  }
214
 
220
 
221
  // Not elseif; either request.
222
  if ( $taxonomy )
223
+ \add_filter( "manage_{$taxonomy}_custom_column", [ $this, '_output_column_contents_for_term' ], 1, 3 );
224
 
225
  if ( $screen_id ) {
226
  // Everything but inline-save-tax action.
227
+ \add_filter( "manage_{$screen_id}_columns", [ $this, '_add_column' ], 10, 1 );
228
 
229
  /**
230
  * Always load pages and posts.
237
  * Action "inline-save-tax" does not POST 'screen'.
238
  *
239
  * @see WP Core wp_ajax_inline_save_tax():
240
+ * `_get_list_table( 'WP_Terms_List_Table', array( 'screen' => "edit-$taxonomy" ) );`
241
  */
242
+ \add_filter( "manage_edit-{$taxonomy}_columns", [ $this, '_add_column' ], 1, 1 );
243
  }
244
  // phpcs:enable, WordPress.Security.NonceVerification
245
  }
inc/classes/bridges/ping.class.php CHANGED
@@ -25,6 +25,8 @@ namespace The_SEO_Framework\Bridges;
25
 
26
  \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
 
 
 
28
  /**
29
  * Pings search engines.
30
  *
@@ -110,7 +112,7 @@ final class Ping {
110
  */
111
  public static function ping_search_engines() {
112
 
113
- $tsf = \the_seo_framework();
114
 
115
  if ( $tsf->get_option( 'site_noindex' ) || ! $tsf->is_blog_public() ) return;
116
 
@@ -164,16 +166,14 @@ final class Ping {
164
  */
165
  public static function ping_google() {
166
 
167
- if ( \the_seo_framework()->use_core_sitemaps() ) {
168
- $url = \get_sitemap_url( 'index' );
169
- } else {
170
- $url = \The_SEO_Framework\Bridges\Sitemap::get_instance()->get_expected_sitemap_endpoint_url();
171
- }
172
 
173
  if ( ! $url ) return;
174
 
175
- $pingurl = 'https://www.google.com/ping?sitemap=' . rawurlencode( $url );
176
- \wp_safe_remote_get( $pingurl, [ 'timeout' => 3 ] );
 
 
177
  }
178
 
179
  /**
@@ -188,15 +188,32 @@ final class Ping {
188
  */
189
  public static function ping_bing() {
190
 
191
- if ( \the_seo_framework()->use_core_sitemaps() ) {
192
- $url = \get_sitemap_url( 'index' );
193
- } else {
194
- $url = \The_SEO_Framework\Bridges\Sitemap::get_instance()->get_expected_sitemap_endpoint_url();
195
- }
196
 
197
  if ( ! $url ) return;
198
 
199
- $pingurl = 'https://www.bing.com/ping?sitemap=' . rawurlencode( $url );
200
- \wp_safe_remote_get( $pingurl, [ 'timeout' => 3 ] );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  }
202
  }
25
 
26
  \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
 
28
+ use function \The_SEO_Framework\memo;
29
+
30
  /**
31
  * Pings search engines.
32
  *
112
  */
113
  public static function ping_search_engines() {
114
 
115
+ $tsf = \tsf();
116
 
117
  if ( $tsf->get_option( 'site_noindex' ) || ! $tsf->is_blog_public() ) return;
118
 
166
  */
167
  public static function ping_google() {
168
 
169
+ $url = static::get_ping_url();
 
 
 
 
170
 
171
  if ( ! $url ) return;
172
 
173
+ \wp_safe_remote_get(
174
+ 'https://www.google.com/ping?sitemap=' . rawurlencode( $url ),
175
+ [ 'timeout' => 3 ]
176
+ );
177
  }
178
 
179
  /**
188
  */
189
  public static function ping_bing() {
190
 
191
+ $url = static::get_ping_url();
 
 
 
 
192
 
193
  if ( ! $url ) return;
194
 
195
+ \wp_safe_remote_get(
196
+ 'https://www.bing.com/ping?sitemap=' . rawurlencode( $url ),
197
+ [ 'timeout' => 3 ]
198
+ );
199
+ }
200
+
201
+ /**
202
+ * Return the base sitemap's ping URL.
203
+ * Memoizes the return value.
204
+ *
205
+ * @since 4.2.0
206
+ *
207
+ * @return string The ping URL. Empty string on failure.
208
+ */
209
+ public static function get_ping_url() {
210
+ return memo() ?? memo(
211
+ (
212
+ \tsf()->use_core_sitemaps()
213
+ ? \get_sitemap_url( 'index' )
214
+ : \The_SEO_Framework\Bridges\Sitemap::get_instance()->get_expected_sitemap_endpoint_url()
215
+ )
216
+ ?: ''
217
+ );
218
  }
219
  }
inc/classes/bridges/plugintable.class.php CHANGED
@@ -48,12 +48,12 @@ final class PluginTable {
48
 
49
  $tsf_links = [];
50
 
51
- $tsf = \the_seo_framework();
52
 
53
- if ( $tsf->load_options ) {
54
  $tsf_links['settings'] = sprintf(
55
  '<a href="%s">%s</a>',
56
- \esc_url( \admin_url( 'admin.php?page=' . $tsf->seo_settings_page_slug ) ),
57
  \esc_html__( 'Settings', 'autodescription' )
58
  );
59
  }
@@ -121,7 +121,9 @@ final class PluginTable {
121
  '<a href="%s" rel="noreferrer noopener nofollow" target="_blank">%s</a>',
122
  [
123
  'https://tsf.fyi/extension-manager',
124
- $_get_em ? \esc_html_x( 'Get Extension Manager', 'Extension Manager is a product name; do not translate it.', 'autodescription' ) : 'Extension Manager',
 
 
125
  ]
126
  ),
127
  ]
48
 
49
  $tsf_links = [];
50
 
51
+ $tsf = \tsf();
52
 
53
+ if ( ! $tsf->is_headless['settings'] ) {
54
  $tsf_links['settings'] = sprintf(
55
  '<a href="%s">%s</a>',
56
+ \esc_url( \admin_url( "admin.php?page={$tsf->seo_settings_page_slug}" ) ),
57
  \esc_html__( 'Settings', 'autodescription' )
58
  );
59
  }
121
  '<a href="%s" rel="noreferrer noopener nofollow" target="_blank">%s</a>',
122
  [
123
  'https://tsf.fyi/extension-manager',
124
+ $_get_em
125
+ ? \esc_html_x( 'Get Extension Manager', 'Extension Manager is a product name; do not translate it.', 'autodescription' )
126
+ : 'Extension Manager',
127
  ]
128
  ),
129
  ]
inc/classes/bridges/postsettings.class.php CHANGED
@@ -48,7 +48,7 @@ final class PostSettings {
48
  */
49
  public static function _prepare_meta_box( $post_type ) {
50
 
51
- $tsf = \the_seo_framework();
52
 
53
  $label = $tsf->get_post_type_label( $post_type );
54
 
@@ -65,7 +65,7 @@ final class PostSettings {
65
  */
66
  $priority = (string) \apply_filters( 'the_seo_framework_metabox_priority', 'high' );
67
 
68
- if ( $tsf->is_front_page_by_id( $tsf->get_the_real_ID() ) ) {
69
  if ( $tsf->can_access_settings() ) {
70
  $schema = \is_rtl() ? '%2$s - %1$s' : '%1$s - %2$s';
71
  $title = sprintf(
@@ -73,7 +73,7 @@ final class PostSettings {
73
  \esc_html__( 'Homepage SEO Settings', 'autodescription' ),
74
  \The_SEO_Framework\Interpreters\HTML::make_info(
75
  \__( 'The SEO Settings may take precedence over these settings.', 'autodescription' ),
76
- $tsf->seo_settings_page_url(),
77
  false
78
  )
79
  );
@@ -89,8 +89,10 @@ final class PostSettings {
89
  // Implies `\get_current_screen()->id`. Is always 'post'.
90
  $screen_id = 'post';
91
 
92
- \add_meta_box( $box_id, $title, __CLASS__ . '::_meta_box', $post_type, $context, $priority, [] );
93
- \add_filter( "postbox_classes_{$screen_id}_{$box_id}", __CLASS__ . '::_add_postbox_class' );
 
 
94
  }
95
 
96
  /**
@@ -112,8 +114,8 @@ final class PostSettings {
112
  * @param bool $use_tabs Whether to output tabs, only works when $tabs count is greater than 1.
113
  */
114
  public static function _flex_nav_tab_wrapper( $id, $tabs = [], $use_tabs = true ) { // phpcs:ignore,VariableAnalysis
115
- \the_seo_framework()->get_view( 'edit/wrap-nav', get_defined_vars() );
116
- \the_seo_framework()->get_view( 'edit/wrap-content', get_defined_vars() );
117
  }
118
 
119
  /**
@@ -125,7 +127,7 @@ final class PostSettings {
125
 
126
  static::output_nonce_field();
127
 
128
- $tsf = \the_seo_framework();
129
 
130
  /**
131
  * @since 2.9.0
@@ -153,9 +155,8 @@ final class PostSettings {
153
  */
154
  public static function _add_postbox_class( $classes = [] ) {
155
 
156
- if ( \the_seo_framework()->is_gutenberg_page() ) {
157
  $classes[] = 'tsf-is-block-editor';
158
- }
159
 
160
  return $classes;
161
  }
@@ -167,7 +168,7 @@ final class PostSettings {
167
  * @since 4.0.0
168
  */
169
  private static function output_nonce_field() {
170
- $tsf = \the_seo_framework();
171
  \wp_nonce_field( $tsf->inpost_nonce_field, $tsf->inpost_nonce_name );
172
  }
173
 
@@ -181,7 +182,7 @@ final class PostSettings {
181
  * @since 2.9.0
182
  */
183
  \do_action( 'the_seo_framework_pre_page_inpost_general_tab' );
184
- \the_seo_framework()->get_view( 'edit/seo-settings-singular', [], 'general' );
185
  /**
186
  * @since 2.9.0
187
  */
@@ -198,7 +199,7 @@ final class PostSettings {
198
  * @since 2.9.0
199
  */
200
  \do_action( 'the_seo_framework_pre_page_inpost_visibility_tab' );
201
- \the_seo_framework()->get_view( 'edit/seo-settings-singular', [], 'visibility' );
202
  /**
203
  * @since 2.9.0
204
  */
@@ -215,7 +216,7 @@ final class PostSettings {
215
  * @since 2.9.0
216
  */
217
  \do_action( 'the_seo_framework_pre_page_inpost_social_tab' );
218
- \the_seo_framework()->get_view( 'edit/seo-settings-singular', [], 'social' );
219
  /**
220
  * @since 2.9.0
221
  */
48
  */
49
  public static function _prepare_meta_box( $post_type ) {
50
 
51
+ $tsf = \tsf();
52
 
53
  $label = $tsf->get_post_type_label( $post_type );
54
 
65
  */
66
  $priority = (string) \apply_filters( 'the_seo_framework_metabox_priority', 'high' );
67
 
68
+ if ( $tsf->is_real_front_page_by_id( $tsf->get_the_real_ID() ) ) {
69
  if ( $tsf->can_access_settings() ) {
70
  $schema = \is_rtl() ? '%2$s - %1$s' : '%1$s - %2$s';
71
  $title = sprintf(
73
  \esc_html__( 'Homepage SEO Settings', 'autodescription' ),
74
  \The_SEO_Framework\Interpreters\HTML::make_info(
75
  \__( 'The SEO Settings may take precedence over these settings.', 'autodescription' ),
76
+ $tsf->get_seo_settings_page_url(),
77
  false
78
  )
79
  );
89
  // Implies `\get_current_screen()->id`. Is always 'post'.
90
  $screen_id = 'post';
91
 
92
+ $class = static::class;
93
+
94
+ \add_meta_box( $box_id, $title, "$class::_meta_box", $post_type, $context, $priority, [] );
95
+ \add_filter( "postbox_classes_{$screen_id}_{$box_id}", "$class::_add_postbox_class" );
96
  }
97
 
98
  /**
114
  * @param bool $use_tabs Whether to output tabs, only works when $tabs count is greater than 1.
115
  */
116
  public static function _flex_nav_tab_wrapper( $id, $tabs = [], $use_tabs = true ) { // phpcs:ignore,VariableAnalysis
117
+ \tsf()->get_view( 'edit/wrap-nav', get_defined_vars() );
118
+ \tsf()->get_view( 'edit/wrap-content', get_defined_vars() );
119
  }
120
 
121
  /**
127
 
128
  static::output_nonce_field();
129
 
130
+ $tsf = \tsf();
131
 
132
  /**
133
  * @since 2.9.0
155
  */
156
  public static function _add_postbox_class( $classes = [] ) {
157
 
158
+ if ( \tsf()->is_gutenberg_page() )
159
  $classes[] = 'tsf-is-block-editor';
 
160
 
161
  return $classes;
162
  }
168
  * @since 4.0.0
169
  */
170
  private static function output_nonce_field() {
171
+ $tsf = \tsf();
172
  \wp_nonce_field( $tsf->inpost_nonce_field, $tsf->inpost_nonce_name );
173
  }
174
 
182
  * @since 2.9.0
183
  */
184
  \do_action( 'the_seo_framework_pre_page_inpost_general_tab' );
185
+ \tsf()->get_view( 'edit/seo-settings-singular', [], 'general_tab' );
186
  /**
187
  * @since 2.9.0
188
  */
199
  * @since 2.9.0
200
  */
201
  \do_action( 'the_seo_framework_pre_page_inpost_visibility_tab' );
202
+ \tsf()->get_view( 'edit/seo-settings-singular', [], 'visibility_tab' );
203
  /**
204
  * @since 2.9.0
205
  */
216
  * @since 2.9.0
217
  */
218
  \do_action( 'the_seo_framework_pre_page_inpost_social_tab' );
219
+ \tsf()->get_view( 'edit/seo-settings-singular', [], 'social_tab' );
220
  /**
221
  * @since 2.9.0
222
  */
inc/classes/bridges/scripts.class.php CHANGED
@@ -25,18 +25,6 @@ namespace The_SEO_Framework\Bridges;
25
 
26
  \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
 
28
- /**
29
- * Sets up class loader as file is loaded.
30
- * This is done asynchronously, because static calls are handled prior and after.
31
- *
32
- * @see EOF. Because of the autoloader and (future) trait calling, we can't do it before the class is read.
33
- * @link https://bugs.php.net/bug.php?id=75771
34
- */
35
- $_load_scripts_class = function() {
36
- // phpcs:ignore, TSF.Performance.Opcodes.ShouldHaveNamespaceEscape
37
- new Scripts();
38
- };
39
-
40
  /**
41
  * Prepares admin GUI scripts. Auto-invokes everything the moment this file is required.
42
  * Relies on \The_SEO_Framework\Builders\Scripts to register and load scripts.
@@ -48,17 +36,10 @@ $_load_scripts_class = function() {
48
  * @since 4.0.0
49
  * @see \The_SEO_Framework\Builders\Scripts
50
  * @access protected
51
- * Use static calls The_SEO_Framework\Bridges\Scripts::funcname()
52
  * @final Can't be extended.
53
  */
54
  final class Scripts {
55
 
56
- /**
57
- * @since 4.0.0
58
- * @var \The_SEO_Framework\Bridges\Scripts $instance The instance.
59
- */
60
- private static $instance;
61
-
62
  /**
63
  * Prepares the class and loads constructor.
64
  *
@@ -66,12 +47,14 @@ final class Scripts {
66
  * this class is needed yet.
67
  *
68
  * @since 4.0.0
 
 
69
  */
70
  public static function prepare() {}
71
 
72
  /**
73
- * The constructor. Can't be instantiated externally from this file.
74
- * Kills PHP on subsequent duplicated request. Enforces singleton.
75
  *
76
  * This probably autoloads at action "admin_enqueue_scripts", priority "0".
77
  *
@@ -79,13 +62,7 @@ final class Scripts {
79
  * @access private
80
  * @internal
81
  */
82
- public function __construct() {
83
-
84
- static $count = 0;
85
- 0 === $count++ or \wp_die( 'Don\'t instance <code>' . __CLASS__ . '</code>.' );
86
-
87
- static::$instance = &$this;
88
- }
89
 
90
  /**
91
  * Initializes scripts based on admin query.
@@ -96,7 +73,7 @@ final class Scripts {
96
  */
97
  public static function _init() {
98
 
99
- $tsf = \the_seo_framework();
100
 
101
  $_scripts = [
102
  static::get_tsf_scripts(),
@@ -201,9 +178,8 @@ final class Scripts {
201
  if ( is_scalar( $values ) )
202
  return static::decode_entities( $values );
203
 
204
- foreach ( $values as &$v ) {
205
  $v = static::decode_entities( $v );
206
- }
207
 
208
  return $values;
209
  }
@@ -215,12 +191,11 @@ final class Scripts {
215
  */
216
  public static function prepare_media_scripts() {
217
 
218
- $tsf = \the_seo_framework();
219
  $args = [];
220
 
221
- if ( $tsf->is_post_edit() ) {
222
  $args['post'] = $tsf->get_the_real_admin_ID();
223
- }
224
 
225
  \wp_enqueue_media( $args );
226
  }
@@ -248,7 +223,7 @@ final class Scripts {
248
  [
249
  'id' => 'tsf',
250
  'type' => 'css',
251
- 'deps' => [ 'tsf-tt' ],
252
  'autoload' => true,
253
  'hasrtl' => false,
254
  'name' => 'tsf',
@@ -258,7 +233,7 @@ final class Scripts {
258
  [
259
  'id' => 'tsf',
260
  'type' => 'js',
261
- 'deps' => [ 'jquery', 'tsf-tt', 'wp-util' ],
262
  'autoload' => true,
263
  'name' => 'tsf',
264
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
@@ -277,8 +252,7 @@ final class Scripts {
277
  'edit_posts' => \current_user_can( 'edit_posts' ) ? \wp_create_nonce( 'tsf-ajax-edit_posts' ) : false,
278
  ],
279
  'states' => [
280
- 'isRTL' => (bool) \is_rtl(),
281
- 'debug' => \the_seo_framework()->script_debug,
282
  ],
283
  ],
284
  ],
@@ -323,7 +297,7 @@ final class Scripts {
323
  [
324
  'id' => 'tsf-tt',
325
  'type' => 'js',
326
- 'deps' => [ 'jquery' ],
327
  'autoload' => true,
328
  'name' => 'tt',
329
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
@@ -344,7 +318,7 @@ final class Scripts {
344
  [
345
  'id' => 'tsf-ays',
346
  'type' => 'js',
347
- 'deps' => [ 'jquery' ],
348
  'autoload' => true,
349
  'name' => 'ays',
350
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
@@ -366,6 +340,7 @@ final class Scripts {
366
  *
367
  * @since 4.0.0
368
  * @since 4.1.0 Now depends on title and description scripts.
 
369
  *
370
  * @return array The script params.
371
  */
@@ -384,15 +359,11 @@ final class Scripts {
384
  [
385
  'id' => 'tsf-le',
386
  'type' => 'js',
387
- 'deps' => [ 'jquery', 'tsf-title', 'tsf-description', 'tsf' ],
388
  'autoload' => true,
389
  'name' => 'le',
390
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
391
  'ver' => THE_SEO_FRAMEWORK_VERSION,
392
- 'l10n' => [
393
- 'name' => 'tsfLeL10n',
394
- 'data' => [],
395
- ],
396
  ],
397
  ];
398
  }
@@ -407,15 +378,25 @@ final class Scripts {
407
  */
408
  public static function get_seo_settings_scripts() {
409
 
410
- $tsf = \the_seo_framework();
411
 
412
  $front_id = $tsf->get_the_front_page_ID();
413
 
414
  return [
 
 
 
 
 
 
 
 
 
 
415
  [
416
  'id' => 'tsf-settings',
417
  'type' => 'js',
418
- 'deps' => [ 'jquery', 'tsf-ays', 'tsf-title', 'tsf-description', 'tsf', 'tsf-tabs', 'tsf-tt', 'wp-color-picker', 'wp-util' ],
419
  'autoload' => true,
420
  'name' => 'settings',
421
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
@@ -433,16 +414,6 @@ final class Scripts {
433
  'file' => $tsf->get_view_location( 'templates/settings/settings' ),
434
  ],
435
  ],
436
- [
437
- 'id' => 'tsf-settings',
438
- 'type' => 'css',
439
- 'deps' => [ 'tsf', 'tsf-tt', 'wp-color-picker' ],
440
- 'autoload' => true,
441
- 'hasrtl' => false,
442
- 'name' => 'settings',
443
- 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
444
- 'ver' => THE_SEO_FRAMEWORK_VERSION,
445
- ],
446
  ];
447
  }
448
 
@@ -456,7 +427,7 @@ final class Scripts {
456
  */
457
  public static function get_post_edit_scripts() {
458
 
459
- $tsf = \the_seo_framework();
460
  $id = $tsf->get_the_real_ID();
461
 
462
  $is_static_frontpage = $tsf->is_static_frontpage( $id );
@@ -470,6 +441,24 @@ final class Scripts {
470
  }
471
 
472
  return [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
473
  [
474
  'id' => 'tsf-post',
475
  'type' => 'js',
@@ -495,24 +484,6 @@ final class Scripts {
495
  ],
496
  ],
497
  ],
498
- [
499
- 'id' => 'tsf-post',
500
- 'type' => 'css',
501
- 'deps' => [ 'tsf-tt', 'tsf' ],
502
- 'autoload' => true,
503
- 'hasrtl' => false,
504
- 'name' => 'post',
505
- 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
506
- 'ver' => THE_SEO_FRAMEWORK_VERSION,
507
- 'inline' => [
508
- '.tsf-flex-nav-tab .tsf-flex-nav-tab-radio:checked + .tsf-flex-nav-tab-label' => [
509
- 'box-shadow:0 -2px 0 0 {{$color_accent}} inset, 0 0 0 0 {{$color_accent}} inset',
510
- ],
511
- '.tsf-flex-nav-tab .tsf-flex-nav-tab-radio:focus + .tsf-flex-nav-tab-label:not(.tsf-no-focus-ring)' => [
512
- 'box-shadow:0 -2px 0 0 {{$color_accent}} inset, 0 0 0 1px {{$color_accent}} inset',
513
- ],
514
- ],
515
- ],
516
  ];
517
  }
518
 
@@ -521,25 +492,41 @@ final class Scripts {
521
  *
522
  * @since 4.0.0
523
  * @since 4.1.0 Updated l10n.data.
 
524
  *
525
  * @return array The script params.
526
  */
527
  public static function get_term_edit_scripts() {
528
 
529
- $tsf = \the_seo_framework();
530
  $taxonomy = $tsf->get_current_taxonomy();
531
 
532
  $additions_forced_disabled = (bool) $tsf->get_option( 'title_rem_additions' );
533
 
534
- $term_prefix = $tsf->use_generated_archive_prefix( \get_taxonomy( $taxonomy ) )
535
- ? $tsf->prepend_tax_label_prefix( '', $taxonomy )
 
 
 
 
 
536
  : '';
537
 
538
  return [
 
 
 
 
 
 
 
 
 
 
539
  [
540
  'id' => 'tsf-term',
541
  'type' => 'js',
542
- 'deps' => [ 'jquery', 'tsf-ays', 'tsf-title', 'tsf-description', 'tsf-social', 'tsf-tt', 'tsf' ],
543
  'autoload' => true,
544
  'name' => 'term',
545
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
@@ -554,16 +541,6 @@ final class Scripts {
554
  ],
555
  ],
556
  ],
557
- [
558
- 'id' => 'tsf-term',
559
- 'type' => 'css',
560
- 'deps' => [ 'tsf-tt', 'tsf' ],
561
- 'autoload' => true,
562
- 'hasrtl' => false,
563
- 'name' => 'term',
564
- 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
565
- 'ver' => THE_SEO_FRAMEWORK_VERSION,
566
- ],
567
  ];
568
  }
569
 
@@ -571,6 +548,7 @@ final class Scripts {
571
  * Returns Gutenberg compatibility scripts params.
572
  *
573
  * @since 4.0.0
 
574
  *
575
  * @return array The script params.
576
  */
@@ -579,15 +557,11 @@ final class Scripts {
579
  [
580
  'id' => 'tsf-gbc',
581
  'type' => 'js',
582
- 'deps' => [ 'jquery', 'tsf', 'tsf-post', 'wp-editor', 'wp-data', 'lodash', 'react' ],
583
  'autoload' => true,
584
  'name' => 'gbc',
585
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
586
  'ver' => THE_SEO_FRAMEWORK_VERSION,
587
- 'l10n' => [
588
- 'name' => 'tsfGBCL10n',
589
- 'data' => [],
590
- ],
591
  ],
592
  ];
593
  }
@@ -596,6 +570,7 @@ final class Scripts {
596
  * Returns Tabs scripts params.
597
  *
598
  * @since 4.1.3
 
599
  *
600
  * @return array The script params.
601
  */
@@ -608,10 +583,6 @@ final class Scripts {
608
  'name' => 'tabs',
609
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
610
  'ver' => THE_SEO_FRAMEWORK_VERSION,
611
- 'l10n' => [
612
- 'name' => 'tsfTabsL10n',
613
- 'data' => [],
614
- ],
615
  ];
616
  }
617
 
@@ -671,12 +642,12 @@ final class Scripts {
671
  */
672
  public static function get_title_scripts() {
673
 
674
- $tsf = \the_seo_framework();
675
 
676
  return [
677
  'id' => 'tsf-title',
678
  'type' => 'js',
679
- 'deps' => [ 'jquery', 'tsf' ],
680
  'autoload' => true,
681
  'name' => 'title',
682
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
@@ -707,7 +678,7 @@ final class Scripts {
707
  * Returns Description scripts params.
708
  *
709
  * @since 4.0.0
710
- * @since 4.1.0 No longer outputs l10n (data).
711
  *
712
  * @return array The script params.
713
  */
@@ -715,7 +686,7 @@ final class Scripts {
715
  return [
716
  'id' => 'tsf-description',
717
  'type' => 'js',
718
- 'deps' => [ 'jquery', 'tsf' ],
719
  'autoload' => true,
720
  'name' => 'description',
721
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
@@ -727,102 +698,20 @@ final class Scripts {
727
  * Returns Social scripts params.
728
  *
729
  * @since 4.0.0
 
730
  *
731
  * @return array The script params.
732
  */
733
  public static function get_social_scripts() {
734
-
735
- $tsf = \the_seo_framework();
736
-
737
- $_query = [
738
- 'id' => $tsf->is_seo_settings_page() ? $tsf->get_the_front_page_ID() : $tsf->get_the_real_ID(),
739
- 'taxonomy' => $tsf->get_current_taxonomy(),
740
- ];
741
-
742
- // These placeholders are required as there are three description lengths: search, og, twitter.
743
- $settings_placeholders = [
744
- 'ogDesc' => '',
745
- 'twDesc' => '',
746
- ];
747
-
748
- // These locks are required as we have an extra homepage metabox that can override social settings.
749
- // PH = placeholder
750
- $home_locks = array_fill_keys(
751
- [
752
- 'ogTitleLock',
753
- 'ogTitlePHLock',
754
- 'ogDescriptionLock',
755
- 'ogDescriptionPHLock',
756
-
757
- 'twTitleLock',
758
- 'twTitlePHLock',
759
- 'twDescriptionLock',
760
- 'twDescriptionPHLock',
761
- ],
762
- false
763
- );
764
-
765
- if ( $tsf->has_page_on_front() ) {
766
- if ( $tsf->is_seo_settings_page() ) {
767
- $home_locks = [
768
- 'ogTitlePHLock' => (bool) $tsf->get_post_meta_item( '_open_graph_title', $_query['id'] ),
769
- 'twTitlePHLock' => (bool) $tsf->get_post_meta_item( '_twitter_title', $_query['id'] ),
770
- 'twDescriptionPHLock' => (bool) $tsf->get_post_meta_item( '_twitter_description', $_query['id'] ),
771
- 'ogDescriptionPHLock' => (bool) $tsf->get_post_meta_item( '_open_graph_description', $_query['id'] ),
772
- ];
773
-
774
- $_homepage_desc_placeholder = $tsf->get_post_meta_item( '_genesis_description', $_query['id'] );
775
-
776
- $settings_placeholders = [
777
- 'ogDesc' => $_homepage_desc_placeholder,
778
- 'twDesc' => $_homepage_desc_placeholder,
779
- ];
780
- } elseif ( ! $_query['taxonomy'] && $tsf->is_static_frontpage( $_query['id'] ) ) {
781
- $home_locks = [
782
- 'ogTitleLock' => (bool) $tsf->get_option( 'homepage_og_title' ),
783
- 'ogDescriptionLock' => (bool) $tsf->get_option( 'homepage_og_description' ),
784
- 'twTitleLock' => (bool) $tsf->get_option( 'homepage_twitter_title' ),
785
- 'twDescriptionLock' => (bool) $tsf->get_option( 'homepage_twitter_description' ),
786
- ];
787
-
788
- $_homepage_desc_placeholder = $tsf->get_option( 'homepage_description' );
789
-
790
- $settings_placeholders = [
791
- 'ogDesc' => $_homepage_desc_placeholder,
792
- 'twDesc' => $_homepage_desc_placeholder,
793
- ];
794
- }
795
- }
796
-
797
- $settings_placeholders['ogDesc'] = $tsf->s_description(
798
- $settings_placeholders['ogDesc'] ?: $tsf->get_generated_open_graph_description( $_query, false )
799
- );
800
-
801
- $settings_placeholders['twDesc'] = $tsf->s_description(
802
- $settings_placeholders['twDesc'] ?: $tsf->get_generated_twitter_description( $_query, false )
803
- );
804
-
805
  return [
806
  'id' => 'tsf-social',
807
  'type' => 'js',
808
- 'deps' => [ 'jquery', 'tsf' ],
809
  'autoload' => true,
810
  'name' => 'social',
811
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
812
  'ver' => THE_SEO_FRAMEWORK_VERSION,
813
- 'l10n' => [
814
- 'name' => 'tsfSocialL10n',
815
- 'data' => [
816
- 'params' => [
817
- 'homeLocks' => $home_locks,
818
- ],
819
- 'states' => [
820
- 'placeholders' => static::decode_all_entities( $settings_placeholders ),
821
- ],
822
- ],
823
- ],
824
  ];
825
-
826
  }
827
 
828
  /**
@@ -835,7 +724,7 @@ final class Scripts {
835
  */
836
  public static function get_primaryterm_scripts() {
837
 
838
- $tsf = \the_seo_framework();
839
 
840
  $id = $tsf->get_the_real_admin_ID();
841
 
@@ -848,8 +737,7 @@ final class Scripts {
848
  foreach ( $_taxonomies as $_t ) {
849
  if ( ! $tsf->is_taxonomy_supported( $_t->name ) ) continue;
850
 
851
- $singular_name = $tsf->get_tax_type_label( $_t->name );
852
-
853
  $primary_term_id = $tsf->get_primary_term_id( $id, $_t->name ) ?: 0;
854
 
855
  if ( ! $primary_term_id ) {
@@ -904,7 +792,7 @@ final class Scripts {
904
  'id' => 'tsf-pt-gb',
905
  'name' => 'pt-gb',
906
  ];
907
- $deps = [ 'jquery', 'tsf', 'tsf-post', 'wp-hooks', 'wp-element', 'wp-components', 'wp-url', 'wp-api-fetch', 'lodash', 'react', 'wp-util' ];
908
  } else {
909
  $vars = [
910
  'id' => 'tsf-pt',
@@ -914,6 +802,17 @@ final class Scripts {
914
  }
915
 
916
  return [
 
 
 
 
 
 
 
 
 
 
 
917
  [
918
  'id' => $vars['id'],
919
  'type' => 'js',
@@ -932,17 +831,6 @@ final class Scripts {
932
  'file' => $tsf->get_view_location( 'templates/inpost/primary-term-selector' ),
933
  ],
934
  ],
935
- [
936
- 'id' => 'tsf-pt',
937
- 'type' => 'css',
938
- 'deps' => [ 'tsf-tt' ],
939
- 'autoload' => true,
940
- 'hasrtl' => false,
941
- 'name' => 'pt',
942
- 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
943
- 'ver' => THE_SEO_FRAMEWORK_VERSION,
944
- 'inline' => $inline_css,
945
- ],
946
  ];
947
  }
948
 
@@ -955,13 +843,23 @@ final class Scripts {
955
  */
956
  public static function get_counter_scripts() {
957
 
958
- $tsf = \the_seo_framework();
959
 
960
  return [
 
 
 
 
 
 
 
 
 
 
961
  [
962
  'id' => 'tsf-c',
963
  'type' => 'js',
964
- 'deps' => [ 'jquery', 'tsf-tt', 'tsf' ],
965
  'autoload' => true,
966
  'name' => 'c',
967
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
@@ -979,18 +877,6 @@ final class Scripts {
979
  ],
980
  ],
981
  ],
982
- [
983
- 'id' => 'tsf-c',
984
- 'type' => 'css',
985
- 'deps' => [ 'tsf-tt' ],
986
- 'autoload' => true,
987
- 'hasrtl' => false,
988
- 'name' => 'tsfc',
989
- 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
990
- 'ver' => THE_SEO_FRAMEWORK_VERSION,
991
- ],
992
  ];
993
  }
994
  }
995
-
996
- $_load_scripts_class();
25
 
26
  \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  /**
29
  * Prepares admin GUI scripts. Auto-invokes everything the moment this file is required.
30
  * Relies on \The_SEO_Framework\Builders\Scripts to register and load scripts.
36
  * @since 4.0.0
37
  * @see \The_SEO_Framework\Builders\Scripts
38
  * @access protected
 
39
  * @final Can't be extended.
40
  */
41
  final class Scripts {
42
 
 
 
 
 
 
 
43
  /**
44
  * Prepares the class and loads constructor.
45
  *
47
  * this class is needed yet.
48
  *
49
  * @since 4.0.0
50
+ * @ignore
51
+ * @deprecated
52
  */
53
  public static function prepare() {}
54
 
55
  /**
56
+ * The constructor. Can't be instantiated.
57
+ * Kills PHP. Enforces singleton.
58
  *
59
  * This probably autoloads at action "admin_enqueue_scripts", priority "0".
60
  *
62
  * @access private
63
  * @internal
64
  */
65
+ private function __construct() {}
 
 
 
 
 
 
66
 
67
  /**
68
  * Initializes scripts based on admin query.
73
  */
74
  public static function _init() {
75
 
76
+ $tsf = \tsf();
77
 
78
  $_scripts = [
79
  static::get_tsf_scripts(),
178
  if ( is_scalar( $values ) )
179
  return static::decode_entities( $values );
180
 
181
+ foreach ( $values as &$v )
182
  $v = static::decode_entities( $v );
 
183
 
184
  return $values;
185
  }
191
  */
192
  public static function prepare_media_scripts() {
193
 
194
+ $tsf = \tsf();
195
  $args = [];
196
 
197
+ if ( $tsf->is_post_edit() )
198
  $args['post'] = $tsf->get_the_real_admin_ID();
 
199
 
200
  \wp_enqueue_media( $args );
201
  }
223
  [
224
  'id' => 'tsf',
225
  'type' => 'css',
226
+ 'deps' => [],
227
  'autoload' => true,
228
  'hasrtl' => false,
229
  'name' => 'tsf',
233
  [
234
  'id' => 'tsf',
235
  'type' => 'js',
236
+ 'deps' => [ 'jquery', 'wp-util' ],
237
  'autoload' => true,
238
  'name' => 'tsf',
239
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
252
  'edit_posts' => \current_user_can( 'edit_posts' ) ? \wp_create_nonce( 'tsf-ajax-edit_posts' ) : false,
253
  ],
254
  'states' => [
255
+ 'debug' => \tsf()->script_debug,
 
256
  ],
257
  ],
258
  ],
297
  [
298
  'id' => 'tsf-tt',
299
  'type' => 'js',
300
+ 'deps' => [ 'tsf' ],
301
  'autoload' => true,
302
  'name' => 'tt',
303
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
318
  [
319
  'id' => 'tsf-ays',
320
  'type' => 'js',
321
+ 'deps' => [ 'tsf' ],
322
  'autoload' => true,
323
  'name' => 'ays',
324
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
340
  *
341
  * @since 4.0.0
342
  * @since 4.1.0 Now depends on title and description scripts.
343
+ * @since 4.2.0 No longer registers l10n (data).
344
  *
345
  * @return array The script params.
346
  */
359
  [
360
  'id' => 'tsf-le',
361
  'type' => 'js',
362
+ 'deps' => [ 'tsf-title', 'tsf-description', 'tsf' ],
363
  'autoload' => true,
364
  'name' => 'le',
365
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
366
  'ver' => THE_SEO_FRAMEWORK_VERSION,
 
 
 
 
367
  ],
368
  ];
369
  }
378
  */
379
  public static function get_seo_settings_scripts() {
380
 
381
+ $tsf = \tsf();
382
 
383
  $front_id = $tsf->get_the_front_page_ID();
384
 
385
  return [
386
+ [
387
+ 'id' => 'tsf-settings',
388
+ 'type' => 'css',
389
+ 'deps' => [ 'tsf', 'tsf-tt', 'wp-color-picker' ],
390
+ 'autoload' => true,
391
+ 'hasrtl' => false,
392
+ 'name' => 'settings',
393
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
394
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
395
+ ],
396
  [
397
  'id' => 'tsf-settings',
398
  'type' => 'js',
399
+ 'deps' => [ 'jquery', 'tsf-ays', 'tsf-title', 'tsf-description', 'tsf-social', 'tsf', 'tsf-tabs', 'tsf-tt', 'wp-color-picker', 'wp-util' ],
400
  'autoload' => true,
401
  'name' => 'settings',
402
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
414
  'file' => $tsf->get_view_location( 'templates/settings/settings' ),
415
  ],
416
  ],
 
 
 
 
 
 
 
 
 
 
417
  ];
418
  }
419
 
427
  */
428
  public static function get_post_edit_scripts() {
429
 
430
+ $tsf = \tsf();
431
  $id = $tsf->get_the_real_ID();
432
 
433
  $is_static_frontpage = $tsf->is_static_frontpage( $id );
441
  }
442
 
443
  return [
444
+ [
445
+ 'id' => 'tsf-post',
446
+ 'type' => 'css',
447
+ 'deps' => [ 'tsf-tt', 'tsf' ],
448
+ 'autoload' => true,
449
+ 'hasrtl' => false,
450
+ 'name' => 'post',
451
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
452
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
453
+ 'inline' => [
454
+ '.tsf-flex-nav-tab .tsf-flex-nav-tab-radio:checked + .tsf-flex-nav-tab-label' => [
455
+ 'box-shadow:0 -2px 0 0 {{$color_accent}} inset, 0 0 0 0 {{$color_accent}} inset',
456
+ ],
457
+ '.tsf-flex-nav-tab .tsf-flex-nav-tab-radio:focus + .tsf-flex-nav-tab-label:not(.tsf-no-focus-ring)' => [
458
+ 'box-shadow:0 -2px 0 0 {{$color_accent}} inset, 0 0 0 1px {{$color_accent}} inset',
459
+ ],
460
+ ],
461
+ ],
462
  [
463
  'id' => 'tsf-post',
464
  'type' => 'js',
484
  ],
485
  ],
486
  ],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487
  ];
488
  }
489
 
492
  *
493
  * @since 4.0.0
494
  * @since 4.1.0 Updated l10n.data.
495
+ * @since 4.2.0 Now properly populates use_generated_archive_prefix() with a \WP_Term object.
496
  *
497
  * @return array The script params.
498
  */
499
  public static function get_term_edit_scripts() {
500
 
501
+ $tsf = \tsf();
502
  $taxonomy = $tsf->get_current_taxonomy();
503
 
504
  $additions_forced_disabled = (bool) $tsf->get_option( 'title_rem_additions' );
505
 
506
+ $term_prefix = $tsf->use_generated_archive_prefix( \get_term( $tsf->get_the_real_ID(), $taxonomy ) )
507
+ /* translators: %s: Taxonomy singular name. */
508
+ ? sprintf(
509
+ /* translators: %s: Taxonomy singular name. */
510
+ \_x( '%s:', 'taxonomy term archive title prefix', 'default' ),
511
+ $tsf->get_tax_type_label( $taxonomy )
512
+ )
513
  : '';
514
 
515
  return [
516
+ [
517
+ 'id' => 'tsf-term',
518
+ 'type' => 'css',
519
+ 'deps' => [ 'tsf-tt', 'tsf' ],
520
+ 'autoload' => true,
521
+ 'hasrtl' => false,
522
+ 'name' => 'term',
523
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
524
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
525
+ ],
526
  [
527
  'id' => 'tsf-term',
528
  'type' => 'js',
529
+ 'deps' => [ 'tsf-ays', 'tsf-title', 'tsf-description', 'tsf-social', 'tsf-tt', 'tsf' ],
530
  'autoload' => true,
531
  'name' => 'term',
532
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
541
  ],
542
  ],
543
  ],
 
 
 
 
 
 
 
 
 
 
544
  ];
545
  }
546
 
548
  * Returns Gutenberg compatibility scripts params.
549
  *
550
  * @since 4.0.0
551
+ * @since 4.2.0 No longer registers l10n (data).
552
  *
553
  * @return array The script params.
554
  */
557
  [
558
  'id' => 'tsf-gbc',
559
  'type' => 'js',
560
+ 'deps' => [ 'jquery', 'tsf', 'wp-editor', 'wp-data', 'lodash', 'react' ],
561
  'autoload' => true,
562
  'name' => 'gbc',
563
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
564
  'ver' => THE_SEO_FRAMEWORK_VERSION,
 
 
 
 
565
  ],
566
  ];
567
  }
570
  * Returns Tabs scripts params.
571
  *
572
  * @since 4.1.3
573
+ * @since 4.2.0 No longer registers l10n (data).
574
  *
575
  * @return array The script params.
576
  */
583
  'name' => 'tabs',
584
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
585
  'ver' => THE_SEO_FRAMEWORK_VERSION,
 
 
 
 
586
  ];
587
  }
588
 
642
  */
643
  public static function get_title_scripts() {
644
 
645
+ $tsf = \tsf();
646
 
647
  return [
648
  'id' => 'tsf-title',
649
  'type' => 'js',
650
+ 'deps' => [ 'tsf' ],
651
  'autoload' => true,
652
  'name' => 'title',
653
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
678
  * Returns Description scripts params.
679
  *
680
  * @since 4.0.0
681
+ * @since 4.2.0 No longer registers l10n (data).
682
  *
683
  * @return array The script params.
684
  */
686
  return [
687
  'id' => 'tsf-description',
688
  'type' => 'js',
689
+ 'deps' => [ 'tsf' ],
690
  'autoload' => true,
691
  'name' => 'description',
692
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
698
  * Returns Social scripts params.
699
  *
700
  * @since 4.0.0
701
+ * @since 4.2.0 No longer registers l10n (data).
702
  *
703
  * @return array The script params.
704
  */
705
  public static function get_social_scripts() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
706
  return [
707
  'id' => 'tsf-social',
708
  'type' => 'js',
709
+ 'deps' => [ 'tsf' ],
710
  'autoload' => true,
711
  'name' => 'social',
712
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
713
  'ver' => THE_SEO_FRAMEWORK_VERSION,
 
 
 
 
 
 
 
 
 
 
 
714
  ];
 
715
  }
716
 
717
  /**
724
  */
725
  public static function get_primaryterm_scripts() {
726
 
727
+ $tsf = \tsf();
728
 
729
  $id = $tsf->get_the_real_admin_ID();
730
 
737
  foreach ( $_taxonomies as $_t ) {
738
  if ( ! $tsf->is_taxonomy_supported( $_t->name ) ) continue;
739
 
740
+ $singular_name = $tsf->get_tax_type_label( $_t->name );
 
741
  $primary_term_id = $tsf->get_primary_term_id( $id, $_t->name ) ?: 0;
742
 
743
  if ( ! $primary_term_id ) {
792
  'id' => 'tsf-pt-gb',
793
  'name' => 'pt-gb',
794
  ];
795
+ $deps = [ 'tsf', 'wp-hooks', 'wp-element', 'wp-components', 'wp-url', 'wp-api-fetch', 'lodash', 'react', 'wp-util' ];
796
  } else {
797
  $vars = [
798
  'id' => 'tsf-pt',
802
  }
803
 
804
  return [
805
+ [
806
+ 'id' => 'tsf-pt',
807
+ 'type' => 'css',
808
+ 'deps' => [ 'tsf-tt' ],
809
+ 'autoload' => true,
810
+ 'hasrtl' => false,
811
+ 'name' => 'pt',
812
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
813
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
814
+ 'inline' => $inline_css,
815
+ ],
816
  [
817
  'id' => $vars['id'],
818
  'type' => 'js',
831
  'file' => $tsf->get_view_location( 'templates/inpost/primary-term-selector' ),
832
  ],
833
  ],
 
 
 
 
 
 
 
 
 
 
 
834
  ];
835
  }
836
 
843
  */
844
  public static function get_counter_scripts() {
845
 
846
+ $tsf = \tsf();
847
 
848
  return [
849
+ [
850
+ 'id' => 'tsf-c',
851
+ 'type' => 'css',
852
+ 'deps' => [ 'tsf-tt' ],
853
+ 'autoload' => true,
854
+ 'hasrtl' => false,
855
+ 'name' => 'tsfc',
856
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
857
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
858
+ ],
859
  [
860
  'id' => 'tsf-c',
861
  'type' => 'js',
862
+ 'deps' => [ 'tsf-tt', 'tsf' ],
863
  'autoload' => true,
864
  'name' => 'c',
865
  'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
877
  ],
878
  ],
879
  ],
 
 
 
 
 
 
 
 
 
 
880
  ];
881
  }
882
  }
 
 
inc/classes/bridges/seobar.class.php CHANGED
@@ -1,7 +1,7 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes\Bridges\SeoBar
4
- * @subpackage The_SEO_Framework\SeoBar
5
  */
6
 
7
  namespace The_SEO_Framework\Bridges;
@@ -29,12 +29,12 @@ namespace The_SEO_Framework\Bridges;
29
  * Loads the SEO Bar for administrative tables.
30
  *
31
  * @since 4.0.0
32
- * @uses \The_SEO_Framework\Interpreters\SeoBar
33
- * @see \The_SEO_Framework\Interpreters\SeoBar to generate a bar.
34
  *
35
  * @access private
36
  */
37
- final class SeoBar extends ListTable {
38
 
39
  /**
40
  * @since 4.0.0
@@ -118,7 +118,7 @@ final class SeoBar extends ListTable {
118
  if ( $this->column_name !== $column_name ) return;
119
 
120
  // phpcs:ignore, WordPress.Security.EscapeOutput
121
- echo \The_SEO_Framework\Interpreters\SeoBar::generate_bar( [
122
  'id' => $post_id,
123
  'post_type' => $this->post_type,
124
  ] );
@@ -150,7 +150,7 @@ final class SeoBar extends ListTable {
150
  if ( $this->doing_ajax )
151
  $string .= $this->get_ajax_dispatch_updated_event();
152
 
153
- return \The_SEO_Framework\Interpreters\SeoBar::generate_bar( [
154
  'id' => $term_id,
155
  'taxonomy' => $this->taxonomy,
156
  ] ) . $string;
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Bridges\SEOBar
4
+ * @subpackage The_SEO_Framework\SEOBar
5
  */
6
 
7
  namespace The_SEO_Framework\Bridges;
29
  * Loads the SEO Bar for administrative tables.
30
  *
31
  * @since 4.0.0
32
+ * @uses \The_SEO_Framework\Interpreters\SEOBar
33
+ * @see \The_SEO_Framework\Interpreters\SEOBar to generate a bar.
34
  *
35
  * @access private
36
  */
37
+ final class SEOBar extends ListTable {
38
 
39
  /**
40
  * @since 4.0.0
118
  if ( $this->column_name !== $column_name ) return;
119
 
120
  // phpcs:ignore, WordPress.Security.EscapeOutput
121
+ echo \The_SEO_Framework\Interpreters\SEOBar::generate_bar( [
122
  'id' => $post_id,
123
  'post_type' => $this->post_type,
124
  ] );
150
  if ( $this->doing_ajax )
151
  $string .= $this->get_ajax_dispatch_updated_event();
152
 
153
+ return \The_SEO_Framework\Interpreters\SEOBar::generate_bar( [
154
  'id' => $term_id,
155
  'taxonomy' => $this->taxonomy,
156
  ] ) . $string;
inc/classes/bridges/seosettings.class.php CHANGED
@@ -45,35 +45,40 @@ final class SeoSettings {
45
  */
46
  public static function _register_seo_settings_meta_boxes() {
47
 
 
 
48
  /**
49
  * Various metabox filters.
50
  * Set any to false if you wish the meta box to be removed.
51
  *
52
  * @since 2.2.4
53
  * @since 2.8.0 Added `the_seo_framework_general_metabox` filter.
 
54
  */
55
- $general = (bool) \apply_filters( 'the_seo_framework_general_metabox', true );
56
- $title = (bool) \apply_filters( 'the_seo_framework_title_metabox', true );
57
- $description = (bool) \apply_filters( 'the_seo_framework_description_metabox', true );
58
- $robots = (bool) \apply_filters( 'the_seo_framework_robots_metabox', true );
59
- $home = (bool) \apply_filters( 'the_seo_framework_home_metabox', true );
60
- $social = (bool) \apply_filters( 'the_seo_framework_social_metabox', true );
61
- $schema = (bool) \apply_filters( 'the_seo_framework_schema_metabox', true );
62
- $webmaster = (bool) \apply_filters( 'the_seo_framework_webmaster_metabox', true );
63
- $sitemap = (bool) \apply_filters( 'the_seo_framework_sitemap_metabox', true );
64
- $feed = (bool) \apply_filters( 'the_seo_framework_feed_metabox', true );
65
-
66
- $settings_page_hook = \the_seo_framework()->seo_settings_page_hook;
 
 
 
67
 
68
  // General Meta Box
69
  if ( $general )
70
  \add_meta_box(
71
  'autodescription-general-settings',
72
  \esc_html__( 'General Settings', 'autodescription' ),
73
- __CLASS__ . '::_general_metabox',
74
  $settings_page_hook,
75
- 'main',
76
- []
77
  );
78
 
79
  // Title Meta Box
@@ -81,10 +86,9 @@ final class SeoSettings {
81
  \add_meta_box(
82
  'autodescription-title-settings',
83
  \esc_html__( 'Title Settings', 'autodescription' ),
84
- __CLASS__ . '::_title_metabox',
85
  $settings_page_hook,
86
- 'main',
87
- []
88
  );
89
 
90
  // Description Meta Box
@@ -92,10 +96,9 @@ final class SeoSettings {
92
  \add_meta_box(
93
  'autodescription-description-settings',
94
  \esc_html__( 'Description Meta Settings', 'autodescription' ),
95
- __CLASS__ . '::_description_metabox',
96
  $settings_page_hook,
97
- 'main',
98
- []
99
  );
100
 
101
  // Homepage Meta Box
@@ -103,10 +106,18 @@ final class SeoSettings {
103
  \add_meta_box(
104
  'autodescription-homepage-settings',
105
  \esc_html__( 'Homepage Settings', 'autodescription' ),
106
- __CLASS__ . '::_homepage_metabox',
107
  $settings_page_hook,
108
- 'main',
109
- []
 
 
 
 
 
 
 
 
110
  );
111
 
112
  // Social Meta Box
@@ -114,10 +125,9 @@ final class SeoSettings {
114
  \add_meta_box(
115
  'autodescription-social-settings',
116
  \esc_html__( 'Social Meta Settings', 'autodescription' ),
117
- __CLASS__ . '::_social_metabox',
118
  $settings_page_hook,
119
- 'main',
120
- []
121
  );
122
 
123
  // Schema Meta Box
@@ -125,10 +135,9 @@ final class SeoSettings {
125
  \add_meta_box(
126
  'autodescription-schema-settings',
127
  \esc_html__( 'Schema.org Settings', 'autodescription' ),
128
- __CLASS__ . '::_schema_metabox',
129
  $settings_page_hook,
130
- 'main',
131
- []
132
  );
133
 
134
  // Robots Meta Box
@@ -136,10 +145,9 @@ final class SeoSettings {
136
  \add_meta_box(
137
  'autodescription-robots-settings',
138
  \esc_html__( 'Robots Meta Settings', 'autodescription' ),
139
- __CLASS__ . '::_robots_metabox',
140
  $settings_page_hook,
141
- 'main',
142
- []
143
  );
144
 
145
  // Webmaster Meta Box
@@ -147,10 +155,9 @@ final class SeoSettings {
147
  \add_meta_box(
148
  'autodescription-webmaster-settings',
149
  \esc_html__( 'Webmaster Meta Settings', 'autodescription' ),
150
- __CLASS__ . '::_webmaster_metabox',
151
  $settings_page_hook,
152
- 'main',
153
- []
154
  );
155
 
156
  // Sitemaps Meta Box
@@ -158,10 +165,9 @@ final class SeoSettings {
158
  \add_meta_box(
159
  'autodescription-sitemap-settings',
160
  \esc_html__( 'Sitemap Settings', 'autodescription' ),
161
- __CLASS__ . '::_sitemaps_metabox',
162
  $settings_page_hook,
163
- 'main',
164
- []
165
  );
166
 
167
  // Feed Meta Box
@@ -169,10 +175,9 @@ final class SeoSettings {
169
  \add_meta_box(
170
  'autodescription-feed-settings',
171
  \esc_html__( 'Feed Settings', 'autodescription' ),
172
- __CLASS__ . '::_feed_metabox',
173
  $settings_page_hook,
174
- 'main',
175
- []
176
  );
177
  }
178
 
@@ -196,8 +201,9 @@ final class SeoSettings {
196
  * @param bool $use_tabs Whether to output tabs, only works when $tabs count is greater than 1.
197
  */
198
  public static function _nav_tab_wrapper( $id, $tabs = [], $use_tabs = true ) { // phpcs:ignore,VariableAnalysis
199
- \the_seo_framework()->get_view( 'admin/wrap-nav', get_defined_vars() );
200
- \the_seo_framework()->get_view( 'admin/wrap-content', get_defined_vars() );
 
201
  }
202
 
203
  /**
@@ -211,7 +217,7 @@ final class SeoSettings {
211
  * @since 3.0.0
212
  */
213
  \do_action( 'the_seo_framework_pre_seo_settings' );
214
- \the_seo_framework()->get_view( 'admin/seo-settings-wrap' );
215
  /**
216
  * @since 3.0.0
217
  */
@@ -225,7 +231,7 @@ final class SeoSettings {
225
  * @access private
226
  */
227
  public static function _output_columns() {
228
- \the_seo_framework()->get_view( 'admin/seo-settings-columns' );
229
  }
230
 
231
  /**
@@ -233,16 +239,13 @@ final class SeoSettings {
233
  *
234
  * @since 4.0.0
235
  * @access private
236
- *
237
- * @param \WP_Post|null $post The current post object.
238
- * @param array $args The metabox arguments.
239
  */
240
- public static function _general_metabox( $post = null, $args = [] ) {
241
  /**
242
  * @since 2.8.0
243
  */
244
  \do_action( 'the_seo_framework_general_metabox_before' );
245
- \the_seo_framework()->get_view( 'admin/metaboxes/general-metabox', $args );
246
  /**
247
  * @since 2.8.0
248
  */
@@ -257,7 +260,7 @@ final class SeoSettings {
257
  * @see static::general_metabox() : Callback for General Settings box.
258
  */
259
  public static function _general_metabox_general_tab() {
260
- \the_seo_framework()->get_view( 'admin/metaboxes/general-metabox', [], 'general' );
261
  }
262
 
263
  /**
@@ -268,7 +271,7 @@ final class SeoSettings {
268
  * @see static::general_metabox() : Callback for General Settings box.
269
  */
270
  public static function _general_metabox_layout_tab() {
271
- \the_seo_framework()->get_view( 'admin/metaboxes/general-metabox', [], 'layout' );
272
  }
273
 
274
  /**
@@ -279,7 +282,7 @@ final class SeoSettings {
279
  * @see static::general_metabox() : Callback for General Settings box.
280
  */
281
  public static function _general_metabox_performance_tab() {
282
- \the_seo_framework()->get_view( 'admin/metaboxes/general-metabox', [], 'performance' );
283
  }
284
 
285
  /**
@@ -290,7 +293,7 @@ final class SeoSettings {
290
  * @see static::general_metabox() : Callback for General Settings box.
291
  */
292
  public static function _general_metabox_canonical_tab() {
293
- \the_seo_framework()->get_view( 'admin/metaboxes/general-metabox', [], 'canonical' );
294
  }
295
 
296
  /**
@@ -301,7 +304,7 @@ final class SeoSettings {
301
  * @see static::general_metabox() : Callback for General Settings box.
302
  */
303
  public static function _general_metabox_timestamps_tab() {
304
- \the_seo_framework()->get_view( 'admin/metaboxes/general-metabox', [], 'timestamps' );
305
  }
306
 
307
  /**
@@ -312,7 +315,7 @@ final class SeoSettings {
312
  * @see static::general_metabox() : Callback for General Settings box.
313
  */
314
  public static function _general_metabox_exclusions_tab() {
315
- \the_seo_framework()->get_view( 'admin/metaboxes/general-metabox', [], 'exclusions' );
316
  }
317
 
318
  /**
@@ -320,16 +323,13 @@ final class SeoSettings {
320
  *
321
  * @since 4.0.0
322
  * @access private
323
- *
324
- * @param \WP_Post|null $post The current post object.
325
- * @param array $args The metabox arguments.
326
  */
327
- public static function _title_metabox( $post = null, $args = [] ) {
328
  /**
329
  * @since 2.5.0 or earlier.
330
  */
331
  \do_action( 'the_seo_framework_title_metabox_before' );
332
- \the_seo_framework()->get_view( 'admin/metaboxes/title-metabox', $args );
333
  /**
334
  * @since 2.5.0 or earlier.
335
  */
@@ -344,7 +344,7 @@ final class SeoSettings {
344
  * @see static::title_metabox() : Callback for Title Settings box.
345
  */
346
  public static function _title_metabox_general_tab() {
347
- \the_seo_framework()->get_view( 'admin/metaboxes/title-metabox', [], 'general' );
348
  }
349
 
350
  /**
@@ -357,7 +357,7 @@ final class SeoSettings {
357
  * @param array $args The variables to pass to the metabox tab.
358
  */
359
  public static function _title_metabox_additions_tab( $args ) {
360
- \the_seo_framework()->get_view( 'admin/metaboxes/title-metabox', $args, 'additions' );
361
  }
362
 
363
  /**
@@ -370,7 +370,7 @@ final class SeoSettings {
370
  * @param array $args The variables to pass to the metabox tab.
371
  */
372
  public static function _title_metabox_prefixes_tab( $args ) {
373
- \the_seo_framework()->get_view( 'admin/metaboxes/title-metabox', $args, 'prefixes' );
374
  }
375
 
376
  /**
@@ -378,16 +378,13 @@ final class SeoSettings {
378
  *
379
  * @since 4.0.0
380
  * @access private
381
- *
382
- * @param \WP_Post|null $post The current post object.
383
- * @param array $args The metabox arguments.
384
  */
385
- public static function _description_metabox( $post = null, $args = [] ) {
386
  /**
387
  * @since 2.5.0 or earlier.
388
  */
389
  \do_action( 'the_seo_framework_description_metabox_before' );
390
- \the_seo_framework()->get_view( 'admin/metaboxes/description-metabox', $args );
391
  /**
392
  * @since 2.5.0 or earlier.
393
  */
@@ -399,16 +396,13 @@ final class SeoSettings {
399
  *
400
  * @since 4.0.0
401
  * @access private
402
- *
403
- * @param \WP_Post|null $post The current post object.
404
- * @param array $args The metabox arguments.
405
  */
406
- public static function _robots_metabox( $post = null, $args = [] ) {
407
  /**
408
  * @since 2.5.0 or earlier.
409
  */
410
  \do_action( 'the_seo_framework_robots_metabox_before' );
411
- \the_seo_framework()->get_view( 'admin/metaboxes/robots-metabox', $args );
412
  /**
413
  * @since 2.5.0 or earlier.
414
  */
@@ -416,18 +410,18 @@ final class SeoSettings {
416
  }
417
 
418
  /**
419
- * Robots Metabox General Tab output.
420
  *
421
  * @since 4.0.0
422
  * @access private
423
  * @see static::robots_metabox() Callback for Robots Settings box.
424
  */
425
  public static function _robots_metabox_general_tab() {
426
- \the_seo_framework()->get_view( 'admin/metaboxes/robots-metabox', [], 'general' );
427
  }
428
 
429
  /**
430
- * Robots Metabox "No-: Index/Follow/Archive" Tab output.
431
  *
432
  * @since 4.0.0
433
  * @access private
@@ -436,7 +430,7 @@ final class SeoSettings {
436
  * @param array $args The variables to pass to the metabox tab.
437
  */
438
  public static function _robots_metabox_no_tab( $args ) {
439
- \the_seo_framework()->get_view( 'admin/metaboxes/robots-metabox', $args, 'no' );
440
  }
441
 
442
  /**
@@ -444,16 +438,13 @@ final class SeoSettings {
444
  *
445
  * @since 4.0.0
446
  * @access private
447
- *
448
- * @param \WP_Post|null $post The current post object.
449
- * @param array $args The navigation tabs args.
450
  */
451
- public static function _homepage_metabox( $post = null, $args = [] ) {
452
  /**
453
  * @since 2.5.0 or earlier.
454
  */
455
  \do_action( 'the_seo_framework_homepage_metabox_before' );
456
- \the_seo_framework()->get_view( 'admin/metaboxes/homepage-metabox', $args );
457
  /**
458
  * @since 2.5.0 or earlier.
459
  */
@@ -461,47 +452,101 @@ final class SeoSettings {
461
  }
462
 
463
  /**
464
- * Homepage Metabox General Tab Output.
465
  *
466
  * @since 4.0.0
467
  * @access private
468
  * @see static::homepage_metabox() Callback for Homepage Settings box.
469
  */
470
  public static function _homepage_metabox_general_tab() {
471
- \the_seo_framework()->get_view( 'admin/metaboxes/homepage-metabox', [], 'general' );
472
  }
473
 
474
  /**
475
- * Homepage Metabox Additions Tab Output.
476
  *
477
  * @since 4.0.0
478
  * @access private
479
  * @see static::homepage_metabox() Callback for Homepage Settings box.
480
  */
481
  public static function _homepage_metabox_additions_tab() {
482
- \the_seo_framework()->get_view( 'admin/metaboxes/homepage-metabox', [], 'additions' );
483
  }
484
 
485
  /**
486
- * Homepage Metabox Robots Tab Output
487
  *
488
  * @since 4.0.0
489
  * @access private
490
  * @see static::homepage_metabox() Callback for Homepage Settings box.
491
  */
492
  public static function _homepage_metabox_robots_tab() {
493
- \the_seo_framework()->get_view( 'admin/metaboxes/homepage-metabox', [], 'robots' );
494
  }
495
 
496
  /**
497
- * Homepage Metabox Social Tab Output
498
  *
499
  * @since 4.0.0
500
  * @access private
501
  * @see static::homepage_metabox() Callback for Homepage Settings box.
502
  */
503
  public static function _homepage_metabox_social_tab() {
504
- \the_seo_framework()->get_view( 'admin/metaboxes/homepage-metabox', [], 'social' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  }
506
 
507
  /**
@@ -509,16 +554,13 @@ final class SeoSettings {
509
  *
510
  * @since 4.0.0
511
  * @access private
512
- *
513
- * @param \WP_Post|null $post The current post object.
514
- * @param array $args The navigation tabs args.
515
  */
516
- public static function _social_metabox( $post = null, $args = [] ) {
517
  /**
518
  * @since 2.5.0 or earlier.
519
  */
520
  \do_action( 'the_seo_framework_social_metabox_before' );
521
- \the_seo_framework()->get_view( 'admin/metaboxes/social-metabox', $args );
522
  /**
523
  * @since 2.5.0 or earlier.
524
  */
@@ -526,58 +568,58 @@ final class SeoSettings {
526
  }
527
 
528
  /**
529
- * Social Metabox General Tab output.
530
  *
531
  * @since 4.0.0
532
  * @access private
533
  * @see static::social_metabox() Callback for Social Settings box.
534
  */
535
  public static function _social_metabox_general_tab() {
536
- \the_seo_framework()->get_view( 'admin/metaboxes/social-metabox', [], 'general' );
537
  }
538
 
539
  /**
540
- * Social Metabox Facebook Tab output.
541
  *
542
  * @since 4.0.0
543
  * @access private
544
  * @see static::social_metabox() Callback for Social Settings box.
545
  */
546
  public static function _social_metabox_facebook_tab() {
547
- \the_seo_framework()->get_view( 'admin/metaboxes/social-metabox', [], 'facebook' );
548
  }
549
 
550
  /**
551
- * Social Metabox Twitter Tab output.
552
  *
553
  * @since 4.0.0
554
  * @access private
555
  * @see static::social_metabox() Callback for Social Settings box.
556
  */
557
  public static function _social_metabox_twitter_tab() {
558
- \the_seo_framework()->get_view( 'admin/metaboxes/social-metabox', [], 'twitter' );
559
  }
560
 
561
  /**
562
- * Social Metabox oEmbed Tab output.
563
  *
564
  * @since 4.0.5
565
  * @access private
566
  * @see static::social_metabox() Callback for Social Settings box.
567
  */
568
  public static function _social_metabox_oembed_tab() {
569
- \the_seo_framework()->get_view( 'admin/metaboxes/social-metabox', [], 'oembed' );
570
  }
571
 
572
  /**
573
- * Social Metabox PostDates Tab output.
574
  *
575
  * @since 4.0.0
576
  * @access private
577
  * @see static::social_metabox() Callback for Social Settings box.
578
  */
579
  public static function _social_metabox_postdates_tab() {
580
- \the_seo_framework()->get_view( 'admin/metaboxes/social-metabox', [], 'postdates' );
581
  }
582
 
583
  /**
@@ -585,16 +627,13 @@ final class SeoSettings {
585
  *
586
  * @since 4.0.0
587
  * @access private
588
- *
589
- * @param \WP_Post|null $post The current post object.
590
- * @param array $args The navigation tabs args.
591
  */
592
- public static function _webmaster_metabox( $post = null, $args = [] ) {
593
  /**
594
  * @since 2.5.0 or earlier.
595
  */
596
  \do_action( 'the_seo_framework_webmaster_metabox_before' );
597
- \the_seo_framework()->get_view( 'admin/metaboxes/webmaster-metabox', $args );
598
  /**
599
  * @since 2.5.0 or earlier.
600
  */
@@ -607,16 +646,13 @@ final class SeoSettings {
607
  * @since 4.0.0
608
  * @access private
609
  * @see static::sitemaps_metabox() Callback for Sitemaps Settings box.
610
- *
611
- * @param \WP_Post|null $post The current post object.
612
- * @param array $args The navigation tabs args.
613
  */
614
- public static function _sitemaps_metabox( $post = null, $args = [] ) {
615
  /**
616
  * @since 2.5.0 or earlier.
617
  */
618
  \do_action( 'the_seo_framework_sitemaps_metabox_before' );
619
- \the_seo_framework()->get_view( 'admin/metaboxes/sitemaps-metabox', $args );
620
  /**
621
  * @since 2.5.0 or earlier.
622
  */
@@ -624,75 +660,72 @@ final class SeoSettings {
624
  }
625
 
626
  /**
627
- * Sitemaps Metabox General Tab output.
628
  *
629
  * @since 4.0.0
630
  * @access private
631
  * @see static::sitemaps_metabox() Callback for Sitemaps Settings box.
632
  */
633
  public static function _sitemaps_metabox_general_tab() {
634
- \the_seo_framework()->get_view( 'admin/metaboxes/sitemaps-metabox', [], 'general' );
635
  }
636
 
637
  /**
638
- * Sitemaps Metabox Robots Tab output.
639
  *
640
  * @since 4.0.0
641
  * @access private
642
  * @see static::sitemaps_metabox() Callback for Sitemaps Settings box.
643
  */
644
  public static function _sitemaps_metabox_robots_tab() {
645
- \the_seo_framework()->get_view( 'admin/metaboxes/sitemaps-metabox', [], 'robots' );
646
  }
647
 
648
  /**
649
- * Sitemaps Metabox Metadata Tab output.
650
  *
651
  * @since 4.0.0
652
  * @access private
653
  * @see static::sitemaps_metabox() Callback for Sitemaps Settings box.
654
  */
655
  public static function _sitemaps_metabox_metadata_tab() {
656
- \the_seo_framework()->get_view( 'admin/metaboxes/sitemaps-metabox', [], 'metadata' );
657
  }
658
 
659
  /**
660
- * Sitemaps Metabox Notify Tab output.
661
  *
662
  * @since 4.0.0
663
  * @access private
664
  * @see static::sitemaps_metabox() Callback for Sitemaps Settings box.
665
  */
666
  public static function _sitemaps_metabox_notify_tab() {
667
- \the_seo_framework()->get_view( 'admin/metaboxes/sitemaps-metabox', [], 'notify' );
668
  }
669
 
670
  /**
671
- * Sitemaps Metabox Style Tab output.
672
  *
673
  * @since 4.0.0
674
  * @access private
675
  * @see static::sitemaps_metabox() Callback for Sitemaps Settings box.
676
  */
677
  public static function _sitemaps_metabox_style_tab() {
678
- \the_seo_framework()->get_view( 'admin/metaboxes/sitemaps-metabox', [], 'style' );
679
  }
680
 
681
  /**
682
- * Feed Metabox on the Site SEO Settings page.
683
  *
684
  * @since 4.0.0
685
  * @access private
686
- *
687
- * @param \WP_Post|null $post The current post object.
688
- * @param array $args The navigation tabs args.
689
  */
690
- public static function _feed_metabox( $post = null, $args = [] ) {
691
  /**
692
  * @since 2.5.2
693
  */
694
  \do_action( 'the_seo_framework_feed_metabox_before' );
695
- \the_seo_framework()->get_view( 'admin/metaboxes/feed-metabox', $args );
696
  /**
697
  * @since 2.5.2
698
  */
@@ -700,20 +733,17 @@ final class SeoSettings {
700
  }
701
 
702
  /**
703
- * Schema Metabox on the Site SEO Settings page.
704
  *
705
  * @since 4.0.0
706
  * @access private
707
- *
708
- * @param \WP_Post|null $post The current post object.
709
- * @param array $args The navigation tabs args.
710
  */
711
- public static function _schema_metabox( $post = null, $args = [] ) {
712
  /**
713
  * @since 2.6.0
714
  */
715
  \do_action( 'the_seo_framework_schema_metabox_before' );
716
- \the_seo_framework()->get_view( 'admin/metaboxes/schema-metabox', $args );
717
  /**
718
  * @since 2.6.0
719
  */
@@ -721,24 +751,24 @@ final class SeoSettings {
721
  }
722
 
723
  /**
724
- * Schema Metabox Structure Tab output.
725
  *
726
  * @since 4.0.0
727
  * @access private
728
  * @see static::schema_metabox() Callback for Schema.org Settings box.
729
  */
730
  public static function _schema_metabox_structure_tab() {
731
- \the_seo_framework()->get_view( 'admin/metaboxes/schema-metabox', [], 'structure' );
732
  }
733
 
734
  /**
735
- * Schema Metabox PResence Tab output.
736
  *
737
  * @since 4.0.0
738
  * @access private
739
  * @see static::schema_metabox() Callback for Schema.org Settings box.
740
  */
741
  public static function _schema_metabox_presence_tab() {
742
- \the_seo_framework()->get_view( 'admin/metaboxes/schema-metabox', [], 'presence' );
743
  }
744
  }
45
  */
46
  public static function _register_seo_settings_meta_boxes() {
47
 
48
+ $tsf = \tsf();
49
+
50
  /**
51
  * Various metabox filters.
52
  * Set any to false if you wish the meta box to be removed.
53
  *
54
  * @since 2.2.4
55
  * @since 2.8.0 Added `the_seo_framework_general_metabox` filter.
56
+ * @since 4.2.0 Added `the_seo_framework_post_type_archive_metabox` filter.
57
  */
58
+ $general = (bool) \apply_filters( 'the_seo_framework_general_metabox', true );
59
+ $title = (bool) \apply_filters( 'the_seo_framework_title_metabox', true );
60
+ $description = (bool) \apply_filters( 'the_seo_framework_description_metabox', true );
61
+ $robots = (bool) \apply_filters( 'the_seo_framework_robots_metabox', true );
62
+ $home = (bool) \apply_filters( 'the_seo_framework_home_metabox', true );
63
+ $post_type_archive = (bool) \apply_filters( 'the_seo_framework_post_type_archive_metabox', true );
64
+ $social = (bool) \apply_filters( 'the_seo_framework_social_metabox', true );
65
+ $schema = (bool) \apply_filters( 'the_seo_framework_schema_metabox', true );
66
+ $webmaster = (bool) \apply_filters( 'the_seo_framework_webmaster_metabox', true );
67
+ $sitemap = (bool) \apply_filters( 'the_seo_framework_sitemap_metabox', true );
68
+ $feed = (bool) \apply_filters( 'the_seo_framework_feed_metabox', true );
69
+
70
+ $settings_page_hook = $tsf->seo_settings_page_hook;
71
+
72
+ $class = static::class;
73
 
74
  // General Meta Box
75
  if ( $general )
76
  \add_meta_box(
77
  'autodescription-general-settings',
78
  \esc_html__( 'General Settings', 'autodescription' ),
79
+ "$class::_general_metabox",
80
  $settings_page_hook,
81
+ 'main'
 
82
  );
83
 
84
  // Title Meta Box
86
  \add_meta_box(
87
  'autodescription-title-settings',
88
  \esc_html__( 'Title Settings', 'autodescription' ),
89
+ "$class::_title_metabox",
90
  $settings_page_hook,
91
+ 'main'
 
92
  );
93
 
94
  // Description Meta Box
96
  \add_meta_box(
97
  'autodescription-description-settings',
98
  \esc_html__( 'Description Meta Settings', 'autodescription' ),
99
+ "$class::_description_metabox",
100
  $settings_page_hook,
101
+ 'main'
 
102
  );
103
 
104
  // Homepage Meta Box
106
  \add_meta_box(
107
  'autodescription-homepage-settings',
108
  \esc_html__( 'Homepage Settings', 'autodescription' ),
109
+ "$class::_homepage_metabox",
110
  $settings_page_hook,
111
+ 'main'
112
+ );
113
+
114
+ if ( $post_type_archive && $tsf->get_public_post_type_archives() )
115
+ \add_meta_box(
116
+ 'autodescription-post-type-archive-settings',
117
+ \esc_html__( 'Post Type Archive Settings', 'autodescription' ),
118
+ "$class::_post_type_archive_metabox",
119
+ $settings_page_hook,
120
+ 'main'
121
  );
122
 
123
  // Social Meta Box
125
  \add_meta_box(
126
  'autodescription-social-settings',
127
  \esc_html__( 'Social Meta Settings', 'autodescription' ),
128
+ "$class::_social_metabox",
129
  $settings_page_hook,
130
+ 'main'
 
131
  );
132
 
133
  // Schema Meta Box
135
  \add_meta_box(
136
  'autodescription-schema-settings',
137
  \esc_html__( 'Schema.org Settings', 'autodescription' ),
138
+ "$class::_schema_metabox",
139
  $settings_page_hook,
140
+ 'main'
 
141
  );
142
 
143
  // Robots Meta Box
145
  \add_meta_box(
146
  'autodescription-robots-settings',
147
  \esc_html__( 'Robots Meta Settings', 'autodescription' ),
148
+ "$class::_robots_metabox",
149
  $settings_page_hook,
150
+ 'main'
 
151
  );
152
 
153
  // Webmaster Meta Box
155
  \add_meta_box(
156
  'autodescription-webmaster-settings',
157
  \esc_html__( 'Webmaster Meta Settings', 'autodescription' ),
158
+ "$class::_webmaster_metabox",
159
  $settings_page_hook,
160
+ 'main'
 
161
  );
162
 
163
  // Sitemaps Meta Box
165
  \add_meta_box(
166
  'autodescription-sitemap-settings',
167
  \esc_html__( 'Sitemap Settings', 'autodescription' ),
168
+ "$class::_sitemaps_metabox",
169
  $settings_page_hook,
170
+ 'main'
 
171
  );
172
 
173
  // Feed Meta Box
175
  \add_meta_box(
176
  'autodescription-feed-settings',
177
  \esc_html__( 'Feed Settings', 'autodescription' ),
178
+ "$class::_feed_metabox",
179
  $settings_page_hook,
180
+ 'main'
 
181
  );
182
  }
183
 
201
  * @param bool $use_tabs Whether to output tabs, only works when $tabs count is greater than 1.
202
  */
203
  public static function _nav_tab_wrapper( $id, $tabs = [], $use_tabs = true ) { // phpcs:ignore,VariableAnalysis
204
+ $tsf = \tsf();
205
+ $tsf->get_view( 'settings/wrap-nav', get_defined_vars() );
206
+ $tsf->get_view( 'settings/wrap-content', get_defined_vars() );
207
  }
208
 
209
  /**
217
  * @since 3.0.0
218
  */
219
  \do_action( 'the_seo_framework_pre_seo_settings' );
220
+ \tsf()->get_view( 'settings/wrap' );
221
  /**
222
  * @since 3.0.0
223
  */
231
  * @access private
232
  */
233
  public static function _output_columns() {
234
+ \tsf()->get_view( 'settings/columns' );
235
  }
236
 
237
  /**
239
  *
240
  * @since 4.0.0
241
  * @access private
 
 
 
242
  */
243
+ public static function _general_metabox() {
244
  /**
245
  * @since 2.8.0
246
  */
247
  \do_action( 'the_seo_framework_general_metabox_before' );
248
+ \tsf()->get_view( 'settings/metaboxes/general' );
249
  /**
250
  * @since 2.8.0
251
  */
260
  * @see static::general_metabox() : Callback for General Settings box.
261
  */
262
  public static function _general_metabox_general_tab() {
263
+ \tsf()->get_view( 'settings/metaboxes/general', [], 'general_tab' );
264
  }
265
 
266
  /**
271
  * @see static::general_metabox() : Callback for General Settings box.
272
  */
273
  public static function _general_metabox_layout_tab() {
274
+ \tsf()->get_view( 'settings/metaboxes/general', [], 'layout_tab' );
275
  }
276
 
277
  /**
282
  * @see static::general_metabox() : Callback for General Settings box.
283
  */
284
  public static function _general_metabox_performance_tab() {
285
+ \tsf()->get_view( 'settings/metaboxes/general', [], 'performance_tab' );
286
  }
287
 
288
  /**
293
  * @see static::general_metabox() : Callback for General Settings box.
294
  */
295
  public static function _general_metabox_canonical_tab() {
296
+ \tsf()->get_view( 'settings/metaboxes/general', [], 'canonical_tab' );
297
  }
298
 
299
  /**
304
  * @see static::general_metabox() : Callback for General Settings box.
305
  */
306
  public static function _general_metabox_timestamps_tab() {
307
+ \tsf()->get_view( 'settings/metaboxes/general', [], 'timestamps_tab' );
308
  }
309
 
310
  /**
315
  * @see static::general_metabox() : Callback for General Settings box.
316
  */
317
  public static function _general_metabox_exclusions_tab() {
318
+ \tsf()->get_view( 'settings/metaboxes/general', [], 'exclusions_tab' );
319
  }
320
 
321
  /**
323
  *
324
  * @since 4.0.0
325
  * @access private
 
 
 
326
  */
327
+ public static function _title_metabox() {
328
  /**
329
  * @since 2.5.0 or earlier.
330
  */
331
  \do_action( 'the_seo_framework_title_metabox_before' );
332
+ \tsf()->get_view( 'settings/metaboxes/title' );
333
  /**
334
  * @since 2.5.0 or earlier.
335
  */
344
  * @see static::title_metabox() : Callback for Title Settings box.
345
  */
346
  public static function _title_metabox_general_tab() {
347
+ \tsf()->get_view( 'settings/metaboxes/title', [], 'general_tab' );
348
  }
349
 
350
  /**
357
  * @param array $args The variables to pass to the metabox tab.
358
  */
359
  public static function _title_metabox_additions_tab( $args ) {
360
+ \tsf()->get_view( 'settings/metaboxes/title', $args, 'additions_tab' );
361
  }
362
 
363
  /**
370
  * @param array $args The variables to pass to the metabox tab.
371
  */
372
  public static function _title_metabox_prefixes_tab( $args ) {
373
+ \tsf()->get_view( 'settings/metaboxes/title', $args, 'prefixes_tab' );
374
  }
375
 
376
  /**
378
  *
379
  * @since 4.0.0
380
  * @access private
 
 
 
381
  */
382
+ public static function _description_metabox() {
383
  /**
384
  * @since 2.5.0 or earlier.
385
  */
386
  \do_action( 'the_seo_framework_description_metabox_before' );
387
+ \tsf()->get_view( 'settings/metaboxes/description' );
388
  /**
389
  * @since 2.5.0 or earlier.
390
  */
396
  *
397
  * @since 4.0.0
398
  * @access private
 
 
 
399
  */
400
+ public static function _robots_metabox() {
401
  /**
402
  * @since 2.5.0 or earlier.
403
  */
404
  \do_action( 'the_seo_framework_robots_metabox_before' );
405
+ \tsf()->get_view( 'settings/metaboxes/robots' );
406
  /**
407
  * @since 2.5.0 or earlier.
408
  */
410
  }
411
 
412
  /**
413
+ * Robots Meta Box General Tab output.
414
  *
415
  * @since 4.0.0
416
  * @access private
417
  * @see static::robots_metabox() Callback for Robots Settings box.
418
  */
419
  public static function _robots_metabox_general_tab() {
420
+ \tsf()->get_view( 'settings/metaboxes/robots', [], 'general_tab' );
421
  }
422
 
423
  /**
424
+ * Robots Meta Box "No-: Index/Follow/Archive" Tab output.
425
  *
426
  * @since 4.0.0
427
  * @access private
430
  * @param array $args The variables to pass to the metabox tab.
431
  */
432
  public static function _robots_metabox_no_tab( $args ) {
433
+ \tsf()->get_view( 'settings/metaboxes/robots', $args, 'no_tab' );
434
  }
435
 
436
  /**
438
  *
439
  * @since 4.0.0
440
  * @access private
 
 
 
441
  */
442
+ public static function _homepage_metabox() {
443
  /**
444
  * @since 2.5.0 or earlier.
445
  */
446
  \do_action( 'the_seo_framework_homepage_metabox_before' );
447
+ \tsf()->get_view( 'settings/metaboxes/homepage' );
448
  /**
449
  * @since 2.5.0 or earlier.
450
  */
452
  }
453
 
454
  /**
455
+ * Homepage meta box General Tab Output.
456
  *
457
  * @since 4.0.0
458
  * @access private
459
  * @see static::homepage_metabox() Callback for Homepage Settings box.
460
  */
461
  public static function _homepage_metabox_general_tab() {
462
+ \tsf()->get_view( 'settings/metaboxes/homepage', [], 'general_tab' );
463
  }
464
 
465
  /**
466
+ * Homepage meta box Additions Tab Output.
467
  *
468
  * @since 4.0.0
469
  * @access private
470
  * @see static::homepage_metabox() Callback for Homepage Settings box.
471
  */
472
  public static function _homepage_metabox_additions_tab() {
473
+ \tsf()->get_view( 'settings/metaboxes/homepage', [], 'additions_tab' );
474
  }
475
 
476
  /**
477
+ * Homepage meta box Robots Tab Output
478
  *
479
  * @since 4.0.0
480
  * @access private
481
  * @see static::homepage_metabox() Callback for Homepage Settings box.
482
  */
483
  public static function _homepage_metabox_robots_tab() {
484
+ \tsf()->get_view( 'settings/metaboxes/homepage', [], 'robots_tab' );
485
  }
486
 
487
  /**
488
+ * Homepage meta box Social Tab Output
489
  *
490
  * @since 4.0.0
491
  * @access private
492
  * @see static::homepage_metabox() Callback for Homepage Settings box.
493
  */
494
  public static function _homepage_metabox_social_tab() {
495
+ \tsf()->get_view( 'settings/metaboxes/homepage', [], 'social_tab' );
496
+ }
497
+
498
+ /**
499
+ * Post Type Archive meta box on the Site SEO Settings page.
500
+ *
501
+ * @since 4.2.0
502
+ * @access private
503
+ */
504
+ public static function _post_type_archive_metabox() {
505
+ /**
506
+ * @since 4.2.0
507
+ */
508
+ \do_action( 'the_seo_framework_post_type_archive_before' );
509
+ \tsf()->get_view( 'settings/metaboxes/post-type-archive' );
510
+ /**
511
+ * @since 4.2.0
512
+ */
513
+ \do_action( 'the_seo_framework_post_type_archive_after' );
514
+ }
515
+
516
+ /**
517
+ * Social Meta Box General Tab output.
518
+ *
519
+ * @since 4.2.0
520
+ * @access private
521
+ *
522
+ * @param array $args The variables to pass to the metabox tab.
523
+ */
524
+ public static function _post_type_archive_metabox_general_tab( $args ) {
525
+ \tsf()->get_view( 'settings/metaboxes/post-type-archive', $args, 'general_tab' );
526
+ }
527
+
528
+ /**
529
+ * Post Type Archive meta box on the Site SEO Settings page.
530
+ *
531
+ * @since 4.2.0
532
+ * @access private
533
+ *
534
+ * @param array $args The variables to pass to the metabox tab.
535
+ */
536
+ public static function _post_type_archive_metabox_social_tab( $args ) {
537
+ \tsf()->get_view( 'settings/metaboxes/post-type-archive', $args, 'social_tab' );
538
+ }
539
+
540
+ /**
541
+ * Post Type Archive meta box on the Site SEO Settings page.
542
+ *
543
+ * @since 4.2.0
544
+ * @access private
545
+ *
546
+ * @param array $args The variables to pass to the metabox tab.
547
+ */
548
+ public static function _post_type_archive_metabox_visibility_tab( $args ) {
549
+ \tsf()->get_view( 'settings/metaboxes/post-type-archive', $args, 'visibility_tab' );
550
  }
551
 
552
  /**
554
  *
555
  * @since 4.0.0
556
  * @access private
 
 
 
557
  */
558
+ public static function _social_metabox() {
559
  /**
560
  * @since 2.5.0 or earlier.
561
  */
562
  \do_action( 'the_seo_framework_social_metabox_before' );
563
+ \tsf()->get_view( 'settings/metaboxes/social' );
564
  /**
565
  * @since 2.5.0 or earlier.
566
  */
568
  }
569
 
570
  /**
571
+ * Social Meta Box General Tab output.
572
  *
573
  * @since 4.0.0
574
  * @access private
575
  * @see static::social_metabox() Callback for Social Settings box.
576
  */
577
  public static function _social_metabox_general_tab() {
578
+ \tsf()->get_view( 'settings/metaboxes/social', [], 'general_tab' );
579
  }
580
 
581
  /**
582
+ * Social Meta Box Facebook Tab output.
583
  *
584
  * @since 4.0.0
585
  * @access private
586
  * @see static::social_metabox() Callback for Social Settings box.
587
  */
588
  public static function _social_metabox_facebook_tab() {
589
+ \tsf()->get_view( 'settings/metaboxes/social', [], 'facebook_tab' );
590
  }
591
 
592
  /**
593
+ * Social Meta Box Twitter Tab output.
594
  *
595
  * @since 4.0.0
596
  * @access private
597
  * @see static::social_metabox() Callback for Social Settings box.
598
  */
599
  public static function _social_metabox_twitter_tab() {
600
+ \tsf()->get_view( 'settings/metaboxes/social', [], 'twitter_tab' );
601
  }
602
 
603
  /**
604
+ * Social Meta Box oEmbed Tab output.
605
  *
606
  * @since 4.0.5
607
  * @access private
608
  * @see static::social_metabox() Callback for Social Settings box.
609
  */
610
  public static function _social_metabox_oembed_tab() {
611
+ \tsf()->get_view( 'settings/metaboxes/social', [], 'oembed_tab' );
612
  }
613
 
614
  /**
615
+ * Social Meta Box PostDates Tab output.
616
  *
617
  * @since 4.0.0
618
  * @access private
619
  * @see static::social_metabox() Callback for Social Settings box.
620
  */
621
  public static function _social_metabox_postdates_tab() {
622
+ \tsf()->get_view( 'settings/metaboxes/social', [], 'postdates_tab' );
623
  }
624
 
625
  /**
627
  *
628
  * @since 4.0.0
629
  * @access private
 
 
 
630
  */
631
+ public static function _webmaster_metabox() {
632
  /**
633
  * @since 2.5.0 or earlier.
634
  */
635
  \do_action( 'the_seo_framework_webmaster_metabox_before' );
636
+ \tsf()->get_view( 'settings/metaboxes/webmaster' );
637
  /**
638
  * @since 2.5.0 or earlier.
639
  */
646
  * @since 4.0.0
647
  * @access private
648
  * @see static::sitemaps_metabox() Callback for Sitemaps Settings box.
 
 
 
649
  */
650
+ public static function _sitemaps_metabox() {
651
  /**
652
  * @since 2.5.0 or earlier.
653
  */
654
  \do_action( 'the_seo_framework_sitemaps_metabox_before' );
655
+ \tsf()->get_view( 'settings/metaboxes/sitemaps' );
656
  /**
657
  * @since 2.5.0 or earlier.
658
  */
660
  }
661
 
662
  /**
663
+ * Sitemaps Meta Box General Tab output.
664
  *
665
  * @since 4.0.0
666
  * @access private
667
  * @see static::sitemaps_metabox() Callback for Sitemaps Settings box.
668
  */
669
  public static function _sitemaps_metabox_general_tab() {
670
+ \tsf()->get_view( 'settings/metaboxes/sitemaps', [], 'general_tab' );
671
  }
672
 
673
  /**
674
+ * Sitemaps Meta Box Robots Tab output.
675
  *
676
  * @since 4.0.0
677
  * @access private
678
  * @see static::sitemaps_metabox() Callback for Sitemaps Settings box.
679
  */
680
  public static function _sitemaps_metabox_robots_tab() {
681
+ \tsf()->get_view( 'settings/metaboxes/sitemaps', [], 'robots_tab' );
682
  }
683
 
684
  /**
685
+ * Sitemaps Meta Box Metadata Tab output.
686
  *
687
  * @since 4.0.0
688
  * @access private
689
  * @see static::sitemaps_metabox() Callback for Sitemaps Settings box.
690
  */
691
  public static function _sitemaps_metabox_metadata_tab() {
692
+ \tsf()->get_view( 'settings/metaboxes/sitemaps', [], 'metadata_tab' );
693
  }
694
 
695
  /**
696
+ * Sitemaps Meta Box Notify Tab output.
697
  *
698
  * @since 4.0.0
699
  * @access private
700
  * @see static::sitemaps_metabox() Callback for Sitemaps Settings box.
701
  */
702
  public static function _sitemaps_metabox_notify_tab() {
703
+ \tsf()->get_view( 'settings/metaboxes/sitemaps', [], 'notify_tab' );
704
  }
705
 
706
  /**
707
+ * Sitemaps Meta Box Style Tab output.
708
  *
709
  * @since 4.0.0
710
  * @access private
711
  * @see static::sitemaps_metabox() Callback for Sitemaps Settings box.
712
  */
713
  public static function _sitemaps_metabox_style_tab() {
714
+ \tsf()->get_view( 'settings/metaboxes/sitemaps', [], 'style_tab' );
715
  }
716
 
717
  /**
718
+ * Feed meta box on the Site SEO Settings page.
719
  *
720
  * @since 4.0.0
721
  * @access private
 
 
 
722
  */
723
+ public static function _feed_metabox() {
724
  /**
725
  * @since 2.5.2
726
  */
727
  \do_action( 'the_seo_framework_feed_metabox_before' );
728
+ \tsf()->get_view( 'settings/metaboxes/feed' );
729
  /**
730
  * @since 2.5.2
731
  */
733
  }
734
 
735
  /**
736
+ * Schema meta box on the Site SEO Settings page.
737
  *
738
  * @since 4.0.0
739
  * @access private
 
 
 
740
  */
741
+ public static function _schema_metabox() {
742
  /**
743
  * @since 2.6.0
744
  */
745
  \do_action( 'the_seo_framework_schema_metabox_before' );
746
+ \tsf()->get_view( 'settings/metaboxes/schema' );
747
  /**
748
  * @since 2.6.0
749
  */
751
  }
752
 
753
  /**
754
+ * Schema Meta Box Structure Tab output.
755
  *
756
  * @since 4.0.0
757
  * @access private
758
  * @see static::schema_metabox() Callback for Schema.org Settings box.
759
  */
760
  public static function _schema_metabox_structure_tab() {
761
+ \tsf()->get_view( 'settings/metaboxes/schema', [], 'structure_tab' );
762
  }
763
 
764
  /**
765
+ * Schema Meta Box Presence Tab output.
766
  *
767
  * @since 4.0.0
768
  * @access private
769
  * @see static::schema_metabox() Callback for Schema.org Settings box.
770
  */
771
  public static function _schema_metabox_presence_tab() {
772
+ \tsf()->get_view( 'settings/metaboxes/schema', [], 'presence_tab' );
773
  }
774
  }
inc/classes/bridges/sitemap.class.php CHANGED
@@ -25,16 +25,9 @@ namespace The_SEO_Framework\Bridges;
25
 
26
  \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
 
28
- /**
29
- * Sets up class loader as file is loaded.
30
- * This is done asynchronously, because static calls are handled prior and after.
31
- *
32
- * @see EOF. Because of the autoloader and (future) trait calling, we can't do it before the class is read.
33
- * @link https://bugs.php.net/bug.php?id=75771
34
- */
35
- $_load_sitemap_class = function() {
36
- // phpcs:ignore, TSF.Performance.Opcodes.ShouldHaveNamespaceEscape
37
- new Sitemap();
38
  };
39
 
40
  /**
@@ -65,7 +58,7 @@ final class Sitemap {
65
  * @return \The_SEO_Framework\Bridges\Sitemap $instance
66
  */
67
  public static function get_instance() {
68
- return static::$instance;
69
  }
70
 
71
  /**
@@ -76,7 +69,9 @@ final class Sitemap {
76
  *
77
  * @since 4.0.0
78
  */
79
- public static function prepare() {}
 
 
80
 
81
  /**
82
  * The constructor. Can't be instantiated externally from this file.
@@ -93,8 +88,7 @@ final class Sitemap {
93
  static $count = 0;
94
  0 === $count++ or \wp_die( 'Don\'t instance <code>' . __CLASS__ . '</code>.' );
95
 
96
- static::$tsf = \the_seo_framework();
97
- static::$instance = &$this;
98
  }
99
 
100
  /**
@@ -188,52 +182,53 @@ final class Sitemap {
188
  * @return array The sitemap endpoints with their callbacks.
189
  */
190
  public function get_sitemap_endpoint_list() {
191
- static $list;
192
- /**
193
- * @since 4.0.0
194
- * @since 4.0.2 Made the endpoints' regex case-insensitive.
195
- * @link Example: https://github.com/sybrew/tsf-term-sitemap
196
- * @param array $list The endpoints: {
197
- * 'id' => array: {
198
- * 'cache_id' => string Optional. The cache key to use for locking. Defaults to index 'id'.
199
- * 'endpoint' => string The expected "pretty" endpoint, meant for administrative display.
200
- * 'epregex' => string The endpoint regex, following the home path regex.
201
- * N.B. Be wary of case sensitivity. Append the i-flag.
202
- * N.B. Trailing slashes will cause the match to fail.
203
- * N.B. Use ASCII-endpoints only. Don't play with UTF-8 or translation strings.
204
- * 'callback' => callable The callback for the sitemap output.
205
- * Tip: You can pass arbitrary indexes. Prefix them with an underscore to ensure forward compatibility.
206
- * Tip: In the callback, use
207
- * `\The_SEO_Framework\Bridges\Sitemap::get_instance()->get_sitemap_endpoint_list()[$sitemap_id]`
208
- * It returns the arguments you've passed in this filter; including your arbitrary indexes.
209
- * 'robots' => bool Whether the endpoint should be mentioned in the robots.txt file.
210
- * }
211
- * }
212
- */
213
- return $list = $list ?: \apply_filters(
214
- 'the_seo_framework_sitemap_endpoint_list',
215
- [
216
- 'base' => [
217
- 'lock_id' => 'base',
218
- 'endpoint' => 'sitemap.xml',
219
- 'regex' => '/^sitemap\.xml/i',
220
- 'callback' => static::class . '::output_base_sitemap',
221
- 'robots' => true,
222
- ],
223
- 'index' => [
224
- 'lock_id' => 'base',
225
- 'endpoint' => 'sitemap_index.xml',
226
- 'regex' => '/^sitemap_index\.xml/i',
227
- 'callback' => static::class . '::output_base_sitemap',
228
- 'robots' => false,
229
- ],
230
- 'xsl-stylesheet' => [
231
- 'endpoint' => 'sitemap.xsl',
232
- 'regex' => '/^sitemap\.xsl/i',
233
- 'callback' => static::class . '::output_stylesheet',
234
- 'robots' => false,
235
- ],
236
- ]
 
237
  );
238
  }
239
 
@@ -289,7 +284,7 @@ final class Sitemap {
289
 
290
  if ( ! isset( $ep_list[ $sitemap_id ] ) ) return false;
291
 
292
- $lock_id = isset( $ep_list[ $sitemap_id ]['lock_id'] ) ? $ep_list[ $sitemap_id ]['lock_id'] : $sitemap_id;
293
 
294
  return static::$tsf->generate_cache_key( 0, '', 'sitemap_lock' ) . "_{$lock_id}";
295
  }
@@ -409,6 +404,8 @@ final class Sitemap {
409
  header( 'Cache-Control: max-age=1800', true );
410
  }
411
 
 
 
412
  static::$tsf->get_view( 'sitemap/xsl-stylesheet' );
413
  exit;
414
  }
@@ -455,20 +452,15 @@ final class Sitemap {
455
  */
456
  $schemas = (array) \apply_filters( 'the_seo_framework_sitemap_schemas', $schemas );
457
 
458
- $urlset = '<urlset';
459
- foreach ( $schemas as $type => $values ) {
460
- $urlset .= ' ' . $type . '="';
461
- if ( \is_array( $values ) ) {
462
- $urlset .= implode( ' ', $values );
463
- } else {
464
- $urlset .= $values;
465
  }
466
- $urlset .= '"';
467
- }
468
- $urlset .= '>';
469
 
470
- // phpcs:ignore, WordPress.Security.EscapeOutput -- Output is static from filter.
471
- echo $urlset . "\n";
472
  }
473
 
474
  /**
@@ -495,7 +487,13 @@ final class Sitemap {
495
  */
496
  return \apply_filters(
497
  'the_seo_framework_sitemap_base_path',
498
- rtrim( parse_url( \get_home_url(), PHP_URL_PATH ), '/' )
 
 
 
 
 
 
499
  );
500
  }
501
 
@@ -624,16 +622,14 @@ final class Sitemap {
624
  * @since 2.8.0 Renamed from clean_up_globals().
625
  * @since 4.0.0 1. Moved to \The_SEO_Framework\Bridges\Sitemap
626
  * 2. Renamed from clean_up_globals_for_sitemap()
 
627
  *
628
  * @param bool $get_freed_memory Whether to return the freed memory in bytes.
629
  * @return int $freed_memory in bytes
630
  */
631
  private function clean_up_globals( $get_freed_memory = false ) {
632
 
633
- static $freed_memory = null;
634
-
635
- if ( $get_freed_memory )
636
- return $freed_memory;
637
 
638
  $memory = memory_get_usage();
639
 
@@ -667,8 +663,6 @@ final class Sitemap {
667
  // This one requires to be an array for wp_texturize(). There's an API, let's use it:
668
  \remove_all_shortcodes();
669
 
670
- $freed_memory = $memory - memory_get_usage();
671
  }
672
  }
673
-
674
- $_load_sitemap_class();
25
 
26
  \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
 
28
+ use function \The_SEO_Framework\{
29
+ memo,
30
+ umemo,
 
 
 
 
 
 
 
31
  };
32
 
33
  /**
58
  * @return \The_SEO_Framework\Bridges\Sitemap $instance
59
  */
60
  public static function get_instance() {
61
+ return static::$instance ?? ( static::$instance = new static );
62
  }
63
 
64
  /**
69
  *
70
  * @since 4.0.0
71
  */
72
+ public static function prepare() {
73
+ static::get_instance();
74
+ }
75
 
76
  /**
77
  * The constructor. Can't be instantiated externally from this file.
88
  static $count = 0;
89
  0 === $count++ or \wp_die( 'Don\'t instance <code>' . __CLASS__ . '</code>.' );
90
 
91
+ static::$tsf = \tsf();
 
92
  }
93
 
94
  /**
182
  * @return array The sitemap endpoints with their callbacks.
183
  */
184
  public function get_sitemap_endpoint_list() {
185
+ return memo() ?? memo(
186
+ /**
187
+ * @since 4.0.0
188
+ * @since 4.0.2 Made the endpoints' regex case-insensitive.
189
+ * @link Example: https://github.com/sybrew/tsf-term-sitemap
190
+ * @param array $list The endpoints: {
191
+ * 'id' => array: {
192
+ * 'cache_id' => string Optional. The cache key to use for locking. Defaults to index 'id'.
193
+ * 'endpoint' => string The expected "pretty" endpoint, meant for administrative display.
194
+ * 'epregex' => string The endpoint regex, following the home path regex.
195
+ * N.B. Be wary of case sensitivity. Append the i-flag.
196
+ * N.B. Trailing slashes will cause the match to fail.
197
+ * N.B. Use ASCII-endpoints only. Don't play with UTF-8 or translation strings.
198
+ * 'callback' => callable The callback for the sitemap output.
199
+ * Tip: You can pass arbitrary indexes. Prefix them with an underscore to ensure forward compatibility.
200
+ * Tip: In the callback, use
201
+ * `\The_SEO_Framework\Bridges\Sitemap::get_instance()->get_sitemap_endpoint_list()[$sitemap_id]`
202
+ * It returns the arguments you've passed in this filter; including your arbitrary indexes.
203
+ * 'robots' => bool Whether the endpoint should be mentioned in the robots.txt file.
204
+ * }
205
+ * }
206
+ */
207
+ \apply_filters(
208
+ 'the_seo_framework_sitemap_endpoint_list',
209
+ [
210
+ 'base' => [
211
+ 'lock_id' => 'base',
212
+ 'endpoint' => 'sitemap.xml',
213
+ 'regex' => '/^sitemap\.xml/i',
214
+ 'callback' => [ static::class, 'output_base_sitemap' ],
215
+ 'robots' => true,
216
+ ],
217
+ 'index' => [
218
+ 'lock_id' => 'base',
219
+ 'endpoint' => 'sitemap_index.xml',
220
+ 'regex' => '/^sitemap_index\.xml/i',
221
+ 'callback' => [ static::class, 'output_base_sitemap' ],
222
+ 'robots' => false,
223
+ ],
224
+ 'xsl-stylesheet' => [
225
+ 'endpoint' => 'sitemap.xsl',
226
+ 'regex' => '/^sitemap\.xsl/i',
227
+ 'callback' => [ static::class, 'output_stylesheet' ],
228
+ 'robots' => false,
229
+ ],
230
+ ]
231
+ )
232
  );
233
  }
234
 
284
 
285
  if ( ! isset( $ep_list[ $sitemap_id ] ) ) return false;
286
 
287
+ $lock_id = $ep_list[ $sitemap_id ]['lock_id'] ?? $sitemap_id;
288
 
289
  return static::$tsf->generate_cache_key( 0, '', 'sitemap_lock' ) . "_{$lock_id}";
290
  }
404
  header( 'Cache-Control: max-age=1800', true );
405
  }
406
 
407
+ \The_SEO_Framework\Interpreters\Sitemap_XSL::prepare();
408
+
409
  static::$tsf->get_view( 'sitemap/xsl-stylesheet' );
410
  exit;
411
  }
452
  */
453
  $schemas = (array) \apply_filters( 'the_seo_framework_sitemap_schemas', $schemas );
454
 
455
+ array_walk(
456
+ $schemas,
457
+ static function( &$schema, $key ) {
458
+ $schema = sprintf( '%s="%s"', $key, implode( ' ', (array) $schema ) );
 
 
 
459
  }
460
+ );
 
 
461
 
462
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- Output is expected to be escaped.
463
+ printf( "<urlset %s>\n", implode( ' ', $schemas ) );
464
  }
465
 
466
  /**
487
  */
488
  return \apply_filters(
489
  'the_seo_framework_sitemap_base_path',
490
+ rtrim(
491
+ parse_url(
492
+ static::$tsf->get_home_url(),
493
+ PHP_URL_PATH
494
+ ),
495
+ '/'
496
+ )
497
  );
498
  }
499
 
622
  * @since 2.8.0 Renamed from clean_up_globals().
623
  * @since 4.0.0 1. Moved to \The_SEO_Framework\Bridges\Sitemap
624
  * 2. Renamed from clean_up_globals_for_sitemap()
625
+ * @since 4.2.0 Now always returns the freed memory.
626
  *
627
  * @param bool $get_freed_memory Whether to return the freed memory in bytes.
628
  * @return int $freed_memory in bytes
629
  */
630
  private function clean_up_globals( $get_freed_memory = false ) {
631
 
632
+ if ( $get_freed_memory ) return memo() ?? 0;
 
 
 
633
 
634
  $memory = memory_get_usage();
635
 
663
  // This one requires to be an array for wp_texturize(). There's an API, let's use it:
664
  \remove_all_shortcodes();
665
 
666
+ return memo( $memory - memory_get_usage() );
667
  }
668
  }
 
 
inc/classes/bridges/termsettings.class.php CHANGED
@@ -60,7 +60,7 @@ final class TermSettings {
60
  * @since 2.9.0
61
  */
62
  \do_action( 'the_seo_framework_pre_tt_inpost_box' );
63
- \the_seo_framework()->get_view( 'edit/seo-settings-tt', get_defined_vars() );
64
  /**
65
  * @since 2.9.0
66
  */
60
  * @since 2.9.0
61
  */
62
  \do_action( 'the_seo_framework_pre_tt_inpost_box' );
63
+ \tsf()->get_view( 'edit/seo-settings-tt', get_defined_vars() );
64
  /**
65
  * @since 2.9.0
66
  */
inc/classes/bridges/usersettings.class.php CHANGED
@@ -43,7 +43,7 @@ final class UserSettings {
43
  *
44
  * @param \WP_User $user WP_User object.
45
  */
46
- public static function _prepare_setting_fields( \WP_User $user ) {
47
 
48
  if ( ! $user->has_cap( THE_SEO_FRAMEWORK_AUTHOR_INFO_CAP ) ) return;
49
 
@@ -57,12 +57,12 @@ final class UserSettings {
57
  *
58
  * @param \WP_User $user WP_User object.
59
  */
60
- private static function add_user_author_fields( \WP_User $user ) {
61
  /**
62
  * @since 4.1.4
63
  */
64
  \do_action( 'the_seo_framework_before_author_fields' );
65
- \the_seo_framework()->get_view( 'profile/author', get_defined_vars() );
66
  /**
67
  * @since 4.1.4
68
  */
43
  *
44
  * @param \WP_User $user WP_User object.
45
  */
46
+ public static function _prepare_setting_fields( $user ) {
47
 
48
  if ( ! $user->has_cap( THE_SEO_FRAMEWORK_AUTHOR_INFO_CAP ) ) return;
49
 
57
  *
58
  * @param \WP_User $user WP_User object.
59
  */
60
+ private static function add_user_author_fields( $user ) { // phpcs:ignore, VariableAnalysis.CodeAnalysis.VariableAnalysis -- get_defined_vars() is used later.
61
  /**
62
  * @since 4.1.4
63
  */
64
  \do_action( 'the_seo_framework_before_author_fields' );
65
+ \tsf()->get_view( 'profile/author', get_defined_vars() );
66
  /**
67
  * @since 4.1.4
68
  */
inc/classes/builders/coresitemaps/main.class.php CHANGED
@@ -32,11 +32,11 @@ namespace The_SEO_Framework\Builders\CoreSitemaps;
32
  *
33
  * @access private
34
  */
35
- class Main extends \The_SEO_Framework\Builders\Sitemap {
36
 
37
  /**
38
  * @since 4.1.2
39
- * @var \The_SEO_Framework\Builders\Sitemap
40
  */
41
  private static $instance;
42
 
@@ -45,7 +45,7 @@ class Main extends \The_SEO_Framework\Builders\Sitemap {
45
  *
46
  * @since 4.1.2
47
  *
48
- * @return \The_SEO_Framework\Builders\Sitemap $instance
49
  */
50
  public static function get_instance() {
51
  return static::$instance ?: ( static::$instance = new static );
@@ -77,9 +77,8 @@ class Main extends \The_SEO_Framework\Builders\Sitemap {
77
  */
78
  public static function _filter_add_provider( $provider, $name ) {
79
 
80
- if ( ! $provider instanceof \WP_Sitemaps_Provider ) {
81
  return $provider;
82
- }
83
 
84
  switch ( $name ) {
85
  case 'posts':
@@ -91,10 +90,9 @@ class Main extends \The_SEO_Framework\Builders\Sitemap {
91
  case 'users':
92
  // This option is not reversible through means other than filters.
93
  // static::$tsf isn't set, because static doesn't require instantiation here.
94
- if ( \the_seo_framework()->get_option( 'author_noindex' ) )
95
  $provider = null;
96
  break;
97
-
98
  default:
99
  break;
100
  }
32
  *
33
  * @access private
34
  */
35
+ class Main extends \The_SEO_Framework\Builders\Sitemap\Main {
36
 
37
  /**
38
  * @since 4.1.2
39
+ * @var \The_SEO_Framework\Builders\Sitemap\Main
40
  */
41
  private static $instance;
42
 
45
  *
46
  * @since 4.1.2
47
  *
48
+ * @return \The_SEO_Framework\Builders\Sitemap\Main $instance
49
  */
50
  public static function get_instance() {
51
  return static::$instance ?: ( static::$instance = new static );
77
  */
78
  public static function _filter_add_provider( $provider, $name ) {
79
 
80
+ if ( ! $provider instanceof \WP_Sitemaps_Provider )
81
  return $provider;
 
82
 
83
  switch ( $name ) {
84
  case 'posts':
90
  case 'users':
91
  // This option is not reversible through means other than filters.
92
  // static::$tsf isn't set, because static doesn't require instantiation here.
93
+ if ( \tsf()->get_option( 'author_noindex' ) )
94
  $provider = null;
95
  break;
 
96
  default:
97
  break;
98
  }
inc/classes/builders/coresitemaps/posts.class.php CHANGED
@@ -40,21 +40,26 @@ class Posts extends \WP_Sitemaps_Posts {
40
  * Copied from parent and augmented slightly to return
41
  *
42
  * @since 4.1.2
 
 
43
  * @source \WP_Sitemaps_Posts\get_url_list()
44
  * @TEMP https://wordpress.slack.com/archives/CTKTGNJJW/p1604995479019700
45
  * @link <https://core.trac.wordpress.org/ticket/51860>
 
46
  *
47
- * @param int $page_num Page of results.
48
- * @param string $post_type Optional. Post type name. Default empty.
49
  * @return array Array of URLs for a sitemap.
50
  */
51
- public function get_url_list( $page_num, $post_type = '' ) {
 
 
 
52
  // Bail early if the queried post type is not supported.
53
  $supported_types = $this->get_object_subtypes();
54
 
55
- if ( ! isset( $supported_types[ $post_type ] ) ) {
56
  return [];
57
- }
58
 
59
  /**
60
  * Filters the posts URL list before it is generated.
@@ -75,9 +80,8 @@ class Posts extends \WP_Sitemaps_Posts {
75
  $page_num
76
  );
77
 
78
- if ( null !== $url_list ) {
79
  return $url_list;
80
- }
81
 
82
  $args = $this->get_posts_query_args( $post_type );
83
  $args['paged'] = $page_num;
@@ -89,7 +93,7 @@ class Posts extends \WP_Sitemaps_Posts {
89
  /**
90
  * @augmented This differs from the inherented.
91
  */
92
- $tsf = \the_seo_framework();
93
  $main = Main::get_instance();
94
  $show_modified = (bool) $tsf->get_option( 'sitemaps_modified' );
95
  $timestamp_format = $tsf->get_timestamp_format();
@@ -122,16 +126,19 @@ class Posts extends \WP_Sitemaps_Posts {
122
  'order' => 'DESC',
123
  'offset' => 0,
124
  ],
125
- \OBJECT
126
  );
127
 
128
- $lastmod = isset( $latests_posts[0]->post_date_gmt ) ? $latests_posts[0]->post_date_gmt : '0000-00-00 00:00:00';
129
-
130
  /**
131
  * @since 4.1.1
132
  * @param string $lastmod The lastmod time in SQL notation (`Y-m-d H:i:s`). Expected to explicitly follow that format!
133
  */
134
- $sitemap_entry['lastmod'] = (string) \apply_filters( 'the_seo_framework_sitemap_blog_lastmod', $lastmod );
 
 
 
 
 
135
  }
136
 
137
  /**
@@ -146,7 +153,7 @@ class Posts extends \WP_Sitemaps_Posts {
146
  }
147
  }
148
 
149
- foreach ( $query->posts as $post ) {
150
  /**
151
  * @augmented This if-statement prevents including the post in the sitemap when conditions apply.
152
  */
@@ -161,9 +168,9 @@ class Posts extends \WP_Sitemaps_Posts {
161
  * @augmented Adds lastmod to sitemap entry.
162
  */
163
  if ( $show_modified ) {
164
- $lastmod = isset( $post->post_modified_gmt ) ? $post->post_modified_gmt : false;
165
 
166
- if ( $lastmod && '0000-00-00 00:00:00' !== $lastmod )
167
  $sitemap_entry['lastmod'] = $tsf->gmt2date( $timestamp_format, $lastmod );
168
  }
169
 
@@ -178,7 +185,7 @@ class Posts extends \WP_Sitemaps_Posts {
178
  */
179
  $sitemap_entry = \apply_filters( 'wp_sitemaps_posts_entry', $sitemap_entry, $post, $post_type );
180
  $url_list[] = $sitemap_entry;
181
- }
182
 
183
  return $url_list;
184
  }
40
  * Copied from parent and augmented slightly to return
41
  *
42
  * @since 4.1.2
43
+ * @since 4.2.0 Renamed `$post_type` to `$object_subtype` to match parent class
44
+ * for PHP 8 named parameter support. (Backport WP 5.9)
45
  * @source \WP_Sitemaps_Posts\get_url_list()
46
  * @TEMP https://wordpress.slack.com/archives/CTKTGNJJW/p1604995479019700
47
  * @link <https://core.trac.wordpress.org/ticket/51860>
48
+ * @link <https://core.trac.wordpress.org/changeset/51787>
49
  *
50
+ * @param int $page_num Page of results.
51
+ * @param string $object_subtype Optional. Post type name. Default empty.
52
  * @return array Array of URLs for a sitemap.
53
  */
54
+ public function get_url_list( $page_num, $object_subtype = '' ) {
55
+ // Restores the more descriptive, specific name for use within this method.
56
+ $post_type = $object_subtype;
57
+
58
  // Bail early if the queried post type is not supported.
59
  $supported_types = $this->get_object_subtypes();
60
 
61
+ if ( ! isset( $supported_types[ $post_type ] ) )
62
  return [];
 
63
 
64
  /**
65
  * Filters the posts URL list before it is generated.
80
  $page_num
81
  );
82
 
83
+ if ( null !== $url_list )
84
  return $url_list;
 
85
 
86
  $args = $this->get_posts_query_args( $post_type );
87
  $args['paged'] = $page_num;
93
  /**
94
  * @augmented This differs from the inherented.
95
  */
96
+ $tsf = \tsf();
97
  $main = Main::get_instance();
98
  $show_modified = (bool) $tsf->get_option( 'sitemaps_modified' );
99
  $timestamp_format = $tsf->get_timestamp_format();
126
  'order' => 'DESC',
127
  'offset' => 0,
128
  ],
129
+ OBJECT
130
  );
131
 
 
 
132
  /**
133
  * @since 4.1.1
134
  * @param string $lastmod The lastmod time in SQL notation (`Y-m-d H:i:s`). Expected to explicitly follow that format!
135
  */
136
+ $sitemap_entry['lastmod'] = (string) \apply_filters_ref_array(
137
+ 'the_seo_framework_sitemap_blog_lastmod',
138
+ [
139
+ $latests_posts[0]->post_date_gmt ?? '0000-00-00 00:00:00',
140
+ ]
141
+ );
142
  }
143
 
144
  /**
153
  }
154
  }
155
 
156
+ foreach ( $query->posts as $post ) :
157
  /**
158
  * @augmented This if-statement prevents including the post in the sitemap when conditions apply.
159
  */
168
  * @augmented Adds lastmod to sitemap entry.
169
  */
170
  if ( $show_modified ) {
171
+ $lastmod = $post->post_modified_gmt ?? '0000-00-00 00:00:00';
172
 
173
+ if ( '0000-00-00 00:00:00' !== $lastmod )
174
  $sitemap_entry['lastmod'] = $tsf->gmt2date( $timestamp_format, $lastmod );
175
  }
176
 
185
  */
186
  $sitemap_entry = \apply_filters( 'wp_sitemaps_posts_entry', $sitemap_entry, $post, $post_type );
187
  $url_list[] = $sitemap_entry;
188
+ endforeach;
189
 
190
  return $url_list;
191
  }
inc/classes/builders/coresitemaps/taxonomies.class.php CHANGED
@@ -38,22 +38,25 @@ class Taxonomies extends \WP_Sitemaps_Taxonomies {
38
  * Gets a URL list for a taxonomy sitemap.
39
  *
40
  * @since 4.1.2
 
 
41
  * @source \WP_Sitemaps_Taxonomies\get_url_list()
42
  * @TEMP https://wordpress.slack.com/archives/CTKTGNJJW/p1604995479019700
43
  * @link <https://core.trac.wordpress.org/ticket/51860>
 
44
  *
45
- * @param int $page_num Page of results.
46
- * @param string $taxonomy Optional. Taxonomy name. Default empty.
47
  * @return array Array of URLs for a sitemap.
48
  */
49
- public function get_url_list( $page_num, $taxonomy = '' ) {
50
-
 
51
  $supported_types = $this->get_object_subtypes();
52
 
53
  // Bail early if the queried taxonomy is not supported.
54
- if ( ! isset( $supported_types[ $taxonomy ] ) ) {
55
  return [];
56
- }
57
 
58
  /**
59
  * Filters the taxonomies URL list before it is generated.
@@ -74,9 +77,8 @@ class Taxonomies extends \WP_Sitemaps_Taxonomies {
74
  $page_num
75
  );
76
 
77
- if ( null !== $url_list ) {
78
  return $url_list;
79
- }
80
 
81
  $url_list = [];
82
 
@@ -90,37 +92,34 @@ class Taxonomies extends \WP_Sitemaps_Taxonomies {
90
 
91
  $taxonomy_terms = new \WP_Term_Query( $args );
92
 
93
- if ( ! empty( $taxonomy_terms->terms ) ) {
94
- foreach ( $taxonomy_terms->terms as $term ) {
95
- /**
96
- * @augmented This if-statement prevents including the term in the sitemap when conditions apply.
97
- */
98
- if ( ! $main->is_term_included_in_sitemap( $term, $taxonomy ) )
99
- continue;
100
-
101
- $term_link = \get_term_link( $term, $taxonomy );
102
-
103
- if ( \is_wp_error( $term_link ) ) {
104
- continue;
105
- }
106
-
107
- $sitemap_entry = [
108
- 'loc' => $term_link,
109
- ];
110
-
111
- /**
112
- * Filters the sitemap entry for an individual term.
113
- *
114
- * @since WP Core 5.5.0
115
- *
116
- * @param array $sitemap_entry Sitemap entry for the term.
117
- * @param WP_Term $term Term object.
118
- * @param string $taxonomy Taxonomy name.
119
- */
120
- $sitemap_entry = \apply_filters( 'wp_sitemaps_taxonomies_entry', $sitemap_entry, $term, $taxonomy );
121
- $url_list[] = $sitemap_entry;
122
- }
123
- }
124
 
125
  return $url_list;
126
  }
38
  * Gets a URL list for a taxonomy sitemap.
39
  *
40
  * @since 4.1.2
41
+ * @since 4.2.0 Renamed `$taxonomy` to `$object_subtype` to match parent class
42
+ * for PHP 8 named parameter support. (Backport WP 5.9)
43
  * @source \WP_Sitemaps_Taxonomies\get_url_list()
44
  * @TEMP https://wordpress.slack.com/archives/CTKTGNJJW/p1604995479019700
45
  * @link <https://core.trac.wordpress.org/ticket/51860>
46
+ * @link <https://core.trac.wordpress.org/changeset/51787>
47
  *
48
+ * @param int $page_num Page of results.
49
+ * @param string $object_subtype Optional. Taxonomy name. Default empty.
50
  * @return array Array of URLs for a sitemap.
51
  */
52
+ public function get_url_list( $page_num, $object_subtype = '' ) {
53
+ // Restores the more descriptive, specific name for use within this method.
54
+ $taxonomy = $object_subtype;
55
  $supported_types = $this->get_object_subtypes();
56
 
57
  // Bail early if the queried taxonomy is not supported.
58
+ if ( ! isset( $supported_types[ $taxonomy ] ) )
59
  return [];
 
60
 
61
  /**
62
  * Filters the taxonomies URL list before it is generated.
77
  $page_num
78
  );
79
 
80
+ if ( null !== $url_list )
81
  return $url_list;
 
82
 
83
  $url_list = [];
84
 
92
 
93
  $taxonomy_terms = new \WP_Term_Query( $args );
94
 
95
+ foreach ( $taxonomy_terms->terms ?? [] as $term ) :
96
+ /**
97
+ * @augmented This if-statement prevents including the term in the sitemap when conditions apply.
98
+ */
99
+ if ( ! $main->is_term_included_in_sitemap( $term, $taxonomy ) )
100
+ continue;
101
+
102
+ $term_link = \get_term_link( $term, $taxonomy );
103
+
104
+ if ( \is_wp_error( $term_link ) )
105
+ continue;
106
+
107
+ $sitemap_entry = [
108
+ 'loc' => $term_link,
109
+ ];
110
+
111
+ /**
112
+ * Filters the sitemap entry for an individual term.
113
+ *
114
+ * @since WP Core 5.5.0
115
+ *
116
+ * @param array $sitemap_entry Sitemap entry for the term.
117
+ * @param WP_Term $term Term object.
118
+ * @param string $taxonomy Taxonomy name.
119
+ */
120
+ $sitemap_entry = \apply_filters( 'wp_sitemaps_taxonomies_entry', $sitemap_entry, $term, $taxonomy );
121
+ $url_list[] = $sitemap_entry;
122
+ endforeach;
 
 
 
123
 
124
  return $url_list;
125
  }
inc/classes/builders/images.class.php CHANGED
@@ -37,7 +37,7 @@ final class Images {
37
  * @internal
38
  * @var int MAX_CONTENT_IMAGES The maximum number of images to get from the content.
39
  */
40
- const MAX_CONTENT_IMAGES = 5;
41
 
42
  /**
43
  * The constructor. Or rather, the lack thereof.
@@ -52,7 +52,7 @@ final class Images {
52
  * @since 4.0.0
53
  * @generator
54
  *
55
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
56
  * Leave null to autodetermine query.
57
  * @param string $size The size of the image to get.
58
  * @yield array : {
@@ -62,7 +62,7 @@ final class Images {
62
  */
63
  public static function get_attachment_image_details( $args = null, $size = 'full' ) {
64
 
65
- $id = isset( $args['id'] ) ? $args['id'] : \the_seo_framework()->get_the_real_ID();
66
 
67
  if ( $id ) {
68
  yield [
@@ -83,7 +83,7 @@ final class Images {
83
  * @since 4.0.0
84
  * @generator
85
  *
86
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
87
  * Leave null to autodetermine query.
88
  * @param string $size The size of the image to get.
89
  * @yield array : {
@@ -93,7 +93,7 @@ final class Images {
93
  */
94
  public static function get_featured_image_details( $args = null, $size = 'full' ) {
95
 
96
- $post_id = isset( $args['id'] ) ? $args['id'] : \the_seo_framework()->get_the_real_ID();
97
  $id = \get_post_thumbnail_id( $post_id );
98
 
99
  if ( $id ) {
@@ -115,11 +115,13 @@ final class Images {
115
  * @since 4.0.0
116
  * @since 4.0.5 1. Now strips tags before looking for images.
117
  * 2. Now only yields at most 5 images.
 
 
118
  * @generator
119
  * @TODO consider matching these images with wp-content/uploads items via database calls, which is heavy...
120
  * Combine query, instead of using WP API? Only do that for the first image, instead?
121
  *
122
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
123
  * Leave null to autodetermine query.
124
  * @param string $size The size of the image to get.
125
  * @yield array : {
@@ -129,14 +131,14 @@ final class Images {
129
  */
130
  public static function get_content_image_details( $args = null, $size = 'full' ) {
131
 
132
- $tsf = \the_seo_framework();
133
 
134
  if ( null === $args ) {
135
  if ( $tsf->is_singular() ) {
136
  $content = $tsf->get_post_content();
137
  }
138
  } else {
139
- if ( $args['taxonomy'] ) {
140
  $content = '';
141
  } else {
142
  $content = $tsf->get_post_content( $args['id'] );
@@ -158,7 +160,7 @@ final class Images {
158
  );
159
  // TODO can we somehow limit this search to static::MAX_CONTENT_IMAGES? -> We could, via preg_match(), but the opcodes won't help.
160
  preg_match_all(
161
- '/<img[^>]+src=(\"|\')?([^\"\'>\s]+)\1?.*?>/mi',
162
  $content,
163
  $matches,
164
  PREG_SET_ORDER
@@ -166,7 +168,7 @@ final class Images {
166
  }
167
 
168
  if ( $matches ) {
169
- for ( $i = 0; $i++ < static::MAX_CONTENT_IMAGES; ) {
170
  // Fewer than MAX_CONTENT_IMAGES matched.
171
  if ( ! isset( $matches[ $i ][2] ) ) break;
172
 
@@ -195,7 +197,7 @@ final class Images {
195
  * @since 4.0.0
196
  * @generator
197
  *
198
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
199
  * Leave null to autodetermine query.
200
  * @param string $size The size of the image to get.
201
  * @yield array : {
@@ -205,7 +207,7 @@ final class Images {
205
  */
206
  public static function get_fallback_image_details( $args = null, $size = 'full' ) {
207
 
208
- $tsf = \the_seo_framework();
209
 
210
  yield [
211
  'url' => $tsf->get_option( 'social_image_fb_url' ) ?: '',
@@ -221,7 +223,7 @@ final class Images {
221
  * @since 4.0.0
222
  * @generator
223
  *
224
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
225
  * Leave null to autodetermine query.
226
  * @param string $size The size of the image to get.
227
  * @yield array : {
@@ -252,7 +254,7 @@ final class Images {
252
  * @since 4.0.0
253
  * @generator
254
  *
255
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
256
  * Leave null to autodetermine query.
257
  * @param string $size The size of the image to get.
258
  * @yield array : {
@@ -283,7 +285,7 @@ final class Images {
283
  * @since 4.0.0
284
  * @generator
285
  *
286
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
287
  * Leave null to autodetermine query.
288
  * @param string $size The size of the image to get.
289
  * @yield array : {
37
  * @internal
38
  * @var int MAX_CONTENT_IMAGES The maximum number of images to get from the content.
39
  */
40
+ private const MAX_CONTENT_IMAGES = 5;
41
 
42
  /**
43
  * The constructor. Or rather, the lack thereof.
52
  * @since 4.0.0
53
  * @generator
54
  *
55
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
56
  * Leave null to autodetermine query.
57
  * @param string $size The size of the image to get.
58
  * @yield array : {
62
  */
63
  public static function get_attachment_image_details( $args = null, $size = 'full' ) {
64
 
65
+ $id = $args['id'] ?? \tsf()->get_the_real_ID();
66
 
67
  if ( $id ) {
68
  yield [
83
  * @since 4.0.0
84
  * @generator
85
  *
86
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
87
  * Leave null to autodetermine query.
88
  * @param string $size The size of the image to get.
89
  * @yield array : {
93
  */
94
  public static function get_featured_image_details( $args = null, $size = 'full' ) {
95
 
96
+ $post_id = $args['id'] ?? \tsf()->get_the_real_ID();
97
  $id = \get_post_thumbnail_id( $post_id );
98
 
99
  if ( $id ) {
115
  * @since 4.0.0
116
  * @since 4.0.5 1. Now strips tags before looking for images.
117
  * 2. Now only yields at most 5 images.
118
+ * @since 4.2.0 1. Fixed OB1 error causing the first image to be ignored.
119
+ * 2. Now supports the `$args['pta']` index.
120
  * @generator
121
  * @TODO consider matching these images with wp-content/uploads items via database calls, which is heavy...
122
  * Combine query, instead of using WP API? Only do that for the first image, instead?
123
  *
124
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
125
  * Leave null to autodetermine query.
126
  * @param string $size The size of the image to get.
127
  * @yield array : {
131
  */
132
  public static function get_content_image_details( $args = null, $size = 'full' ) {
133
 
134
+ $tsf = \tsf();
135
 
136
  if ( null === $args ) {
137
  if ( $tsf->is_singular() ) {
138
  $content = $tsf->get_post_content();
139
  }
140
  } else {
141
+ if ( $args['taxonomy'] || $args['pta'] ) {
142
  $content = '';
143
  } else {
144
  $content = $tsf->get_post_content( $args['id'] );
160
  );
161
  // TODO can we somehow limit this search to static::MAX_CONTENT_IMAGES? -> We could, via preg_match(), but the opcodes won't help.
162
  preg_match_all(
163
+ '/<img[^>]+?src=(\"|\')?([^\"\'>\s]+)\1?[^>]*?>/mi',
164
  $content,
165
  $matches,
166
  PREG_SET_ORDER
168
  }
169
 
170
  if ( $matches ) {
171
+ for ( $i = 0; $i < static::MAX_CONTENT_IMAGES; $i++ ) {
172
  // Fewer than MAX_CONTENT_IMAGES matched.
173
  if ( ! isset( $matches[ $i ][2] ) ) break;
174
 
197
  * @since 4.0.0
198
  * @generator
199
  *
200
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
201
  * Leave null to autodetermine query.
202
  * @param string $size The size of the image to get.
203
  * @yield array : {
207
  */
208
  public static function get_fallback_image_details( $args = null, $size = 'full' ) {
209
 
210
+ $tsf = \tsf();
211
 
212
  yield [
213
  'url' => $tsf->get_option( 'social_image_fb_url' ) ?: '',
223
  * @since 4.0.0
224
  * @generator
225
  *
226
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
227
  * Leave null to autodetermine query.
228
  * @param string $size The size of the image to get.
229
  * @yield array : {
254
  * @since 4.0.0
255
  * @generator
256
  *
257
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
258
  * Leave null to autodetermine query.
259
  * @param string $size The size of the image to get.
260
  * @yield array : {
285
  * @since 4.0.0
286
  * @generator
287
  *
288
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
289
  * Leave null to autodetermine query.
290
  * @param string $size The size of the image to get.
291
  * @yield array : {
inc/classes/builders/robots/args.class.php ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Classes\Builders\Robots\Args
4
+ * @subpackage The_SEO_Framework\Getter\Robots
5
+ */
6
+
7
+ namespace The_SEO_Framework\Builders\Robots;
8
+
9
+ /**
10
+ * The SEO Framework plugin
11
+ * Copyright (C) 2021 Sybre Waaijer, CyberWire B.V. (https://cyberwire.nl/)
12
+ *
13
+ * This program is free software: you can redistribute it and/or modify
14
+ * it under the terms of the GNU General Public License version 3 as published
15
+ * by the Free Software Foundation.
16
+ *
17
+ * This program is distributed in the hope that it will be useful,
18
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
+ * GNU General Public License for more details.
21
+ *
22
+ * You should have received a copy of the GNU General Public License
23
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
24
+ */
25
+
26
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
+
28
+ /**
29
+ * Engine for robots generator by arguments.
30
+ *
31
+ * @since 4.2.0
32
+ * @access private
33
+ * Not part of the public API.
34
+ * @final Can't be extended.
35
+ */
36
+ final class Args extends Factory {
37
+
38
+ // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- You don't love PHP7.
39
+ // phpcs:disable, PSR2.ControlStructures.SwitchDeclaration.TerminatingComment -- You hate goto.
40
+ // phpcs:disable, Generic.WhiteSpace.ScopeIndent.IncorrectExact -- You hate gotoo.
41
+ /**
42
+ * Generates robots assertions for no[index|archive|follow].
43
+ *
44
+ * Yields true when "noindex/noarchive/nofollow", yields false when "index/archive/follow".
45
+ *
46
+ * @since 4.2.0
47
+ * @generator
48
+ *
49
+ * @param string $type The robots generator type (noindex, nofollow...).
50
+ */
51
+ protected static function assert_no( $type ) {
52
+
53
+ // Remit FETCH_STATIC_PROP_R opcode calls every time we'd otherwise use static::$tsf/static::$args hereinafter.
54
+ $tsf = static::$tsf;
55
+ $args = static::$args;
56
+
57
+ $asserting_noindex = 'noindex' === $type;
58
+
59
+ meta_settings: {
60
+ // We assert options here for a jump to meta_settings might be unaware.
61
+ if ( static::$options & \The_SEO_Framework\ROBOTS_IGNORE_SETTINGS )
62
+ goto after_meta_settings;
63
+
64
+ $qubit = null;
65
+
66
+ if ( $args['taxonomy'] ) {
67
+ $qubit = (int) $tsf->get_term_meta_item( $type, $args['id'] );
68
+ } elseif ( $args['id'] ) {
69
+ $qubit = (int) $tsf->get_post_meta_item( "_genesis_$type", $args['id'] );
70
+ } elseif ( $args['pta'] ) {
71
+ $qubit = (int) $tsf->get_post_type_archive_meta_item( $type, $args['pta'] );
72
+ }
73
+
74
+ switch ( isset( $qubit ) ) :
75
+ case false:
76
+ // Page doesn't support metadata.
77
+ break;
78
+ case $qubit < -.33:
79
+ // 'Force' index.
80
+ yield 'meta_qubit_force' => false;
81
+ // Override with index protection.
82
+ goto index_protection;
83
+ case $qubit > .33:
84
+ // Force noindex.
85
+ yield 'meta_qubit_force' => true;
86
+ // We won't override this. Terminate generator. "goto end".
87
+ // No break, generator stops here anyway.
88
+ default:
89
+ // qubit is (closer to) 0. Assert we use _default, albeit false.
90
+ yield 'meta_qubit_default' => false;
91
+ endswitch;
92
+ }
93
+ after_meta_settings:;
94
+
95
+ globals: {
96
+ yield 'globals_site' => (bool) $tsf->get_option( "site_$type" );
97
+
98
+ if ( $args['taxonomy'] ) {
99
+ $asserting_noindex and yield from static::assert_noindex_query_pass( '404' );
100
+
101
+ yield 'globals_taxonomy' => $tsf->is_taxonomy_robots_set( $type, $args['taxonomy'] );
102
+
103
+ // Store values from each post type bound to the taxonomy.
104
+ foreach ( $tsf->get_post_types_from_taxonomy( $args['taxonomy'] ) as $post_type )
105
+ $_is_post_type_robots_set[] = $tsf->is_post_type_robots_set( $type, $post_type );
106
+
107
+ // Only enable if _all_ post types have been marked with 'no*'. Return false if no post types are found (corner case).
108
+ yield 'globals_post_type_all' => isset( $_is_post_type_robots_set ) && ! \in_array( false, $_is_post_type_robots_set, true );
109
+ } elseif ( $args['pta'] ) {
110
+ yield 'globals_post_type' => $tsf->is_post_type_robots_set( $type, $args['pta'] );
111
+ } else {
112
+ // $args['id'] can be empty, pointing to a plausible homepage query.
113
+ if ( $tsf->is_real_front_page_by_id( $args['id'] ) )
114
+ yield 'globals_homepage' => (bool) $tsf->get_option( "homepage_$type" );
115
+
116
+ if ( $args['id'] )
117
+ yield 'globals_post_type' => $tsf->is_post_type_robots_set( $type, \get_post_type( $args['id'] ) );
118
+ }
119
+ }
120
+
121
+ index_protection: if ( $asserting_noindex ) {
122
+ // We assert options here for a jump to index_protection might be unaware.
123
+ if ( static::$options & \The_SEO_Framework\ROBOTS_IGNORE_PROTECTION )
124
+ goto after_index_protection;
125
+
126
+ if ( ! $args['taxonomy'] )
127
+ yield from static::assert_noindex_query_pass( 'protected' );
128
+ }
129
+ after_index_protection:;
130
+
131
+ end:;
132
+ }
133
+ // phpcs:enable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
134
+ // phpcs:enable, PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
135
+ // phpcs:disable, Generic.WhiteSpace.ScopeIndent.IncorrectExact
136
+
137
+ /**
138
+ * Generates robots assertions for noindex in passes.
139
+ *
140
+ * @since 4.2.0
141
+ * @generator
142
+ *
143
+ * @param string $pass The passage to assert.
144
+ */
145
+ private static function assert_noindex_query_pass( $pass ) {
146
+
147
+ // Remit FETCH_STATIC_PROP_R opcode calls every time we'd otherwise use static::$tsf/static::$args hereinafter.
148
+ // $tsf = static::$tsf;
149
+ $args = static::$args;
150
+
151
+ switch ( $pass ) :
152
+ case '404':
153
+ yield '404' => empty( \get_term( $args['id'], $args['taxonomy'] )->count );
154
+ break;
155
+
156
+ case 'protected':
157
+ // We get the "real ID" for WordPress might fault parsing a nefariously forged request.
158
+ yield 'protected' => static::$tsf->is_protected( $args['id'] );
159
+ break;
160
+ endswitch;
161
+ }
162
+ }
inc/classes/builders/robots/factory.class.php ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Classes\Builders\Robots\Factory
4
+ * @subpackage The_SEO_Framework\Getter\Robots
5
+ */
6
+
7
+ namespace The_SEO_Framework\Builders\Robots;
8
+
9
+ /**
10
+ * The SEO Framework plugin
11
+ * Copyright (C) 2021 Sybre Waaijer, CyberWire B.V. (https://cyberwire.nl/)
12
+ *
13
+ * This program is free software: you can redistribute it and/or modify
14
+ * it under the terms of the GNU General Public License version 3 as published
15
+ * by the Free Software Foundation.
16
+ *
17
+ * This program is distributed in the hope that it will be useful,
18
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
+ * GNU General Public License for more details.
21
+ *
22
+ * You should have received a copy of the GNU General Public License
23
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
24
+ */
25
+
26
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
+
28
+ /**
29
+ * Factory engine for robots generator.
30
+ *
31
+ * @since 4.2.0
32
+ * @access private
33
+ * Not part of the public API.
34
+ */
35
+ class Factory {
36
+
37
+ /**
38
+ * @since 4.2.0
39
+ * @param int The starter. A unique ID sent to start the generator switcher.
40
+ */
41
+ public const START = 0b01000011011110010110001001110010;
42
+
43
+ /**
44
+ * @since 4.2.0
45
+ * @param int The halter. A unique ID sent to stop the generator switcher.
46
+ */
47
+ public const HALT = 0b01010111011010010111001001100101;
48
+
49
+ /**
50
+ * @since 4.2.0
51
+ * @var \The_SEO_Framework\Load The SEO Framework class.
52
+ */
53
+ protected static $tsf;
54
+
55
+ /**
56
+ * @since 4.2.0
57
+ * @var array|null Null to autodetermine query, otherwise the query arguments. : {
58
+ * int $id The Post, Page or Term ID to generate robots for.
59
+ * string $taxonomy The taxonomy.
60
+ * }
61
+ */
62
+ protected static $args;
63
+
64
+ /**
65
+ * @since 4.2.0
66
+ * @var int Modifies return values/assertions. See const ROBOTS_* at /bootstrap/define.php
67
+ */
68
+ protected static $options;
69
+
70
+ /**
71
+ * Contructor, does nothing but instigate TSF.
72
+ *
73
+ * @since 4.2.0
74
+ */
75
+ public function __construct() {
76
+ static::$tsf = static::$tsf ?? \tsf();
77
+ }
78
+
79
+ /**
80
+ * Sets parameters.
81
+ *
82
+ * @since 4.2.0
83
+ * @access private
84
+ *
85
+ * @param null|array $args The robots meta arguments, leave null to autodetermine query : {
86
+ * int $id The Post, Page or Term ID to generate the URL for.
87
+ * string $taxonomy The taxonomy.
88
+ * }
89
+ * @param int $options Modifies return values/assertions. See const ROBOTS_* at /bootstrap/define.php
90
+ * @return Factory $this
91
+ */
92
+ public function set( $args = null, $options = 0 ) {
93
+ static::$args = $args;
94
+ static::$options = $options;
95
+ return $this;
96
+ }
97
+
98
+ /**
99
+ * Generates robots assertions.
100
+ *
101
+ * @since 4.2.0
102
+ * @access private
103
+ * @generator
104
+ */
105
+ public static function generator() {
106
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition.Found -- Shhh. It's OK.
107
+ while ( true ) switch ( $sender = yield static::START ) :
108
+ case 'noindex':
109
+ case 'nofollow':
110
+ case 'noarchive':
111
+ foreach ( static::assert_no( $sender ) as $key => $value ) {
112
+ yield $key => $value;
113
+ if ( $value ) {
114
+ yield static::HALT;
115
+ break;
116
+ }
117
+ }
118
+ break;
119
+
120
+ case 'max_snippet':
121
+ case 'max_image_preview':
122
+ case 'max_video_preview':
123
+ yield from static::assert_copyright( $sender );
124
+ yield static::HALT;
125
+ break;
126
+
127
+ default:
128
+ static::$tsf->_doing_it_wrong(
129
+ __METHOD__,
130
+ sprintf( 'Unregistered robots-generator getter provided: <code>%s</code>.', \esc_html( $sender ) ),
131
+ '4.2.0'
132
+ );
133
+ yield static::HALT;
134
+ break;
135
+ endswitch;
136
+ }
137
+
138
+ /**
139
+ * Generates robots assertions for copyright options.
140
+ *
141
+ * @since 4.2.0
142
+ * @access private
143
+ * @generator
144
+ *
145
+ * @param string $type The robots generator type (noindex, nofollow...).
146
+ */
147
+ final protected static function assert_copyright( $type ) {
148
+
149
+ // Remit FETCH_STATIC_PROP_R opcode calls every time we'd otherwise use static::$tsf hereinafter.
150
+ $tsf = static::$tsf;
151
+
152
+ $option = $type;
153
+
154
+ if ( 'max_snippet' === $type )
155
+ $option = 'max_snippet_length';
156
+
157
+ $tsf->get_option( 'set_copyright_directives' )
158
+ and yield 'globals_copyright' => $tsf->get_option( $option );
159
+ }
160
+ }
inc/classes/builders/robots/index.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * People aren't that worried about what you're doing or what you're saying,
4
+ * so you can drift around the world relatively anonymously.
5
+ * You must not feel persecuted and examined.
6
+ * Liberate yourself from that idea that people are watching you.
7
+ *
8
+ * - Russell Brand
9
+ */
inc/classes/builders/robots/main.class.php ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Classes\Builders\Robots\Main
4
+ * @subpackage The_SEO_Framework\Getter\Robots
5
+ */
6
+
7
+ namespace The_SEO_Framework\Builders\Robots;
8
+
9
+ /**
10
+ * The SEO Framework plugin
11
+ * Copyright (C) 2021 Sybre Waaijer, CyberWire B.V. (https://cyberwire.nl/)
12
+ *
13
+ * This program is free software: you can redistribute it and/or modify
14
+ * it under the terms of the GNU General Public License version 3 as published
15
+ * by the Free Software Foundation.
16
+ *
17
+ * This program is distributed in the hope that it will be useful,
18
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
+ * GNU General Public License for more details.
21
+ *
22
+ * You should have received a copy of the GNU General Public License
23
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
24
+ */
25
+
26
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
+
28
+ use function \The_SEO_Framework\umemo;
29
+
30
+ /**
31
+ * Generates robots meta.
32
+ *
33
+ * @since 4.2.0
34
+ * @access protected
35
+ * Instantiation of class is not part of the public API.
36
+ * @final Can't be extended.
37
+ */
38
+ final class Main {
39
+
40
+ /**
41
+ * @since 4.2.0
42
+ * @var array|null Null to autodetermine query, otherwise the query arguments. : {
43
+ * int $id The Post, Page or Term ID to generate robots for.
44
+ * string $taxonomy The taxonomy.
45
+ * }
46
+ */
47
+ private $args;
48
+
49
+ /**
50
+ * @since 4.2.0
51
+ * @var int Modifies return values/assertions. See const ROBOTS_* at /bootstrap/define.php
52
+ */
53
+ private $options;
54
+
55
+ /**
56
+ * @since 4.2.0
57
+ * @var Main This instance.
58
+ */
59
+ private static $instance;
60
+
61
+ /**
62
+ * @since 4.2.0
63
+ * @param array List of registered getters.
64
+ */
65
+ private const GETTERS = [
66
+ 'noindex',
67
+ 'nofollow',
68
+ 'noarchive',
69
+ 'max_snippet',
70
+ 'max_image_preview',
71
+ 'max_video_preview',
72
+ ];
73
+
74
+ /**
75
+ * The constructor. Or rather, the lack thereof.
76
+ *
77
+ * @since 4.2.0
78
+ * @access private
79
+ */
80
+ private function __construct() { }
81
+
82
+ /**
83
+ * Creates and returns the instance.
84
+ *
85
+ * @since 4.2.0
86
+ *
87
+ * @return Main
88
+ */
89
+ public static function instance() {
90
+ return static::$instance ?? ( static::$instance = new static );
91
+ }
92
+
93
+ /**
94
+ * Sets class parameters.
95
+ *
96
+ * @since 4.2.0
97
+ * @access private
98
+ *
99
+ * @param null|array $args The robots meta arguments, leave null to autodetermine query : {
100
+ * int $id The Post, Page or Term ID to generate the URL for.
101
+ * string $taxonomy The taxonomy.
102
+ * }
103
+ * @param int $options Modifies return values/assertions. See const ROBOTS_* at /bootstrap/define.php
104
+ * @return Main $this
105
+ */
106
+ public function set( $args = null, $options = 0 ) {
107
+ $this->args = $args;
108
+ $this->options = $options;
109
+ return $this;
110
+ }
111
+
112
+ /**
113
+ * Gets the robots values.
114
+ *
115
+ * @since 4.2.0
116
+ * @access public
117
+ *
118
+ * @param null|array $get The robots types to retrieve. See class constant GETTERS for valid values.
119
+ * @return array The robots-values results. Assert values may be true-esque.
120
+ */
121
+ public function get( $get = null ) {
122
+
123
+ // If this leads to 0 getters, so be it: The dev might've used a deprecated value, which is fine. Continue method.
124
+ $get = ( $get ?? false )
125
+ ? array_intersect( static::GETTERS, $get )
126
+ : static::GETTERS;
127
+
128
+ // Remit FETCH_OBJ_R opcode calls every time we'd otherwise use $this->options hereinafter.
129
+ $options = $this->options;
130
+
131
+ $options & \The_SEO_Framework\ROBOTS_ASSERT
132
+ and $this->reset_assertions();
133
+
134
+ $factory = $this->get_factory();
135
+ $halt = $factory::HALT;
136
+ $start = $factory::START;
137
+ $generator = $factory->set(
138
+ $this->args,
139
+ $options
140
+ )->generator();
141
+
142
+ $results = [];
143
+
144
+ foreach ( $get as $g ) {
145
+ $generator->send( $g );
146
+
147
+ do {
148
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition.Found -- Shhh. It's OK. I'm a professional.
149
+ if ( ( $r = $generator->current() ) === $halt ) continue; // goto while() -- motivating generator.
150
+
151
+ $results[ $g ] = $r;
152
+
153
+ $options & \The_SEO_Framework\ROBOTS_ASSERT
154
+ and $this->store_assertion( $g, $generator->key(), $r );
155
+ // We could send anything, really. But this is the only method that loops and yields at the same time.
156
+ } while ( $start !== $generator->send( true ) );
157
+ }
158
+
159
+ return $results;
160
+ }
161
+
162
+ /**
163
+ * Returns the robots factory. Factory changes depending on input arguments.
164
+ *
165
+ * @since 4.2.0
166
+ * @factory
167
+ *
168
+ * @return The_SEO_Framework\Builders\Robots\<Args|Query>
169
+ */
170
+ private function get_factory() {
171
+ return umemo( __METHOD__, null, isset( $this->args ) )
172
+ ?? umemo(
173
+ __METHOD__,
174
+ isset( $this->args ) ? new Args : new Query,
175
+ isset( $this->args )
176
+ );
177
+ }
178
+
179
+ /**
180
+ * Captures and returns the robots-assertions.
181
+ *
182
+ * @since 4.2.0
183
+ * @access public
184
+ *
185
+ * @collector
186
+ * @access protected
187
+ * Do not call this method by reference. Only use it to read the return value.
188
+ * @return array The collected assertions. Returned by reference.
189
+ */
190
+ public function &collect_assertions() {
191
+ static $collection = [];
192
+ return $collection;
193
+ }
194
+
195
+ /**
196
+ * Stores the robots-assertions.
197
+ *
198
+ * @since 4.2.0
199
+ * @see $this->collect_assertions()
200
+ *
201
+ * @param string $get The robots type getter name (noindex, nofollow...).
202
+ * @param string $assertion The assertion name (is_404, no_posts);
203
+ * @param string $result The assertion's result.
204
+ */
205
+ private function store_assertion( $get, $assertion, $result ) {
206
+ $this->collect_assertions()[ $get ][ $assertion ] = $result;
207
+ }
208
+
209
+ /**
210
+ * Resets the robots-assertions.
211
+ *
212
+ * @since 4.2.0
213
+ * @see $this->collect_assertions()
214
+ */
215
+ private function reset_assertions() {
216
+ // phpcs:ignore, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- No function by reference support?
217
+ $collection = &$this->collect_assertions();
218
+ $collection = [];
219
+ }
220
+ }
inc/classes/builders/robots/query.class.php ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Classes\Builders\Robots\Query
4
+ * @subpackage The_SEO_Framework\Getter\Robots
5
+ */
6
+
7
+ namespace The_SEO_Framework\Builders\Robots;
8
+
9
+ /**
10
+ * The SEO Framework plugin
11
+ * Copyright (C) 2021 Sybre Waaijer, CyberWire B.V. (https://cyberwire.nl/)
12
+ *
13
+ * This program is free software: you can redistribute it and/or modify
14
+ * it under the terms of the GNU General Public License version 3 as published
15
+ * by the Free Software Foundation.
16
+ *
17
+ * This program is distributed in the hope that it will be useful,
18
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
+ * GNU General Public License for more details.
21
+ *
22
+ * You should have received a copy of the GNU General Public License
23
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
24
+ */
25
+
26
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
+
28
+ /**
29
+ * Engine for robots generator via query.
30
+ *
31
+ * @since 4.2.0
32
+ * @access private
33
+ * Not part of the public API.
34
+ * @final Can't be extended.
35
+ */
36
+ final class Query extends Factory {
37
+
38
+ // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- You don't love PHP7.
39
+ // phpcs:disable, PSR2.ControlStructures.SwitchDeclaration.TerminatingComment -- You hate goto.
40
+ // phpcs:disable, Generic.WhiteSpace.ScopeIndent.IncorrectExact -- You hate gotoo.
41
+ /**
42
+ * Generates robots assertions for no[index|archive|follow].
43
+ *
44
+ * Yields true when "noindex/noarchive/nofollow", yields false when "index/archive/follow".
45
+ *
46
+ * @since 4.2.0
47
+ * @generator
48
+ *
49
+ * @param string $type The robots generator type (noindex, nofollow...).
50
+ */
51
+ protected static function assert_no( $type ) {
52
+
53
+ // Remit FETCH_STATIC_PROP_R opcode calls every time we'd otherwise use static::$tsf hereinafter.
54
+ $tsf = static::$tsf;
55
+
56
+ $asserting_noindex = 'noindex' === $type;
57
+
58
+ meta_settings: {
59
+ // We assert options here for a jump to meta_settings might be unaware.
60
+ if ( static::$options & \The_SEO_Framework\ROBOTS_IGNORE_SETTINGS )
61
+ goto after_meta_settings;
62
+
63
+ $qubit = null;
64
+
65
+ if ( $tsf->is_term_meta_capable() ) {
66
+ $qubit = (int) $tsf->get_term_meta_item( $type );
67
+ } elseif ( $tsf->is_singular() ) {
68
+ $qubit = (int) $tsf->get_post_meta_item( "_genesis_$type" );
69
+ } elseif ( \is_post_type_archive() ) {
70
+ $qubit = (int) $tsf->get_post_type_archive_meta_item( $type );
71
+ }
72
+
73
+ switch ( isset( $qubit ) ) :
74
+ case false:
75
+ // Page doesn't support metadata.
76
+ break;
77
+ case $qubit < -.33:
78
+ // 'Force' index.
79
+ yield 'meta_qubit_force' => false;
80
+ // Override with index protection.
81
+ goto index_protection;
82
+ case $qubit > .33:
83
+ // Force noindex.
84
+ yield 'meta_qubit_force' => true;
85
+ // We won't override this. Terminate generator. "goto end".
86
+ // No break, generator stops here anyway.
87
+ default:
88
+ // qubit is (closer to) 0. Assert we use _default, albeit false.
89
+ yield 'meta_qubit_default' => false;
90
+ endswitch;
91
+ }
92
+ after_meta_settings:;
93
+
94
+ globals: {
95
+ yield 'globals_site' => (bool) $tsf->get_option( "site_$type" );
96
+
97
+ if ( $tsf->is_real_front_page() ) {
98
+ yield 'globals_homepage' => (bool) $tsf->get_option( "homepage_$type" );
99
+
100
+ if ( ! ( static::$options & \The_SEO_Framework\ROBOTS_IGNORE_PROTECTION ) )
101
+ $asserting_noindex and yield from static::assert_noindex_query_pass( 'paged_home' );
102
+ } else {
103
+ $asserting_noindex and yield from static::assert_noindex_query_pass( '404' );
104
+
105
+ if ( ! ( static::$options & \The_SEO_Framework\ROBOTS_IGNORE_PROTECTION ) )
106
+ if ( $asserting_noindex && ( $tsf->is_archive() || $tsf->is_singular_archive() ) )
107
+ yield from static::assert_noindex_query_pass( 'paged' );
108
+
109
+ if ( $tsf->is_archive() ) {
110
+ if ( $tsf->is_author() ) {
111
+ yield 'globals_author' => (bool) $tsf->get_option( "author_$type" );
112
+ } elseif ( $tsf->is_date() ) {
113
+ yield 'globals_date' => (bool) $tsf->get_option( "date_$type" );
114
+ }
115
+ } elseif ( $tsf->is_search() ) {
116
+ yield 'globals_search' => (bool) $tsf->get_option( "search_$type" );
117
+ }
118
+ }
119
+
120
+ // is_real_front_page() can still be singular or archive. Thus, this conditional block is split up.
121
+ if ( $tsf->is_archive() ) {
122
+ if ( $tsf->is_category() || $tsf->is_tag() || $tsf->is_tax() ) {
123
+ yield 'globals_taxonomy' => $tsf->is_taxonomy_robots_set( $type, $tsf->get_current_taxonomy() );
124
+
125
+ // Store values from each post type bound to the taxonomy.
126
+ foreach ( $tsf->get_post_types_from_taxonomy() as $post_type )
127
+ $_is_post_type_robots_set[] = $tsf->is_post_type_robots_set( $type, $post_type );
128
+
129
+ // Only enable if _all_ post types have been marked with 'no*'. Return false if no post types are found (corner case).
130
+ yield 'globals_post_type_all' => isset( $_is_post_type_robots_set ) && ! \in_array( false, $_is_post_type_robots_set, true );
131
+ } elseif ( \is_post_type_archive() ) {
132
+ yield 'globals_post_type' => $tsf->is_post_type_robots_set( $type, $tsf->get_current_post_type() );
133
+ }
134
+ } elseif ( $tsf->is_singular() ) {
135
+ yield 'globals_post_type' => $tsf->is_post_type_robots_set( $type, $tsf->get_current_post_type() );
136
+ }
137
+ }
138
+
139
+ index_protection: if ( $asserting_noindex ) {
140
+ // We assert options here for a jump to index_protection might be unaware.
141
+ if ( static::$options & \The_SEO_Framework\ROBOTS_IGNORE_PROTECTION )
142
+ goto after_index_protection;
143
+
144
+ if ( $tsf->is_singular() ) {
145
+ // A reiteration of the very same code as above... but, homepage may not always be singular.
146
+ // The conditions below MUST overwrite this, too. So, this is the perfect placement.
147
+ if ( $tsf->is_real_front_page() )
148
+ yield from static::assert_noindex_query_pass( 'paged_home' );
149
+
150
+ yield from static::assert_noindex_query_pass( 'protected' );
151
+
152
+ /**
153
+ * N.B. WordPress protects this query variable with options 'page_comments'
154
+ * and 'default_comments_page' via `redirect_canonical()`, so we don't have to.
155
+ * For reference, it fires `remove_query_arg( 'cpage', $redirect['query'] )`;
156
+ */
157
+ if ( (int) \get_query_var( 'cpage', 0 ) > 0 )
158
+ yield from static::assert_noindex_query_pass( 'cpage' );
159
+ }
160
+ }
161
+ after_index_protection:;
162
+
163
+ exploit_protection: if ( $tsf->is_query_exploited() ) {
164
+ if ( \in_array( $type, [ 'noindex', 'nofollow' ], true ) )
165
+ yield 'query_protection' => true;
166
+ }
167
+ after_exploit_protection:;
168
+
169
+ end:;
170
+ }
171
+ // phpcs:enable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
172
+ // phpcs:enable, PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
173
+ // phpcs:enable, Generic.WhiteSpace.ScopeIndent.IncorrectExact
174
+
175
+ /**
176
+ * Generates robots assertions for noindex in passes.
177
+ *
178
+ * @since 4.2.0
179
+ * @generator
180
+ *
181
+ * @param string $pass The passage to assert.
182
+ */
183
+ private static function assert_noindex_query_pass( $pass ) {
184
+ // Remit FETCH_STATIC_PROP_R opcode calls every time we'd otherwise use static::$tsf hereinafter.
185
+ $tsf = static::$tsf;
186
+
187
+ switch ( $pass ) :
188
+ case 'paged_home':
189
+ yield 'paged_home' => ( $tsf->get_option( 'home_paged_noindex' ) && ( $tsf->page() > 1 || $tsf->paged() > 1 ) );
190
+ break;
191
+
192
+ case '404':
193
+ if ( $tsf->is_singular_archive() ) :
194
+ /**
195
+ * Pagination overflow protection via 404 test.
196
+ *
197
+ * When there are no posts, the first page will NOT relay 404;
198
+ * which is exactly as intended. All other pages will relay 404.
199
+ *
200
+ * We do not test the post_count here, because we want to have
201
+ * the first page indexable via user-intent only. Concordingly, too
202
+ * because we cannot assert this via the administrative dashboard.
203
+ */
204
+ yield '404' => $tsf->is_404();
205
+ else :
206
+ /**
207
+ * Check for 404, or if archive is empty: set noindex for those.
208
+ *
209
+ * Don't check this on the homepage. The homepage is sacred in this regard,
210
+ * because page builders and templates can and will take over.
211
+ *
212
+ * Don't use empty(), null is regarded as indexable; it's why we coalesce to true whence null.
213
+ *
214
+ * post_count can be 0, which is false -> thus yield true -> noindex.
215
+ * post_count can be null, which is true -> thus yield false -> index.
216
+ * post count can be 5, which is true => thus yield false -> index.
217
+ */
218
+ if ( $GLOBALS['wp_query']->post_count ?? true ) {
219
+ yield '404' => false;
220
+ } else {
221
+ /**
222
+ * We recommend using this filter ONLY for archives that have useful content but no "posts" attached.
223
+ * For example: a specially custom-developed author page for an author that never published a post.
224
+ *
225
+ * This filter won't run when a few other conditions for noindex have been met.
226
+ *
227
+ * @since 4.1.4
228
+ * @link <https://github.com/sybrew/the-seo-framework/issues/194#issuecomment-864298702>
229
+ * @param bool $noindex Whether to enable no posts protection.
230
+ */
231
+ yield '404' => (bool) \apply_filters( 'the_seo_framework_enable_noindex_no_posts', true );
232
+ }
233
+ endif;
234
+ break;
235
+
236
+ case 'paged':
237
+ // Advanced Query Protection protects further against pagination attacks. No need to have that here.
238
+ yield 'paged' => $tsf->get_option( 'paged_noindex' ) && $tsf->paged() > 1;
239
+ break;
240
+
241
+ case 'protected':
242
+ // We get the "real ID" for WordPress might fault parsing a nefariously forged request.
243
+ yield 'protected' => $tsf->is_protected( $tsf->get_the_real_ID() );
244
+ break;
245
+
246
+ case 'cpage':
247
+ /**
248
+ * We do not recommend using this filter as it'll likely get those pages flagged as
249
+ * duplicated by Google anyway; unless the theme strips or trims the content.
250
+ *
251
+ * This filter won't run when other conditions for noindex have been met.
252
+ *
253
+ * @since 4.0.5
254
+ * @param bool $noindex Whether to enable comment pagination protection.
255
+ */
256
+ yield 'cpage' => \apply_filters( 'the_seo_framework_enable_noindex_comment_pagination', true );
257
+ break;
258
+ endswitch;
259
+ }
260
+ }
inc/classes/builders/scripts.class.php CHANGED
@@ -25,17 +25,7 @@ namespace The_SEO_Framework\Builders;
25
 
26
  \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
 
28
- /**
29
- * Sets up class loader as file is loaded.
30
- * This is done asynchronously, because static calls are handled prior and after.
31
- *
32
- * @see EOF. Because of the autoloader and (future) trait calling, we can't do it before the class is read.
33
- * @link https://bugs.php.net/bug.php?id=75771
34
- */
35
- $_load_scripts_class = function() {
36
- // phpcs:ignore, TSF.Performance.Opcodes.ShouldHaveNamespaceEscape
37
- new Scripts();
38
- };
39
 
40
  /**
41
  * Registers and outputs admin GUI scripts. Auto-invokes everything the moment
@@ -63,8 +53,8 @@ final class Scripts {
63
  * @var int <bit 01> REGISTERED
64
  * @var int <bit 10> LOADED (rather, enqueued)
65
  */
66
- const REGISTERED = 0b01;
67
- const LOADED = 0b10;
68
 
69
  /**
70
  * @since 3.1.0
@@ -106,11 +96,12 @@ final class Scripts {
106
  *
107
  * @since 3.1.0
108
  */
109
- public static function prepare() {}
 
 
110
 
111
  /**
112
- * The constructor. Can't be instantiated externally from this file.
113
- * Kills PHP on subsequent duplicated request. Enforces singleton.
114
  *
115
  * This probably autoloads at action "admin_enqueue_scripts", priority "0".
116
  *
@@ -118,19 +109,13 @@ final class Scripts {
118
  * @access private
119
  * @internal
120
  */
121
- public function __construct() {
122
-
123
- static $count = 0;
124
- 0 === $count++ or \wp_die( 'Don\'t instance <code>' . __CLASS__ . '</code>.' );
125
-
126
- static::$instance = &$this;
127
-
128
  // These fail when called in the body.
129
  \add_filter( 'admin_body_class', [ $this, '_add_body_class' ] );
130
  \add_action( 'in_admin_header', [ $this, '_print_tsfjs_script' ] );
131
 
132
- \add_action( 'admin_enqueue_scripts', [ $this, '_prepare_admin_scripts' ], 1 );
133
- \add_action( 'admin_footer', [ $this, '_output_templates' ], 999 );
134
  }
135
 
136
  /**
@@ -144,8 +129,8 @@ final class Scripts {
144
  * @return string
145
  */
146
  public function _add_body_class( $classes ) {
147
- // Add spaces at both sides, because who knows what others do.
148
- return ' tsf-no-js ' . $classes;
149
  }
150
 
151
  /**
@@ -184,7 +169,7 @@ final class Scripts {
184
  * @return int <bit>
185
  */
186
  public static function get_status_of( $id, $type ) {
187
- return isset( static::$queue[ $type ][ $id ] ) ? static::$queue[ $type ][ $id ] : 0b0;
188
  }
189
 
190
  /**
@@ -208,7 +193,7 @@ final class Scripts {
208
 
209
  if ( \The_SEO_Framework\_has_run( __METHOD__ ) ) return;
210
 
211
- \add_action( 'admin_footer', [ static::class, 'enqueue' ], 998 );
212
  }
213
 
214
  /**
@@ -245,7 +230,7 @@ final class Scripts {
245
  * }
246
  * }
247
  */
248
- public static function register( array $script ) {
249
  if ( array_values( $script ) === $script ) {
250
  foreach ( $script as $s ) static::register( $s );
251
  return;
@@ -284,11 +269,10 @@ final class Scripts {
284
 
285
  static::forward_known_script( $id, $type );
286
 
287
- if ( static::get_status_of( $id, $type ) & static::REGISTERED ) {
288
- if ( ! ( static::get_status_of( $id, $type ) & static::LOADED ) ) {
289
- static::load_script( $id, $type );
290
- }
291
- }
292
  }
293
 
294
  /**
@@ -316,7 +300,7 @@ final class Scripts {
316
  * @uses static::egister_script()
317
  */
318
  private function forward_known_scripts() {
319
- //= Register them first to accomodate for dependencies.
320
  foreach ( static::$scripts as $s ) {
321
  if ( static::get_status_of( $s['id'], $s['type'] ) & static::REGISTERED ) continue;
322
  static::forward_script( $s );
@@ -352,7 +336,7 @@ final class Scripts {
352
  *
353
  * @param array $s The script.
354
  */
355
- private static function forward_script( array $s ) {
356
 
357
  $instance = static::$instance;
358
  $registered = false;
@@ -378,7 +362,7 @@ final class Scripts {
378
  if ( $registered ) {
379
  isset( static::$queue[ $s['type'] ][ $s['id'] ] )
380
  and static::$queue[ $s['type'] ][ $s['id'] ] |= static::REGISTERED
381
- or static::$queue[ $s['type'] ][ $s['id'] ] = static::REGISTERED; // phpcs:ignore, WordPress.WhiteSpace
382
  }
383
  }
384
 
@@ -409,7 +393,7 @@ final class Scripts {
409
  if ( $loaded ) {
410
  isset( static::$queue[ $type ][ $id ] )
411
  and static::$queue[ $type ][ $id ] |= static::LOADED
412
- or static::$queue[ $type ][ $id ] = static::LOADED; // phpcs:ignore, WordPress.WhiteSpace
413
  }
414
  }
415
 
@@ -423,12 +407,12 @@ final class Scripts {
423
  * @param array $type Either 'js' or 'css'.
424
  * @return string The file URL.
425
  */
426
- private function generate_file_url( array $script, $type = 'js' ) {
427
 
428
  static $min, $rtl;
429
 
430
  if ( ! isset( $min, $rtl ) ) {
431
- $min = \the_seo_framework()->script_debug ? '' : '.min';
432
  $rtl = \is_rtl() ? '.rtl' : '';
433
  }
434
 
@@ -449,14 +433,16 @@ final class Scripts {
449
  * @since 3.1.0
450
  * @uses $this->convert_color_css()
451
  *
452
- * @param array $styles The styles to add.
453
  * @return string
454
  */
455
- private function create_inline_css( array $styles ) {
456
 
457
  $out = '';
 
458
  foreach ( $styles as $selector => $css ) {
459
- $out .= $selector . '{' . implode( ';', $this->convert_color_css( $css ) ) . '}';
 
460
  }
461
 
462
  return $out;
@@ -467,15 +453,15 @@ final class Scripts {
467
  *
468
  * @since 4.0.0
469
  *
470
- * @param array $scripts The scripts to add.
471
  * @return string
472
  */
473
- private function create_inline_js( array $scripts ) {
474
 
475
  $out = '';
476
- foreach ( $scripts as $script ) {
 
477
  $out .= ";$script";
478
- }
479
 
480
  return $out;
481
  }
@@ -488,7 +474,7 @@ final class Scripts {
488
  * @param array $css The CSS to convert.
489
  * @return array $css
490
  */
491
- private function convert_color_css( array $css ) {
492
 
493
  static $c_ck, $c_cv;
494
  // Memoize the conversion types.
@@ -496,10 +482,10 @@ final class Scripts {
496
  $_scheme = \get_user_option( 'admin_color' ) ?: 'fresh';
497
  $_colors = $GLOBALS['_wp_admin_css_colors'];
498
 
499
- $tsf = \the_seo_framework();
500
 
501
  if (
502
- ! isset( $_colors[ $_scheme ]->colors ) // phpcs:ignore, WordPress.WhiteSpace
503
  || ! \is_array( $_colors[ $_scheme ]->colors )
504
  || \count( $_colors[ $_scheme ]->colors ) < 4 // unexpected scheme, ignore and override.
505
  ) {
@@ -554,15 +540,15 @@ final class Scripts {
554
  * 'args' => array $args. Optional,
555
  * }
556
  */
557
- private function register_template( $id, array $templates ) {
558
- //= Wrap template if it's only one on the base.
559
  if ( isset( $templates['file'] ) )
560
  $templates = [ $templates ];
561
 
562
  foreach ( $templates as $t ) {
563
  static::$templates[ $id ][] = [
564
  $t['file'],
565
- isset( $t['args'] ) ? $t['args'] : [],
566
  ];
567
  }
568
  }
@@ -606,21 +592,19 @@ final class Scripts {
606
  * @since 3.2.4 Enabled entropy to prevent system sleep.
607
  * @uses static::$include_secret
608
  *
609
- * @param string $file The file location.
610
- * @param array $args The registered view arguments.
611
  */
612
- private function output_view( $file, array $args ) {
613
 
614
  foreach ( $args as $_key => $_val )
615
  $$_key = $_val;
616
  unset( $_key, $_val, $args );
617
 
618
- //= Prevents private-includes hijacking.
619
  // phpcs:ignore, VariableAnalysis.CodeAnalysis.VariableAnalysis -- Read the include?
620
  static::$include_secret = $_secret = mt_rand() . uniqid( '', true );
621
  include $file;
622
  static::$include_secret = null;
623
  }
624
  }
625
-
626
- $_load_scripts_class();
25
 
26
  \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
 
28
+ Scripts::prepare();
 
 
 
 
 
 
 
 
 
 
29
 
30
  /**
31
  * Registers and outputs admin GUI scripts. Auto-invokes everything the moment
53
  * @var int <bit 01> REGISTERED
54
  * @var int <bit 10> LOADED (rather, enqueued)
55
  */
56
+ private const REGISTERED = 0b01;
57
+ private const LOADED = 0b10;
58
 
59
  /**
60
  * @since 3.1.0
96
  *
97
  * @since 3.1.0
98
  */
99
+ public static function prepare() {
100
+ static::$instance ?? ( static::$instance = new static );
101
+ }
102
 
103
  /**
104
+ * The constructor.
 
105
  *
106
  * This probably autoloads at action "admin_enqueue_scripts", priority "0".
107
  *
109
  * @access private
110
  * @internal
111
  */
112
+ private function __construct() {
 
 
 
 
 
 
113
  // These fail when called in the body.
114
  \add_filter( 'admin_body_class', [ $this, '_add_body_class' ] );
115
  \add_action( 'in_admin_header', [ $this, '_print_tsfjs_script' ] );
116
 
117
+ \add_action( 'admin_enqueue_scripts', [ $this, '_prepare_admin_scripts' ], 1 ); // Magic number: likely 1 after this is called.
118
+ \add_action( 'admin_footer', [ $this, '_output_templates' ], 999 ); // Magic number: later is less likely to collide?
119
  }
120
 
121
  /**
129
  * @return string
130
  */
131
  public function _add_body_class( $classes ) {
132
+ // Add spaces on both sides, because who knows what others do.
133
+ return " tsf-no-js $classes";
134
  }
135
 
136
  /**
169
  * @return int <bit>
170
  */
171
  public static function get_status_of( $id, $type ) {
172
+ return static::$queue[ $type ][ $id ] ?? 0b0;
173
  }
174
 
175
  /**
193
 
194
  if ( \The_SEO_Framework\_has_run( __METHOD__ ) ) return;
195
 
196
+ \add_action( 'admin_footer', [ static::class, 'enqueue' ], 998 ); // Magic number: 1 before output_templates.
197
  }
198
 
199
  /**
230
  * }
231
  * }
232
  */
233
+ public static function register( $script ) {
234
  if ( array_values( $script ) === $script ) {
235
  foreach ( $script as $s ) static::register( $s );
236
  return;
269
 
270
  static::forward_known_script( $id, $type );
271
 
272
+ $status = static::get_status_of( $id, $type );
273
+
274
+ if ( ( $status & static::REGISTERED ) && ! ( $status & static::LOADED ) )
275
+ static::load_script( $id, $type );
 
276
  }
277
 
278
  /**
300
  * @uses static::egister_script()
301
  */
302
  private function forward_known_scripts() {
303
+ // Register them first to accomodate for dependencies.
304
  foreach ( static::$scripts as $s ) {
305
  if ( static::get_status_of( $s['id'], $s['type'] ) & static::REGISTERED ) continue;
306
  static::forward_script( $s );
336
  *
337
  * @param array $s The script.
338
  */
339
+ private static function forward_script( $s ) {
340
 
341
  $instance = static::$instance;
342
  $registered = false;
362
  if ( $registered ) {
363
  isset( static::$queue[ $s['type'] ][ $s['id'] ] )
364
  and static::$queue[ $s['type'] ][ $s['id'] ] |= static::REGISTERED
365
+ or static::$queue[ $s['type'] ][ $s['id'] ] = static::REGISTERED;
366
  }
367
  }
368
 
393
  if ( $loaded ) {
394
  isset( static::$queue[ $type ][ $id ] )
395
  and static::$queue[ $type ][ $id ] |= static::LOADED
396
+ or static::$queue[ $type ][ $id ] = static::LOADED;
397
  }
398
  }
399
 
407
  * @param array $type Either 'js' or 'css'.
408
  * @return string The file URL.
409
  */
410
+ private function generate_file_url( $script, $type = 'js' ) {
411
 
412
  static $min, $rtl;
413
 
414
  if ( ! isset( $min, $rtl ) ) {
415
+ $min = \tsf()->script_debug ? '' : '.min';
416
  $rtl = \is_rtl() ? '.rtl' : '';
417
  }
418
 
433
  * @since 3.1.0
434
  * @uses $this->convert_color_css()
435
  *
436
+ * @param iterable $styles The styles to add.
437
  * @return string
438
  */
439
+ private function create_inline_css( $styles ) {
440
 
441
  $out = '';
442
+
443
  foreach ( $styles as $selector => $css ) {
444
+ $css = implode( ';', $this->convert_color_css( $css ) );
445
+ $out .= "$selector{$css}";
446
  }
447
 
448
  return $out;
453
  *
454
  * @since 4.0.0
455
  *
456
+ * @param iterable $scripts The scripts to add.
457
  * @return string
458
  */
459
+ private function create_inline_js( $scripts ) {
460
 
461
  $out = '';
462
+
463
+ foreach ( $scripts as $script )
464
  $out .= ";$script";
 
465
 
466
  return $out;
467
  }
474
  * @param array $css The CSS to convert.
475
  * @return array $css
476
  */
477
+ private function convert_color_css( $css ) {
478
 
479
  static $c_ck, $c_cv;
480
  // Memoize the conversion types.
482
  $_scheme = \get_user_option( 'admin_color' ) ?: 'fresh';
483
  $_colors = $GLOBALS['_wp_admin_css_colors'];
484
 
485
+ $tsf = \tsf();
486
 
487
  if (
488
+ ! isset( $_colors[ $_scheme ]->colors )
489
  || ! \is_array( $_colors[ $_scheme ]->colors )
490
  || \count( $_colors[ $_scheme ]->colors ) < 4 // unexpected scheme, ignore and override.
491
  ) {
540
  * 'args' => array $args. Optional,
541
  * }
542
  */
543
+ private function register_template( $id, $templates ) {
544
+ // Wrap template if it's only one on the base.
545
  if ( isset( $templates['file'] ) )
546
  $templates = [ $templates ];
547
 
548
  foreach ( $templates as $t ) {
549
  static::$templates[ $id ][] = [
550
  $t['file'],
551
+ $t['args'] ?? [],
552
  ];
553
  }
554
  }
592
  * @since 3.2.4 Enabled entropy to prevent system sleep.
593
  * @uses static::$include_secret
594
  *
595
+ * @param string $file The file location.
596
+ * @param iterable $args The registered view arguments.
597
  */
598
+ private function output_view( $file, $args ) {
599
 
600
  foreach ( $args as $_key => $_val )
601
  $$_key = $_val;
602
  unset( $_key, $_val, $args );
603
 
604
+ // Prevents private-includes hijacking.
605
  // phpcs:ignore, VariableAnalysis.CodeAnalysis.VariableAnalysis -- Read the include?
606
  static::$include_secret = $_secret = mt_rand() . uniqid( '', true );
607
  include $file;
608
  static::$include_secret = null;
609
  }
610
  }
 
 
inc/classes/builders/seobar/index.php ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Tell me and I forget. Teach me and I remember. Involve me and I learn.
4
+ *
5
+ * - Benjamin Franklin
6
+ */
inc/classes/builders/{seobar.class.php → seobar/main.class.php} RENAMED
@@ -1,10 +1,10 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes\Builders\SeoBar
4
- * @subpackage The_SEO_Framework\SeoBar
5
  */
6
 
7
- namespace The_SEO_Framework\Builders;
8
 
9
  /**
10
  * The SEO Framework plugin
@@ -29,17 +29,18 @@ namespace The_SEO_Framework\Builders;
29
  * Generates the SEO Bar.
30
  *
31
  * @since 4.0.0
 
32
  * Mind the late static binding. We use "self" if the variable is shared between instances.
33
  * We use "static" if the variable isn't shared between instances.
34
  * @link <https://www.php.net/manual/en/language.oop5.late-static-bindings.php>
35
  *
36
  * @access private
37
- * Use \The_SEO_Framework\Interpreters\SeoBar::generate_bar() instead.
38
  * @internal
39
  * @abstract Implements test_{$*}, see property $tests and method `_run_test()` for what * may be.
40
- * @see \The_SEO_Framework\Interpreters\SeoBar
41
  */
42
- abstract class SeoBar {
43
 
44
  /**
45
  * @since 4.0.0
@@ -80,7 +81,7 @@ abstract class SeoBar {
80
  /**
81
  * @since 4.0.0
82
  * Not shared between instances
83
- * @var \The_SEO_Framework\Builders\SeoBar_* $instance The instance.
84
  */
85
  protected static $instance;
86
 
@@ -92,8 +93,7 @@ abstract class SeoBar {
92
  * @since 4.0.0
93
  */
94
  final protected function __construct() {
95
- static::$instance = &$this;
96
- self::$tsf = self::$tsf ?: \the_seo_framework();
97
  $this->prime_cache();
98
  }
99
 
@@ -105,8 +105,7 @@ abstract class SeoBar {
105
  * @return static
106
  */
107
  final public static function get_instance() {
108
- static::$instance instanceof static or new static;
109
- return static::$instance;
110
  }
111
 
112
  /**
@@ -133,17 +132,15 @@ abstract class SeoBar {
133
  * @return mixed|null The cache value. Null on failure.
134
  */
135
  final protected static function get_cache( $key ) {
136
- return isset( self::$cache[ $key ] ) ? self::$cache[ $key ] : null;
137
  }
138
 
139
  /**
140
  * Runs all SEO bar tests.
141
  *
142
- * @since ?.?.?
143
  * @access private
144
  * @generator
145
- * @TODO only available from PHP 7+
146
- * @ignore
147
  *
148
  * @param array $query : {
149
  * int $id : Required. The current post or term ID.
@@ -155,11 +152,9 @@ abstract class SeoBar {
155
  * string $test => array The testing results.
156
  * }
157
  */
158
- // phpcs:disable, Squiz.PHP.CommentedOutCode -- Ignore. PHP 7.0+
159
- // public static function _run_all_tests( array $query ) {
160
- // yield from static::_run_test( static::$tests, $query );
161
- // }
162
- // phpcs:enable, Squiz.PHP.CommentedOutCode
163
 
164
  /**
165
  * Runs one or more SEO bar tests.
@@ -180,7 +175,7 @@ abstract class SeoBar {
180
  * string $test => array $item The SEO Bar compatible results.
181
  * }
182
  */
183
- final public function _run_test( $tests, array $query ) {
184
 
185
  $tests = array_intersect( static::$tests, (array) $tests );
186
 
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Builders\SEOBar\Main
4
+ * @subpackage The_SEO_Framework\SEOBar
5
  */
6
 
7
+ namespace The_SEO_Framework\Builders\SEOBar;
8
 
9
  /**
10
  * The SEO Framework plugin
29
  * Generates the SEO Bar.
30
  *
31
  * @since 4.0.0
32
+ * @since 4.2.0 Renamed to `The_SEO_Framework\Builders\SEOBar\Main` from `The_SEO_Framework\Builders\SeoBar`
33
  * Mind the late static binding. We use "self" if the variable is shared between instances.
34
  * We use "static" if the variable isn't shared between instances.
35
  * @link <https://www.php.net/manual/en/language.oop5.late-static-bindings.php>
36
  *
37
  * @access private
38
+ * Use \The_SEO_Framework\Interpreters\SEOBar::generate_bar() instead.
39
  * @internal
40
  * @abstract Implements test_{$*}, see property $tests and method `_run_test()` for what * may be.
41
+ * @see \The_SEO_Framework\Interpreters\SEOBar
42
  */
43
+ abstract class Main {
44
 
45
  /**
46
  * @since 4.0.0
81
  /**
82
  * @since 4.0.0
83
  * Not shared between instances
84
+ * @var \The_SEO_Framework\Builders\SEOBar_* $instance The instance.
85
  */
86
  protected static $instance;
87
 
93
  * @since 4.0.0
94
  */
95
  final protected function __construct() {
96
+ self::$tsf = self::$tsf ?: \tsf();
 
97
  $this->prime_cache();
98
  }
99
 
105
  * @return static
106
  */
107
  final public static function get_instance() {
108
+ return static::$instance ?? ( static::$instance = new static );
 
109
  }
110
 
111
  /**
132
  * @return mixed|null The cache value. Null on failure.
133
  */
134
  final protected static function get_cache( $key ) {
135
+ return self::$cache[ $key ] ?? null;
136
  }
137
 
138
  /**
139
  * Runs all SEO bar tests.
140
  *
141
+ * @since 4.2.0
142
  * @access private
143
  * @generator
 
 
144
  *
145
  * @param array $query : {
146
  * int $id : Required. The current post or term ID.
152
  * string $test => array The testing results.
153
  * }
154
  */
155
+ public function _run_all_tests( $query ) {
156
+ yield from $this->_run_test( static::$tests, $query );
157
+ }
 
 
158
 
159
  /**
160
  * Runs one or more SEO bar tests.
175
  * string $test => array $item The SEO Bar compatible results.
176
  * }
177
  */
178
+ final public function _run_test( $tests, $query ) {
179
 
180
  $tests = array_intersect( static::$tests, (array) $tests );
181
 
inc/classes/builders/{seobar-page.class.php → seobar/page.class.php} RENAMED
@@ -1,10 +1,10 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes\Builders\SeoBar\Page
4
- * @subpackage The_SEO_Framework\SeoBar
5
  */
6
 
7
- namespace The_SEO_Framework\Builders;
8
 
9
  /**
10
  * The SEO Framework plugin
@@ -29,13 +29,14 @@ namespace The_SEO_Framework\Builders;
29
  * Generates the SEO Bar for posts.
30
  *
31
  * @since 4.0.0
 
32
  *
33
  * @access private
34
  * @internal
35
- * @see \The_SEO_Framework\Interpreters\SeoBar
36
- * Use \The_SEO_Framework\Interpreters\SeoBar::generate_bar() instead.
37
  */
38
- final class SeoBar_Page extends SeoBar {
39
 
40
  /**
41
  * @since 4.0.0
@@ -92,21 +93,25 @@ final class SeoBar_Page extends SeoBar {
92
  'post' => \get_post( static::$query['id'] ),
93
  'meta' => static::$tsf->get_post_meta( static::$query['id'], true ), // Use TSF cache--TSF initializes it anyway.
94
  'states' => [
95
- 'ishome' => static::$tsf->is_real_front_page_by_id( static::$query['id'] ),
96
- 'locale' => \get_locale(),
97
- 'isprotected' => static::$tsf->is_protected( static::$query['id'] ),
98
- 'isdraft' => static::$tsf->is_draft( static::$query['id'] ),
99
- 'robotsmeta' => array_merge(
100
  [
101
  'noindex' => false,
102
  'nofollow' => false,
103
  'noarchive' => false,
104
  ],
105
- static::$tsf->generate_robots_meta( [
106
- 'id' => static::$query['id'],
107
- 'taxonomy' => '',
108
- ] )
 
109
  ),
 
 
 
110
  ],
111
  ];
112
  }
@@ -132,7 +137,7 @@ final class SeoBar_Page extends SeoBar {
132
  * @return array $item : {
133
  * string $symbol : The displayed symbol that identifies your bar.
134
  * string $title : The title of the assessment.
135
- * int $status : Power of two. See \The_SEO_Framework\Interpreters\SeoBar's class constants.
136
  * string $reason : The final assessment: The reason for the $status. The latest state-changing reason is used.
137
  * string $assess : The assessments on why the reason is set. Keep it short and concise!
138
  * Does not accept HTML for performant ARIA support.
@@ -168,7 +173,7 @@ final class SeoBar_Page extends SeoBar {
168
  ],
169
  'reason' => [
170
  'incomplete' => \__( 'Incomplete.', 'autodescription' ),
171
- 'duplicated' => \__( 'The branding is duplicated.', 'autodescription' ),
172
  'notbranded' => \__( 'Not branded.', 'autodescription' ),
173
  'syntax' => \__( 'Found markup syntax.', 'autodescription' ),
174
  ],
@@ -176,7 +181,7 @@ final class SeoBar_Page extends SeoBar {
176
  'generated' => [
177
  'symbol' => \_x( 'TG', 'Title Generated', 'autodescription' ),
178
  'title' => \__( 'Title, generated', 'autodescription' ),
179
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
180
  'reason' => \__( 'Automatically generated.', 'autodescription' ),
181
  'assess' => [
182
  'base' => \__( "It's built from the page title.", 'autodescription' ),
@@ -185,7 +190,7 @@ final class SeoBar_Page extends SeoBar {
185
  'custom' => [
186
  'symbol' => \_x( 'T', 'Title', 'autodescription' ),
187
  'title' => \__( 'Title', 'autodescription' ),
188
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
189
  'reason' => \__( 'Obtained from page SEO meta input.', 'autodescription' ),
190
  'assess' => [
191
  'base' => \__( "It's built from page SEO meta input.", 'autodescription' ),
@@ -195,14 +200,11 @@ final class SeoBar_Page extends SeoBar {
195
  ]
196
  );
197
 
198
- $title_args = [
199
- 'id' => static::$query['id'],
200
- 'taxonomy' => '',
201
- ];
202
 
203
  // TODO instead of getting values from the options API, why don't we store the parameters and allow them to be modified?
204
  // This way, we can implement real-time live-edit AJAX SEO bar items...
205
- $title_part = static::$tsf->get_filtered_raw_custom_field_title( $title_args, false );
206
 
207
  if ( \strlen( $title_part ) ) {
208
  $item = $cache['defaults']['custom'];
@@ -217,7 +219,7 @@ final class SeoBar_Page extends SeoBar {
217
  }
218
 
219
  if ( static::$tsf->has_yoast_syntax( $title_part ) ) {
220
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
221
  $item['reason'] = $cache['reason']['syntax'];
222
  $item['assess']['syntax'] = $cache['assess']['syntax'];
223
 
@@ -232,18 +234,18 @@ final class SeoBar_Page extends SeoBar {
232
  $item['assess']['base'] = \__( "It's built using the site title.", 'autodescription' );
233
  }
234
 
235
- $title_part = static::$tsf->get_filtered_raw_generated_title( $title_args, false );
236
  }
237
 
238
  if ( ! $title_part ) {
239
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
240
  $item['reason'] = $cache['reason']['incomplete'];
241
  $item['assess']['empty'] = $cache['assess']['empty'];
242
 
243
  // Further assessments must be made later. Halt assertion here to prevent confusion.
244
  return $item;
245
  } elseif ( $title_part === $cache['params']['untitled'] ) {
246
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
247
  $item['reason'] = $cache['reason']['incomplete'];
248
  $item['assess']['untitled'] = $cache['assess']['untitled'];
249
 
@@ -283,24 +285,21 @@ final class SeoBar_Page extends SeoBar {
283
  }
284
  }
285
 
286
- // phpcs:disable, PEAR.Functions.FunctionCallSignature.Indent
287
- $brand_count =
288
- \strlen( $cache['params']['blogname_quoted'] )
289
  ? preg_match_all(
290
  "/{$cache['params']['blogname_quoted']}/ui",
291
  $title,
292
  $matches
293
  )
294
  : 0;
295
- // phpcs:enable, PEAR.Functions.FunctionCallSignature.Indent
296
 
297
  if ( ! $brand_count ) {
298
  // Override branding state.
299
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN;
300
  $item['reason'] = $cache['reason']['notbranded'];
301
  $item['assess']['branding'] = $cache['assess']['branding']['not'];
302
  } elseif ( $brand_count > 1 ) {
303
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
304
  $item['reason'] = $cache['reason']['duplicated'];
305
  $item['assess']['duplicated'] = $cache['assess']['duplicated'];
306
 
@@ -319,19 +318,19 @@ final class SeoBar_Page extends SeoBar {
319
  $guidelines_i18n = static::get_cache( 'general/i18n/inputguidelines' );
320
 
321
  if ( $title_len < $guidelines['lower'] ) {
322
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
323
  $item['reason'] = $guidelines_i18n['shortdot']['farTooShort'];
324
  $length_i18n = $guidelines_i18n['long']['farTooShort'];
325
  } elseif ( $title_len < $guidelines['goodLower'] ) {
326
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
327
  $item['reason'] = $guidelines_i18n['shortdot']['tooShort'];
328
  $length_i18n = $guidelines_i18n['long']['tooShort'];
329
  } elseif ( $title_len > $guidelines['upper'] ) {
330
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
331
  $item['reason'] = $guidelines_i18n['shortdot']['farTooLong'];
332
  $length_i18n = $guidelines_i18n['long']['farTooLong'];
333
  } elseif ( $title_len > $guidelines['goodUpper'] ) {
334
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
335
  $item['reason'] = $guidelines_i18n['shortdot']['tooLong'];
336
  $length_i18n = $guidelines_i18n['long']['tooLong'];
337
  } else {
@@ -377,21 +376,21 @@ final class SeoBar_Page extends SeoBar {
377
  'builder' => \__( 'A page builder is used that renders content dynamically, so no description can be generated for performance and privacy reasons. Consider providing a custom description.', 'autodescription' ),
378
  'protected' => \__( 'The page is protected, so no description is generated.', 'autodescription' ),
379
  'excerpt' => \__( "It's built from the page excerpt field.", 'autodescription' ),
380
- /* translators: %s = list of duplicated words */
381
- 'dupes' => \__( 'Found duplicated words: %s', 'autodescription' ),
382
  'syntax' => \__( "Markup syntax was found that isn't transformed. Consider rewriting the custom description.", 'autodescription' ),
383
  ],
384
  'reason' => [
385
  'empty' => \__( 'Empty.', 'autodescription' ),
386
- 'founddupe' => \__( 'Found duplicated words.', 'autodescription' ),
387
- 'foundmanydupe' => \__( 'Found too many duplicated words.', 'autodescription' ),
388
  'syntax' => \__( 'Found markup syntax.', 'autodescription' ),
389
  ],
390
  'defaults' => [
391
  'generated' => [
392
  'symbol' => \_x( 'DG', 'Description Generated', 'autodescription' ),
393
  'title' => \__( 'Description, generated', 'autodescription' ),
394
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
395
  'reason' => \__( 'Automatically generated.', 'autodescription' ),
396
  'assess' => [
397
  'base' => \__( "It's built from the page content.", 'autodescription' ),
@@ -400,7 +399,7 @@ final class SeoBar_Page extends SeoBar {
400
  'emptynoauto' => [
401
  'symbol' => \_x( 'D', 'Description', 'autodescription' ),
402
  'title' => \__( 'Description', 'autodescription' ),
403
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
404
  'reason' => \__( 'Empty.', 'autodescription' ),
405
  'assess' => [
406
  'noauto' => \__( 'No page description is set.', 'autodescription' ),
@@ -409,7 +408,7 @@ final class SeoBar_Page extends SeoBar {
409
  'custom' => [
410
  'symbol' => \_x( 'D', 'Description', 'autodescription' ),
411
  'title' => \__( 'Description', 'autodescription' ),
412
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
413
  'reason' => \__( 'Obtained from the page SEO meta input.', 'autodescription' ),
414
  'assess' => [
415
  'base' => \__( "It's built from the page SEO meta input.", 'autodescription' ),
@@ -419,10 +418,7 @@ final class SeoBar_Page extends SeoBar {
419
  ]
420
  );
421
 
422
- $desc_args = [
423
- 'id' => static::$query['id'],
424
- 'taxonomy' => '',
425
- ];
426
 
427
  // TODO instead of getting values from the options API, why don't we store the parameters and allow them to be modified?
428
  // This way, we can implement real-time live-edit AJAX SEO bar items...
@@ -441,7 +437,7 @@ final class SeoBar_Page extends SeoBar {
441
  }
442
 
443
  if ( static::$tsf->has_yoast_syntax( $desc ) ) {
444
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
445
  $item['reason'] = $cache['reason']['syntax'];
446
  $item['assess']['syntax'] = $cache['assess']['syntax'];
447
 
@@ -466,13 +462,13 @@ final class SeoBar_Page extends SeoBar {
466
  unset( $item['assess']['base'] );
467
 
468
  if ( static::$tsf->uses_non_html_page_builder( static::$query['id'] ) ) {
469
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN;
470
  $item['assess']['empty'] = $cache['assess']['builder'];
471
  } elseif ( static::$tsf->is_protected( static::$query['id'] ) ) {
472
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN;
473
  $item['assess']['empty'] = $cache['assess']['protected'];
474
  } else {
475
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_UNDEFINED;
476
  $item['assess']['empty'] = $cache['assess']['empty'];
477
  }
478
 
@@ -486,11 +482,11 @@ final class SeoBar_Page extends SeoBar {
486
  }
487
 
488
  // Fetch words that are outputted more than 3 times.
489
- $duplicated_words = static::$tsf->get_word_count( $desc, 3, 5, $cache['params']['dupe_short'] );
490
 
491
- if ( $duplicated_words ) {
492
  $dupes = [];
493
- foreach ( $duplicated_words as $_dw ) :
494
  // Keep abbreviations... WordPress, make multibyte support mandatory already.
495
  // $_word = ctype_upper( reset( $_dw ) ) ? reset( $_dw ) : mb_strtolower( reset( $_dw ) );
496
 
@@ -504,18 +500,18 @@ final class SeoBar_Page extends SeoBar {
504
 
505
  $item['assess']['dupe'] = implode( ' ', $dupes );
506
 
507
- $max = max( $duplicated_words );
508
  $max = reset( $max );
509
 
510
  // Warn when more than 3x triplet+/quintet+ words are found.
511
- if ( $max > 3 || \count( $duplicated_words ) > 1 ) {
512
  // This must be resolved.
513
  $item['reason'] = $cache['reason']['foundmanydupe'];
514
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
515
  return $item;
516
  } else {
517
  $item['reason'] = $cache['reason']['founddupe'];
518
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
519
  }
520
  }
521
 
@@ -530,19 +526,19 @@ final class SeoBar_Page extends SeoBar {
530
  );
531
 
532
  if ( $desc_len < $guidelines['lower'] ) {
533
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
534
  $item['reason'] = $guidelines_i18n['shortdot']['farTooShort'];
535
  $length_i18n = $guidelines_i18n['long']['farTooShort'];
536
  } elseif ( $desc_len < $guidelines['goodLower'] ) {
537
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
538
  $item['reason'] = $guidelines_i18n['shortdot']['tooShort'];
539
  $length_i18n = $guidelines_i18n['long']['tooShort'];
540
  } elseif ( $desc_len > $guidelines['upper'] ) {
541
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
542
  $item['reason'] = $guidelines_i18n['shortdot']['farTooLong'];
543
  $length_i18n = $guidelines_i18n['long']['farTooLong'];
544
  } elseif ( $desc_len > $guidelines['goodUpper'] ) {
545
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
546
  $item['reason'] = $guidelines_i18n['shortdot']['tooLong'];
547
  $length_i18n = $guidelines_i18n['long']['tooLong'];
548
  } else {
@@ -592,7 +588,7 @@ final class SeoBar_Page extends SeoBar {
592
  'index' => [
593
  'symbol' => \_x( 'I', 'Indexing', 'autodescription' ),
594
  'title' => \__( 'Indexing', 'autodescription' ),
595
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
596
  'reason' => \__( 'Page may be indexed.', 'autodescription' ),
597
  'assess' => [
598
  'base' => \__( 'The robots meta tag allows indexing.', 'autodescription' ),
@@ -601,7 +597,7 @@ final class SeoBar_Page extends SeoBar {
601
  'noindex' => [
602
  'symbol' => \_x( 'I', 'Indexing', 'autodescription' ),
603
  'title' => \__( 'Indexing', 'autodescription' ),
604
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
605
  'reason' => \__( 'Page may not be indexed.', 'autodescription' ),
606
  'assess' => [
607
  'base' => \__( 'The robots meta tag does not allow indexing.', 'autodescription' ),
@@ -610,7 +606,7 @@ final class SeoBar_Page extends SeoBar {
610
  'draft' => [
611
  'symbol' => \_x( 'I', 'Indexing', 'autodescription' ),
612
  'title' => \__( 'Indexing', 'autodescription' ),
613
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
614
  'reason' => \__( 'Page is invisible.', 'autodescription' ),
615
  'assess' => [
616
  'base' => \__( "This page isn't published and can't be found publicly.", 'autodescription' ),
@@ -633,7 +629,7 @@ final class SeoBar_Page extends SeoBar {
633
  $robots_global = static::get_cache( 'general/detect/robotsglobal' );
634
 
635
  if ( ! $robots_global['blogpublic'] ) {
636
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
637
  $item['reason'] = $cache['reason']['notpublic'];
638
 
639
  unset( $item['assess']['base'] );
@@ -650,7 +646,7 @@ final class SeoBar_Page extends SeoBar {
650
  if ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
651
  // Don't trickle when noindex is not set, as this may be filtered.
652
  if ( $this->query_cache['states']['isprotected'] ) {
653
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN;
654
  $item['reason'] = $cache['reason']['protected'];
655
 
656
  $item['assess']['protected'] = $cache['assess']['protected'];
@@ -688,7 +684,7 @@ final class SeoBar_Page extends SeoBar {
688
  'get_custom_field' => true,
689
  ] );
690
  if ( $permalink !== $canonical ) {
691
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN;
692
  $item['reason'] = $cache['reason']['canonicalurl'];
693
 
694
  $item['assess']['protected'] = $cache['assess']['canonicalurl'];
@@ -746,7 +742,7 @@ final class SeoBar_Page extends SeoBar {
746
  'follow' => [
747
  'symbol' => \_x( 'F', 'Following', 'autodescription' ),
748
  'title' => \__( 'Following', 'autodescription' ),
749
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
750
  'reason' => \__( 'Page links may be followed.', 'autodescription' ),
751
  'assess' => [
752
  'base' => \__( 'The robots meta tag allows link following.', 'autodescription' ),
@@ -755,7 +751,7 @@ final class SeoBar_Page extends SeoBar {
755
  'nofollow' => [
756
  'symbol' => \_x( 'F', 'Following', 'autodescription' ),
757
  'title' => \__( 'Following', 'autodescription' ),
758
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
759
  'reason' => \__( 'Page links may not be followed.', 'autodescription' ),
760
  'assess' => [
761
  'base' => \__( 'The robots meta tag does not allow link following.', 'autodescription' ),
@@ -764,7 +760,7 @@ final class SeoBar_Page extends SeoBar {
764
  'draft' => [
765
  'symbol' => \_x( 'F', 'Following', 'autodescription' ),
766
  'title' => \__( 'Following', 'autodescription' ),
767
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
768
  'reason' => \__( 'Page is invisible.', 'autodescription' ),
769
  'assess' => [
770
  'base' => \__( "This page isn't published and can't be found publicly.", 'autodescription' ),
@@ -787,7 +783,7 @@ final class SeoBar_Page extends SeoBar {
787
  $robots_global = static::get_cache( 'general/detect/robotsglobal' );
788
 
789
  if ( ! $robots_global['blogpublic'] ) {
790
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
791
  $item['reason'] = $cache['reason']['notpublic'];
792
 
793
  unset( $item['assess']['base'] );
@@ -834,7 +830,7 @@ final class SeoBar_Page extends SeoBar {
834
 
835
  if ( ! $this->query_cache['states']['robotsmeta']['nofollow'] ) {
836
  if ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
837
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
838
  $item['assess']['noindex'] = $cache['assess']['noindex'];
839
  }
840
 
@@ -877,7 +873,7 @@ final class SeoBar_Page extends SeoBar {
877
  'archive' => [
878
  'symbol' => \_x( 'A', 'Archiving', 'autodescription' ),
879
  'title' => \__( 'Archiving', 'autodescription' ),
880
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
881
  'reason' => \__( 'Page may be archived.', 'autodescription' ),
882
  'assess' => [
883
  'base' => \__( 'The robots meta tag allows archiving.', 'autodescription' ),
@@ -886,7 +882,7 @@ final class SeoBar_Page extends SeoBar {
886
  'noarchive' => [
887
  'symbol' => \_x( 'A', 'Archiving', 'autodescription' ),
888
  'title' => \__( 'Archiving', 'autodescription' ),
889
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
890
  'reason' => \__( 'Page may not be archived.', 'autodescription' ),
891
  'assess' => [
892
  'base' => \__( 'The robots meta tag does not allow archiving.', 'autodescription' ),
@@ -895,7 +891,7 @@ final class SeoBar_Page extends SeoBar {
895
  'draft' => [
896
  'symbol' => \_x( 'A', 'Archiving', 'autodescription' ),
897
  'title' => \__( 'Archiving', 'autodescription' ),
898
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
899
  'reason' => \__( 'Page is invisible.', 'autodescription' ),
900
  'assess' => [
901
  'base' => \__( "This page isn't published and can't be found publicly.", 'autodescription' ),
@@ -918,7 +914,7 @@ final class SeoBar_Page extends SeoBar {
918
  $robots_global = static::get_cache( 'general/detect/robotsglobal' );
919
 
920
  if ( ! $robots_global['blogpublic'] ) {
921
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
922
  $item['reason'] = $cache['reason']['notpublic'];
923
 
924
  unset( $item['assess']['base'] );
@@ -965,7 +961,7 @@ final class SeoBar_Page extends SeoBar {
965
 
966
  if ( ! $this->query_cache['states']['robotsmeta']['noarchive'] ) {
967
  if ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
968
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
969
  $item['assess']['noindex'] = $cache['assess']['noindex'];
970
  }
971
 
@@ -989,28 +985,33 @@ final class SeoBar_Page extends SeoBar {
989
  protected function test_redirect() {
990
 
991
  if ( empty( $this->query_cache['meta']['redirect'] ) ) {
992
- return static::get_cache( 'page/redirect/default/0' ) ?: static::set_cache(
993
  'page/redirect/default/0',
994
  [
995
  'symbol' => \_x( 'R', 'Redirect', 'autodescription' ),
996
  'title' => \__( 'Redirection', 'autodescription' ),
997
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
998
  'reason' => \__( 'Page does not redirect visitors.', 'autodescription' ),
999
  'assess' => [
1000
- 'redirect' => \__( 'All visitors and crawlers may access this page.', 'autodescription' ),
1001
  ],
1002
  'meta' => [
1003
  'blocking' => false,
1004
  ],
1005
  ]
1006
  );
 
 
 
 
 
1007
  } else {
1008
  return static::get_cache( 'post/redirect/default/1' ) ?: static::set_cache(
1009
  'post/redirect/default/1',
1010
  [
1011
  'symbol' => \_x( 'R', 'Redirect', 'autodescription' ),
1012
  'title' => \__( 'Redirection', 'autodescription' ),
1013
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
1014
  'reason' => \__( 'Page redirects visitors.', 'autodescription' ),
1015
  'assess' => [
1016
  'redirect' => \__( 'All visitors and crawlers are being redirected. So, no other SEO enhancements are effective.', 'autodescription' ),
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Builders\SEOBar\Page
4
+ * @subpackage The_SEO_Framework\SEOBar
5
  */
6
 
7
+ namespace The_SEO_Framework\Builders\SEOBar;
8
 
9
  /**
10
  * The SEO Framework plugin
29
  * Generates the SEO Bar for posts.
30
  *
31
  * @since 4.0.0
32
+ * @since 4.2.0 Renamed to `The_SEO_Framework\Builders\SEOBar\Page` from `The_SEO_Framework\Builders\SeoBar_Page`
33
  *
34
  * @access private
35
  * @internal
36
+ * @see \The_SEO_Framework\Interpreters\SEOBar
37
+ * Use \The_SEO_Framework\Interpreters\SEOBar::generate_bar() instead.
38
  */
39
+ final class Page extends Main {
40
 
41
  /**
42
  * @since 4.0.0
93
  'post' => \get_post( static::$query['id'] ),
94
  'meta' => static::$tsf->get_post_meta( static::$query['id'], true ), // Use TSF cache--TSF initializes it anyway.
95
  'states' => [
96
+ 'ishome' => static::$tsf->is_real_front_page_by_id( static::$query['id'] ),
97
+ 'locale' => \get_locale(),
98
+ 'isprotected' => static::$tsf->is_protected( static::$query['id'] ),
99
+ 'isdraft' => static::$tsf->is_draft( static::$query['id'] ),
100
+ 'robotsmeta' => array_merge(
101
  [
102
  'noindex' => false,
103
  'nofollow' => false,
104
  'noarchive' => false,
105
  ],
106
+ static::$tsf->generate_robots_meta(
107
+ [ 'id' => static::$query['id'] ],
108
+ [ 'noindex', 'nofollow', 'noarchive' ],
109
+ \The_SEO_Framework\ROBOTS_ASSERT
110
+ )
111
  ),
112
+ // We don't use this... yet. I couldn't find a way to properly implement the assertions in the right order.
113
+ // The asserter should be leading, but the SEO Bar should be readable.
114
+ 'robotsassert' => static::$tsf->retrieve_robots_meta_assertions(),
115
  ],
116
  ];
117
  }
137
  * @return array $item : {
138
  * string $symbol : The displayed symbol that identifies your bar.
139
  * string $title : The title of the assessment.
140
+ * int $status : Power of two. See \The_SEO_Framework\Interpreters\SEOBar's class constants.
141
  * string $reason : The final assessment: The reason for the $status. The latest state-changing reason is used.
142
  * string $assess : The assessments on why the reason is set. Keep it short and concise!
143
  * Does not accept HTML for performant ARIA support.
173
  ],
174
  'reason' => [
175
  'incomplete' => \__( 'Incomplete.', 'autodescription' ),
176
+ 'duplicated' => \__( 'The branding is repeated.', 'autodescription' ),
177
  'notbranded' => \__( 'Not branded.', 'autodescription' ),
178
  'syntax' => \__( 'Found markup syntax.', 'autodescription' ),
179
  ],
181
  'generated' => [
182
  'symbol' => \_x( 'TG', 'Title Generated', 'autodescription' ),
183
  'title' => \__( 'Title, generated', 'autodescription' ),
184
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_GOOD,
185
  'reason' => \__( 'Automatically generated.', 'autodescription' ),
186
  'assess' => [
187
  'base' => \__( "It's built from the page title.", 'autodescription' ),
190
  'custom' => [
191
  'symbol' => \_x( 'T', 'Title', 'autodescription' ),
192
  'title' => \__( 'Title', 'autodescription' ),
193
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_GOOD,
194
  'reason' => \__( 'Obtained from page SEO meta input.', 'autodescription' ),
195
  'assess' => [
196
  'base' => \__( "It's built from page SEO meta input.", 'autodescription' ),
200
  ]
201
  );
202
 
203
+ $title_args = [ 'id' => static::$query['id'] ];
 
 
 
204
 
205
  // TODO instead of getting values from the options API, why don't we store the parameters and allow them to be modified?
206
  // This way, we can implement real-time live-edit AJAX SEO bar items...
207
+ $title_part = static::$tsf->get_filtered_raw_custom_field_title( $title_args );
208
 
209
  if ( \strlen( $title_part ) ) {
210
  $item = $cache['defaults']['custom'];
219
  }
220
 
221
  if ( static::$tsf->has_yoast_syntax( $title_part ) ) {
222
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
223
  $item['reason'] = $cache['reason']['syntax'];
224
  $item['assess']['syntax'] = $cache['assess']['syntax'];
225
 
234
  $item['assess']['base'] = \__( "It's built using the site title.", 'autodescription' );
235
  }
236
 
237
+ $title_part = static::$tsf->get_filtered_raw_generated_title( $title_args );
238
  }
239
 
240
  if ( ! $title_part ) {
241
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
242
  $item['reason'] = $cache['reason']['incomplete'];
243
  $item['assess']['empty'] = $cache['assess']['empty'];
244
 
245
  // Further assessments must be made later. Halt assertion here to prevent confusion.
246
  return $item;
247
  } elseif ( $title_part === $cache['params']['untitled'] ) {
248
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
249
  $item['reason'] = $cache['reason']['incomplete'];
250
  $item['assess']['untitled'] = $cache['assess']['untitled'];
251
 
285
  }
286
  }
287
 
288
+ $brand_count = \strlen( $cache['params']['blogname_quoted'] )
 
 
289
  ? preg_match_all(
290
  "/{$cache['params']['blogname_quoted']}/ui",
291
  $title,
292
  $matches
293
  )
294
  : 0;
 
295
 
296
  if ( ! $brand_count ) {
297
  // Override branding state.
298
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN;
299
  $item['reason'] = $cache['reason']['notbranded'];
300
  $item['assess']['branding'] = $cache['assess']['branding']['not'];
301
  } elseif ( $brand_count > 1 ) {
302
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
303
  $item['reason'] = $cache['reason']['duplicated'];
304
  $item['assess']['duplicated'] = $cache['assess']['duplicated'];
305
 
318
  $guidelines_i18n = static::get_cache( 'general/i18n/inputguidelines' );
319
 
320
  if ( $title_len < $guidelines['lower'] ) {
321
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
322
  $item['reason'] = $guidelines_i18n['shortdot']['farTooShort'];
323
  $length_i18n = $guidelines_i18n['long']['farTooShort'];
324
  } elseif ( $title_len < $guidelines['goodLower'] ) {
325
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_OKAY;
326
  $item['reason'] = $guidelines_i18n['shortdot']['tooShort'];
327
  $length_i18n = $guidelines_i18n['long']['tooShort'];
328
  } elseif ( $title_len > $guidelines['upper'] ) {
329
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
330
  $item['reason'] = $guidelines_i18n['shortdot']['farTooLong'];
331
  $length_i18n = $guidelines_i18n['long']['farTooLong'];
332
  } elseif ( $title_len > $guidelines['goodUpper'] ) {
333
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_OKAY;
334
  $item['reason'] = $guidelines_i18n['shortdot']['tooLong'];
335
  $length_i18n = $guidelines_i18n['long']['tooLong'];
336
  } else {
376
  'builder' => \__( 'A page builder is used that renders content dynamically, so no description can be generated for performance and privacy reasons. Consider providing a custom description.', 'autodescription' ),
377
  'protected' => \__( 'The page is protected, so no description is generated.', 'autodescription' ),
378
  'excerpt' => \__( "It's built from the page excerpt field.", 'autodescription' ),
379
+ /* translators: %s = list of repeated words */
380
+ 'dupes' => \__( 'Found repeated words: %s', 'autodescription' ),
381
  'syntax' => \__( "Markup syntax was found that isn't transformed. Consider rewriting the custom description.", 'autodescription' ),
382
  ],
383
  'reason' => [
384
  'empty' => \__( 'Empty.', 'autodescription' ),
385
+ 'founddupe' => \__( 'Found repeated words.', 'autodescription' ),
386
+ 'foundmanydupe' => \__( 'Found too many repeated words.', 'autodescription' ),
387
  'syntax' => \__( 'Found markup syntax.', 'autodescription' ),
388
  ],
389
  'defaults' => [
390
  'generated' => [
391
  'symbol' => \_x( 'DG', 'Description Generated', 'autodescription' ),
392
  'title' => \__( 'Description, generated', 'autodescription' ),
393
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_GOOD,
394
  'reason' => \__( 'Automatically generated.', 'autodescription' ),
395
  'assess' => [
396
  'base' => \__( "It's built from the page content.", 'autodescription' ),
399
  'emptynoauto' => [
400
  'symbol' => \_x( 'D', 'Description', 'autodescription' ),
401
  'title' => \__( 'Description', 'autodescription' ),
402
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN,
403
  'reason' => \__( 'Empty.', 'autodescription' ),
404
  'assess' => [
405
  'noauto' => \__( 'No page description is set.', 'autodescription' ),
408
  'custom' => [
409
  'symbol' => \_x( 'D', 'Description', 'autodescription' ),
410
  'title' => \__( 'Description', 'autodescription' ),
411
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_GOOD,
412
  'reason' => \__( 'Obtained from the page SEO meta input.', 'autodescription' ),
413
  'assess' => [
414
  'base' => \__( "It's built from the page SEO meta input.", 'autodescription' ),
418
  ]
419
  );
420
 
421
+ $desc_args = [ 'id' => static::$query['id'] ];
 
 
 
422
 
423
  // TODO instead of getting values from the options API, why don't we store the parameters and allow them to be modified?
424
  // This way, we can implement real-time live-edit AJAX SEO bar items...
437
  }
438
 
439
  if ( static::$tsf->has_yoast_syntax( $desc ) ) {
440
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
441
  $item['reason'] = $cache['reason']['syntax'];
442
  $item['assess']['syntax'] = $cache['assess']['syntax'];
443
 
462
  unset( $item['assess']['base'] );
463
 
464
  if ( static::$tsf->uses_non_html_page_builder( static::$query['id'] ) ) {
465
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN;
466
  $item['assess']['empty'] = $cache['assess']['builder'];
467
  } elseif ( static::$tsf->is_protected( static::$query['id'] ) ) {
468
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN;
469
  $item['assess']['empty'] = $cache['assess']['protected'];
470
  } else {
471
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_UNDEFINED;
472
  $item['assess']['empty'] = $cache['assess']['empty'];
473
  }
474
 
482
  }
483
 
484
  // Fetch words that are outputted more than 3 times.
485
+ $repeated_words = static::$tsf->get_word_count( $desc, 3, 5, $cache['params']['dupe_short'] );
486
 
487
+ if ( $repeated_words ) {
488
  $dupes = [];
489
+ foreach ( $repeated_words as $_dw ) :
490
  // Keep abbreviations... WordPress, make multibyte support mandatory already.
491
  // $_word = ctype_upper( reset( $_dw ) ) ? reset( $_dw ) : mb_strtolower( reset( $_dw ) );
492
 
500
 
501
  $item['assess']['dupe'] = implode( ' ', $dupes );
502
 
503
+ $max = max( $repeated_words );
504
  $max = reset( $max );
505
 
506
  // Warn when more than 3x triplet+/quintet+ words are found.
507
+ if ( $max > 3 || \count( $repeated_words ) > 1 ) {
508
  // This must be resolved.
509
  $item['reason'] = $cache['reason']['foundmanydupe'];
510
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
511
  return $item;
512
  } else {
513
  $item['reason'] = $cache['reason']['founddupe'];
514
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_OKAY;
515
  }
516
  }
517
 
526
  );
527
 
528
  if ( $desc_len < $guidelines['lower'] ) {
529
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
530
  $item['reason'] = $guidelines_i18n['shortdot']['farTooShort'];
531
  $length_i18n = $guidelines_i18n['long']['farTooShort'];
532
  } elseif ( $desc_len < $guidelines['goodLower'] ) {
533
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_OKAY;
534
  $item['reason'] = $guidelines_i18n['shortdot']['tooShort'];
535
  $length_i18n = $guidelines_i18n['long']['tooShort'];
536
  } elseif ( $desc_len > $guidelines['upper'] ) {
537
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
538
  $item['reason'] = $guidelines_i18n['shortdot']['farTooLong'];
539
  $length_i18n = $guidelines_i18n['long']['farTooLong'];
540
  } elseif ( $desc_len > $guidelines['goodUpper'] ) {
541
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_OKAY;
542
  $item['reason'] = $guidelines_i18n['shortdot']['tooLong'];
543
  $length_i18n = $guidelines_i18n['long']['tooLong'];
544
  } else {
588
  'index' => [
589
  'symbol' => \_x( 'I', 'Indexing', 'autodescription' ),
590
  'title' => \__( 'Indexing', 'autodescription' ),
591
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_GOOD,
592
  'reason' => \__( 'Page may be indexed.', 'autodescription' ),
593
  'assess' => [
594
  'base' => \__( 'The robots meta tag allows indexing.', 'autodescription' ),
597
  'noindex' => [
598
  'symbol' => \_x( 'I', 'Indexing', 'autodescription' ),
599
  'title' => \__( 'Indexing', 'autodescription' ),
600
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN,
601
  'reason' => \__( 'Page may not be indexed.', 'autodescription' ),
602
  'assess' => [
603
  'base' => \__( 'The robots meta tag does not allow indexing.', 'autodescription' ),
606
  'draft' => [
607
  'symbol' => \_x( 'I', 'Indexing', 'autodescription' ),
608
  'title' => \__( 'Indexing', 'autodescription' ),
609
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN,
610
  'reason' => \__( 'Page is invisible.', 'autodescription' ),
611
  'assess' => [
612
  'base' => \__( "This page isn't published and can't be found publicly.", 'autodescription' ),
629
  $robots_global = static::get_cache( 'general/detect/robotsglobal' );
630
 
631
  if ( ! $robots_global['blogpublic'] ) {
632
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
633
  $item['reason'] = $cache['reason']['notpublic'];
634
 
635
  unset( $item['assess']['base'] );
646
  if ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
647
  // Don't trickle when noindex is not set, as this may be filtered.
648
  if ( $this->query_cache['states']['isprotected'] ) {
649
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN;
650
  $item['reason'] = $cache['reason']['protected'];
651
 
652
  $item['assess']['protected'] = $cache['assess']['protected'];
684
  'get_custom_field' => true,
685
  ] );
686
  if ( $permalink !== $canonical ) {
687
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN;
688
  $item['reason'] = $cache['reason']['canonicalurl'];
689
 
690
  $item['assess']['protected'] = $cache['assess']['canonicalurl'];
742
  'follow' => [
743
  'symbol' => \_x( 'F', 'Following', 'autodescription' ),
744
  'title' => \__( 'Following', 'autodescription' ),
745
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_GOOD,
746
  'reason' => \__( 'Page links may be followed.', 'autodescription' ),
747
  'assess' => [
748
  'base' => \__( 'The robots meta tag allows link following.', 'autodescription' ),
751
  'nofollow' => [
752
  'symbol' => \_x( 'F', 'Following', 'autodescription' ),
753
  'title' => \__( 'Following', 'autodescription' ),
754
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN,
755
  'reason' => \__( 'Page links may not be followed.', 'autodescription' ),
756
  'assess' => [
757
  'base' => \__( 'The robots meta tag does not allow link following.', 'autodescription' ),
760
  'draft' => [
761
  'symbol' => \_x( 'F', 'Following', 'autodescription' ),
762
  'title' => \__( 'Following', 'autodescription' ),
763
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN,
764
  'reason' => \__( 'Page is invisible.', 'autodescription' ),
765
  'assess' => [
766
  'base' => \__( "This page isn't published and can't be found publicly.", 'autodescription' ),
783
  $robots_global = static::get_cache( 'general/detect/robotsglobal' );
784
 
785
  if ( ! $robots_global['blogpublic'] ) {
786
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
787
  $item['reason'] = $cache['reason']['notpublic'];
788
 
789
  unset( $item['assess']['base'] );
830
 
831
  if ( ! $this->query_cache['states']['robotsmeta']['nofollow'] ) {
832
  if ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
833
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_OKAY;
834
  $item['assess']['noindex'] = $cache['assess']['noindex'];
835
  }
836
 
873
  'archive' => [
874
  'symbol' => \_x( 'A', 'Archiving', 'autodescription' ),
875
  'title' => \__( 'Archiving', 'autodescription' ),
876
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_GOOD,
877
  'reason' => \__( 'Page may be archived.', 'autodescription' ),
878
  'assess' => [
879
  'base' => \__( 'The robots meta tag allows archiving.', 'autodescription' ),
882
  'noarchive' => [
883
  'symbol' => \_x( 'A', 'Archiving', 'autodescription' ),
884
  'title' => \__( 'Archiving', 'autodescription' ),
885
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN,
886
  'reason' => \__( 'Page may not be archived.', 'autodescription' ),
887
  'assess' => [
888
  'base' => \__( 'The robots meta tag does not allow archiving.', 'autodescription' ),
891
  'draft' => [
892
  'symbol' => \_x( 'A', 'Archiving', 'autodescription' ),
893
  'title' => \__( 'Archiving', 'autodescription' ),
894
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN,
895
  'reason' => \__( 'Page is invisible.', 'autodescription' ),
896
  'assess' => [
897
  'base' => \__( "This page isn't published and can't be found publicly.", 'autodescription' ),
914
  $robots_global = static::get_cache( 'general/detect/robotsglobal' );
915
 
916
  if ( ! $robots_global['blogpublic'] ) {
917
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
918
  $item['reason'] = $cache['reason']['notpublic'];
919
 
920
  unset( $item['assess']['base'] );
961
 
962
  if ( ! $this->query_cache['states']['robotsmeta']['noarchive'] ) {
963
  if ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
964
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_OKAY;
965
  $item['assess']['noindex'] = $cache['assess']['noindex'];
966
  }
967
 
985
  protected function test_redirect() {
986
 
987
  if ( empty( $this->query_cache['meta']['redirect'] ) ) {
988
+ $default = static::get_cache( 'page/redirect/default/0' ) ?: static::set_cache(
989
  'page/redirect/default/0',
990
  [
991
  'symbol' => \_x( 'R', 'Redirect', 'autodescription' ),
992
  'title' => \__( 'Redirection', 'autodescription' ),
993
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_GOOD,
994
  'reason' => \__( 'Page does not redirect visitors.', 'autodescription' ),
995
  'assess' => [
996
+ 'redirect' => \__( 'Visitors and crawlers may view this page.', 'autodescription' ),
997
  ],
998
  'meta' => [
999
  'blocking' => false,
1000
  ],
1001
  ]
1002
  );
1003
+
1004
+ if ( $this->query_cache['states']['isdraft'] )
1005
+ $default['assess']['redirect'] = \__( 'Visitors and crawlers may view this page once published.', 'autodescription' );
1006
+
1007
+ return $default;
1008
  } else {
1009
  return static::get_cache( 'post/redirect/default/1' ) ?: static::set_cache(
1010
  'post/redirect/default/1',
1011
  [
1012
  'symbol' => \_x( 'R', 'Redirect', 'autodescription' ),
1013
  'title' => \__( 'Redirection', 'autodescription' ),
1014
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN,
1015
  'reason' => \__( 'Page redirects visitors.', 'autodescription' ),
1016
  'assess' => [
1017
  'redirect' => \__( 'All visitors and crawlers are being redirected. So, no other SEO enhancements are effective.', 'autodescription' ),
inc/classes/builders/{seobar-term.class.php → seobar/term.class.php} RENAMED
@@ -1,10 +1,10 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes\Builders\SeoBar\Term
4
- * @subpackage The_SEO_Framework\SeoBar
5
  */
6
 
7
- namespace The_SEO_Framework\Builders;
8
 
9
  /**
10
  * The SEO Framework plugin
@@ -29,13 +29,14 @@ namespace The_SEO_Framework\Builders;
29
  * Generates the SEO Bar for posts.
30
  *
31
  * @since 4.0.0
 
32
  *
33
  * @access private
34
  * @internal
35
- * @see \The_SEO_Framework\Interpreters\SeoBar
36
- * Use \The_SEO_Framework\Interpreters\SeoBar::generate_bar() instead.
37
  */
38
- final class SeoBar_Term extends SeoBar {
39
 
40
  /**
41
  * @since 4.0.0
@@ -100,20 +101,27 @@ final class SeoBar_Term extends SeoBar {
100
  'term' => $term,
101
  'meta' => static::$tsf->get_term_meta( static::$query['id'], true ), // Use TSF cache--TSF initializes it anyway.
102
  'states' => [
103
- 'locale' => \get_locale(),
104
- 'isempty' => empty( $term->count ),
105
- 'posttypes' => static::$tsf->get_post_types_from_taxonomy( static::$query['taxonomy'] ),
106
- 'robotsmeta' => array_merge(
107
  [
108
  'noindex' => false,
109
  'nofollow' => false,
110
  'noarchive' => false,
111
  ],
112
- static::$tsf->generate_robots_meta( [
113
- 'id' => static::$query['id'],
114
- 'taxonomy' => static::$query['taxonomy'],
115
- ] )
 
 
 
 
116
  ),
 
 
 
117
  ],
118
  ];
119
  }
@@ -141,7 +149,7 @@ final class SeoBar_Term extends SeoBar {
141
  * @return array $item : {
142
  * string $symbol : The displayed symbol that identifies your bar.
143
  * string $title : The title of the assessment.
144
- * int $status : Power of two. See \The_SEO_Framework\Interpreters\SeoBar's class constants.
145
  * string $reason : The final assessment: The reason for the $status. The latest state-changing reason is used.
146
  * string $assess : The assessments on why the reason is set. Keep it short and concise!
147
  * Does not accept HTML for performant ARIA support.
@@ -177,7 +185,7 @@ final class SeoBar_Term extends SeoBar {
177
  ],
178
  'reason' => [
179
  'incomplete' => \__( 'Incomplete.', 'autodescription' ),
180
- 'duplicated' => \__( 'The branding is duplicated.', 'autodescription' ),
181
  'notbranded' => \__( 'Not branded.', 'autodescription' ),
182
  'syntax' => \__( 'Found markup syntax.', 'autodescription' ),
183
  ],
@@ -185,7 +193,7 @@ final class SeoBar_Term extends SeoBar {
185
  'generated' => [
186
  'symbol' => \_x( 'TG', 'Title Generated', 'autodescription' ),
187
  'title' => \__( 'Title, generated', 'autodescription' ),
188
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
189
  'reason' => \__( 'Automatically generated.', 'autodescription' ),
190
  'assess' => [
191
  'base' => \__( "It's built from the term name.", 'autodescription' ),
@@ -194,7 +202,7 @@ final class SeoBar_Term extends SeoBar {
194
  'custom' => [
195
  'symbol' => \_x( 'T', 'Title', 'autodescription' ),
196
  'title' => \__( 'Title', 'autodescription' ),
197
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
198
  'reason' => \__( 'Obtained from term SEO meta input.', 'autodescription' ),
199
  'assess' => [
200
  'base' => \__( "It's built from term SEO meta input.", 'autodescription' ),
@@ -211,13 +219,13 @@ final class SeoBar_Term extends SeoBar {
211
 
212
  // TODO instead of getting values from the options API, why don't we store the parameters and allow them to be modified?
213
  // This way, we can implement real-time live-edit AJAX SEO bar items...
214
- $title_part = static::$tsf->get_filtered_raw_custom_field_title( $title_args, false );
215
 
216
  if ( \strlen( $title_part ) ) {
217
  $item = $cache['defaults']['custom'];
218
 
219
  if ( static::$tsf->has_yoast_syntax( $title_part, false ) ) {
220
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
221
  $item['reason'] = $cache['reason']['syntax'];
222
  $item['assess']['syntax'] = $cache['assess']['syntax'];
223
 
@@ -231,18 +239,18 @@ final class SeoBar_Term extends SeoBar {
231
  $item['assess']['prefixed'] = $cache['assess']['prefixed'];
232
  }
233
 
234
- $title_part = static::$tsf->get_filtered_raw_generated_title( $title_args, false );
235
  }
236
 
237
  if ( ! $title_part ) {
238
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
239
  $item['reason'] = $cache['reason']['incomplete'];
240
  $item['assess']['empty'] = $cache['assess']['empty'];
241
 
242
  // Further assessments must be made later. Halt assertion here to prevent confusion.
243
  return $item;
244
  } elseif ( $title_part === $cache['params']['untitled'] ) {
245
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
246
  $item['reason'] = $cache['reason']['incomplete'];
247
  $item['assess']['untitled'] = $cache['assess']['untitled'];
248
 
@@ -267,24 +275,21 @@ final class SeoBar_Term extends SeoBar {
267
  $item['assess']['branding'] = $cache['assess']['branding']['manual'];
268
  }
269
 
270
- // phpcs:disable, PEAR.Functions.FunctionCallSignature.Indent
271
- $brand_count =
272
- \strlen( $cache['params']['blogname_quoted'] )
273
  ? preg_match_all(
274
  "/{$cache['params']['blogname_quoted']}/ui",
275
  $title,
276
  $matches
277
  )
278
  : 0;
279
- // phpcs:enable, PEAR.Functions.FunctionCallSignature.Indent
280
 
281
  if ( ! $brand_count ) {
282
  // Override branding state.
283
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN;
284
  $item['reason'] = $cache['reason']['notbranded'];
285
  $item['assess']['branding'] = $cache['assess']['branding']['not'];
286
  } elseif ( $brand_count > 1 ) {
287
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
288
  $item['reason'] = $cache['reason']['duplicated'];
289
  $item['assess']['duplicated'] = $cache['assess']['duplicated'];
290
 
@@ -303,19 +308,19 @@ final class SeoBar_Term extends SeoBar {
303
  $guidelines_i18n = static::get_cache( 'general/i18n/inputguidelines' );
304
 
305
  if ( $title_len < $guidelines['lower'] ) {
306
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
307
  $item['reason'] = $guidelines_i18n['shortdot']['farTooShort'];
308
  $length_i18n = $guidelines_i18n['long']['farTooShort'];
309
  } elseif ( $title_len < $guidelines['goodLower'] ) {
310
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
311
  $item['reason'] = $guidelines_i18n['shortdot']['tooShort'];
312
  $length_i18n = $guidelines_i18n['long']['tooShort'];
313
  } elseif ( $title_len > $guidelines['upper'] ) {
314
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
315
  $item['reason'] = $guidelines_i18n['shortdot']['farTooLong'];
316
  $length_i18n = $guidelines_i18n['long']['farTooLong'];
317
  } elseif ( $title_len > $guidelines['goodUpper'] ) {
318
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
319
  $item['reason'] = $guidelines_i18n['shortdot']['tooLong'];
320
  $length_i18n = $guidelines_i18n['long']['tooLong'];
321
  } else {
@@ -358,21 +363,21 @@ final class SeoBar_Term extends SeoBar {
358
  ],
359
  'assess' => [
360
  'empty' => \__( 'No description could be generated.', 'autodescription' ),
361
- /* translators: %s = list of duplicated words */
362
- 'dupes' => \__( 'Found duplicated words: %s', 'autodescription' ),
363
  'syntax' => \__( "Markup syntax was found that isn't transformed. Consider rewriting the custom description.", 'autodescription' ),
364
  ],
365
  'reason' => [
366
  'empty' => \__( 'Empty.', 'autodescription' ),
367
- 'founddupe' => \__( 'Found duplicated words.', 'autodescription' ),
368
- 'foundmanydupe' => \__( 'Found too many duplicated words.', 'autodescription' ),
369
  'syntax' => \__( 'Found markup syntax.', 'autodescription' ),
370
  ],
371
  'defaults' => [
372
  'generated' => [
373
  'symbol' => \_x( 'DG', 'Description Generated', 'autodescription' ),
374
  'title' => \__( 'Description, generated', 'autodescription' ),
375
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
376
  'reason' => \__( 'Automatically generated.', 'autodescription' ),
377
  'assess' => [
378
  'base' => \__( "It's built from the term description field.", 'autodescription' ),
@@ -381,7 +386,7 @@ final class SeoBar_Term extends SeoBar {
381
  'emptynoauto' => [
382
  'symbol' => \_x( 'D', 'Description', 'autodescription' ),
383
  'title' => \__( 'Description', 'autodescription' ),
384
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
385
  'reason' => \__( 'Empty.', 'autodescription' ),
386
  'assess' => [
387
  'noauto' => \__( 'No term description is set.', 'autodescription' ),
@@ -390,7 +395,7 @@ final class SeoBar_Term extends SeoBar {
390
  'custom' => [
391
  'symbol' => \_x( 'D', 'Description', 'autodescription' ),
392
  'title' => \__( 'Description', 'autodescription' ),
393
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
394
  'reason' => \__( 'Obtained from the term SEO meta input.', 'autodescription' ),
395
  'assess' => [
396
  'base' => \__( "It's built from the term SEO meta input.", 'autodescription' ),
@@ -413,7 +418,7 @@ final class SeoBar_Term extends SeoBar {
413
  $item = $cache['defaults']['custom'];
414
 
415
  if ( static::$tsf->has_yoast_syntax( $desc ) ) {
416
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
417
  $item['reason'] = $cache['reason']['syntax'];
418
  $item['assess']['syntax'] = $cache['assess']['syntax'];
419
 
@@ -431,7 +436,7 @@ final class SeoBar_Term extends SeoBar {
431
  $desc = static::$tsf->get_generated_description( $desc_args, false );
432
 
433
  if ( ! \strlen( $desc ) ) {
434
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_UNDEFINED;
435
  $item['reason'] = $cache['reason']['empty'];
436
 
437
  // This is now inaccurate, purge it.
@@ -446,11 +451,11 @@ final class SeoBar_Term extends SeoBar {
446
  }
447
 
448
  // Fetch words that are outputted more than 3 times.
449
- $duplicated_words = static::$tsf->get_word_count( $desc, 3, 5, $cache['params']['dupe_short'] );
450
 
451
- if ( $duplicated_words ) {
452
  $dupes = [];
453
- foreach ( $duplicated_words as $_dw ) :
454
  // Keep abbreviations... WordPress, make multibyte support mandatory already.
455
  // $_word = ctype_upper( reset( $_dw ) ) ? reset( $_dw ) : mb_strtolower( reset( $_dw ) );
456
 
@@ -464,18 +469,18 @@ final class SeoBar_Term extends SeoBar {
464
 
465
  $item['assess']['dupe'] = implode( ' ', $dupes );
466
 
467
- $max = max( $duplicated_words );
468
  $max = reset( $max );
469
 
470
  // Warn when more than 3x triplet+/quintet+ words are found.
471
- if ( $max > 3 || \count( $duplicated_words ) > 1 ) {
472
  // This must be resolved.
473
  $item['reason'] = $cache['reason']['foundmanydupe'];
474
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
475
  return $item;
476
  } else {
477
  $item['reason'] = $cache['reason']['founddupe'];
478
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
479
  }
480
  }
481
 
@@ -490,19 +495,19 @@ final class SeoBar_Term extends SeoBar {
490
  );
491
 
492
  if ( $desc_len < $guidelines['lower'] ) {
493
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
494
  $item['reason'] = $guidelines_i18n['shortdot']['farTooShort'];
495
  $length_i18n = $guidelines_i18n['long']['farTooShort'];
496
  } elseif ( $desc_len < $guidelines['goodLower'] ) {
497
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
498
  $item['reason'] = $guidelines_i18n['shortdot']['tooShort'];
499
  $length_i18n = $guidelines_i18n['long']['tooShort'];
500
  } elseif ( $desc_len > $guidelines['upper'] ) {
501
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
502
  $item['reason'] = $guidelines_i18n['shortdot']['farTooLong'];
503
  $length_i18n = $guidelines_i18n['long']['farTooLong'];
504
  } elseif ( $desc_len > $guidelines['goodUpper'] ) {
505
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
506
  $item['reason'] = $guidelines_i18n['shortdot']['tooLong'];
507
  $length_i18n = $guidelines_i18n['long']['tooLong'];
508
  } else {
@@ -555,7 +560,7 @@ final class SeoBar_Term extends SeoBar {
555
  'index' => [
556
  'symbol' => \_x( 'I', 'Indexing', 'autodescription' ),
557
  'title' => \__( 'Indexing', 'autodescription' ),
558
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
559
  'reason' => \__( 'Term may be indexed.', 'autodescription' ),
560
  'assess' => [
561
  'base' => \__( 'The robots meta tag allows indexing.', 'autodescription' ),
@@ -564,7 +569,7 @@ final class SeoBar_Term extends SeoBar {
564
  'noindex' => [
565
  'symbol' => \_x( 'I', 'Indexing', 'autodescription' ),
566
  'title' => \__( 'Indexing', 'autodescription' ),
567
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
568
  'reason' => \__( 'Term may not be indexed.', 'autodescription' ),
569
  'assess' => [
570
  'base' => \__( 'The robots meta tag does not allow indexing.', 'autodescription' ),
@@ -583,7 +588,7 @@ final class SeoBar_Term extends SeoBar {
583
  }
584
 
585
  if ( ! $robots_global['blogpublic'] ) {
586
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
587
  $item['reason'] = $cache['reason']['notpublic'];
588
 
589
  unset( $item['assess']['base'] );
@@ -639,7 +644,7 @@ final class SeoBar_Term extends SeoBar {
639
  'get_custom_field' => true,
640
  ] );
641
  if ( $permalink !== $canonical ) {
642
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN;
643
  $item['reason'] = $cache['reason']['canonicalurl'];
644
 
645
  $item['assess']['protected'] = $cache['assess']['canonicalurl'];
@@ -649,13 +654,13 @@ final class SeoBar_Term extends SeoBar {
649
  if ( $this->query_cache['states']['isempty'] ) {
650
  if ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
651
  // Everything's as intended...
652
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN;
653
  $item['reason'] = $cache['reason']['empty'];
654
 
655
  $item['assess']['empty'] = $cache['assess']['empty'];
656
  } else {
657
  // Something's wrong. Maybe override, maybe filter, maybe me.
658
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
659
 
660
  $item['reason'] = $cache['reason']['emptyoverride'];
661
  $item['assess']['empty'] = $cache['assess']['emptyoverride'];
@@ -701,7 +706,7 @@ final class SeoBar_Term extends SeoBar {
701
  'follow' => [
702
  'symbol' => \_x( 'F', 'Following', 'autodescription' ),
703
  'title' => \__( 'Following', 'autodescription' ),
704
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
705
  'reason' => \__( 'Term links may be followed.', 'autodescription' ),
706
  'assess' => [
707
  'base' => \__( 'The robots meta tag allows link following.', 'autodescription' ),
@@ -710,7 +715,7 @@ final class SeoBar_Term extends SeoBar {
710
  'nofollow' => [
711
  'symbol' => \_x( 'F', 'Following', 'autodescription' ),
712
  'title' => \__( 'Following', 'autodescription' ),
713
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
714
  'reason' => \__( 'Term links may not be followed.', 'autodescription' ),
715
  'assess' => [
716
  'base' => \__( 'The robots meta tag does not allow link following.', 'autodescription' ),
@@ -729,7 +734,7 @@ final class SeoBar_Term extends SeoBar {
729
  }
730
 
731
  if ( ! $robots_global['blogpublic'] ) {
732
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
733
  $item['reason'] = $cache['reason']['notpublic'];
734
 
735
  unset( $item['assess']['base'] );
@@ -774,7 +779,7 @@ final class SeoBar_Term extends SeoBar {
774
 
775
  if ( ! $this->query_cache['states']['robotsmeta']['nofollow'] ) {
776
  if ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
777
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
778
  $item['assess']['noindex'] = $cache['assess']['noindex'];
779
  }
780
 
@@ -818,7 +823,7 @@ final class SeoBar_Term extends SeoBar {
818
  'archive' => [
819
  'symbol' => \_x( 'A', 'Archiving', 'autodescription' ),
820
  'title' => \__( 'Archiving', 'autodescription' ),
821
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
822
  'reason' => \__( 'Term may be archived.', 'autodescription' ),
823
  'assess' => [
824
  'base' => \__( 'The robots meta tag allows archiving.', 'autodescription' ),
@@ -827,7 +832,7 @@ final class SeoBar_Term extends SeoBar {
827
  'noarchive' => [
828
  'symbol' => \_x( 'A', 'Archiving', 'autodescription' ),
829
  'title' => \__( 'Archiving', 'autodescription' ),
830
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
831
  'reason' => \__( 'Term may not be archived.', 'autodescription' ),
832
  'assess' => [
833
  'base' => \__( 'The robots meta tag does not allow archiving.', 'autodescription' ),
@@ -846,7 +851,7 @@ final class SeoBar_Term extends SeoBar {
846
  }
847
 
848
  if ( ! $robots_global['blogpublic'] ) {
849
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
850
  $item['reason'] = $cache['reason']['notpublic'];
851
 
852
  unset( $item['assess']['base'] );
@@ -891,7 +896,7 @@ final class SeoBar_Term extends SeoBar {
891
 
892
  if ( ! $this->query_cache['states']['robotsmeta']['noarchive'] ) {
893
  if ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
894
- $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
895
  $item['assess']['noindex'] = $cache['assess']['noindex'];
896
  }
897
 
@@ -919,7 +924,7 @@ final class SeoBar_Term extends SeoBar {
919
  [
920
  'symbol' => \_x( 'R', 'Redirect', 'autodescription' ),
921
  'title' => \__( 'Redirection', 'autodescription' ),
922
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
923
  'reason' => \__( 'Term does not redirect visitors.', 'autodescription' ),
924
  'assess' => [
925
  'redirect' => \__( 'All visitors and crawlers may access this page.', 'autodescription' ),
@@ -935,7 +940,7 @@ final class SeoBar_Term extends SeoBar {
935
  [
936
  'symbol' => \_x( 'R', 'Redirect', 'autodescription' ),
937
  'title' => \__( 'Redirection', 'autodescription' ),
938
- 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
939
  'reason' => \__( 'Term redirects visitors.', 'autodescription' ),
940
  'assess' => [
941
  'redirect' => \__( 'All visitors and crawlers are being redirected. So, no other SEO enhancements are effective.', 'autodescription' ),
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Builders\SEOBar\Term
4
+ * @subpackage The_SEO_Framework\SEOBar
5
  */
6
 
7
+ namespace The_SEO_Framework\Builders\SEOBar;
8
 
9
  /**
10
  * The SEO Framework plugin
29
  * Generates the SEO Bar for posts.
30
  *
31
  * @since 4.0.0
32
+ * @since 4.2.0 Renamed to `The_SEO_Framework\Builders\SEOBar\Term` from `The_SEO_Framework\Builders\SeoBar_Term`
33
  *
34
  * @access private
35
  * @internal
36
+ * @see \The_SEO_Framework\Interpreters\SEOBar
37
+ * Use \The_SEO_Framework\Interpreters\SEOBar::generate_bar() instead.
38
  */
39
+ final class Term extends Main {
40
 
41
  /**
42
  * @since 4.0.0
101
  'term' => $term,
102
  'meta' => static::$tsf->get_term_meta( static::$query['id'], true ), // Use TSF cache--TSF initializes it anyway.
103
  'states' => [
104
+ 'locale' => \get_locale(),
105
+ 'isempty' => empty( $term->count ),
106
+ 'posttypes' => static::$tsf->get_post_types_from_taxonomy( static::$query['taxonomy'] ),
107
+ 'robotsmeta' => array_merge(
108
  [
109
  'noindex' => false,
110
  'nofollow' => false,
111
  'noarchive' => false,
112
  ],
113
+ static::$tsf->generate_robots_meta(
114
+ [
115
+ 'id' => static::$query['id'],
116
+ 'taxonomy' => static::$query['taxonomy'],
117
+ ],
118
+ [ 'noindex', 'nofollow', 'noarchive' ],
119
+ \The_SEO_Framework\ROBOTS_ASSERT
120
+ )
121
  ),
122
+ // We don't use this... yet. I couldn't find a way to properly implement the assertions in the right order.
123
+ // The asserter should be leading, but the SEO Bar should be readable.
124
+ 'robotsassert' => static::$tsf->retrieve_robots_meta_assertions(),
125
  ],
126
  ];
127
  }
149
  * @return array $item : {
150
  * string $symbol : The displayed symbol that identifies your bar.
151
  * string $title : The title of the assessment.
152
+ * int $status : Power of two. See \The_SEO_Framework\Interpreters\SEOBar's class constants.
153
  * string $reason : The final assessment: The reason for the $status. The latest state-changing reason is used.
154
  * string $assess : The assessments on why the reason is set. Keep it short and concise!
155
  * Does not accept HTML for performant ARIA support.
185
  ],
186
  'reason' => [
187
  'incomplete' => \__( 'Incomplete.', 'autodescription' ),
188
+ 'duplicated' => \__( 'The branding is repeated.', 'autodescription' ),
189
  'notbranded' => \__( 'Not branded.', 'autodescription' ),
190
  'syntax' => \__( 'Found markup syntax.', 'autodescription' ),
191
  ],
193
  'generated' => [
194
  'symbol' => \_x( 'TG', 'Title Generated', 'autodescription' ),
195
  'title' => \__( 'Title, generated', 'autodescription' ),
196
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_GOOD,
197
  'reason' => \__( 'Automatically generated.', 'autodescription' ),
198
  'assess' => [
199
  'base' => \__( "It's built from the term name.", 'autodescription' ),
202
  'custom' => [
203
  'symbol' => \_x( 'T', 'Title', 'autodescription' ),
204
  'title' => \__( 'Title', 'autodescription' ),
205
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_GOOD,
206
  'reason' => \__( 'Obtained from term SEO meta input.', 'autodescription' ),
207
  'assess' => [
208
  'base' => \__( "It's built from term SEO meta input.", 'autodescription' ),
219
 
220
  // TODO instead of getting values from the options API, why don't we store the parameters and allow them to be modified?
221
  // This way, we can implement real-time live-edit AJAX SEO bar items...
222
+ $title_part = static::$tsf->get_filtered_raw_custom_field_title( $title_args );
223
 
224
  if ( \strlen( $title_part ) ) {
225
  $item = $cache['defaults']['custom'];
226
 
227
  if ( static::$tsf->has_yoast_syntax( $title_part, false ) ) {
228
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
229
  $item['reason'] = $cache['reason']['syntax'];
230
  $item['assess']['syntax'] = $cache['assess']['syntax'];
231
 
239
  $item['assess']['prefixed'] = $cache['assess']['prefixed'];
240
  }
241
 
242
+ $title_part = static::$tsf->get_filtered_raw_generated_title( $title_args );
243
  }
244
 
245
  if ( ! $title_part ) {
246
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
247
  $item['reason'] = $cache['reason']['incomplete'];
248
  $item['assess']['empty'] = $cache['assess']['empty'];
249
 
250
  // Further assessments must be made later. Halt assertion here to prevent confusion.
251
  return $item;
252
  } elseif ( $title_part === $cache['params']['untitled'] ) {
253
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
254
  $item['reason'] = $cache['reason']['incomplete'];
255
  $item['assess']['untitled'] = $cache['assess']['untitled'];
256
 
275
  $item['assess']['branding'] = $cache['assess']['branding']['manual'];
276
  }
277
 
278
+ $brand_count = \strlen( $cache['params']['blogname_quoted'] )
 
 
279
  ? preg_match_all(
280
  "/{$cache['params']['blogname_quoted']}/ui",
281
  $title,
282
  $matches
283
  )
284
  : 0;
 
285
 
286
  if ( ! $brand_count ) {
287
  // Override branding state.
288
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN;
289
  $item['reason'] = $cache['reason']['notbranded'];
290
  $item['assess']['branding'] = $cache['assess']['branding']['not'];
291
  } elseif ( $brand_count > 1 ) {
292
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
293
  $item['reason'] = $cache['reason']['duplicated'];
294
  $item['assess']['duplicated'] = $cache['assess']['duplicated'];
295
 
308
  $guidelines_i18n = static::get_cache( 'general/i18n/inputguidelines' );
309
 
310
  if ( $title_len < $guidelines['lower'] ) {
311
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
312
  $item['reason'] = $guidelines_i18n['shortdot']['farTooShort'];
313
  $length_i18n = $guidelines_i18n['long']['farTooShort'];
314
  } elseif ( $title_len < $guidelines['goodLower'] ) {
315
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_OKAY;
316
  $item['reason'] = $guidelines_i18n['shortdot']['tooShort'];
317
  $length_i18n = $guidelines_i18n['long']['tooShort'];
318
  } elseif ( $title_len > $guidelines['upper'] ) {
319
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
320
  $item['reason'] = $guidelines_i18n['shortdot']['farTooLong'];
321
  $length_i18n = $guidelines_i18n['long']['farTooLong'];
322
  } elseif ( $title_len > $guidelines['goodUpper'] ) {
323
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_OKAY;
324
  $item['reason'] = $guidelines_i18n['shortdot']['tooLong'];
325
  $length_i18n = $guidelines_i18n['long']['tooLong'];
326
  } else {
363
  ],
364
  'assess' => [
365
  'empty' => \__( 'No description could be generated.', 'autodescription' ),
366
+ /* translators: %s = list of repeated words */
367
+ 'dupes' => \__( 'Found repeated words: %s', 'autodescription' ),
368
  'syntax' => \__( "Markup syntax was found that isn't transformed. Consider rewriting the custom description.", 'autodescription' ),
369
  ],
370
  'reason' => [
371
  'empty' => \__( 'Empty.', 'autodescription' ),
372
+ 'founddupe' => \__( 'Found repeated words.', 'autodescription' ),
373
+ 'foundmanydupe' => \__( 'Found too many repeated words.', 'autodescription' ),
374
  'syntax' => \__( 'Found markup syntax.', 'autodescription' ),
375
  ],
376
  'defaults' => [
377
  'generated' => [
378
  'symbol' => \_x( 'DG', 'Description Generated', 'autodescription' ),
379
  'title' => \__( 'Description, generated', 'autodescription' ),
380
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_GOOD,
381
  'reason' => \__( 'Automatically generated.', 'autodescription' ),
382
  'assess' => [
383
  'base' => \__( "It's built from the term description field.", 'autodescription' ),
386
  'emptynoauto' => [
387
  'symbol' => \_x( 'D', 'Description', 'autodescription' ),
388
  'title' => \__( 'Description', 'autodescription' ),
389
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN,
390
  'reason' => \__( 'Empty.', 'autodescription' ),
391
  'assess' => [
392
  'noauto' => \__( 'No term description is set.', 'autodescription' ),
395
  'custom' => [
396
  'symbol' => \_x( 'D', 'Description', 'autodescription' ),
397
  'title' => \__( 'Description', 'autodescription' ),
398
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_GOOD,
399
  'reason' => \__( 'Obtained from the term SEO meta input.', 'autodescription' ),
400
  'assess' => [
401
  'base' => \__( "It's built from the term SEO meta input.", 'autodescription' ),
418
  $item = $cache['defaults']['custom'];
419
 
420
  if ( static::$tsf->has_yoast_syntax( $desc ) ) {
421
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
422
  $item['reason'] = $cache['reason']['syntax'];
423
  $item['assess']['syntax'] = $cache['assess']['syntax'];
424
 
436
  $desc = static::$tsf->get_generated_description( $desc_args, false );
437
 
438
  if ( ! \strlen( $desc ) ) {
439
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_UNDEFINED;
440
  $item['reason'] = $cache['reason']['empty'];
441
 
442
  // This is now inaccurate, purge it.
451
  }
452
 
453
  // Fetch words that are outputted more than 3 times.
454
+ $repeated_words = static::$tsf->get_word_count( $desc, 3, 5, $cache['params']['dupe_short'] );
455
 
456
+ if ( $repeated_words ) {
457
  $dupes = [];
458
+ foreach ( $repeated_words as $_dw ) :
459
  // Keep abbreviations... WordPress, make multibyte support mandatory already.
460
  // $_word = ctype_upper( reset( $_dw ) ) ? reset( $_dw ) : mb_strtolower( reset( $_dw ) );
461
 
469
 
470
  $item['assess']['dupe'] = implode( ' ', $dupes );
471
 
472
+ $max = max( $repeated_words );
473
  $max = reset( $max );
474
 
475
  // Warn when more than 3x triplet+/quintet+ words are found.
476
+ if ( $max > 3 || \count( $repeated_words ) > 1 ) {
477
  // This must be resolved.
478
  $item['reason'] = $cache['reason']['foundmanydupe'];
479
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
480
  return $item;
481
  } else {
482
  $item['reason'] = $cache['reason']['founddupe'];
483
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_OKAY;
484
  }
485
  }
486
 
495
  );
496
 
497
  if ( $desc_len < $guidelines['lower'] ) {
498
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
499
  $item['reason'] = $guidelines_i18n['shortdot']['farTooShort'];
500
  $length_i18n = $guidelines_i18n['long']['farTooShort'];
501
  } elseif ( $desc_len < $guidelines['goodLower'] ) {
502
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_OKAY;
503
  $item['reason'] = $guidelines_i18n['shortdot']['tooShort'];
504
  $length_i18n = $guidelines_i18n['long']['tooShort'];
505
  } elseif ( $desc_len > $guidelines['upper'] ) {
506
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
507
  $item['reason'] = $guidelines_i18n['shortdot']['farTooLong'];
508
  $length_i18n = $guidelines_i18n['long']['farTooLong'];
509
  } elseif ( $desc_len > $guidelines['goodUpper'] ) {
510
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_OKAY;
511
  $item['reason'] = $guidelines_i18n['shortdot']['tooLong'];
512
  $length_i18n = $guidelines_i18n['long']['tooLong'];
513
  } else {
560
  'index' => [
561
  'symbol' => \_x( 'I', 'Indexing', 'autodescription' ),
562
  'title' => \__( 'Indexing', 'autodescription' ),
563
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_GOOD,
564
  'reason' => \__( 'Term may be indexed.', 'autodescription' ),
565
  'assess' => [
566
  'base' => \__( 'The robots meta tag allows indexing.', 'autodescription' ),
569
  'noindex' => [
570
  'symbol' => \_x( 'I', 'Indexing', 'autodescription' ),
571
  'title' => \__( 'Indexing', 'autodescription' ),
572
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN,
573
  'reason' => \__( 'Term may not be indexed.', 'autodescription' ),
574
  'assess' => [
575
  'base' => \__( 'The robots meta tag does not allow indexing.', 'autodescription' ),
588
  }
589
 
590
  if ( ! $robots_global['blogpublic'] ) {
591
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
592
  $item['reason'] = $cache['reason']['notpublic'];
593
 
594
  unset( $item['assess']['base'] );
644
  'get_custom_field' => true,
645
  ] );
646
  if ( $permalink !== $canonical ) {
647
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN;
648
  $item['reason'] = $cache['reason']['canonicalurl'];
649
 
650
  $item['assess']['protected'] = $cache['assess']['canonicalurl'];
654
  if ( $this->query_cache['states']['isempty'] ) {
655
  if ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
656
  // Everything's as intended...
657
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN;
658
  $item['reason'] = $cache['reason']['empty'];
659
 
660
  $item['assess']['empty'] = $cache['assess']['empty'];
661
  } else {
662
  // Something's wrong. Maybe override, maybe filter, maybe me.
663
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
664
 
665
  $item['reason'] = $cache['reason']['emptyoverride'];
666
  $item['assess']['empty'] = $cache['assess']['emptyoverride'];
706
  'follow' => [
707
  'symbol' => \_x( 'F', 'Following', 'autodescription' ),
708
  'title' => \__( 'Following', 'autodescription' ),
709
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_GOOD,
710
  'reason' => \__( 'Term links may be followed.', 'autodescription' ),
711
  'assess' => [
712
  'base' => \__( 'The robots meta tag allows link following.', 'autodescription' ),
715
  'nofollow' => [
716
  'symbol' => \_x( 'F', 'Following', 'autodescription' ),
717
  'title' => \__( 'Following', 'autodescription' ),
718
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN,
719
  'reason' => \__( 'Term links may not be followed.', 'autodescription' ),
720
  'assess' => [
721
  'base' => \__( 'The robots meta tag does not allow link following.', 'autodescription' ),
734
  }
735
 
736
  if ( ! $robots_global['blogpublic'] ) {
737
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
738
  $item['reason'] = $cache['reason']['notpublic'];
739
 
740
  unset( $item['assess']['base'] );
779
 
780
  if ( ! $this->query_cache['states']['robotsmeta']['nofollow'] ) {
781
  if ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
782
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_OKAY;
783
  $item['assess']['noindex'] = $cache['assess']['noindex'];
784
  }
785
 
823
  'archive' => [
824
  'symbol' => \_x( 'A', 'Archiving', 'autodescription' ),
825
  'title' => \__( 'Archiving', 'autodescription' ),
826
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_GOOD,
827
  'reason' => \__( 'Term may be archived.', 'autodescription' ),
828
  'assess' => [
829
  'base' => \__( 'The robots meta tag allows archiving.', 'autodescription' ),
832
  'noarchive' => [
833
  'symbol' => \_x( 'A', 'Archiving', 'autodescription' ),
834
  'title' => \__( 'Archiving', 'autodescription' ),
835
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN,
836
  'reason' => \__( 'Term may not be archived.', 'autodescription' ),
837
  'assess' => [
838
  'base' => \__( 'The robots meta tag does not allow archiving.', 'autodescription' ),
851
  }
852
 
853
  if ( ! $robots_global['blogpublic'] ) {
854
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_BAD;
855
  $item['reason'] = $cache['reason']['notpublic'];
856
 
857
  unset( $item['assess']['base'] );
896
 
897
  if ( ! $this->query_cache['states']['robotsmeta']['noarchive'] ) {
898
  if ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
899
+ $item['status'] = \The_SEO_Framework\Interpreters\SEOBar::STATE_OKAY;
900
  $item['assess']['noindex'] = $cache['assess']['noindex'];
901
  }
902
 
924
  [
925
  'symbol' => \_x( 'R', 'Redirect', 'autodescription' ),
926
  'title' => \__( 'Redirection', 'autodescription' ),
927
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_GOOD,
928
  'reason' => \__( 'Term does not redirect visitors.', 'autodescription' ),
929
  'assess' => [
930
  'redirect' => \__( 'All visitors and crawlers may access this page.', 'autodescription' ),
940
  [
941
  'symbol' => \_x( 'R', 'Redirect', 'autodescription' ),
942
  'title' => \__( 'Redirection', 'autodescription' ),
943
+ 'status' => \The_SEO_Framework\Interpreters\SEOBar::STATE_UNKNOWN,
944
  'reason' => \__( 'Term redirects visitors.', 'autodescription' ),
945
  'assess' => [
946
  'redirect' => \__( 'All visitors and crawlers are being redirected. So, no other SEO enhancements are effective.', 'autodescription' ),
inc/classes/builders/sitemap.class.php CHANGED
@@ -25,261 +25,18 @@ namespace The_SEO_Framework\Builders;
25
 
26
  \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
 
 
28
  /**
29
  * Generates the sitemap.
30
  *
31
  * @since 4.0.0
 
 
32
  * @abstract
 
 
33
  *
34
- * @access public
 
35
  */
36
- abstract class Sitemap {
37
-
38
- /**
39
- * @var null|\The_SEO_Framework\Load
40
- */
41
- protected static $tsf = null;
42
-
43
- /**
44
- * Constructor.
45
- *
46
- * @since 4.0.0
47
- */
48
- final public function __construct() {
49
- static::$tsf = \the_seo_framework();
50
- }
51
-
52
- /**
53
- * Destructor.
54
- *
55
- * @since 4.0.0
56
- */
57
- final public function __destruct() {
58
- static::$tsf = null;
59
- }
60
-
61
- /**
62
- * Prepares sitemap generation by raising the memory limit and fixing the timezone.
63
- *
64
- * @since 4.0.0
65
- * @since 4.0.4 Now sets timezone to UTC to fix WP 5.3 bug <https://core.trac.wordpress.org/ticket/48623>
66
- */
67
- final public function prepare_generation() {
68
-
69
- \wp_raise_memory_limit( 'sitemap' );
70
-
71
- // Set timezone according to settings.
72
- static::$tsf->set_timezone( 'UTC' );
73
- }
74
-
75
- /**
76
- * Shuts down the sitemap generator.
77
- *
78
- * @since 4.0.0
79
- */
80
- final public function shutdown_generation() {
81
- static::$tsf->reset_timezone();
82
- }
83
-
84
- /**
85
- * Generates and returns the sitemap content.
86
- * We recommend you overwriting this method to include caching.
87
- *
88
- * @since 4.1.2
89
- * @abstract
90
- * TODO consider adding ...$args?
91
- *
92
- * @return string The sitemap content.
93
- */
94
- public function generate_sitemap() {
95
-
96
- $this->prepare_generation();
97
-
98
- $sitemap = $this->build_sitemap();
99
-
100
- $this->shutdown_generation();
101
-
102
- return $sitemap;
103
- }
104
-
105
- /**
106
- * Returns the sitemap content.
107
- *
108
- * @since 4.0.0
109
- * @abstract
110
- *
111
- * @return string The sitemap content.
112
- */
113
- abstract public function build_sitemap();
114
-
115
- /**
116
- * Creates XML entry from array input.
117
- * Input is expected to be escaped and XML-safe.
118
- *
119
- * Note: Not final, other classes may overwrite this.
120
- *
121
- * @since 4.1.1
122
- *
123
- * @param array $data The data to create an XML item from. Expected to be escaped and XML-safe!
124
- * @param int $level The iteration level. Default 1 (one level in from urlset).
125
- * Affects non-mandatory tab indentation for readability.
126
- * @return string The XML data.
127
- */
128
- protected function create_xml_entry( $data, $level = 1 ) {
129
-
130
- $out = '';
131
-
132
- foreach ( $data as $key => $value ) {
133
- $tabs = str_repeat( "\t", $level );
134
-
135
- if ( \is_array( $value ) )
136
- $value = "\n" . $this->create_xml_entry( $value, $level + 1 ) . $tabs;
137
-
138
- $out .= "$tabs<$key>$value</$key>\n";
139
- }
140
-
141
- return $out;
142
- }
143
-
144
- /**
145
- * Determines if post is possibly included in the sitemap.
146
- *
147
- * This is a weak check, as the filter might not be present outside of the sitemap's scope.
148
- * The URL also isn't checked, nor the position.
149
- *
150
- * @since 3.0.4
151
- * @since 3.0.6 First filter value now works as intended.
152
- * @since 3.1.0 1. Resolved a PHP notice when ID is 0, resulting in returning false-esque unintentionally.
153
- * 2. Now accepts 0 in the filter.
154
- * @since 4.0.0 1. Now tests qubit options.
155
- * 2. FALSE: Now tests for redirect settings. <- it never did! We did document this though...
156
- * 3. First parameter can now be a post object.
157
- * 4. If the first parameter is 0, it's now indicative of a home-as-blog page.
158
- * 5. Moved to \The_SEO_Framework\Builders\Sitemap
159
- * @since 4.1.4 TRUE: Now tests for redirect settings.
160
- *
161
- * @param int $post_id The Post ID to check.
162
- * @return bool True if included, false otherwise.
163
- */
164
- final public function is_post_included_in_sitemap( $post_id ) {
165
-
166
- static $excluded = null;
167
- if ( null === $excluded ) {
168
- /**
169
- * @since 2.5.2
170
- * @since 2.8.0 No longer accepts '0' as entry.
171
- * @since 3.1.0 '0' is accepted again.
172
- * @param int[] $excluded Sequential list of excluded IDs: [ int ...post_id ]
173
- */
174
- $excluded = (array) \apply_filters( 'the_seo_framework_sitemap_exclude_ids', [] );
175
-
176
- if ( empty( $excluded ) ) {
177
- $excluded = [];
178
- } else {
179
- // isset() is faster than in_array(). So, we flip it.
180
- $excluded = array_flip( $excluded );
181
- }
182
- }
183
-
184
- $included = ! isset( $excluded[ $post_id ] );
185
-
186
- while ( $included ) :
187
- $_args = [
188
- 'id' => $post_id,
189
- 'taxonomy' => '',
190
- ];
191
-
192
- // ROBOTS_IGNORE_PROTECTION as we don't need to test 'private' (because of sole 'publish'), and 'password' (because of false 'has_password')
193
- $meta = static::$tsf->generate_robots_meta( $_args, null, \The_SEO_Framework\ROBOTS_IGNORE_PROTECTION );
194
- $included = ! ( isset( $meta['noindex'] ) && 'noindex' === $meta['noindex'] );
195
-
196
- if ( ! $included ) break;
197
-
198
- $included = ! static::$tsf->get_redirect_url( $_args );
199
- break;
200
- endwhile;
201
-
202
- return $included;
203
- }
204
-
205
- /**
206
- * Determines if post is possibly included in the sitemap.
207
- *
208
- * This is a weak check, as the filter might not be present outside of the sitemap's scope.
209
- * The URL also isn't checked, nor the position.
210
- *
211
- * @since 4.0.0
212
- * @since 4.1.4 Now tests for redirect settings.
213
- * @see https://github.com/sybrew/tsf-term-sitemap for example.
214
- *
215
- * @param int $term_id The Term ID to check.
216
- * @param string $taxonomy The taxonomy.
217
- * @return bool True if included, false otherwise.
218
- */
219
- final public function is_term_included_in_sitemap( $term_id, $taxonomy ) {
220
-
221
- static $excluded = null;
222
- if ( null === $excluded ) {
223
- /**
224
- * @since 4.0.0
225
- * @param int[] $excluded Sequential list of excluded IDs: [ int ...term_id ]
226
- */
227
- $excluded = (array) \apply_filters( 'the_seo_framework_sitemap_exclude_term_ids', [] );
228
-
229
- if ( empty( $excluded ) ) {
230
- $excluded = [];
231
- } else {
232
- // isset() is faster than in_array(). So, we flip it.
233
- $excluded = array_flip( $excluded );
234
- }
235
- }
236
-
237
- $included = ! isset( $excluded[ $term_id ] );
238
-
239
- // Yes, 90% of this code code isn't DRY. However, terms != posts. terms == posts, though :).
240
- // Really: <https://core.trac.wordpress.org/ticket/50568>
241
- while ( $included ) :
242
- $_args = [
243
- 'id' => $term_id,
244
- 'taxonomy' => $taxonomy,
245
- ];
246
-
247
- // ROBOTS_IGNORE_PROTECTION is not tested for terms. However, we may use that later.
248
- $meta = static::$tsf->generate_robots_meta( $_args, null, \The_SEO_Framework\ROBOTS_IGNORE_PROTECTION );
249
- $included = ! ( isset( $meta['noindex'] ) && 'noindex' === $meta['noindex'] );
250
-
251
- if ( ! $included ) break;
252
-
253
- $included = ! static::$tsf->get_redirect_url( $_args );
254
- break;
255
- endwhile;
256
-
257
- return $included;
258
- }
259
-
260
- /**
261
- * Returns the sitemap post query limit.
262
- *
263
- * @since 3.1.0
264
- * @since 4.0.0 Moved to \The_SEO_Framework\Builders\Sitemap
265
- *
266
- * @param bool $hierarchical Whether the query is for hierarchical post types or not.
267
- * @return int The post limit
268
- */
269
- final protected function get_sitemap_post_limit( $hierarchical = false ) {
270
- /**
271
- * @since 2.2.9
272
- * @since 2.8.0 Increased to 1200 from 700.
273
- * @since 3.1.0 Now returns an option value; it falls back to the default value if not set.
274
- * @since 4.0.0 1. The default is now 3000, from 1200.
275
- * 2. Now passes a second parameter.
276
- * @param int $total_post_limit
277
- * @param bool $hierarchical Whether the query is for hierarchical post types or not.
278
- */
279
- return (int) \apply_filters(
280
- 'the_seo_framework_sitemap_post_limit',
281
- static::$tsf->get_option( 'sitemap_query_limit' ),
282
- $hierarchical
283
- );
284
- }
285
- }
25
 
26
  \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
 
28
+ \tsf()->_deprecated_function( 'The_SEO_Framework\Builders\Sitemap', '4.2.0', 'The_SEO_Framework\Builders\Sitemap\Main' );
29
  /**
30
  * Generates the sitemap.
31
  *
32
  * @since 4.0.0
33
+ * @since 4.2.0 1. Moved to \The_SEO_Framework\Builders\Sitemap\Main
34
+ * 2. Deprecated.
35
  * @abstract
36
+ * @deprecated
37
+ * @ignore
38
  *
39
+ * @access protected
40
+ * Use \The_SEO_Framework\Builders\Sitemap\Main instead.
41
  */
42
+ class_alias( 'The_SEO_Framework\Builders\Sitemap\Main', 'The_SEO_Framework\Builders\Sitemap', true );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/classes/builders/{sitemap-base.class.php → sitemap/base.class.php} RENAMED
@@ -4,7 +4,7 @@
4
  * @subpackage The_SEO_Framework\Sitemap
5
  */
6
 
7
- namespace The_SEO_Framework\Builders;
8
 
9
  /**
10
  * The SEO Framework plugin
@@ -25,14 +25,17 @@ namespace The_SEO_Framework\Builders;
25
 
26
  \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
 
 
 
28
  /**
29
  * Generates the base sitemap.
30
  *
31
  * @since 4.0.0
 
32
  *
33
  * @access private
34
  */
35
- class Sitemap_Base extends Sitemap {
36
 
37
  /**
38
  * @since 4.1.2
@@ -145,7 +148,7 @@ class Sitemap_Base extends Sitemap {
145
  * @since 4.0.0 1. Now assesses all public post types, in favor of qubit options.
146
  * 2. Improved performance by a factor of two+.
147
  * 3. Renamed method from "generate_sitemap" to abstract extension "build_sitemap".
148
- * 4. Moved to \The_SEO_Framework\Builders\Sitemap_Base
149
  * @abstract
150
  *
151
  * @return string The sitemap content.
@@ -155,7 +158,6 @@ class Sitemap_Base extends Sitemap {
155
  $content = '';
156
  $count = 0;
157
 
158
- $show_priority = (bool) static::$tsf->get_option( 'sitemaps_priority' );
159
  $show_modified = (bool) static::$tsf->get_option( 'sitemaps_modified' );
160
 
161
  /**
@@ -168,19 +170,17 @@ class Sitemap_Base extends Sitemap {
168
  $content .= sprintf(
169
  '<!-- %s -->',
170
  sprintf(
171
- (
172
- $this->base_is_prerendering
173
- /* translators: %s = timestamp */
174
- ? \esc_html__( 'Sitemap is prerendered on %s', 'autodescription' )
175
- /* translators: %s = timestamp */
176
- : \esc_html__( 'Sitemap is generated on %s', 'autodescription' )
177
- ),
178
  \current_time( 'Y-m-d H:i:s \G\M\T' )
179
  )
180
  ) . "\n";
181
 
182
  foreach ( $this->generate_front_and_blog_url_items(
183
- compact( 'show_priority', 'show_modified' ),
184
  $count
185
  ) as $_values ) {
186
  $content .= $this->build_url_item( $_values );
@@ -303,7 +303,7 @@ class Sitemap_Base extends Sitemap {
303
 
304
  foreach ( $this->generate_url_item_values(
305
  $_items,
306
- compact( 'show_priority', 'show_modified', 'total_items' ),
307
  $count
308
  ) as $_values ) {
309
  $content .= $this->build_url_item( $_values );
@@ -311,7 +311,7 @@ class Sitemap_Base extends Sitemap {
311
 
312
  if ( \has_filter( 'the_seo_framework_sitemap_additional_urls' ) ) {
313
  foreach ( $this->generate_additional_base_urls(
314
- compact( 'show_priority', 'show_modified', 'count' ),
315
  $count
316
  ) as $_values ) {
317
  $content .= $this->build_url_item( $_values );
@@ -324,9 +324,9 @@ class Sitemap_Base extends Sitemap {
324
  *
325
  * @since 2.5.2
326
  * @since 4.0.0 Added $args parameter.
 
327
  * @param string $extend Custom sitemap extension. Must be escaped.
328
  * @param array $args : {
329
- * bool $show_priority : Whether to display priority
330
  * bool $show_modified : Whether to display modified date.
331
  * int $total_itemns : Estimate: The total sitemap items before adding additional URLs.
332
  * }
@@ -335,7 +335,7 @@ class Sitemap_Base extends Sitemap {
335
  'the_seo_framework_sitemap_extend',
336
  [
337
  '',
338
- compact( 'show_priority', 'show_modified', 'count' ),
339
  ]
340
  );
341
 
@@ -362,86 +362,59 @@ class Sitemap_Base extends Sitemap {
362
  */
363
  protected function generate_front_and_blog_url_items( $args, &$count = 0 ) {
364
 
365
- $front_page_id = (int) \get_option( 'page_on_front' );
366
- $posts_page_id = (int) \get_option( 'page_for_posts' );
367
-
368
  if ( static::$tsf->has_page_on_front() ) {
369
- if ( $front_page_id && $this->is_post_included_in_sitemap( $front_page_id ) ) {
370
- // TODO use this instead @ PHP7
371
- // yield from $this->generate_url_item_values()
372
-
373
- // Reset.
374
- $_values = [];
375
- $_values['loc'] = static::$tsf->create_canonical_url(
376
- [
377
- 'id' => $front_page_id,
378
- 'taxonomy' => '',
379
- ]
380
  );
381
-
382
- if ( $args['show_modified'] ) {
383
- $post = \get_post( $front_page_id );
384
- $_values['lastmod'] = isset( $post->post_modified_gmt ) ? $post->post_modified_gmt : false;
385
- }
386
-
387
- if ( $args['show_priority'] ) {
388
- $_values['priority'] = '1.0';
389
- }
390
-
391
- ++$count;
392
- yield $_values;
393
  }
394
- if ( $posts_page_id && $this->is_post_included_in_sitemap( $posts_page_id ) ) {
395
- // Reset.
396
- $_values = [];
397
- $_values['loc'] = static::$tsf->create_canonical_url(
398
- [
399
- 'id' => $posts_page_id,
400
- 'taxonomy' => '',
401
- ]
402
- );
403
-
404
- if ( $args['show_modified'] ) {
405
- $latests_posts = \wp_get_recent_posts(
406
- [
407
- 'numberposts' => 1,
408
- 'post_type' => 'post',
409
- 'post_status' => 'publish',
410
- 'has_password' => false,
411
- 'orderby' => 'post_date',
412
- 'order' => 'DESC',
413
- 'offset' => 0,
414
- ],
415
- \OBJECT
416
- );
417
- $latest_post = isset( $latests_posts[0] ) ? $latests_posts[0] : null;
418
- $_publish_post = isset( $latest_post->post_date_gmt ) ? $latest_post->post_date_gmt : '0000-00-00 00:00:00';
419
 
420
- $post = \get_post( $posts_page_id );
421
- $_lastmod_blog = isset( $post->post_modified_gmt ) ? $post->post_modified_gmt : '0000-00-00 00:00:00';
422
-
423
- if ( strtotime( $_publish_post ) > strtotime( $_lastmod_blog ) ) {
424
- $_values['lastmod'] = $_publish_post;
425
- } else {
426
- $_values['lastmod'] = $_lastmod_blog;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
  }
428
 
429
- /**
430
- * @since 4.1.1
431
- * @param string $lastmod The lastmod time in SQL notation (`Y-m-d H:i:s`). Expected to explicitly follow that format!
432
- */
433
- $_values['lastmod'] = (string) \apply_filters( 'the_seo_framework_sitemap_blog_lastmod', $_values['lastmod'] );
434
- }
435
-
436
- if ( $args['show_priority'] ) {
437
- $_values['priority'] = '1.0';
438
  }
439
-
440
- ++$count;
441
- yield $_values;
442
  }
443
  } else {
444
- // Blog page as front.
445
  if ( $this->is_post_included_in_sitemap( 0 ) ) {
446
  // Reset.
447
  $_values = [];
@@ -458,20 +431,19 @@ class Sitemap_Base extends Sitemap {
458
  'order' => 'DESC',
459
  'offset' => 0,
460
  ],
461
- \OBJECT
462
  );
463
 
464
- $_values['lastmod'] = isset( $latests_posts[0]->post_date_gmt ) ? $latests_posts[0]->post_date_gmt : '0000-00-00 00:00:00';
465
-
466
  /**
467
  * @since 4.1.1
468
  * @param string $lastmod The lastmod time in SQL notation (`Y-m-d H:i:s`). Expected to explicitly follow that format!
469
  */
470
- $_values['lastmod'] = (string) \apply_filters( 'the_seo_framework_sitemap_blog_lastmod', $_values['lastmod'] );
471
- }
472
-
473
- if ( $args['show_priority'] ) {
474
- $_values['priority'] = '1.0';
 
475
  }
476
 
477
  ++$count;
@@ -499,36 +471,24 @@ class Sitemap_Base extends Sitemap {
499
  */
500
  protected function generate_url_item_values( $post_ids, $args, &$count = 0 ) {
501
 
502
- static $using_external_object_cache = null;
503
-
504
- $using_external_object_cache = isset( $using_external_object_cache ) ? $using_external_object_cache : (bool) \wp_using_ext_object_cache();
505
-
506
  foreach ( $post_ids as $post_id ) {
507
  // Setup post cache, which is also used in is_post_included_in_sitemap() and create_canonical_url().
508
  $post = \get_post( $post_id );
509
 
510
  if ( $this->is_post_included_in_sitemap( $post_id ) ) {
511
- $_values = [];
512
- $_values['loc'] = static::$tsf->create_canonical_url(
513
- [
514
- 'id' => $post_id,
515
- 'taxonomy' => '',
516
- ]
517
- );
518
 
519
  if ( $args['show_modified'] )
520
- $_values['lastmod'] = isset( $post->post_modified_gmt ) ? $post->post_modified_gmt : '0000-00-00 00:00:00';
521
-
522
- if ( $args['show_priority'] ) {
523
- // Add at least 1 to prevent going negative. We added 8 extra (= 9) to smoothen the slope.
524
- $_values['priority'] = .949999 - ( $count / ( $args['total_items'] + 9 ) );
525
- }
526
 
527
  ++$count;
528
  yield $_values;
529
  }
530
 
531
- $using_external_object_cache or \clean_post_cache( $post );
 
532
  }
533
  }
534
 
@@ -549,20 +509,17 @@ class Sitemap_Base extends Sitemap {
549
 
550
  if ( empty( $args['loc'] ) ) return '';
551
 
552
- static $timestamp_format = null;
553
-
554
- if ( ! isset( $timestamp_format ) )
555
- $timestamp_format = static::$tsf->get_timestamp_format();
556
-
557
  $xml = [
558
  'loc' => $args['loc'], // Already escaped.
559
  ];
560
 
561
- if ( isset( $args['lastmod'] ) && '0000-00-00 00:00:00' !== $args['lastmod'] )
562
- $xml['lastmod'] = static::$tsf->gmt2date( $timestamp_format, $args['lastmod'] );
563
 
564
- if ( isset( $args['priority'] ) && is_numeric( $args['priority'] ) )
565
- $xml['priority'] = number_format( $args['priority'], 1, '.', ',' );
 
 
566
 
567
  return $this->create_xml_entry( [ 'url' => $xml ], 1 );
568
  }
@@ -571,13 +528,12 @@ class Sitemap_Base extends Sitemap {
571
  * Retrieves additional URLs and builds items from them.
572
  *
573
  * @since 4.0.0
574
- * @since 4.0.1 : 1. Converted to generator and iterator. Therefore, renamed function.
575
- * 2. Now actually does something.
576
  * @generator
577
  * @iterator
578
  *
579
  * @param array $args : {
580
- * bool $show_priority : Whether to display priority
581
  * bool $show_modified : Whether to display modified date.
582
  * int $count : The total sitemap items before adding additional URLs.
583
  * }
@@ -589,12 +545,12 @@ class Sitemap_Base extends Sitemap {
589
  * }
590
  */
591
  protected function generate_additional_base_urls( $args, &$count = 0 ) {
592
-
593
  /**
594
  * @since 2.5.2
595
  * @since 3.2.2 Invalid URLs are now skipped.
596
  * @since 4.0.0 Added $args parameter.
597
- * @example return value: [ 'http://example.com' => [ 'lastmod' => '14-01-2018', 'priority' => 0.9 ] ]
 
598
  * @param array $custom_urls : {
599
  * string (key) $url The absolute url to the page. : array {
600
  * string $lastmod : UNIXTIME <GMT+0> Last modified date, e.g. "2016-01-26 13:04:55"
@@ -602,7 +558,6 @@ class Sitemap_Base extends Sitemap {
602
  * }
603
  * }
604
  * @param array $args : {
605
- * bool $show_priority : Whether to display priority
606
  * bool $show_modified : Whether to display modified date.
607
  * int $count : Estimate: The total sitemap items before adding additional URLs.
608
  * }
@@ -625,9 +580,6 @@ class Sitemap_Base extends Sitemap {
625
  if ( $args['show_modified'] )
626
  $_values['lastmod'] = ! empty( $values['lastmod'] ) ? $values['lastmod'] : '0000-00-00 00:00:00';
627
 
628
- if ( $args['show_priority'] )
629
- $_values['priority'] = ! empty( $values['priority'] ) ? $values['priority'] : 0.9;
630
-
631
  ++$count;
632
  yield $_values;
633
  }
4
  * @subpackage The_SEO_Framework\Sitemap
5
  */
6
 
7
+ namespace The_SEO_Framework\Builders\Sitemap;
8
 
9
  /**
10
  * The SEO Framework plugin
25
 
26
  \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
 
28
+ use function \The_SEO_Framework\umemo;
29
+
30
  /**
31
  * Generates the base sitemap.
32
  *
33
  * @since 4.0.0
34
+ * @since 4.2.0 Renamed to `The_SEO_Framework\Builders\Sitemap\Base` from `The_SEO_Framework\Builders\Sitemap_Base`
35
  *
36
  * @access private
37
  */
38
+ class Base extends Main {
39
 
40
  /**
41
  * @since 4.1.2
148
  * @since 4.0.0 1. Now assesses all public post types, in favor of qubit options.
149
  * 2. Improved performance by a factor of two+.
150
  * 3. Renamed method from "generate_sitemap" to abstract extension "build_sitemap".
151
+ * 4. Moved to \The_SEO_Framework\Builders\Sitemap\Base
152
  * @abstract
153
  *
154
  * @return string The sitemap content.
158
  $content = '';
159
  $count = 0;
160
 
 
161
  $show_modified = (bool) static::$tsf->get_option( 'sitemaps_modified' );
162
 
163
  /**
170
  $content .= sprintf(
171
  '<!-- %s -->',
172
  sprintf(
173
+ $this->base_is_prerendering
174
+ /* translators: %s = timestamp */
175
+ ? \esc_html__( 'Sitemap is prerendered on %s', 'autodescription' )
176
+ /* translators: %s = timestamp */
177
+ : \esc_html__( 'Sitemap is generated on %s', 'autodescription' ),
 
 
178
  \current_time( 'Y-m-d H:i:s \G\M\T' )
179
  )
180
  ) . "\n";
181
 
182
  foreach ( $this->generate_front_and_blog_url_items(
183
+ compact( 'show_modified' ),
184
  $count
185
  ) as $_values ) {
186
  $content .= $this->build_url_item( $_values );
303
 
304
  foreach ( $this->generate_url_item_values(
305
  $_items,
306
+ compact( 'show_modified', 'total_items' ),
307
  $count
308
  ) as $_values ) {
309
  $content .= $this->build_url_item( $_values );
311
 
312
  if ( \has_filter( 'the_seo_framework_sitemap_additional_urls' ) ) {
313
  foreach ( $this->generate_additional_base_urls(
314
+ compact( 'show_modified', 'count' ),
315
  $count
316
  ) as $_values ) {
317
  $content .= $this->build_url_item( $_values );
324
  *
325
  * @since 2.5.2
326
  * @since 4.0.0 Added $args parameter.
327
+ * @since 4.2.0 No longer forwards the 'show_priority' index in the second ($args) parameter.
328
  * @param string $extend Custom sitemap extension. Must be escaped.
329
  * @param array $args : {
 
330
  * bool $show_modified : Whether to display modified date.
331
  * int $total_itemns : Estimate: The total sitemap items before adding additional URLs.
332
  * }
335
  'the_seo_framework_sitemap_extend',
336
  [
337
  '',
338
+ compact( 'show_modified', 'count' ),
339
  ]
340
  );
341
 
362
  */
363
  protected function generate_front_and_blog_url_items( $args, &$count = 0 ) {
364
 
 
 
 
365
  if ( static::$tsf->has_page_on_front() ) {
366
+ $front_page_id = (int) \get_option( 'page_on_front' );
367
+ $posts_page_id = (int) \get_option( 'page_for_posts' );
368
+
369
+ if ( $front_page_id ) {
370
+ yield from $this->generate_url_item_values(
371
+ [ $front_page_id ],
372
+ $args,
373
+ $count
 
 
 
374
  );
 
 
 
 
 
 
 
 
 
 
 
 
375
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
 
377
+ if ( $posts_page_id && $this->is_post_included_in_sitemap( $posts_page_id ) ) {
378
+ foreach ( $this->generate_url_item_values(
379
+ [ $posts_page_id ],
380
+ $args,
381
+ $count
382
+ ) as $_values ) {
383
+ if ( $_values['loc'] && $args['show_modified'] ) {
384
+ $latests_posts = \wp_get_recent_posts(
385
+ [
386
+ 'numberposts' => 1,
387
+ 'post_type' => 'post',
388
+ 'post_status' => 'publish',
389
+ 'has_password' => false,
390
+ 'orderby' => 'post_date',
391
+ 'order' => 'DESC',
392
+ 'offset' => 0,
393
+ ],
394
+ OBJECT
395
+ );
396
+ $_publish_post = $latests_posts[0]->post_date_gmt ?? '0000-00-00 00:00:00';
397
+ $_lastmod_blog = $_values['lastmod']; // Inferred from generator generate_url_item_values()
398
+
399
+ /**
400
+ * @since 4.1.1
401
+ * @param string $lastmod The lastmod time in SQL notation (`Y-m-d H:i:s`). Expected to explicitly follow that format!
402
+ */
403
+ $_values['lastmod'] = (string) \apply_filters_ref_array(
404
+ 'the_seo_framework_sitemap_blog_lastmod',
405
+ [
406
+ strtotime( $_publish_post ) > strtotime( $_lastmod_blog )
407
+ ? $_publish_post
408
+ : $_lastmod_blog,
409
+ ]
410
+ );
411
  }
412
 
413
+ yield $_values;
 
 
 
 
 
 
 
 
414
  }
 
 
 
415
  }
416
  } else {
417
+ // Blog page as front. Unique; cannot go through generate_url_item_values().
418
  if ( $this->is_post_included_in_sitemap( 0 ) ) {
419
  // Reset.
420
  $_values = [];
431
  'order' => 'DESC',
432
  'offset' => 0,
433
  ],
434
+ OBJECT
435
  );
436
 
 
 
437
  /**
438
  * @since 4.1.1
439
  * @param string $lastmod The lastmod time in SQL notation (`Y-m-d H:i:s`). Expected to explicitly follow that format!
440
  */
441
+ $_values['lastmod'] = (string) \apply_filters_ref_array(
442
+ 'the_seo_framework_sitemap_blog_lastmod',
443
+ [
444
+ $latests_posts[0]->post_date_gmt ?? '0000-00-00 00:00:00',
445
+ ]
446
+ );
447
  }
448
 
449
  ++$count;
471
  */
472
  protected function generate_url_item_values( $post_ids, $args, &$count = 0 ) {
473
 
 
 
 
 
474
  foreach ( $post_ids as $post_id ) {
475
  // Setup post cache, which is also used in is_post_included_in_sitemap() and create_canonical_url().
476
  $post = \get_post( $post_id );
477
 
478
  if ( $this->is_post_included_in_sitemap( $post_id ) ) {
479
+ $_values = [
480
+ 'loc' => static::$tsf->create_canonical_url( [ 'id' => $post_id ] ),
481
+ ];
 
 
 
 
482
 
483
  if ( $args['show_modified'] )
484
+ $_values['lastmod'] = $post->post_modified_gmt ?? '0000-00-00 00:00:00';
 
 
 
 
 
485
 
486
  ++$count;
487
  yield $_values;
488
  }
489
 
490
+ // Only clean post cache when NOT using an external object caching plugin.
491
+ \wp_using_ext_object_cache() or \clean_post_cache( $post );
492
  }
493
  }
494
 
509
 
510
  if ( empty( $args['loc'] ) ) return '';
511
 
 
 
 
 
 
512
  $xml = [
513
  'loc' => $args['loc'], // Already escaped.
514
  ];
515
 
516
+ if ( isset( $args['lastmod'] ) && '0000-00-00 00:00:00' !== $args['lastmod'] ) {
517
+ static $timestamp_format;
518
 
519
+ $timestamp_format = $timestamp_format ?? static::$tsf->get_timestamp_format();
520
+
521
+ $xml['lastmod'] = static::$tsf->gmt2date( $timestamp_format, $args['lastmod'] );
522
+ }
523
 
524
  return $this->create_xml_entry( [ 'url' => $xml ], 1 );
525
  }
528
  * Retrieves additional URLs and builds items from them.
529
  *
530
  * @since 4.0.0
531
+ * @since 4.0.1 1. Converted to generator and iterator. Therefore, renamed function.
532
+ * 2. Now actually does something.
533
  * @generator
534
  * @iterator
535
  *
536
  * @param array $args : {
 
537
  * bool $show_modified : Whether to display modified date.
538
  * int $count : The total sitemap items before adding additional URLs.
539
  * }
545
  * }
546
  */
547
  protected function generate_additional_base_urls( $args, &$count = 0 ) {
 
548
  /**
549
  * @since 2.5.2
550
  * @since 3.2.2 Invalid URLs are now skipped.
551
  * @since 4.0.0 Added $args parameter.
552
+ * @since 4.2.0 No longer forwards the 'show_priority' index in the second ($args) parameter.
553
+ * @example return value: [ 'http://example.com' => [ 'lastmod' => '14-01-2018' ] ]
554
  * @param array $custom_urls : {
555
  * string (key) $url The absolute url to the page. : array {
556
  * string $lastmod : UNIXTIME <GMT+0> Last modified date, e.g. "2016-01-26 13:04:55"
558
  * }
559
  * }
560
  * @param array $args : {
 
561
  * bool $show_modified : Whether to display modified date.
562
  * int $count : Estimate: The total sitemap items before adding additional URLs.
563
  * }
580
  if ( $args['show_modified'] )
581
  $_values['lastmod'] = ! empty( $values['lastmod'] ) ? $values['lastmod'] : '0000-00-00 00:00:00';
582
 
 
 
 
583
  ++$count;
584
  yield $_values;
585
  }
inc/classes/builders/sitemap/index.php ADDED
File without changes
inc/classes/builders/sitemap/main.class.php ADDED
@@ -0,0 +1,280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Classes\Builders\Sitemap
4
+ * @subpackage The_SEO_Framework\Sitemap
5
+ */
6
+
7
+ namespace The_SEO_Framework\Builders\Sitemap;
8
+
9
+ /**
10
+ * The SEO Framework plugin
11
+ * Copyright (C) 2019 - 2021 Sybre Waaijer, CyberWire B.V. (https://cyberwire.nl/)
12
+ *
13
+ * This program is free software: you can redistribute it and/or modify
14
+ * it under the terms of the GNU General Public License version 3 as published
15
+ * by the Free Software Foundation.
16
+ *
17
+ * This program is distributed in the hope that it will be useful,
18
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
+ * GNU General Public License for more details.
21
+ *
22
+ * You should have received a copy of the GNU General Public License
23
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
24
+ */
25
+
26
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
+
28
+ /**
29
+ * Generates the sitemap.
30
+ *
31
+ * @since 4.0.0
32
+ * @since 4.2.0 Renamed to `The_SEO_Framework\Builders\Sitemap\Main` from `The_SEO_Framework\Builders\Sitemap`
33
+ * @abstract
34
+ *
35
+ * @access public
36
+ */
37
+ abstract class Main {
38
+
39
+ /**
40
+ * @var null|\The_SEO_Framework\Load
41
+ */
42
+ protected static $tsf = null;
43
+
44
+ /**
45
+ * Constructor.
46
+ *
47
+ * @since 4.0.0
48
+ */
49
+ final public function __construct() {
50
+ static::$tsf = \tsf();
51
+ }
52
+
53
+ /**
54
+ * Destructor.
55
+ *
56
+ * @since 4.0.0
57
+ */
58
+ final public function __destruct() {
59
+ static::$tsf = null;
60
+ }
61
+
62
+ /**
63
+ * Prepares sitemap generation by raising the memory limit and fixing the timezone.
64
+ *
65
+ * @since 4.0.0
66
+ * @since 4.0.4 Now sets timezone to UTC to fix WP 5.3 bug <https://core.trac.wordpress.org/ticket/48623>
67
+ * @since 4.2.0 No longer sets timezone.
68
+ */
69
+ final public function prepare_generation() {
70
+ \wp_raise_memory_limit( 'sitemap' );
71
+ }
72
+
73
+ /**
74
+ * Shuts down the sitemap generator.
75
+ *
76
+ * @since 4.0.0
77
+ * @since 4.2.0 No longer resets timezone.
78
+ * @ignore
79
+ */
80
+ final public function shutdown_generation() { }
81
+
82
+ /**
83
+ * Generates and returns the sitemap content.
84
+ * We recommend you overwriting this method to include caching.
85
+ *
86
+ * @since 4.1.2
87
+ * @abstract
88
+ * TODO consider adding ...$args?
89
+ *
90
+ * @return string The sitemap content.
91
+ */
92
+ public function generate_sitemap() {
93
+
94
+ $this->prepare_generation();
95
+
96
+ $sitemap = $this->build_sitemap();
97
+
98
+ $this->shutdown_generation();
99
+
100
+ return $sitemap;
101
+ }
102
+
103
+ /**
104
+ * Returns the sitemap content.
105
+ *
106
+ * @since 4.0.0
107
+ * @abstract
108
+ *
109
+ * @return string The sitemap content.
110
+ */
111
+ abstract public function build_sitemap();
112
+
113
+ /**
114
+ * Creates XML entry from array input.
115
+ * Input is expected to be escaped and XML-safe.
116
+ *
117
+ * Note: Not final, other classes may overwrite this.
118
+ *
119
+ * @since 4.1.1
120
+ *
121
+ * @param iterable $data The data to create an XML item from. Expected to be escaped and XML-safe!
122
+ * @param int $level The iteration level. Default 1 (one level in from urlset).
123
+ * Affects non-mandatory tab indentation for readability.
124
+ * @return string The XML data.
125
+ */
126
+ protected function create_xml_entry( $data, $level = 1 ) {
127
+
128
+ $out = '';
129
+
130
+ foreach ( $data as $key => $value ) {
131
+ $tabs = str_repeat( "\t", $level );
132
+
133
+ if ( \is_array( $value ) )
134
+ $value = "\n" . $this->create_xml_entry( $value, $level + 1 ) . $tabs;
135
+
136
+ $out .= "$tabs<$key>$value</$key>\n";
137
+ }
138
+
139
+ return $out;
140
+ }
141
+
142
+ /**
143
+ * Determines if post is possibly included in the sitemap.
144
+ *
145
+ * This is a weak check, as the filter might not be present outside of the sitemap's scope.
146
+ * The URL also isn't checked, nor the position.
147
+ *
148
+ * @since 3.0.4
149
+ * @since 3.0.6 First filter value now works as intended.
150
+ * @since 3.1.0 1. Resolved a PHP notice when ID is 0, resulting in returning false-esque unintentionally.
151
+ * 2. Now accepts 0 in the filter.
152
+ * @since 4.0.0 1. Now tests qubit options.
153
+ * 2. FALSE: Now tests for redirect settings. <- it never did! We did document this though...
154
+ * 3. First parameter can now be a post object.
155
+ * 4. If the first parameter is 0, it's now indicative of a home-as-blog page.
156
+ * 5. Moved to \The_SEO_Framework\Builders\Sitemap
157
+ * @since 4.1.4 TRUE: Now tests for redirect settings.
158
+ * @since 4.2.0 Now only asserts noindex robots-values, instead of all robots-values, improving performance.
159
+ *
160
+ * @param int $post_id The Post ID to check.
161
+ * @return bool True if included, false otherwise.
162
+ */
163
+ final public function is_post_included_in_sitemap( $post_id ) {
164
+
165
+ static $excluded = null;
166
+ if ( null === $excluded ) {
167
+ /**
168
+ * @since 2.5.2
169
+ * @since 2.8.0 No longer accepts '0' as entry.
170
+ * @since 3.1.0 '0' is accepted again.
171
+ * @param int[] $excluded Sequential list of excluded IDs: [ int ...post_id ]
172
+ */
173
+ $excluded = (array) \apply_filters( 'the_seo_framework_sitemap_exclude_ids', [] );
174
+
175
+ // isset() is faster than in_array(). So, we flip it.
176
+ $excluded = $excluded ? array_flip( $excluded ) : [];
177
+ }
178
+
179
+ $included = ! isset( $excluded[ $post_id ] );
180
+
181
+ while ( $included ) :
182
+ $_args = [ 'id' => $post_id ];
183
+
184
+ // ROBOTS_IGNORE_PROTECTION as we don't need to test 'private' ('post_status'=>'publish'), nor 'password' ('has_password'=>false)
185
+ $included = 'noindex'
186
+ !== (
187
+ static::$tsf->generate_robots_meta( $_args, [ 'noindex' ], \The_SEO_Framework\ROBOTS_IGNORE_PROTECTION )['noindex']
188
+ ?? false // We cast type false for Zend tests strict type before identical-string-comparing.
189
+ );
190
+
191
+ if ( ! $included ) break;
192
+
193
+ $included = ! static::$tsf->get_redirect_url( $_args );
194
+ break;
195
+ endwhile;
196
+
197
+ return $included;
198
+ }
199
+
200
+ /**
201
+ * Determines if post is possibly included in the sitemap.
202
+ *
203
+ * This is a weak check, as the filter might not be present outside of the sitemap's scope.
204
+ * The URL also isn't checked, nor the position.
205
+ *
206
+ * @since 4.0.0
207
+ * @since 4.1.4 Now tests for redirect settings.
208
+ * @since 4.2.0 Now only asserts noindex robots-values, instead of all robots-values, improving performance.
209
+ * @see https://github.com/sybrew/tsf-term-sitemap for example.
210
+ *
211
+ * @param int $term_id The Term ID to check.
212
+ * @param string $taxonomy The taxonomy.
213
+ * @return bool True if included, false otherwise.
214
+ */
215
+ final public function is_term_included_in_sitemap( $term_id, $taxonomy ) {
216
+
217
+ static $excluded = null;
218
+ if ( null === $excluded ) {
219
+ /**
220
+ * @since 4.0.0
221
+ * @param int[] $excluded Sequential list of excluded IDs: [ int ...term_id ]
222
+ */
223
+ $excluded = (array) \apply_filters( 'the_seo_framework_sitemap_exclude_term_ids', [] );
224
+
225
+ // isset() is faster than in_array(). So, we flip it.
226
+ $excluded = $excluded ? array_flip( $excluded ) : [];
227
+ }
228
+
229
+ $included = ! isset( $excluded[ $term_id ] );
230
+
231
+ // Yes, 90% of this code code isn't DRY. However, terms !== posts. terms == posts, though :).
232
+ // Really: <https://core.trac.wordpress.org/ticket/50568>
233
+ while ( $included ) :
234
+ $_args = [
235
+ 'id' => $term_id,
236
+ 'taxonomy' => $taxonomy,
237
+ ];
238
+
239
+ // ROBOTS_IGNORE_PROTECTION is not tested for terms. However, we may use that later.
240
+ $included = 'noindex'
241
+ !== (
242
+ static::$tsf->generate_robots_meta( $_args, [ 'noindex' ], \The_SEO_Framework\ROBOTS_IGNORE_PROTECTION )['noindex']
243
+ ?? false // We cast type false for Zend tests strict type before identical-string-comparing.
244
+ );
245
+
246
+ if ( ! $included ) break;
247
+
248
+ $included = ! static::$tsf->get_redirect_url( $_args );
249
+ break;
250
+ endwhile;
251
+
252
+ return $included;
253
+ }
254
+
255
+ /**
256
+ * Returns the sitemap post query limit.
257
+ *
258
+ * @since 3.1.0
259
+ * @since 4.0.0 Moved to \The_SEO_Framework\Builders\Sitemap
260
+ *
261
+ * @param bool $hierarchical Whether the query is for hierarchical post types or not.
262
+ * @return int The post limit
263
+ */
264
+ final protected function get_sitemap_post_limit( $hierarchical = false ) {
265
+ /**
266
+ * @since 2.2.9
267
+ * @since 2.8.0 Increased to 1200 from 700.
268
+ * @since 3.1.0 Now returns an option value; it falls back to the default value if not set.
269
+ * @since 4.0.0 1. The default is now 3000, from 1200.
270
+ * 2. Now passes a second parameter.
271
+ * @param int $total_post_limit
272
+ * @param bool $hierarchical Whether the query is for hierarchical post types or not.
273
+ */
274
+ return (int) \apply_filters(
275
+ 'the_seo_framework_sitemap_post_limit',
276
+ static::$tsf->get_option( 'sitemap_query_limit' ),
277
+ $hierarchical
278
+ );
279
+ }
280
+ }
inc/classes/cache.class.php CHANGED
@@ -169,7 +169,7 @@ class Cache extends Site_Options {
169
  * @param array $args Additional arguments. They can overwrite $type and $id.
170
  * @return bool true on success, false on failure.
171
  */
172
- public function delete_cache( $type, $id = 0, array $args = [] ) {
173
 
174
  $this->parse_delete_cache_keys( $type, $id, $args );
175
 
@@ -192,7 +192,7 @@ class Cache extends Site_Options {
192
  * @since 3.1.0
193
  *
194
  * @param string $type The flush type. Comes in handy when you use a catch-all function.
195
- * @param int $id The post, page or TT ID. Defaults to the_seo_framework()->get_the_real_ID().
196
  * @param array $args Additional arguments. They can overwrite $type and $id.
197
  * @param bool $success Whether the action cleared.
198
  */
@@ -212,7 +212,7 @@ class Cache extends Site_Options {
212
  */
213
  protected function parse_delete_cache_keys( &$type, &$id, &$args ) {
214
 
215
- //= Don't use cache on fetching ID.
216
  $id = $id ?: $this->get_the_real_ID( false );
217
 
218
  $defaults = [
@@ -249,9 +249,7 @@ class Cache extends Site_Options {
249
  * @param int $expiration Transient expiration date, optional. Expected to not be SQL-escaped.
250
  */
251
  public function set_transient( $transient, $value, $expiration = 0 ) {
252
-
253
- if ( $this->the_seo_framework_use_transients )
254
- \set_transient( $transient, $value, $expiration );
255
  }
256
 
257
  /**
@@ -288,7 +286,7 @@ class Cache extends Site_Options {
288
  */
289
  public function get_exclusion_transient_name() {
290
  $exclude_revision = '1'; // WARNING: SEE NOTE
291
- return $this->add_cache_key_suffix( 'tsf_exclude_' . $exclude_revision );
292
  }
293
 
294
  /**
@@ -300,7 +298,7 @@ class Cache extends Site_Options {
300
  */
301
  public function get_sitemap_transient_name() {
302
  $sitemap_revision = '5';
303
- return $this->get_option( 'cache_sitemap' ) ? $this->add_cache_key_suffix( 'tsf_sitemap_' . $sitemap_revision ) : '';
304
  }
305
 
306
  /**
@@ -310,8 +308,8 @@ class Cache extends Site_Options {
310
  *
311
  * @since 2.3.3
312
  * @since 2.6.0 Refactored.
313
- * @since 2.9.1 : 1. Added early singular type detection.
314
- * 2. Moved generation into another method (v4.1.4: removed method).
315
  * @since 3.1.1 The first parameter is now optional.
316
  * @since 4.1.4 No longer generates a cache key when no `$type` is supplied.
317
  * @TODO since we only support by type, it'd be best to rework this into something simple.
@@ -322,11 +320,7 @@ class Cache extends Site_Options {
322
  * @return string The generated cache key by query or type.
323
  */
324
  public function generate_cache_key( $id = 0, $taxonomy = '', $type = null ) {
325
-
326
- if ( isset( $type ) )
327
- return $this->generate_cache_key_by_type( $id, $taxonomy, $type );
328
-
329
- return '';
330
  }
331
 
332
  /**
@@ -354,7 +348,7 @@ class Cache extends Site_Options {
354
  return $this->add_cache_key_suffix( 'tsf_sitemap_lock' );
355
  default:
356
  $this->_doing_it_wrong( __METHOD__, 'Third parameter must be a known type.', '2.6.5' );
357
- return $this->add_cache_key_suffix( \esc_sql( $type . '_' . $page_id . '_' . $taxonomy ) );
358
  endswitch;
359
  }
360
 
@@ -371,7 +365,10 @@ class Cache extends Site_Options {
371
  * @return string
372
  */
373
  protected function add_cache_key_suffix( $key = '' ) {
374
- return $key . '_' . $GLOBALS['blog_id'] . '_' . strtolower( \get_locale() );
 
 
 
375
  }
376
 
377
  /**
@@ -425,11 +422,12 @@ class Cache extends Site_Options {
425
  'the_seo_framework_sitemap_transient_cleared',
426
  [
427
  'ping_use_cron' => $ping_use_cron,
428
- 'ping_use_cron_prerender' => $ping_use_cron_prerender,
429
  ]
430
  );
431
 
432
  if ( $ping_use_cron ) {
 
433
  \The_SEO_Framework\Bridges\Ping::engage_pinging_cron();
434
  } else {
435
  \The_SEO_Framework\Bridges\Ping::ping_search_engines();
@@ -453,62 +451,60 @@ class Cache extends Site_Options {
453
  */
454
  public function get_excluded_ids_from_cache() {
455
 
456
- if ( $this->is_headless['meta'] ) return [
457
- 'archive' => '',
458
- 'search' => '',
459
- ];
460
-
461
- static $cache = null;
462
-
463
- if ( null === $cache )
464
- $cache = $this->get_transient( $this->get_exclusion_transient_name() );
465
-
466
- if ( false === $cache ) {
467
- global $wpdb;
468
-
469
- $supported_post_types = $this->get_supported_post_types();
470
- $public_post_types = $this->get_public_post_types();
471
-
472
- $join = '';
473
- $where = '';
474
- if ( $supported_post_types !== $public_post_types ) {
475
- // Post types can be registered arbitrarily through other plugins, even manually by non-super-admins. Prepare!
476
- $post_type__in = "'" . implode( "','", array_map( '\\esc_sql', $supported_post_types ) ) . "'";
477
-
478
- // This is as fast as I could make it. Yes, it uses IN, but only on a (tiny) subset of data.
479
- $join = "LEFT JOIN {$wpdb->posts} ON {$wpdb->postmeta}.post_id = {$wpdb->posts}.ID";
480
- $where = "AND {$wpdb->posts}.post_type IN ($post_type__in)";
481
- }
482
-
483
- //= Two separated equals queries are faster than a single IN with 'meta_key'.
484
- // phpcs:disable, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- We prepared our whole lives.
485
- $cache = [
486
- 'archive' => $wpdb->get_results(
487
- "SELECT post_id, meta_value FROM $wpdb->postmeta $join WHERE meta_key = 'exclude_from_archive' $where"
488
- ),
489
- 'search' => $wpdb->get_results(
490
- "SELECT post_id, meta_value FROM $wpdb->postmeta $join WHERE meta_key = 'exclude_local_search' $where"
491
- ),
492
  ];
493
- // phpcs:enable, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
494
-
495
- foreach ( [ 'archive', 'search' ] as $type ) {
496
- array_walk(
497
- $cache[ $type ],
498
- static function( &$v ) {
499
- if ( isset( $v->meta_value, $v->post_id ) && $v->meta_value ) {
500
- $v = (int) $v->post_id;
501
- } else {
502
- $v = false;
503
- }
504
- }
505
- );
506
- $cache[ $type ] = array_filter( $cache[ $type ] );
507
- }
508
 
509
- $this->set_transient( $this->get_exclusion_transient_name(), $cache, 0 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
510
  }
511
 
512
- return $cache;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
513
  }
514
  }
169
  * @param array $args Additional arguments. They can overwrite $type and $id.
170
  * @return bool true on success, false on failure.
171
  */
172
+ public function delete_cache( $type, $id = 0, $args = [] ) {
173
 
174
  $this->parse_delete_cache_keys( $type, $id, $args );
175
 
192
  * @since 3.1.0
193
  *
194
  * @param string $type The flush type. Comes in handy when you use a catch-all function.
195
+ * @param int $id The post, page or TT ID. Defaults to tsf()->get_the_real_ID().
196
  * @param array $args Additional arguments. They can overwrite $type and $id.
197
  * @param bool $success Whether the action cleared.
198
  */
212
  */
213
  protected function parse_delete_cache_keys( &$type, &$id, &$args ) {
214
 
215
+ // Don't use cache on fetching ID.
216
  $id = $id ?: $this->get_the_real_ID( false );
217
 
218
  $defaults = [
249
  * @param int $expiration Transient expiration date, optional. Expected to not be SQL-escaped.
250
  */
251
  public function set_transient( $transient, $value, $expiration = 0 ) {
252
+ $this->the_seo_framework_use_transients and \set_transient( $transient, $value, $expiration );
 
 
253
  }
254
 
255
  /**
286
  */
287
  public function get_exclusion_transient_name() {
288
  $exclude_revision = '1'; // WARNING: SEE NOTE
289
+ return $this->add_cache_key_suffix( "tsf_exclude_{$exclude_revision}" );
290
  }
291
 
292
  /**
298
  */
299
  public function get_sitemap_transient_name() {
300
  $sitemap_revision = '5';
301
+ return $this->get_option( 'cache_sitemap' ) ? $this->add_cache_key_suffix( "tsf_sitemap_{$sitemap_revision}" ) : '';
302
  }
303
 
304
  /**
308
  *
309
  * @since 2.3.3
310
  * @since 2.6.0 Refactored.
311
+ * @since 2.9.1 1. Added early singular type detection.
312
+ * 2. Moved generation into another method (v4.1.4: removed method).
313
  * @since 3.1.1 The first parameter is now optional.
314
  * @since 4.1.4 No longer generates a cache key when no `$type` is supplied.
315
  * @TODO since we only support by type, it'd be best to rework this into something simple.
320
  * @return string The generated cache key by query or type.
321
  */
322
  public function generate_cache_key( $id = 0, $taxonomy = '', $type = null ) {
323
+ return isset( $type ) ? $this->generate_cache_key_by_type( $id, $taxonomy, $type ) : '';
 
 
 
 
324
  }
325
 
326
  /**
348
  return $this->add_cache_key_suffix( 'tsf_sitemap_lock' );
349
  default:
350
  $this->_doing_it_wrong( __METHOD__, 'Third parameter must be a known type.', '2.6.5' );
351
+ return $this->add_cache_key_suffix( \esc_sql( "{$type}_{$page_id}_{$taxonomy}" ) );
352
  endswitch;
353
  }
354
 
365
  * @return string
366
  */
367
  protected function add_cache_key_suffix( $key = '' ) {
368
+
369
+ $locale = strtolower( \get_locale() );
370
+
371
+ return "{$key}_{$GLOBALS['blog_id']}_{$locale}";
372
  }
373
 
374
  /**
422
  'the_seo_framework_sitemap_transient_cleared',
423
  [
424
  'ping_use_cron' => $ping_use_cron,
425
+ 'ping_use_cron_prerender' => $ping_use_cron_prerender, // TODO migrate this so it can run regardless of pinging?
426
  ]
427
  );
428
 
429
  if ( $ping_use_cron ) {
430
+ // This name is wrong. It's not exclusively used for pinging.
431
  \The_SEO_Framework\Bridges\Ping::engage_pinging_cron();
432
  } else {
433
  \The_SEO_Framework\Bridges\Ping::ping_search_engines();
451
  */
452
  public function get_excluded_ids_from_cache() {
453
 
454
+ if ( $this->is_headless['meta'] )
455
+ return [
456
+ 'archive' => '',
457
+ 'search' => '',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
458
  ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
459
 
460
+ $cache = memo() ?? memo( $this->get_transient( $this->get_exclusion_transient_name() ) );
461
+
462
+ if ( false !== $cache ) return $cache;
463
+
464
+ global $wpdb;
465
+
466
+ $supported_post_types = $this->get_supported_post_types();
467
+ $public_post_types = $this->get_public_post_types();
468
+
469
+ $join = '';
470
+ $where = '';
471
+ if ( $supported_post_types !== $public_post_types ) {
472
+ // Post types can be registered arbitrarily through other plugins, even manually by non-super-admins. Prepare!
473
+ $post_type__in = "'" . implode( "','", array_map( 'esc_sql', $supported_post_types ) ) . "'";
474
+
475
+ // This is as fast as I could make it. Yes, it uses IN, but only on a (tiny) subset of data.
476
+ $join = "LEFT JOIN {$wpdb->posts} ON {$wpdb->postmeta}.post_id = {$wpdb->posts}.ID";
477
+ $where = "AND {$wpdb->posts}.post_type IN ($post_type__in)";
478
  }
479
 
480
+ // Two separated equals queries are faster than a single IN with 'meta_key'.
481
+ // phpcs:disable, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- We prepared our whole lives.
482
+ $cache = [
483
+ 'archive' => $wpdb->get_results(
484
+ "SELECT post_id, meta_value FROM $wpdb->postmeta $join WHERE meta_key = 'exclude_from_archive' $where"
485
+ ),
486
+ 'search' => $wpdb->get_results(
487
+ "SELECT post_id, meta_value FROM $wpdb->postmeta $join WHERE meta_key = 'exclude_local_search' $where"
488
+ ),
489
+ ];
490
+ // phpcs:enable, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
491
+
492
+ foreach ( [ 'archive', 'search' ] as $type ) {
493
+ array_walk(
494
+ $cache[ $type ],
495
+ static function( &$v ) {
496
+ if ( isset( $v->meta_value, $v->post_id ) && $v->meta_value ) {
497
+ $v = (int) $v->post_id;
498
+ } else {
499
+ $v = false;
500
+ }
501
+ }
502
+ );
503
+ $cache[ $type ] = array_filter( $cache[ $type ] );
504
+ }
505
+
506
+ $this->set_transient( $this->get_exclusion_transient_name(), $cache, 0 );
507
+
508
+ return memo( $cache );
509
  }
510
  }
inc/classes/core.class.php CHANGED
@@ -31,6 +31,7 @@ namespace The_SEO_Framework;
31
  * Initializes the plugin & Holds plugin core functions.
32
  *
33
  * @since 2.8.0
 
34
  */
35
  class Core {
36
 
@@ -66,7 +67,7 @@ class Core {
66
  final public function __set( $name, $value ) {
67
 
68
  if ( 'load_options' === $name ) {
69
- // $this->_inaccessible_p_or_m( 'the_seo_framework()->load_options', 'since 4.2.0; use constant THE_SEO_FRAMEWORK_HEADLESS' );
70
  $this->is_headless['settings'] = $value;
71
  return;
72
  }
@@ -74,7 +75,7 @@ class Core {
74
  /**
75
  * For now, no deprecation is being handled; as no properties have been deprecated. Just removed.
76
  */
77
- $this->_inaccessible_p_or_m( 'the_seo_framework()->' . $name, 'unknown' );
78
 
79
  // Invoke default behavior: Write variable if it's not protected.
80
  if ( ! isset( $this->$name ) )
@@ -96,11 +97,11 @@ class Core {
96
  final public function __get( $name ) {
97
 
98
  if ( 'load_options' === $name ) {
99
- // $this->_inaccessible_p_or_m( 'the_seo_framework()->load_options', 'since 4.2.0; use constant THE_SEO_FRAMEWORK_HEADLESS' );
100
  return ! $this->is_headless['settings'];
101
  }
102
 
103
- $this->_inaccessible_p_or_m( 'the_seo_framework()->' . $name, 'unknown' );
104
  }
105
 
106
  /**
@@ -117,12 +118,12 @@ class Core {
117
  static $depr_class = null;
118
 
119
  if ( \is_null( $depr_class ) )
120
- $depr_class = new Deprecated;
121
 
122
  if ( \is_callable( [ $depr_class, $name ] ) )
123
  return \call_user_func_array( [ $depr_class, $name ], $arguments );
124
 
125
- $this->_inaccessible_p_or_m( 'the_seo_framework()->' . $name . '()' );
126
  }
127
 
128
  /**
@@ -158,16 +159,18 @@ class Core {
158
  */
159
  public function _include_compat( $what, $type = 'plugin' ) {
160
 
161
- static $included = [];
 
 
162
 
163
- if ( ! isset( $included[ $what ][ $type ] ) ) {
164
- // phpcs:ignore, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- forwarded to include...
165
- $_secret = $this->create_view_secret( uniqid( '', true ) );
166
-
167
- $included[ $what ][ $type ] = (bool) require THE_SEO_FRAMEWORK_DIR_PATH_COMPAT . $type . '-' . $what . '.php';
168
- }
169
 
170
- return $included[ $what ][ $type ];
 
 
 
 
171
  }
172
 
173
  /**
@@ -178,12 +181,12 @@ class Core {
178
  * @access private
179
  * @credits Akismet For some code.
180
  *
181
- * @param string $view The file name.
182
- * @param array $__args The arguments to be supplied within the file name.
183
- * Each array key is converted to a variable with its value attached.
184
- * @param string $instance The instance suffix to call back upon.
185
  */
186
- public function get_view( $view, array $__args = [], $instance = 'main' ) {
187
 
188
  //? A faster extract().
189
  foreach ( $__args as $__k => $__v ) $$__k = $__v;
@@ -208,8 +211,7 @@ class Core {
208
  * @return string|null The stored secret.
209
  */
210
  protected function create_view_secret( $value = null ) {
211
- static $secret;
212
- return $secret = isset( $value ) ? $value : $secret;
213
  }
214
 
215
  /**
@@ -250,7 +252,10 @@ class Core {
250
  * @return string The file instance case.
251
  */
252
  protected function get_view_instance( $base, $instance = 'main' ) {
253
- return $base . '_' . str_replace( '-', '_', $instance );
 
 
 
254
  }
255
 
256
  /**
@@ -262,13 +267,14 @@ class Core {
262
  * @return array The public hierarchical post types.
263
  */
264
  public function get_hierarchical_post_types() {
265
- static $types;
266
- return $types ?: $types = \get_post_types(
267
- [
268
- 'hierarchical' => true,
269
- 'public' => true,
270
- ],
271
- 'names'
 
272
  );
273
  }
274
 
@@ -281,13 +287,14 @@ class Core {
281
  * @return array The public nonhierarchical post types.
282
  */
283
  public function get_nonhierarchical_post_types() {
284
- static $types;
285
- return $types ?: $types = \get_post_types(
286
- [
287
- 'hierarchical' => false,
288
- 'public' => true,
289
- ],
290
- 'names'
 
291
  );
292
  }
293
 
@@ -300,12 +307,11 @@ class Core {
300
  * @return bool Whether external redirect is allowed.
301
  */
302
  public function allow_external_redirect() {
303
- static $cache = null;
304
  /**
305
  * @since 2.1.0
306
  * @param bool $allowed Whether external redirect is allowed.
307
  */
308
- return isset( $cache ) ? $cache : $cache = (bool) \apply_filters( 'the_seo_framework_allow_external_redirect', true );
309
  }
310
 
311
  /**
@@ -318,10 +324,7 @@ class Core {
318
  * @return bool True is blog is public.
319
  */
320
  public function is_blog_public() {
321
-
322
- static $cache = null;
323
-
324
- return isset( $cache ) ? $cache : $cache = (bool) \get_option( 'blog_public' );
325
  }
326
 
327
  /**
@@ -359,10 +362,16 @@ class Core {
359
  public function get_settings_capability() {
360
  /**
361
  * @since 2.6.0
362
- * @todo deprecate 4.2.0, use constant instead.
 
363
  * @param string $capability The user capability required to adjust settings.
364
  */
365
- return (string) \apply_filters( 'the_seo_framework_settings_capability', THE_SEO_FRAMEWORK_SETTINGS_CAP );
 
 
 
 
 
366
  }
367
 
368
  /**
@@ -385,102 +394,14 @@ class Core {
385
  * @return string The escaped SEO Settings page URL.
386
  */
387
  public function get_seo_settings_page_url() {
388
-
389
- if ( ! $this->is_headless['settings'] ) {
390
- $url = html_entity_decode( \menu_page_url( $this->seo_settings_page_slug, false ) );
391
- return \esc_url( $url, [ 'https', 'http' ] );
392
- }
393
-
394
- return '';
395
- }
396
-
397
- /**
398
- * Returns the PHP timezone compatible string.
399
- * UTC offsets are unreliable.
400
- *
401
- * @since 2.6.0
402
- *
403
- * @param bool $guess If true, the timezone will be guessed from the
404
- * WordPress core gmt_offset option.
405
- * @return string PHP Timezone String. May be empty (thus invalid).
406
- */
407
- public function get_timezone_string( $guess = false ) {
408
-
409
- $tzstring = \get_option( 'timezone_string' );
410
-
411
- if ( false !== strpos( $tzstring, 'Etc/GMT' ) )
412
- $tzstring = '';
413
-
414
- if ( $guess && empty( $tzstring ) ) {
415
- $tzstring = $this->get_tzstring_from_offset( \get_option( 'gmt_offset' ) );
416
- }
417
-
418
- return $tzstring;
419
- }
420
-
421
- /**
422
- * Fetches the Timezone String from given offset.
423
- *
424
- * @since 2.6.0
425
- * @since 4.0.0 Removed PHP <5.6 support.
426
- *
427
- * @param int $offset The GMT offzet.
428
- * @return string PHP Timezone String.
429
- */
430
- protected function get_tzstring_from_offset( $offset = 0 ) {
431
- $seconds = round( $offset * HOUR_IN_SECONDS );
432
- return timezone_name_from_abbr( '', $seconds, 1 );
433
- }
434
-
435
- /**
436
- * Sets and resets the timezone.
437
- *
438
- * NOTE: Always call reset_timezone() ASAP. Don't let changes linger, as they can be destructive.
439
- *
440
- * This exists because WordPress's current_time() adds discrepancies between UTC and GMT.
441
- * This is also far more accurate than WordPress's tiny time table.
442
- *
443
- * @TODO Note that WordPress 5.3 no longer requires this, and that we should rely on wp_date() instead.
444
- * So, we should remove this dependency ASAP.
445
- *
446
- * @since 2.6.0
447
- * @since 3.0.6 Now uses the old timezone string when a new one can't be generated.
448
- * @since 4.0.4 Now also unsets the stored timezone string on reset.
449
- * @link http://php.net/manual/en/timezones.php
450
- *
451
- * @param string $tzstring Optional. The PHP Timezone string. Best to leave empty to always get a correct one.
452
- * @param bool $reset Whether to reset to default. Ignoring first parameter.
453
- * @return bool True on success. False on failure.
454
- */
455
- public function set_timezone( $tzstring = '', $reset = false ) {
456
-
457
- static $old_tz = null;
458
-
459
- $old_tz = $old_tz ?: date_default_timezone_get() ?: 'UTC';
460
-
461
- if ( $reset ) {
462
- $_revert_tz = $old_tz;
463
- $old_tz = null;
464
- // phpcs:ignore, WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set
465
- return date_default_timezone_set( $_revert_tz );
466
- }
467
-
468
- if ( empty( $tzstring ) )
469
- $tzstring = $this->get_timezone_string( true ) ?: $old_tz;
470
-
471
- // phpcs:ignore, WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set
472
- return date_default_timezone_set( $tzstring );
473
- }
474
-
475
- /**
476
- * Resets the timezone to default or UTC.
477
- *
478
- * @since 2.6.0
479
- *
480
- * @return bool True on success. False on failure.
481
- */
482
- public function reset_timezone() {
483
- return $this->set_timezone( '', true );
484
  }
485
 
486
  /**
@@ -488,27 +409,21 @@ class Core {
488
  *
489
  * @since 2.7.0
490
  * @since 4.0.4 Now uses `gmdate()` instead of `date()`.
491
- * @see `$this->set_timezone()`
492
- * @see `$this->reset_timezone()`
493
  *
494
  * @param string $format The datetime format.
495
  * @param string $time The GMT time. Expects timezone to be omitted.
496
  * @return string The converted time. Empty string if no $time is given.
497
  */
498
  public function gmt2date( $format = 'Y-m-d', $time = '' ) {
499
-
500
- if ( $time )
501
- return gmdate( $format, strtotime( $time . ' GMT' ) );
502
-
503
- return '';
504
  }
505
 
506
  /**
507
  * Returns timestamp format based on timestamp settings.
508
  *
509
  * @since 3.0.0
510
- * @since 4.1.4: 1. Added options-override parameter.
511
- * 1. Added return value filter.
512
  * @link https://www.w3.org/TR/NOTE-datetime
513
  *
514
  * @param null|bool $override_get_time Whether to override the $get_time from option value.
@@ -516,9 +431,7 @@ class Core {
516
  */
517
  public function get_timestamp_format( $override_get_time = null ) {
518
 
519
- $get_time = isset( $override_get_time )
520
- ? $override_get_time
521
- : $this->uses_time_in_timestamp_format();
522
 
523
  return \apply_filters_ref_array(
524
  'the_seo_framework_timestamp_format',
@@ -545,6 +458,7 @@ class Core {
545
  * Unlike PHP's `array_merge_recursive()`, this method doesn't convert non-unique keys as sequential.
546
  *
547
  * A do-while is faster than while. Sorry for the legibility.
 
548
  *
549
  * @since 4.1.4
550
  *
@@ -556,8 +470,7 @@ class Core {
556
  $i = \count( $arrays );
557
 
558
  if ( 2 === $i ) foreach ( $arrays[1] as $key => $value ) {
559
- $arrays[0][ $key ] =
560
- isset( $arrays[0][ $key ] ) && \is_array( $arrays[0][ $key ] )
561
  ? $this->array_merge_recursive_distinct( $arrays[0][ $key ], $value )
562
  : $value;
563
  } else do {
@@ -572,6 +485,7 @@ class Core {
572
  * Shortens string and adds ellipses when over a threshold in length.
573
  *
574
  * @since 3.1.0
 
575
  *
576
  * @param string $string The string to test and maybe trim
577
  * @param int $over The character limit. Must be over 0 to have effect.
@@ -581,9 +495,8 @@ class Core {
581
  */
582
  public function hellip_if_over( $string, $over = 0 ) {
583
 
584
- if ( $over > 0 && \strlen( $string ) > $over ) {
585
- $string = substr( $string, 0, abs( $over - 2 ) ) . ' &hellip;';
586
- }
587
 
588
  return $string;
589
  }
@@ -602,72 +515,67 @@ class Core {
602
  * @since 4.0.0 1. Now expects PCRE UTF-8 encoding support.
603
  * 2. Moved input-parameter alterting filters outside of this function.
604
  * 3. Short length now works as intended, instead of comparing as less, it compares as less or equal to.
 
 
605
  *
606
  * @param string $string Required. The string to count words in.
607
- * @param int $dupe_count Minimum amount of words to encounter in the string.
608
- * Set to 0 to count all words longer than $short_length.
609
- * @param int $dupe_short Minimum amount of words to encounter in the string that fall under the
610
- * $short_length. Set to 0 to consider all words with $amount.
611
- * @param int $short_length The maximum string length of a word to pass for $dupe_short
612
- * instead of $count. Set to 0 to ignore $count, and use $dupe_short only.
613
  * @return array Containing arrays of words with their count.
614
  */
615
  public function get_word_count( $string, $dupe_count = 3, $dupe_short = 5, $short_length = 3 ) {
616
 
617
- $string = html_entity_decode( $string );
618
- $string = \wp_check_invalid_utf8( $string );
619
 
620
  if ( ! $string ) return [];
621
 
622
- static $use_mb;
623
 
624
- isset( $use_mb ) or ( $use_mb = \extension_loaded( 'mbstring' ) );
625
-
626
- // TODO does this test well for "we're"? We haven't had any reports, though.
627
  $word_list = preg_split(
628
- '/[^\p{L}\p{M}\p{N}\p{Pc}\p{Cc}]+/mu',
629
  $use_mb ? mb_strtolower( $string ) : strtolower( $string ),
630
  -1,
631
  PREG_SPLIT_OFFSET_CAPTURE | PREG_SPLIT_NO_EMPTY
632
  );
633
 
634
- $words_too_many = [];
635
 
636
- if ( \count( $word_list ) ) :
637
- $words = [];
638
- foreach ( $word_list as $wli ) {
639
- //= { $words[ int Offset ] => string Word }
640
- $words[ $wli[1] ] = $wli[0];
641
- }
642
 
643
- $word_count = array_count_values( $words );
 
644
 
645
- // We're going to fetch words based on position, and then flip it to become the key.
646
- $word_keys = array_flip( array_reverse( $words, true ) );
647
 
648
- foreach ( $word_count as $word => $count ) {
649
- if ( ( $use_mb ? mb_strlen( $word ) : \strlen( $word ) ) <= $short_length ) {
650
- $run = $count >= $dupe_short;
651
- } else {
652
- $run = $count >= $dupe_count;
653
- }
654
 
655
- if ( $run ) {
656
- //! Don't use mb_* here. preg_split's offset is in bytes, NOT multibytes.
657
- $args = [
658
- 'pos' => $word_keys[ $word ],
659
- 'len' => \strlen( $word ),
660
- ];
661
 
662
- $first_encountered_word = substr( $string, $args['pos'], $args['len'] );
663
 
664
- // Found words that are used too frequently.
665
- $words_too_many[] = [ $first_encountered_word => $count ];
666
- }
667
  }
668
- endif;
669
 
670
- return $words_too_many;
 
 
671
  }
672
 
673
  /**
@@ -676,31 +584,33 @@ class Core {
676
  * @since 2.8.0
677
  * @since 2.9.0 Now adds a little more relative softness based on rel_lum.
678
  * @since 2.9.2 (Typo): Renamed from 'get_relatitve_fontcolor' to 'get_relative_fontcolor'.
679
- * @since 3.0.4 Now uses WCAG's relative luminance formula
 
 
680
  * @link https://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast-contrast
681
  * @link https://www.w3.org/WAI/GL/wiki/Relative_luminance
682
  *
683
  * @param string $hex The 3 to 6 character RGB hex. The '#' prefix may be added.
 
684
  * @return string The hexadecimal RGB relative font color, without '#' prefix.
685
  */
686
  public function get_relative_fontcolor( $hex = '' ) {
687
 
688
  $hex = ltrim( $hex, '#' );
689
 
690
- // #rgb = #rrggbb
691
- if ( 3 === \strlen( $hex ) )
692
- $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
693
-
694
- $hex = str_split( $hex, 2 );
695
-
696
- // Convert to usable numerics.
697
- $r = hexdec( $hex[0] );
698
- $g = hexdec( $hex[1] );
699
- $b = hexdec( $hex[2] );
700
 
701
  $get_relative_luminance = static function( $v ) {
702
- //= Convert to 0~1 value.
703
- $v /= 255;
704
 
705
  if ( $v > .03928 ) {
706
  $lum = ( ( $v + .055 ) / 1.055 ) ** 2.4;
@@ -710,32 +620,25 @@ class Core {
710
  return $lum;
711
  };
712
 
713
- // Use sRGB for relative luminance.
714
- $sr = 0.2126 * $get_relative_luminance( $r );
715
- $sg = 0.7152 * $get_relative_luminance( $g );
716
- $sb = 0.0722 * $get_relative_luminance( $b );
717
-
718
- $rel_lum = ( $sr + $sg + $sb );
719
 
720
  // Build light greyscale.
721
- $gr = ( $r * 0.2989 / 8 ) * $rel_lum;
722
- $gg = ( $g * 0.5870 / 8 ) * $rel_lum;
723
- $gb = ( $b * 0.1140 / 8 ) * $rel_lum;
724
-
725
- //= Invert colors if they hit luminance boundaries.
726
- if ( $rel_lum < 0.5 ) {
727
- // Build dark greyscale.
728
- $gr = 255 - $gr;
729
- $gg = 255 - $gg;
730
- $gb = 255 - $gb;
731
  }
732
 
733
- // Build RGB hex.
734
- $retr = str_pad( dechex( round( $gr ) ), 2, '0', STR_PAD_LEFT );
735
- $retg = str_pad( dechex( round( $gg ) ), 2, '0', STR_PAD_LEFT );
736
- $retb = str_pad( dechex( round( $gb ) ), 2, '0', STR_PAD_LEFT );
737
-
738
- return $retr . $retg . $retb;
739
  }
740
 
741
  /**
@@ -759,8 +662,8 @@ class Core {
759
  $accent = $this->s_color_hex( $this->get_option( 'sitemap_color_accent' ) );
760
 
761
  $options = [
762
- 'main' => $main ? '#' . $main : '',
763
- 'accent' => $accent ? '#' . $accent : '',
764
  ];
765
 
766
  $options = array_filter( $options );
31
  * Initializes the plugin & Holds plugin core functions.
32
  *
33
  * @since 2.8.0
34
+ * @since 4.2.0 Deprecated $load_options
35
  */
36
  class Core {
37
 
67
  final public function __set( $name, $value ) {
68
 
69
  if ( 'load_options' === $name ) {
70
+ $this->_inaccessible_p_or_m( 'tsf()->load_options', 'since 4.2.0; use constant THE_SEO_FRAMEWORK_HEADLESS' );
71
  $this->is_headless['settings'] = $value;
72
  return;
73
  }
75
  /**
76
  * For now, no deprecation is being handled; as no properties have been deprecated. Just removed.
77
  */
78
+ $this->_inaccessible_p_or_m( "tsf()->$name", 'unknown' );
79
 
80
  // Invoke default behavior: Write variable if it's not protected.
81
  if ( ! isset( $this->$name ) )
97
  final public function __get( $name ) {
98
 
99
  if ( 'load_options' === $name ) {
100
+ $this->_inaccessible_p_or_m( 'tsf()->load_options', 'since 4.2.0; use constant THE_SEO_FRAMEWORK_HEADLESS' );
101
  return ! $this->is_headless['settings'];
102
  }
103
 
104
+ $this->_inaccessible_p_or_m( "tsf()->$name", 'unknown' );
105
  }
106
 
107
  /**
118
  static $depr_class = null;
119
 
120
  if ( \is_null( $depr_class ) )
121
+ $depr_class = new Internal\Deprecated;
122
 
123
  if ( \is_callable( [ $depr_class, $name ] ) )
124
  return \call_user_func_array( [ $depr_class, $name ], $arguments );
125
 
126
+ $this->_inaccessible_p_or_m( "tsf()->$name()" );
127
  }
128
 
129
  /**
159
  */
160
  public function _include_compat( $what, $type = 'plugin' ) {
161
 
162
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
163
+ if ( null !== $memo = memo( null, $what, $type ) ) return $memo;
164
+ unset( $memo );
165
 
166
+ // phpcs:ignore, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- forwarded to include...
167
+ $_secret = $this->create_view_secret( uniqid( '', true ) );
 
 
 
 
168
 
169
+ return memo(
170
+ (bool) require THE_SEO_FRAMEWORK_DIR_PATH_COMPAT . "$type-$what.php",
171
+ $what,
172
+ $type
173
+ );
174
  }
175
 
176
  /**
181
  * @access private
182
  * @credits Akismet For some code.
183
  *
184
+ * @param string $view The file name.
185
+ * @param iterable $__args The arguments to be supplied within the file name.
186
+ * Each array key is converted to a variable with its value attached.
187
+ * @param string $instance The instance suffix to call back upon.
188
  */
189
+ public function get_view( $view, $__args = [], $instance = 'main' ) {
190
 
191
  //? A faster extract().
192
  foreach ( $__args as $__k => $__v ) $$__k = $__v;
211
  * @return string|null The stored secret.
212
  */
213
  protected function create_view_secret( $value = null ) {
214
+ return memo( $value );
 
215
  }
216
 
217
  /**
252
  * @return string The file instance case.
253
  */
254
  protected function get_view_instance( $base, $instance = 'main' ) {
255
+
256
+ $instance = str_replace( '-', '_', $instance );
257
+
258
+ return "{$base}_{$instance}";
259
  }
260
 
261
  /**
267
  * @return array The public hierarchical post types.
268
  */
269
  public function get_hierarchical_post_types() {
270
+ return memo() ?: memo(
271
+ \get_post_types(
272
+ [
273
+ 'hierarchical' => true,
274
+ 'public' => true,
275
+ ],
276
+ 'names'
277
+ )
278
  );
279
  }
280
 
287
  * @return array The public nonhierarchical post types.
288
  */
289
  public function get_nonhierarchical_post_types() {
290
+ return memo() ?: memo(
291
+ \get_post_types(
292
+ [
293
+ 'hierarchical' => false,
294
+ 'public' => true,
295
+ ],
296
+ 'names'
297
+ )
298
  );
299
  }
300
 
307
  * @return bool Whether external redirect is allowed.
308
  */
309
  public function allow_external_redirect() {
 
310
  /**
311
  * @since 2.1.0
312
  * @param bool $allowed Whether external redirect is allowed.
313
  */
314
+ return memo() ?? memo( (bool) \apply_filters( 'the_seo_framework_allow_external_redirect', true ) );
315
  }
316
 
317
  /**
324
  * @return bool True is blog is public.
325
  */
326
  public function is_blog_public() {
327
+ return memo() ?? memo( (bool) \get_option( 'blog_public' ) );
 
 
 
328
  }
329
 
330
  /**
362
  public function get_settings_capability() {
363
  /**
364
  * @since 2.6.0
365
+ * @since 4.2.0 Deprecated. Define constant THE_SEO_FRAMEWORK_SETTINGS_CAP instead.
366
+ * @deprecated
367
  * @param string $capability The user capability required to adjust settings.
368
  */
369
+ return (string) \apply_filters_deprecated(
370
+ 'the_seo_framework_settings_capability',
371
+ [ THE_SEO_FRAMEWORK_SETTINGS_CAP ],
372
+ '4.2.0 of The SEO Framework',
373
+ 'constant THE_SEO_FRAMEWORK_SETTINGS_CAP'
374
+ );
375
  }
376
 
377
  /**
394
  * @return string The escaped SEO Settings page URL.
395
  */
396
  public function get_seo_settings_page_url() {
397
+ return $this->is_headless['settings']
398
+ ? ''
399
+ : \esc_url(
400
+ html_entity_decode(
401
+ \menu_page_url( $this->seo_settings_page_slug, false )
402
+ ),
403
+ [ 'https', 'http' ]
404
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
405
  }
406
 
407
  /**
409
  *
410
  * @since 2.7.0
411
  * @since 4.0.4 Now uses `gmdate()` instead of `date()`.
 
 
412
  *
413
  * @param string $format The datetime format.
414
  * @param string $time The GMT time. Expects timezone to be omitted.
415
  * @return string The converted time. Empty string if no $time is given.
416
  */
417
  public function gmt2date( $format = 'Y-m-d', $time = '' ) {
418
+ return $time ? gmdate( $format, strtotime( $time . ' GMT' ) ) : '';
 
 
 
 
419
  }
420
 
421
  /**
422
  * Returns timestamp format based on timestamp settings.
423
  *
424
  * @since 3.0.0
425
+ * @since 4.1.4 1. Added options-override parameter.
426
+ * 2. Added return value filter.
427
  * @link https://www.w3.org/TR/NOTE-datetime
428
  *
429
  * @param null|bool $override_get_time Whether to override the $get_time from option value.
431
  */
432
  public function get_timestamp_format( $override_get_time = null ) {
433
 
434
+ $get_time = $override_get_time ?? $this->uses_time_in_timestamp_format();
 
 
435
 
436
  return \apply_filters_ref_array(
437
  'the_seo_framework_timestamp_format',
458
  * Unlike PHP's `array_merge_recursive()`, this method doesn't convert non-unique keys as sequential.
459
  *
460
  * A do-while is faster than while. Sorry for the legibility.
461
+ * TODO instead of calling thyself, would a goto not be better?
462
  *
463
  * @since 4.1.4
464
  *
470
  $i = \count( $arrays );
471
 
472
  if ( 2 === $i ) foreach ( $arrays[1] as $key => $value ) {
473
+ $arrays[0][ $key ] = \is_array( $arrays[0][ $key ] ?? null )
 
474
  ? $this->array_merge_recursive_distinct( $arrays[0][ $key ], $value )
475
  : $value;
476
  } else do {
485
  * Shortens string and adds ellipses when over a threshold in length.
486
  *
487
  * @since 3.1.0
488
+ * @since 4.2.0 No longer prepends a space before the hellip.
489
  *
490
  * @param string $string The string to test and maybe trim
491
  * @param int $over The character limit. Must be over 0 to have effect.
495
  */
496
  public function hellip_if_over( $string, $over = 0 ) {
497
 
498
+ if ( $over > 0 && \strlen( $string ) > $over )
499
+ $string = substr( $string, 0, abs( $over - 2 ) ) . '&hellip;';
 
500
 
501
  return $string;
502
  }
515
  * @since 4.0.0 1. Now expects PCRE UTF-8 encoding support.
516
  * 2. Moved input-parameter alterting filters outside of this function.
517
  * 3. Short length now works as intended, instead of comparing as less, it compares as less or equal to.
518
+ * @since 4.2.0 Now supports detection of connector-dashes, connector-punctuation, and closing quotes,
519
+ * and recognizes those as whole words.
520
  *
521
  * @param string $string Required. The string to count words in.
522
+ * @param int $dupe_count Minimum amount of words to encounter in the string.
523
+ * Set to 0 to count all words longer than $short_length.
524
+ * @param int $dupe_short Minimum amount of words to encounter in the string that fall under the
525
+ * $short_length. Set to 0 to consider all words with $amount.
526
+ * @param int $short_length The maximum string length of a word to pass for $dupe_short
527
+ * instead of $count. Set to 0 to ignore $count, and use $dupe_short only.
528
  * @return array Containing arrays of words with their count.
529
  */
530
  public function get_word_count( $string, $dupe_count = 3, $dupe_short = 5, $short_length = 3 ) {
531
 
532
+ $string = \wp_check_invalid_utf8( html_entity_decode( $string ) );
 
533
 
534
  if ( ! $string ) return [];
535
 
536
+ $use_mb = memo( null, 'use_mb' ) ?? memo( \extension_loaded( 'mbstring' ), 'use_mb' );
537
 
 
 
 
538
  $word_list = preg_split(
539
+ '/[^\p{Cc}\p{L}\p{N}\p{Pc}\p{Pd}\p{Pf}\'"]+/mu',
540
  $use_mb ? mb_strtolower( $string ) : strtolower( $string ),
541
  -1,
542
  PREG_SPLIT_OFFSET_CAPTURE | PREG_SPLIT_NO_EMPTY
543
  );
544
 
545
+ if ( ! \count( $word_list ) ) goto end;
546
 
547
+ $words = [];
 
 
 
 
 
548
 
549
+ foreach ( $word_list as [ $_word, $_position ] )
550
+ $words[ $_position ] = $_word;
551
 
552
+ // We're going to fetch words based on position, and then flip it to become the key.
553
+ $word_keys = array_flip( array_reverse( $words, true ) );
554
 
555
+ foreach ( array_count_values( $words ) as $word => $count ) {
556
+ if ( ( $use_mb ? mb_strlen( $word ) : \strlen( $word ) ) <= $short_length ) {
557
+ $assert = $count >= $dupe_short;
558
+ } else {
559
+ $assert = $count >= $dupe_count;
560
+ }
561
 
562
+ if ( $assert ) {
563
+ //! Don't use mb_* here. preg_split's offset is in bytes, NOT multibytes.
564
+ $args = [
565
+ 'pos' => $word_keys[ $word ],
566
+ 'len' => \strlen( $word ),
567
+ ];
568
 
569
+ $first_encountered_word = substr( $string, $args['pos'], $args['len'] );
570
 
571
+ // phpcs:ignore, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- You need more PHP7.
572
+ $words_too_many[] = [ $first_encountered_word => $count ];
 
573
  }
574
+ }
575
 
576
+ end:;
577
+ // phpcs:ignore, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- You don't love PHP7.
578
+ return $words_too_many ?? [];
579
  }
580
 
581
  /**
584
  * @since 2.8.0
585
  * @since 2.9.0 Now adds a little more relative softness based on rel_lum.
586
  * @since 2.9.2 (Typo): Renamed from 'get_relatitve_fontcolor' to 'get_relative_fontcolor'.
587
+ * @since 3.0.4 Now uses WCAG's relative luminance formula.
588
+ * @since 4.2.0 Optimized code, but it now has some rounding changes at the end. This could
589
+ * offset the returned values by 1/255th.
590
  * @link https://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast-contrast
591
  * @link https://www.w3.org/WAI/GL/wiki/Relative_luminance
592
  *
593
  * @param string $hex The 3 to 6 character RGB hex. The '#' prefix may be added.
594
+ * RRGGBBAA is supported, but the Alpha channels won't be returned.
595
  * @return string The hexadecimal RGB relative font color, without '#' prefix.
596
  */
597
  public function get_relative_fontcolor( $hex = '' ) {
598
 
599
  $hex = ltrim( $hex, '#' );
600
 
601
+ // Convert hex to usable numerics.
602
+ [ $r, $g, $b ] = array_map(
603
+ 'hexdec',
604
+ str_split(
605
+ // rgb == rrggbb.
606
+ \strlen( $hex ) >= 6 ? $hex : "$hex[0]$hex[0]$hex[1]$hex[1]$hex[2]$hex[2]",
607
+ 2
608
+ )
609
+ );
 
610
 
611
  $get_relative_luminance = static function( $v ) {
612
+ // Convert to 0~1 value.
613
+ $v /= 0xFF;
614
 
615
  if ( $v > .03928 ) {
616
  $lum = ( ( $v + .055 ) / 1.055 ) ** 2.4;
620
  return $lum;
621
  };
622
 
623
+ // Create Relative Luminance via sRGB.
624
+ $rl = ( 0.2126 * $get_relative_luminance( $r ) )
625
+ + ( 0.7152 * $get_relative_luminance( $g ) )
626
+ + ( 0.0722 * $get_relative_luminance( $b ) );
 
 
627
 
628
  // Build light greyscale.
629
+ $gr = ( $r * 0.2989 / 8 ) * $rl;
630
+ $gg = ( $g * 0.5870 / 8 ) * $rl;
631
+ $gb = ( $b * 0.1140 / 8 ) * $rl;
632
+
633
+ // Invert colors if they hit this luminance boundary.
634
+ if ( $rl < 0.5 ) {
635
+ // Build dark greyscale. bitwise operators round...
636
+ $gr ^= 0xFF;
637
+ $gg ^= 0xFF;
638
+ $gb ^= 0xFF;
639
  }
640
 
641
+ return vsprintf( '%02x%02x%02x', [ $gr, $gg, $gb ] );
 
 
 
 
 
642
  }
643
 
644
  /**
662
  $accent = $this->s_color_hex( $this->get_option( 'sitemap_color_accent' ) );
663
 
664
  $options = [
665
+ 'main' => $main ? "#$main" : '',
666
+ 'accent' => $accent ? "#$accent" : '',
667
  ];
668
 
669
  $options = array_filter( $options );
inc/classes/detect.class.php CHANGED
@@ -39,31 +39,29 @@ class Detect extends Render {
39
  * Memoizes the return value.
40
  *
41
  * @since 2.6.1
42
- * @credits Jetpack for most code.
43
  *
44
  * @return array List of active plugins.
45
  */
46
  public function active_plugins() {
47
 
48
- static $active_plugins = null;
49
-
50
- if ( isset( $active_plugins ) )
51
- return $active_plugins;
52
 
53
  $active_plugins = (array) \get_option( 'active_plugins', [] );
54
 
55
  if ( \is_multisite() ) {
56
  // Due to legacy code, active_sitewide_plugins stores them in the keys,
57
- // whereas active_plugins stores them in the values.
58
  $network_plugins = array_keys( \get_site_option( 'active_sitewide_plugins', [] ) );
59
- if ( $network_plugins ) {
 
60
  $active_plugins = array_merge( $active_plugins, $network_plugins );
61
- }
62
  }
63
 
64
  sort( $active_plugins );
65
 
66
- return $active_plugins = array_unique( $active_plugins );
67
  }
68
 
69
  /**
@@ -78,34 +76,26 @@ class Detect extends Render {
78
 
79
  $conflicting_plugins = [
80
  'seo_tools' => [
81
- 'Yoast SEO' => 'wordpress-seo/wp-seo.php',
82
- 'Yoast SEO Premium' => 'wordpress-seo-premium/wp-seo-premium.php',
83
- 'All in One SEO Pack' => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
84
- 'SEO Ultimate' => 'seo-ultimate/seo-ultimate.php',
85
- 'Gregs High Performance SEO' => 'gregs-high-performance-seo/ghpseo.php',
86
- 'SEOPress' => 'wp-seopress/seopress.php',
87
- 'Rank Math' => 'seo-by-rank-math/rank-math.php',
88
- 'Smart Crawl' => 'smartcrawl-seo/wpmu-dev-seo.php',
89
  ],
90
  'sitemaps' => [
91
- 'Google XML Sitemaps' => 'google-sitemap-generator/sitemap.php',
92
- 'Better WordPress Google XML Sitemaps' => 'bwp-google-xml-sitemaps/bwp-simple-gxs.php', // Remove?
93
- 'Google XML Sitemaps for qTranslate' => 'google-xml-sitemaps-v3-for-qtranslate/sitemap.php', // Remove?
94
- 'XML Sitemap & Google News feeds' => 'xml-sitemap-feed/xml-sitemap.php',
95
- 'Google Sitemap by BestWebSoft' => 'google-sitemap-plugin/google-sitemap-plugin.php',
96
- 'Simple Wp Sitemap' => 'simple-wp-sitemap/simple-wp-sitemap.php',
97
- 'XML Sitemaps' => 'xml-sitemaps/xml-sitemaps.php',
98
  ],
99
  'open_graph' => [
100
  'Facebook Open Graph Meta Tags for WordPress' => 'wonderm00ns-simple-facebook-open-graph-tags/wonderm00n-open-graph.php',
101
- 'Facebook Thumb Fixer' => 'facebook-thumb-fixer/_facebook-thumb-fixer.php',
102
- 'NextGEN Facebook OG' => 'nextgen-facebook/nextgen-facebook.php',
103
- 'Open Graph' => 'opengraph/opengraph.php',
104
- 'Open Graph Protocol Framework' => 'open-graph-protocol-framework/open-graph-protocol-framework.php',
105
  'Shareaholic2' => 'shareaholic/sexy-bookmarks.php',
106
- 'Social Sharing Toolkit' => 'social-sharing-toolkit/social_sharing_toolkit.php',
107
  'WordPress Social Sharing Optimization' => 'wpsso/wpsso.php',
108
- 'WP Facebook Open Graph protocol' => 'wp-facebook-open-graph-protocol/wp-facebook-ogp.php',
109
  ],
110
  'twitter_card' => [],
111
  ];
@@ -114,25 +104,31 @@ class Detect extends Render {
114
  * @since 2.6.0
115
  * @param array $conflicting_plugins The conflicting plugin list.
116
  */
117
- return (array) \apply_filters( 'the_seo_framework_conflicting_plugins', $conflicting_plugins );
118
  }
119
 
120
  /**
121
  * Fetches type of conflicting plugins.
122
  *
123
  * @since 2.6.0
 
124
  *
125
  * @param string $type The Key from $this->conflicting_plugins()
126
  * @return array
127
  */
128
  public function get_conflicting_plugins( $type = 'seo_tools' ) {
129
-
130
- $conflicting_plugins = $this->conflicting_plugins();
131
-
132
- if ( isset( $conflicting_plugins[ $type ] ) )
133
- return (array) \apply_filters( 'the_seo_framework_conflicting_plugins_type', $conflicting_plugins[ $type ], $type );
134
-
135
- return [];
 
 
 
 
 
136
  }
137
 
138
  /**
@@ -141,8 +137,8 @@ class Detect extends Render {
141
  * Note: Class check is 3 times as slow as defined check. Function check is 2 times as slow.
142
  *
143
  * @since 1.3.0
144
- * @since 2.8.0 : 1. Can now check for globals.
145
- * 2. Switched detection order from FAST to SLOW.
146
  * @since 4.0.6 Can no longer autoload classes.
147
  *
148
  * @param array $plugins Array of array for constants, classes and / or functions to check for plugin existence.
@@ -150,41 +146,24 @@ class Detect extends Render {
150
  */
151
  public function detect_plugin( $plugins ) {
152
 
153
- if ( isset( $plugins['globals'] ) ) {
154
- foreach ( $plugins['globals'] as $name ) {
155
- if ( isset( $GLOBALS[ $name ] ) ) {
156
- return true;
157
- }
158
- }
159
- }
160
 
161
  // Check for constants
162
- if ( isset( $plugins['constants'] ) ) {
163
- foreach ( $plugins['constants'] as $name ) {
164
- if ( \defined( $name ) ) {
165
- return true;
166
- }
167
- }
168
- }
169
 
170
  // Check for functions
171
- if ( isset( $plugins['functions'] ) ) {
172
- foreach ( $plugins['functions'] as $name ) {
173
- if ( \function_exists( $name ) ) {
174
- return true;
175
- }
176
- }
177
- }
178
 
179
  // Check for classes
180
- if ( isset( $plugins['classes'] ) ) {
181
- foreach ( $plugins['classes'] as $name ) {
182
- // phpcs:ignore, TSF.Performance.Functions.PHP -- we don't autoload.
183
- if ( class_exists( $name, false ) ) {
184
- return true;
185
- }
186
- }
187
- }
188
 
189
  // No globals, constant, function, or class found to exist
190
  return false;
@@ -197,40 +176,27 @@ class Detect extends Render {
197
  *
198
  * @since 2.5.2
199
  * @since 4.1.4 Fixed sorting algorithm from fribbling-me to resolving-me. Nothing changed but legibility.
 
200
  * @uses $this->detect_plugin_multi()
201
  *
202
- * @param array $plugins Array of array for globals, constants, classes
203
  * and/or functions to check for plugin existence.
204
- * @param bool $use_cache Bypasses cache if false
205
  */
206
- public function can_i_use( array $plugins = [], $use_cache = true ) {
207
 
208
  if ( ! $use_cache )
209
  return $this->detect_plugin_multi( $plugins );
210
 
211
- static $cache = [];
212
-
213
- $mapped = [];
214
-
215
- // Prepare multidimensional array for cache.
216
- foreach ( $plugins as $type => $func ) {
217
- if ( ! \is_array( $func ) )
218
- return false; // doing it wrong...
219
 
220
- sort( $func );
 
221
 
222
- // Glue with underscore and space for debugging purposes.
223
- $mapped[ $type ] = $type . '_' . implode( ' ', $func );
224
- }
225
-
226
- ksort( $mapped );
227
  // phpcs:ignore, WordPress.PHP.DiscouragedPHPFunctions -- No objects are inserted, nor is this ever unserialized.
228
- $key = serialize( $mapped );
229
-
230
- if ( isset( $cache[ $key ] ) )
231
- return $cache[ $key ];
232
 
233
- return $cache[ $key ] = $this->detect_plugin_multi( $plugins );
234
  }
235
 
236
  /**
@@ -238,53 +204,38 @@ class Detect extends Render {
238
  * All parameters must match and return true.
239
  *
240
  * @since 2.5.2
241
- * @since 4.0.6 : 1. Can now check for globals.
242
- * 2. Switched detection order from FAST to SLOW.
243
- * 3. Can no longer autoload classes.
244
  * This method is only used by can_i_use(), and is only effective in the Ultimate Member compat file...
245
  * @TODO deprecate?
246
  *
247
- * @param array $plugins Array of array for constants, classes and / or functions to check for plugin existence.
248
- * @return bool True if ALL functions classes and constants exists or false if plugin constant, class or function not detected.
 
 
249
  */
250
- public function detect_plugin_multi( array $plugins ) {
251
 
252
  // Check for globals
253
- if ( isset( $plugins['globals'] ) ) {
254
- foreach ( $plugins['globals'] as $name ) {
255
- if ( ! isset( $GLOBALS[ $name ] ) ) {
256
- return false;
257
- }
258
- }
259
- }
260
 
261
  // Check for constants
262
- if ( isset( $plugins['constants'] ) ) {
263
- foreach ( $plugins['constants'] as $name ) {
264
- if ( ! \defined( $name ) ) {
265
- return false;
266
- }
267
- }
268
- }
269
 
270
  // Check for functions
271
- if ( isset( $plugins['functions'] ) ) {
272
- foreach ( $plugins['functions'] as $name ) {
273
- if ( ! \function_exists( $name ) ) {
274
- return false;
275
- }
276
- }
277
- }
278
 
279
  // Check for classes
280
- if ( isset( $plugins['classes'] ) ) {
281
- foreach ( $plugins['classes'] as $name ) {
282
- // phpcs:ignore, TSF.Performance.Functions.PHP -- we don't autoload.
283
- if ( ! class_exists( $name, false ) ) {
284
- return false;
285
- }
286
- }
287
- }
288
 
289
  // All classes, functions and constant have been found to exist
290
  return true;
@@ -294,32 +245,21 @@ class Detect extends Render {
294
  * Checks if the (parent) theme name is loaded.
295
  *
296
  * @since 2.1.0
 
297
  *
298
- * @param string|array $themes the current theme name.
299
  * @return bool is theme active.
300
  */
301
  public function is_theme( $themes = '' ) {
302
 
303
- if ( empty( $themes ) )
304
- return false;
305
-
306
- $wp_get_theme = \wp_get_theme();
307
-
308
- $theme_parent = strtolower( $wp_get_theme->get( 'Template' ) );
309
- $theme_name = strtolower( $wp_get_theme->get( 'Name' ) );
310
 
311
- if ( \is_string( $themes ) ) {
312
- $themes = strtolower( $themes );
313
- if ( $themes === $theme_parent || $themes === $theme_name )
314
  return true;
315
- } elseif ( \is_array( $themes ) ) {
316
- foreach ( $themes as $theme ) {
317
- $theme = strtolower( $theme );
318
- if ( $theme === $theme_parent || $theme === $theme_name ) {
319
- return true;
320
- }
321
- }
322
- }
323
 
324
  return false;
325
  }
@@ -336,39 +276,37 @@ class Detect extends Render {
336
  */
337
  public function detect_seo_plugins() {
338
 
339
- static $detected = null;
340
-
341
- if ( isset( $detected ) )
342
- return $detected;
343
 
344
  $active_plugins = $this->active_plugins();
345
 
346
- if ( ! empty( $active_plugins ) ) {
347
- $conflicting_plugins = $this->get_conflicting_plugins( 'seo_tools' );
348
-
349
- foreach ( $conflicting_plugins as $plugin_name => $plugin ) {
350
- if ( \in_array( $plugin, $active_plugins, true ) ) {
351
- /**
352
- * @since 2.6.1
353
- * @since 3.1.0 Added second and third parameters.
354
- * @param bool $detected Whether the plugin should be detected.
355
- * @param string $plugin_name The plugin name as defined in `$this->conflicting_plugins()`.
356
- * @param string $plugin The plugin that's been detected.
357
- */
358
- $detected = \apply_filters_ref_array(
359
- 'the_seo_framework_seo_plugin_detected',
360
- [
361
- true,
362
- $plugin_name,
363
- $plugin,
364
- ]
365
- );
366
- if ( $detected ) break;
367
  }
368
  }
369
  }
370
 
371
- return $detected = (bool) $detected;
372
  }
373
 
374
  /**
@@ -383,43 +321,41 @@ class Detect extends Render {
383
  */
384
  public function detect_og_plugin() {
385
 
386
- static $detected = null;
387
-
388
- if ( isset( $detected ) )
389
- return $detected;
390
-
391
  // Detect SEO plugins beforehand.
392
  if ( $this->detect_seo_plugins() )
393
- return $detected = true;
 
 
 
394
 
395
  $active_plugins = $this->active_plugins();
396
 
397
- if ( ! empty( $active_plugins ) ) {
398
- $conflicting_plugins = $this->get_conflicting_plugins( 'open_graph' );
399
-
400
- foreach ( $conflicting_plugins as $plugin_name => $plugin ) {
401
- if ( \in_array( $plugin, $active_plugins, true ) ) {
402
- /**
403
- * @since 2.6.1
404
- * @since 3.1.0 Added second and third parameters.
405
- * @param bool $detected Whether the plugin should be detected.
406
- * @param string $plugin_name The plugin name as defined in `$this->conflicting_plugins()`.
407
- * @param string $plugin The plugin that's been detected.
408
- */
409
- $detected = \apply_filters_ref_array(
410
- 'the_seo_framework_og_plugin_detected',
411
- [
412
- true,
413
- $plugin_name,
414
- $plugin,
415
- ]
416
- );
417
- if ( $detected ) break;
418
  }
419
  }
420
  }
421
 
422
- return $detected = (bool) $detected;
423
  }
424
 
425
  /**
@@ -433,42 +369,40 @@ class Detect extends Render {
433
  */
434
  public function detect_twitter_card_plugin() {
435
 
436
- static $detected = null;
437
-
438
- if ( isset( $detected ) )
439
- return $detected;
440
-
441
  // Detect SEO plugins beforehand.
442
  if ( $this->detect_seo_plugins() )
443
- return $detected = true;
 
 
 
444
 
445
  $active_plugins = $this->active_plugins();
446
 
447
- if ( ! empty( $active_plugins ) ) {
448
- $conflicting_plugins = $this->get_conflicting_plugins( 'twitter_card' );
449
-
450
- foreach ( $conflicting_plugins as $plugin_name => $plugin ) {
451
- if ( \in_array( $plugin, $active_plugins, true ) ) {
452
- /**
453
- * @since 2.6.1
454
- * @param bool $detected Whether the plugin should be detected.
455
- * @param string $plugin_name The plugin name as defined in `$this->conflicting_plugins()`.
456
- * @param string $plugin The plugin that's been detected.
457
- */
458
- $detected = \apply_filters_ref_array(
459
- 'the_seo_framework_twittercard_plugin_detected',
460
- [
461
- true,
462
- $plugin_name,
463
- $plugin,
464
- ]
465
- );
466
- if ( $detected ) break;
467
  }
468
  }
469
  }
470
 
471
- return $detected = (bool) $detected;
472
  }
473
 
474
  /**
@@ -499,42 +433,40 @@ class Detect extends Render {
499
  */
500
  public function detect_sitemap_plugin() {
501
 
502
- static $detected = null;
503
-
504
- if ( isset( $detected ) )
505
- return $detected;
506
-
507
  // Detect SEO plugins beforehand.
508
  if ( $this->detect_seo_plugins() )
509
- return $detected = true;
 
 
 
510
 
511
  $active_plugins = $this->active_plugins();
512
 
513
- if ( ! empty( $active_plugins ) ) {
514
- $conflicting_plugins = $this->get_conflicting_plugins( 'sitemaps' );
515
-
516
- foreach ( $conflicting_plugins as $plugin_name => $plugin ) {
517
- if ( \in_array( $plugin, $active_plugins, true ) ) {
518
- /**
519
- * @since 2.6.1
520
- * @param bool $detected Whether the plugin should be detected.
521
- * @param string $plugin_name The plugin name as defined in `$this->conflicting_plugins()`.
522
- * @param string $plugin The plugin that's been detected.
523
- */
524
- $detected = \apply_filters(
525
- 'the_seo_framework_sitemap_plugin_detected',
526
- [
527
- true,
528
- $plugin_name,
529
- $plugin,
530
- ]
531
- );
532
- if ( $detected ) break;
533
  }
534
  }
535
  }
536
 
537
- return $detected = (bool) $detected;
538
  }
539
 
540
  /**
@@ -546,65 +478,18 @@ class Detect extends Render {
546
  * @return bool
547
  */
548
  public function use_core_sitemaps() {
549
- static $use;
550
 
551
- if ( isset( $use ) ) return $use;
 
552
 
553
  if ( $this->get_option( 'sitemaps_output' ) )
554
- return $use = false;
555
-
556
- if ( \function_exists( '\\wp_sitemaps_get_server' ) ) {
557
- $wp_sitemaps_server = \wp_sitemaps_get_server();
558
-
559
- return $use =
560
- method_exists( $wp_sitemaps_server, 'sitemaps_enabled' )
561
- && $wp_sitemaps_server->sitemaps_enabled();
562
- }
563
 
564
- return $use = false;
565
- }
566
 
567
- /**
568
- * Detects presence of a page builder.
569
- * Memoizes the return value.
570
- *
571
- * Detects the following builders:
572
- * - Elementor by Elementor LTD
573
- * - Divi Builder by Elegant Themes
574
- * - Visual Composer by WPBakery
575
- * - Page Builder by SiteOrigin
576
- * - Beaver Builder by Fastline Media
577
- *
578
- * @since 4.0.0
579
- * @since 4.0.6 The output is now filterable.
580
- * @TODO deprecate?
581
- * @ignore unused.
582
- *
583
- * @return bool
584
- */
585
- public function detect_page_builder() {
586
-
587
- static $detected = null;
588
-
589
- if ( isset( $detected ) ) return $detected;
590
-
591
- /**
592
- * @since 4.0.6
593
- * @param bool $detected Whether an active page builder is detected.
594
- * @NOTE not to be confused with `the_seo_framework_detect_page_builder`, which tests
595
- * the page builder status for each post individually.
596
- */
597
- return $detected = (bool) \apply_filters(
598
- 'the_seo_framework_page_builder_active',
599
- $this->detect_plugin( [
600
- 'constants' => [
601
- 'ELEMENTOR_VERSION',
602
- 'ET_BUILDER_VERSION',
603
- 'WPB_VC_VERSION',
604
- 'SITEORIGIN_PANELS_VERSION',
605
- 'FL_BUILDER_VERSION',
606
- ],
607
- ] )
608
  );
609
  }
610
 
@@ -620,25 +505,22 @@ class Detect extends Render {
620
  * @return bool
621
  */
622
  public function detect_non_html_page_builder() {
623
-
624
- static $detected = null;
625
-
626
- if ( isset( $detected ) ) return $detected;
627
-
628
- /**
629
- * @since 4.1.0
630
- * @param bool $detected Whether an active page builder that renders content dynamically is detected.
631
- * @NOTE not to be confused with `the_seo_framework_detect_non_html_page_builder`, which tests
632
- * the page builder status for each post individually.
633
- */
634
- return $detected = (bool) \apply_filters(
635
- 'the_seo_framework_shortcode_based_page_builder_active',
636
- $this->detect_plugin( [
637
- 'constants' => [
638
- 'ET_BUILDER_VERSION',
639
- 'WPB_VC_VERSION',
640
- ],
641
- ] )
642
  );
643
  }
644
 
@@ -652,11 +534,8 @@ class Detect extends Render {
652
  * @return bool Whether the robots.txt file exists.
653
  */
654
  public function has_robots_txt() {
655
-
656
- static $has_robots = null;
657
-
658
- if ( isset( $has_robots ) )
659
- return $has_robots;
660
 
661
  // Ensure get_home_path() is declared.
662
  if ( ! \function_exists( '\\get_home_path' ) )
@@ -665,7 +544,7 @@ class Detect extends Render {
665
  $path = \get_home_path() . 'robots.txt';
666
 
667
  // phpcs:ignore, TSF.Performance.Functions.PHP -- we use path, not URL.
668
- return $has_robots = file_exists( $path );
669
  }
670
 
671
  /**
@@ -678,11 +557,8 @@ class Detect extends Render {
678
  * @return bool Whether the sitemap.xml file exists.
679
  */
680
  public function has_sitemap_xml() {
681
-
682
- static $has_map = null;
683
-
684
- if ( isset( $has_map ) )
685
- return $has_map;
686
 
687
  // Ensure get_home_path() is declared.
688
  if ( ! \function_exists( '\\get_home_path' ) )
@@ -691,59 +567,7 @@ class Detect extends Render {
691
  $path = \get_home_path() . 'sitemap.xml';
692
 
693
  // phpcs:ignore, TSF.Performance.Functions.PHP -- we use path, not URL.
694
- return $has_map = file_exists( $path );
695
- }
696
-
697
- /**
698
- * Determines if WP is above or below a version
699
- *
700
- * @since 2.2.1
701
- * @since 2.3.8 Added caching
702
- * @since 2.8.0 No longer overwrites global $wp_version
703
- * @since 3.1.0 1. No longer caches.
704
- * 2. Removed redundant parameter checks.
705
- * 3. Now supports x.yy.zz WordPress versions.
706
- *
707
- * @param string $version the three part version to compare to WordPress
708
- * @param string $compare the comparing operator, default "$version >= Current WP Version"
709
- * @return bool True if the WordPress version comparison passes.
710
- */
711
- public function wp_version( $version = '4.3.0', $compare = '>=' ) {
712
-
713
- $wp_version = $GLOBALS['wp_version'];
714
-
715
- /**
716
- * Add a .0 if WP outputs something like 4.3 instead of 4.3.0
717
- * Does consider 4.xx, which will become 4.xx.0
718
- */
719
- if ( 1 === substr_count( $wp_version, '.' ) )
720
- $wp_version = $wp_version . '.0';
721
-
722
- return (bool) version_compare( $wp_version, $version, $compare );
723
- }
724
-
725
- /**
726
- * Checks for current theme support.
727
- *
728
- * Maintains detection cache, array and strings are mixed through foreach loops.
729
- *
730
- * @since 2.2.5
731
- * @since 3.1.0 Removed caching
732
- * @TODO deprecate me.
733
- *
734
- * @param string|array required $features The features to check for.
735
- * @return bool theme support.
736
- */
737
- public function detect_theme_support( $features ) {
738
-
739
- foreach ( (array) $features as $feature ) {
740
- if ( \current_theme_supports( $feature ) ) {
741
- return true;
742
- }
743
- continue;
744
- }
745
-
746
- return false;
747
  }
748
 
749
  /**
@@ -757,9 +581,8 @@ class Detect extends Render {
757
  */
758
  public function query_supports_seo() {
759
 
760
- static $cache;
761
-
762
- if ( isset( $cache ) ) return $cache;
763
 
764
  switch ( true ) :
765
  case $this->is_feed():
@@ -793,15 +616,14 @@ class Detect extends Render {
793
  * This protects against (accidental) negative-SEO bombarding.
794
  * Support broken queries, so we can noindex them.
795
  */
796
- if ( ! $supported && $this->is_query_exploited() ) {
797
  $supported = true;
798
- }
799
 
800
  /**
801
  * @since 4.0.0
802
  * @param bool $supported Whether the query supports SEO.
803
  */
804
- return $cache = (bool) \apply_filters( 'the_seo_framework_query_supports_seo', $supported );
805
  }
806
 
807
  /**
@@ -843,16 +665,15 @@ class Detect extends Render {
843
  */
844
  public function is_query_exploited() {
845
 
846
- static $exploited;
847
-
848
- if ( isset( $exploited ) ) return $exploited;
849
 
850
  if ( ! $this->get_option( 'advanced_query_protection' ) )
851
- return $exploited = false;
852
 
853
  // When the page ID is not 0, a real page will always be returned.
854
  if ( $this->get_the_real_ID() )
855
- return $exploited = false;
856
 
857
  global $wp_query;
858
 
@@ -892,20 +713,17 @@ class Detect extends Render {
892
  ]
893
  );
894
 
895
- $query = $wp_query->query;
896
- $exploited = false;
897
 
898
  foreach ( $exploitables as $type => $qvs ) :
899
  foreach ( $qvs as $qv ) :
900
- // Don't guess "empty", because falsey or empty-array is also empty.
901
  if ( ! isset( $query[ $qv ] ) ) continue;
902
 
903
  switch ( $type ) :
904
  case 'numeric':
905
- if ( '0' === $query[ $qv ] || ! is_numeric( $query[ $qv ] ) ) {
906
- $exploited = true;
907
- break 3;
908
- }
909
  break;
910
 
911
  case 'numeric_array':
@@ -914,17 +732,13 @@ class Detect extends Render {
914
 
915
  // If WordPress didn't canonical_redirect() the user yet, it's exploited.
916
  // WordPress mitigates this via a 404 query when a numeric value is found.
917
- if ( ! preg_match( '/[0-9]/', $query[ $qv ] ) ) {
918
- $exploited = true;
919
- break 3;
920
- }
921
  break;
922
 
923
  case 'requires_s':
924
- if ( ! isset( $query['s'] ) ) {
925
- $exploited = true;
926
- break 3;
927
- }
928
  break;
929
 
930
  default:
@@ -933,7 +747,36 @@ class Detect extends Render {
933
  endforeach;
934
  endforeach;
935
 
936
- return $exploited;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
937
  }
938
 
939
  /**
@@ -1013,36 +856,69 @@ class Detect extends Render {
1013
  */
1014
  public function post_type_supports_taxonomies( $post_type = '' ) {
1015
 
1016
- static $cache = [];
1017
-
1018
- if ( isset( $cache[ $post_type ] ) )
1019
- return $cache[ $post_type ];
1020
 
1021
  $post_type = $post_type ?: $this->get_current_post_type();
1022
- if ( ! $post_type ) return false;
1023
 
1024
- if ( \get_object_taxonomies( $post_type, 'names' ) )
1025
- return $cache[ $post_type ] = true;
 
1026
 
1027
- return $cache[ $post_type ] = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1028
  }
1029
 
1030
  /**
1031
  * Returns a list of all supported post types.
1032
  *
1033
  * @since 3.1.0
1034
- * @stativar array $cache
1035
  *
1036
- * @return array The supported post types.
1037
  */
1038
  public function get_supported_post_types() {
1039
-
1040
- static $cache = [];
1041
- // Can't be recursively empty. Right?
1042
- if ( $cache ) return $cache;
1043
-
1044
- return $cache = array_values(
1045
- array_filter( $this->get_public_post_types(), [ $this, 'is_post_type_supported' ] )
1046
  );
1047
  }
1048
 
@@ -1053,47 +929,72 @@ class Detect extends Render {
1053
  * @since 4.1.0
1054
  * @since 4.1.4 Now resets the index keys of the return value.
1055
  *
1056
- * @return array All public post types.
1057
  */
1058
  protected function get_public_post_types() {
1059
-
1060
- static $cache = null;
1061
-
1062
- return isset( $cache ) ? $cache : $cache = array_values(
1063
- array_filter(
1064
- array_unique(
1065
- array_merge(
1066
- $this->get_forced_supported_post_types(),
1067
- //? array_values() because get_post_types() gives a sequential array.
1068
- array_keys( (array) \get_post_types( [ 'public' => true ] ) )
 
 
 
 
 
 
 
 
 
 
 
 
1069
  )
1070
- ),
1071
- '\\is_post_type_viewable'
1072
- )
1073
- );
1074
  }
1075
 
1076
  /**
1077
  * Returns a list of builtin public post types.
1078
- * Memoizes the return value.
1079
  *
1080
  * @since 3.1.0
 
1081
  *
1082
- * @return array Forced supported post types.
1083
  */
1084
  protected function get_forced_supported_post_types() {
1085
-
1086
- static $cache = null;
1087
  /**
1088
- * @since 3.1.0
1089
- * @param array $forced Forced supported post types
1090
- */
1091
- return isset( $cache ) ? $cache : $cache = (array) \apply_filters(
1092
  'the_seo_framework_forced_supported_post_types',
1093
- array_values( \get_post_types( [
1094
- 'public' => true,
1095
- '_builtin' => true,
1096
- ] ) )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1097
  );
1098
  }
1099
 
@@ -1103,48 +1004,58 @@ class Detect extends Render {
1103
  *
1104
  * @since 4.1.0
1105
  *
1106
- * @return array The taxonomies that are public.
1107
  */
1108
  protected function get_public_taxonomies() {
1109
-
1110
- static $cache = null;
1111
-
1112
- return isset( $cache ) ? $cache : $cache = array_filter(
1113
- array_unique(
1114
- array_merge(
1115
- $this->get_forced_supported_taxonomies(),
1116
- //? array_values() because get_taxonomies() gives a sequential array.
1117
- array_values( (array) \get_taxonomies( [
1118
- 'public' => true,
1119
- '_builtin' => false,
1120
- ] ) )
 
 
 
 
 
 
 
 
 
 
 
 
1121
  )
1122
- ),
1123
- '\\is_taxonomy_viewable'
1124
- );
1125
  }
1126
 
1127
  /**
1128
  * Returns a list of builtin public taxonomies.
1129
- * Memoizes the return value.
1130
  *
1131
  * @since 4.1.0
 
1132
  *
1133
- * @return array Forced supported taxonomies
1134
  */
1135
  protected function get_forced_supported_taxonomies() {
1136
-
1137
- static $cache = null;
1138
  /**
1139
  * @since 4.1.0
1140
- * @param array $forced Forced supported post types
1141
  */
1142
- return isset( $cache ) ? $cache : $cache = (array) \apply_filters(
1143
  'the_seo_framework_forced_supported_taxonomies',
1144
- array_values( \get_taxonomies( [
1145
- 'public' => true,
1146
- '_builtin' => true,
1147
- ] ) )
 
 
1148
  );
1149
  }
1150
 
@@ -1168,11 +1079,12 @@ class Detect extends Render {
1168
  * @param bool $disabled
1169
  * @param string $post_type
1170
  */
1171
- return \apply_filters( 'the_seo_framework_post_type_disabled',
1172
- isset(
1173
- $this->get_option( 'disabled_post_types' )[ $post_type ]
1174
- ),
1175
- $post_type
 
1176
  );
1177
  }
1178
 
@@ -1193,16 +1105,18 @@ class Detect extends Render {
1193
 
1194
  $disabled = false;
1195
 
1196
- if ( isset( $this->get_option( 'disabled_taxonomies' )[ $taxonomy ] ) ) {
 
1197
  $disabled = true;
1198
  } else {
 
 
1199
  foreach ( $this->get_post_types_from_taxonomy( $taxonomy ) as $type ) {
1200
- // Set here, because the taxonomy might not have post types at all.
1201
- $disabled = true;
1202
  if ( $this->is_post_type_supported( $type ) ) {
1203
  $disabled = false;
1204
  break;
1205
  }
 
1206
  }
1207
  }
1208
 
@@ -1211,7 +1125,13 @@ class Detect extends Render {
1211
  * @param bool $disabled
1212
  * @param string $taxonomy
1213
  */
1214
- return \apply_filters( 'the_seo_framework_taxonomy_disabled', $disabled, $taxonomy );
 
 
 
 
 
 
1215
  }
1216
 
1217
  /**
@@ -1230,8 +1150,8 @@ class Detect extends Render {
1230
  * Detects if we're on a Gutenberg page.
1231
  *
1232
  * @since 3.1.0
1233
- * @since 3.2.0 : 1. Now detects the WP 5.0 block editor.
1234
- * 2. Method is now public.
1235
  *
1236
  * @return bool
1237
  */
@@ -1269,9 +1189,8 @@ class Detect extends Render {
1269
  * @return string URL location of robots.txt. Unescaped.
1270
  */
1271
  public function get_robots_txt_url() {
1272
- global $wp_rewrite;
1273
 
1274
- if ( $wp_rewrite->using_permalinks() && ! $this->is_subdirectory_installation() ) {
1275
  $home = \trailingslashit( $this->set_preferred_url_scheme( $this->get_home_host() ) );
1276
  $path = "{$home}robots.txt";
1277
  } elseif ( $this->has_robots_txt() ) {
@@ -1293,15 +1212,11 @@ class Detect extends Render {
1293
  * @return bool
1294
  */
1295
  public function is_subdirectory_installation() {
1296
-
1297
- static $cache = null;
1298
-
1299
- if ( isset( $cache ) )
1300
- return $cache;
1301
-
1302
- $parsed_url = parse_url( \get_option( 'home' ) );
1303
-
1304
- return $cache = ! empty( $parsed_url['path'] ) && ltrim( $parsed_url['path'], ' \\/' );
1305
  }
1306
 
1307
  /**
@@ -1318,14 +1233,12 @@ class Detect extends Render {
1318
  if ( false === strpos( $text, '%%' ) ) return false;
1319
 
1320
  $tags_simple = [ 'date', 'title', 'parent_title', 'archive_title', 'sitename', 'sitedesc', 'excerpt', 'excerpt_only', 'tag', 'category', 'primary_category', 'category_description', 'tag_description', 'term_description', 'term_title', 'searchphrase', 'sep', 'pt_single', 'pt_plural', 'modified', 'id', 'name', 'user_description', 'page', 'pagetotal', 'pagenumber', 'caption', 'focuskw', 'term404', 'ct_product_cat', 'ct_product_tag', 'wc_shortdesc', 'wc_sku', 'wc_brand', 'wc_price' ];
1321
-
1322
- $_regex = sprintf( '%%%s%%', implode( '|', $tags_simple ) );
1323
 
1324
  if ( preg_match( "/$_regex/i", $text ) ) return true;
1325
 
1326
  $tags_wildcard_end = [ 'cs_', 'ct_desc_', 'ct_pa_' ];
1327
-
1328
- $_regex = sprintf( '%%(%s)[^\s]*?%%', implode( '|', $tags_wildcard_end ) );
1329
 
1330
  if ( preg_match( "/$_regex/", $text ) ) return true;
1331
 
39
  * Memoizes the return value.
40
  *
41
  * @since 2.6.1
42
+ * @credits Jetpack for some code.
43
  *
44
  * @return array List of active plugins.
45
  */
46
  public function active_plugins() {
47
 
48
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
49
+ if ( null !== $memo = memo() ) return $memo;
 
 
50
 
51
  $active_plugins = (array) \get_option( 'active_plugins', [] );
52
 
53
  if ( \is_multisite() ) {
54
  // Due to legacy code, active_sitewide_plugins stores them in the keys,
55
+ // whereas active_plugins stores them in the values. array_keys() resolves the disparity.
56
  $network_plugins = array_keys( \get_site_option( 'active_sitewide_plugins', [] ) );
57
+
58
+ if ( $network_plugins )
59
  $active_plugins = array_merge( $active_plugins, $network_plugins );
 
60
  }
61
 
62
  sort( $active_plugins );
63
 
64
+ return memo( $active_plugins );
65
  }
66
 
67
  /**
76
 
77
  $conflicting_plugins = [
78
  'seo_tools' => [
79
+ 'Yoast SEO' => 'wordpress-seo/wp-seo.php',
80
+ 'Yoast SEO Premium' => 'wordpress-seo-premium/wp-seo-premium.php',
81
+ 'All in One SEO Pack' => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
82
+ 'SEO Ultimate' => 'seo-ultimate/seo-ultimate.php',
83
+ 'SEOPress' => 'wp-seopress/seopress.php',
84
+ 'Rank Math' => 'seo-by-rank-math/rank-math.php',
85
+ 'Smart Crawl' => 'smartcrawl-seo/wpmu-dev-seo.php',
 
86
  ],
87
  'sitemaps' => [
88
+ 'Google XML Sitemaps' => 'google-sitemap-generator/sitemap.php',
89
+ 'XML Sitemap & Google News feeds' => 'xml-sitemap-feed/xml-sitemap.php',
90
+ 'Google Sitemap by BestWebSoft' => 'google-sitemap-plugin/google-sitemap-plugin.php',
91
+ 'Simple Wp Sitemap' => 'simple-wp-sitemap/simple-wp-sitemap.php', // Remove?
 
 
 
92
  ],
93
  'open_graph' => [
94
  'Facebook Open Graph Meta Tags for WordPress' => 'wonderm00ns-simple-facebook-open-graph-tags/wonderm00n-open-graph.php',
95
+ 'Open Graph' => 'opengraph/opengraph.php', // Redundant.
96
+ 'Open Graph Protocol Framework' => 'open-graph-protocol-framework/open-graph-protocol-framework.php', // Redundant.
 
 
97
  'Shareaholic2' => 'shareaholic/sexy-bookmarks.php',
 
98
  'WordPress Social Sharing Optimization' => 'wpsso/wpsso.php',
 
99
  ],
100
  'twitter_card' => [],
101
  ];
104
  * @since 2.6.0
105
  * @param array $conflicting_plugins The conflicting plugin list.
106
  */
107
+ return (array) \apply_filters_ref_array( 'the_seo_framework_conflicting_plugins', [ $conflicting_plugins ] );
108
  }
109
 
110
  /**
111
  * Fetches type of conflicting plugins.
112
  *
113
  * @since 2.6.0
114
+ * @since 4.2.0 Now always runs the filter, even when $type is not registered.
115
  *
116
  * @param string $type The Key from $this->conflicting_plugins()
117
  * @return array
118
  */
119
  public function get_conflicting_plugins( $type = 'seo_tools' ) {
120
+ /**
121
+ * @since 2.6.1
122
+ * @param array $conflicting_plugins Conflicting plugins
123
+ * @param string $type The type of plugins to get.
124
+ */
125
+ return (array) \apply_filters_ref_array(
126
+ 'the_seo_framework_conflicting_plugins_type',
127
+ [
128
+ $this->conflicting_plugins()[ $type ] ?? [],
129
+ $type,
130
+ ]
131
+ );
132
  }
133
 
134
  /**
137
  * Note: Class check is 3 times as slow as defined check. Function check is 2 times as slow.
138
  *
139
  * @since 1.3.0
140
+ * @since 2.8.0 1. Can now check for globals.
141
+ * 2. Switched detection order from FAST to SLOW.
142
  * @since 4.0.6 Can no longer autoload classes.
143
  *
144
  * @param array $plugins Array of array for constants, classes and / or functions to check for plugin existence.
146
  */
147
  public function detect_plugin( $plugins ) {
148
 
149
+ foreach ( $plugins['globals'] ?? [] as $name )
150
+ if ( isset( $GLOBALS[ $name ] ) )
151
+ return true;
 
 
 
 
152
 
153
  // Check for constants
154
+ foreach ( $plugins['constants'] ?? [] as $name )
155
+ if ( \defined( $name ) )
156
+ return true;
 
 
 
 
157
 
158
  // Check for functions
159
+ foreach ( $plugins['functions'] ?? [] as $name )
160
+ if ( \function_exists( $name ) )
161
+ return true;
 
 
 
 
162
 
163
  // Check for classes
164
+ foreach ( $plugins['classes'] ?? [] as $name )
165
+ if ( class_exists( $name, false ) ) // phpcs:ignore, TSF.Performance.Functions.PHP -- we don't autoload.
166
+ return true;
 
 
 
 
 
167
 
168
  // No globals, constant, function, or class found to exist
169
  return false;
176
  *
177
  * @since 2.5.2
178
  * @since 4.1.4 Fixed sorting algorithm from fribbling-me to resolving-me. Nothing changed but legibility.
179
+ * @since 4.2.0 Rewrote sorting algorithm; now, it's actually good.
180
  * @uses $this->detect_plugin_multi()
181
  *
182
+ * @param array[] $plugins Array of array for globals, constants, classes
183
  * and/or functions to check for plugin existence.
184
+ * @param bool $use_cache Bypasses cache if false
185
  */
186
+ public function can_i_use( $plugins = [], $use_cache = true ) {
187
 
188
  if ( ! $use_cache )
189
  return $this->detect_plugin_multi( $plugins );
190
 
191
+ ksort( $plugins );
 
 
 
 
 
 
 
192
 
193
+ foreach ( $plugins as &$test )
194
+ sort( $test );
195
 
 
 
 
 
 
196
  // phpcs:ignore, WordPress.PHP.DiscouragedPHPFunctions -- No objects are inserted, nor is this ever unserialized.
197
+ $key = serialize( $test );
 
 
 
198
 
199
+ return memo( null, $key ) ?? memo( $this->detect_plugin_multi( $plugins ), $key );
200
  }
201
 
202
  /**
204
  * All parameters must match and return true.
205
  *
206
  * @since 2.5.2
207
+ * @since 4.0.6 1. Can now check for globals.
208
+ * 2. Switched detection order from FAST to SLOW.
209
+ * 3. Can no longer autoload classes.
210
  * This method is only used by can_i_use(), and is only effective in the Ultimate Member compat file...
211
  * @TODO deprecate?
212
  *
213
+ * @param array[] $plugins Array of array for constants, classes
214
+ * and / or functions to check for plugin existence.
215
+ * @return bool True if ALL functions classes and constants exists
216
+ * or false if plugin constant, class or function not detected.
217
  */
218
+ public function detect_plugin_multi( $plugins ) {
219
 
220
  // Check for globals
221
+ foreach ( $plugins['globals'] ?? [] as $name )
222
+ if ( ! isset( $GLOBALS[ $name ] ) )
223
+ return false;
 
 
 
 
224
 
225
  // Check for constants
226
+ foreach ( $plugins['constants'] ?? [] as $name )
227
+ if ( ! \defined( $name ) )
228
+ return false;
 
 
 
 
229
 
230
  // Check for functions
231
+ foreach ( $plugins['functions'] ?? [] as $name )
232
+ if ( ! \function_exists( $name ) )
233
+ return false;
 
 
 
 
234
 
235
  // Check for classes
236
+ foreach ( $plugins['classes'] ?? [] as $name )
237
+ if ( ! class_exists( $name, false ) ) // phpcs:ignore, TSF.Performance.Functions.PHP -- we don't autoload.
238
+ return false;
 
 
 
 
 
239
 
240
  // All classes, functions and constant have been found to exist
241
  return true;
245
  * Checks if the (parent) theme name is loaded.
246
  *
247
  * @since 2.1.0
248
+ * @since 4.2.0 No longer "loads" the theme; instead, simply compares input to active theme options.
249
  *
250
+ * @param string|array $themes The theme names to test.
251
  * @return bool is theme active.
252
  */
253
  public function is_theme( $themes = '' ) {
254
 
255
+ $active_theme = [
256
+ strtolower( \get_option( 'stylesheet' ) ), // Parent
257
+ strtolower( \get_option( 'template' ) ), // Child
258
+ ];
 
 
 
259
 
260
+ foreach ( (array) $themes as $theme )
261
+ if ( \in_array( strtolower( $theme ), $active_theme, true ) )
 
262
  return true;
 
 
 
 
 
 
 
 
263
 
264
  return false;
265
  }
276
  */
277
  public function detect_seo_plugins() {
278
 
279
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
280
+ if ( null !== $memo = memo() ) return $memo;
 
 
281
 
282
  $active_plugins = $this->active_plugins();
283
 
284
+ if ( ! $active_plugins ) return memo( false );
285
+
286
+ foreach ( $this->get_conflicting_plugins( 'seo_tools' ) as $plugin_name => $plugin ) {
287
+ if ( \in_array( $plugin, $active_plugins, true ) ) {
288
+ /**
289
+ * @since 2.6.1
290
+ * @since 3.1.0 Added second and third parameters.
291
+ * @param bool $detected Whether the plugin should be detected.
292
+ * @param string $plugin_name The plugin name as defined in `$this->conflicting_plugins()`.
293
+ * @param string $plugin The plugin that's been detected.
294
+ */
295
+ if ( \apply_filters_ref_array(
296
+ 'the_seo_framework_seo_plugin_detected',
297
+ [
298
+ true,
299
+ $plugin_name,
300
+ $plugin,
301
+ ]
302
+ ) ) {
303
+ $detected = true;
304
+ break;
305
  }
306
  }
307
  }
308
 
309
+ return memo( (bool) ( $detected ?? false ) );
310
  }
311
 
312
  /**
321
  */
322
  public function detect_og_plugin() {
323
 
 
 
 
 
 
324
  // Detect SEO plugins beforehand.
325
  if ( $this->detect_seo_plugins() )
326
+ return true;
327
+
328
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
329
+ if ( null !== $memo = memo() ) return $memo;
330
 
331
  $active_plugins = $this->active_plugins();
332
 
333
+ if ( ! $active_plugins ) return memo( false );
334
+
335
+ foreach ( $this->get_conflicting_plugins( 'open_graph' ) as $plugin_name => $plugin ) {
336
+ if ( \in_array( $plugin, $active_plugins, true ) ) {
337
+ /**
338
+ * @since 2.6.1
339
+ * @since 3.1.0 Added second and third parameters.
340
+ * @param bool $detected Whether the plugin should be detected.
341
+ * @param string $plugin_name The plugin name as defined in `$this->conflicting_plugins()`.
342
+ * @param string $plugin The plugin that's been detected.
343
+ */
344
+ if ( \apply_filters_ref_array(
345
+ 'the_seo_framework_og_plugin_detected',
346
+ [
347
+ true,
348
+ $plugin_name,
349
+ $plugin,
350
+ ]
351
+ ) ) {
352
+ $detected = true;
353
+ break;
354
  }
355
  }
356
  }
357
 
358
+ return memo( (bool) ( $detected ?? false ) );
359
  }
360
 
361
  /**
369
  */
370
  public function detect_twitter_card_plugin() {
371
 
 
 
 
 
 
372
  // Detect SEO plugins beforehand.
373
  if ( $this->detect_seo_plugins() )
374
+ return true;
375
+
376
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
377
+ if ( null !== $memo = memo() ) return $memo;
378
 
379
  $active_plugins = $this->active_plugins();
380
 
381
+ if ( ! $active_plugins ) return memo( false );
382
+
383
+ foreach ( $this->get_conflicting_plugins( 'twitter_card' ) as $plugin_name => $plugin ) {
384
+ if ( \in_array( $plugin, $active_plugins, true ) ) {
385
+ /**
386
+ * @since 2.6.1
387
+ * @param bool $detected Whether the plugin should be detected.
388
+ * @param string $plugin_name The plugin name as defined in `$this->conflicting_plugins()`.
389
+ * @param string $plugin The plugin that's been detected.
390
+ */
391
+ if ( \apply_filters_ref_array(
392
+ 'the_seo_framework_twittercard_plugin_detected',
393
+ [
394
+ true,
395
+ $plugin_name,
396
+ $plugin,
397
+ ]
398
+ ) ) {
399
+ $detected = true;
400
+ break;
401
  }
402
  }
403
  }
404
 
405
+ return memo( (bool) ( $detected ?? false ) );
406
  }
407
 
408
  /**
433
  */
434
  public function detect_sitemap_plugin() {
435
 
 
 
 
 
 
436
  // Detect SEO plugins beforehand.
437
  if ( $this->detect_seo_plugins() )
438
+ return true;
439
+
440
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
441
+ if ( null !== $memo = memo() ) return $memo;
442
 
443
  $active_plugins = $this->active_plugins();
444
 
445
+ if ( ! $active_plugins ) return memo( false );
446
+
447
+ foreach ( $this->get_conflicting_plugins( 'sitemaps' ) as $plugin_name => $plugin ) {
448
+ if ( \in_array( $plugin, $active_plugins, true ) ) {
449
+ /**
450
+ * @since 2.6.1
451
+ * @param bool $detected Whether the plugin should be detected.
452
+ * @param string $plugin_name The plugin name as defined in `$this->conflicting_plugins()`.
453
+ * @param string $plugin The plugin that's been detected.
454
+ */
455
+ if ( \apply_filters(
456
+ 'the_seo_framework_sitemap_plugin_detected',
457
+ [
458
+ true,
459
+ $plugin_name,
460
+ $plugin,
461
+ ]
462
+ ) ) {
463
+ $detected = true;
464
+ break;
465
  }
466
  }
467
  }
468
 
469
+ return memo( (bool) ( $detected ?? false ) );
470
  }
471
 
472
  /**
478
  * @return bool
479
  */
480
  public function use_core_sitemaps() {
 
481
 
482
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
483
+ if ( null !== $memo = memo() ) return $memo;
484
 
485
  if ( $this->get_option( 'sitemaps_output' ) )
486
+ return memo( false );
 
 
 
 
 
 
 
 
487
 
488
+ $wp_sitemaps_server = \wp_sitemaps_get_server();
 
489
 
490
+ return memo(
491
+ method_exists( $wp_sitemaps_server, 'sitemaps_enabled' )
492
+ && $wp_sitemaps_server->sitemaps_enabled()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
  );
494
  }
495
 
505
  * @return bool
506
  */
507
  public function detect_non_html_page_builder() {
508
+ return memo() ?? memo(
509
+ /**
510
+ * @since 4.1.0
511
+ * @param bool $detected Whether an active page builder that renders content dynamically is detected.
512
+ * @NOTE not to be confused with `the_seo_framework_detect_non_html_page_builder`, which tests
513
+ * the page builder status for each post individually.
514
+ */
515
+ (bool) \apply_filters(
516
+ 'the_seo_framework_shortcode_based_page_builder_active',
517
+ $this->detect_plugin( [
518
+ 'constants' => [
519
+ 'ET_BUILDER_VERSION',
520
+ 'WPB_VC_VERSION',
521
+ ],
522
+ ] )
523
+ )
 
 
 
524
  );
525
  }
526
 
534
  * @return bool Whether the robots.txt file exists.
535
  */
536
  public function has_robots_txt() {
537
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
538
+ if ( null !== $memo = memo() ) return $memo;
 
 
 
539
 
540
  // Ensure get_home_path() is declared.
541
  if ( ! \function_exists( '\\get_home_path' ) )
544
  $path = \get_home_path() . 'robots.txt';
545
 
546
  // phpcs:ignore, TSF.Performance.Functions.PHP -- we use path, not URL.
547
+ return memo( file_exists( $path ) );
548
  }
549
 
550
  /**
557
  * @return bool Whether the sitemap.xml file exists.
558
  */
559
  public function has_sitemap_xml() {
560
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
561
+ if ( null !== $memo = memo() ) return $memo;
 
 
 
562
 
563
  // Ensure get_home_path() is declared.
564
  if ( ! \function_exists( '\\get_home_path' ) )
567
  $path = \get_home_path() . 'sitemap.xml';
568
 
569
  // phpcs:ignore, TSF.Performance.Functions.PHP -- we use path, not URL.
570
+ return memo( file_exists( $path ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
571
  }
572
 
573
  /**
581
  */
582
  public function query_supports_seo() {
583
 
584
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
585
+ if ( null !== $memo = memo() ) return $memo;
 
586
 
587
  switch ( true ) :
588
  case $this->is_feed():
616
  * This protects against (accidental) negative-SEO bombarding.
617
  * Support broken queries, so we can noindex them.
618
  */
619
+ if ( ! $supported && $this->is_query_exploited() )
620
  $supported = true;
 
621
 
622
  /**
623
  * @since 4.0.0
624
  * @param bool $supported Whether the query supports SEO.
625
  */
626
+ return memo( (bool) \apply_filters( 'the_seo_framework_query_supports_seo', $supported ) );
627
  }
628
 
629
  /**
665
  */
666
  public function is_query_exploited() {
667
 
668
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
669
+ if ( null !== $memo = memo() ) return $memo;
 
670
 
671
  if ( ! $this->get_option( 'advanced_query_protection' ) )
672
+ return memo( false );
673
 
674
  // When the page ID is not 0, a real page will always be returned.
675
  if ( $this->get_the_real_ID() )
676
+ return memo( false );
677
 
678
  global $wp_query;
679
 
713
  ]
714
  );
715
 
716
+ $query = $wp_query->query;
 
717
 
718
  foreach ( $exploitables as $type => $qvs ) :
719
  foreach ( $qvs as $qv ) :
720
+ // Only test isset, because falsey or empty-array is what we need to test against.
721
  if ( ! isset( $query[ $qv ] ) ) continue;
722
 
723
  switch ( $type ) :
724
  case 'numeric':
725
+ if ( '0' === $query[ $qv ] || ! is_numeric( $query[ $qv ] ) )
726
+ return memo( true );
 
 
727
  break;
728
 
729
  case 'numeric_array':
732
 
733
  // If WordPress didn't canonical_redirect() the user yet, it's exploited.
734
  // WordPress mitigates this via a 404 query when a numeric value is found.
735
+ if ( ! preg_match( '/[0-9]/', $query[ $qv ] ) )
736
+ return memo( true );
 
 
737
  break;
738
 
739
  case 'requires_s':
740
+ if ( ! isset( $query['s'] ) )
741
+ return memo( true );
 
 
742
  break;
743
 
744
  default:
747
  endforeach;
748
  endforeach;
749
 
750
+ return memo( false );
751
+ }
752
+
753
+ /**
754
+ * Tests if the post type archive of said post type contains public posts.
755
+ * Memoizes the return value.
756
+ *
757
+ * @since 4.2.0
758
+ *
759
+ * @param string $post_type The post type to test.
760
+ * @return bool True if a post is found in the archive, false otherwise.
761
+ */
762
+ public function has_posts_in_post_type_archive( $post_type ) {
763
+
764
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
765
+ if ( null !== $memo = memo( null, $post_type ) ) return $memo;
766
+
767
+ $query = new \WP_Query( [
768
+ 'posts_per_page' => 1,
769
+ 'post_type' => [ $post_type ],
770
+ 'orderby' => 'date',
771
+ 'order' => 'ASC',
772
+ 'post_status' => 'publish',
773
+ 'has_password' => false,
774
+ 'fields' => 'ids',
775
+ 'cache_results' => false,
776
+ 'no_found_rows' => true,
777
+ ] );
778
+
779
+ return memo( ! empty( $query->posts ), $post_type );
780
  }
781
 
782
  /**
856
  */
857
  public function post_type_supports_taxonomies( $post_type = '' ) {
858
 
859
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
860
+ if ( null !== $memo = memo( null, $post_type ) ) return $memo;
 
 
861
 
862
  $post_type = $post_type ?: $this->get_current_post_type();
 
863
 
864
+ // Return false if no post type if found -- do not memo that, for query call might be too early.
865
+ return $post_type && memo( (bool) \get_object_taxonomies( $post_type, 'names' ), $post_type );
866
+ }
867
 
868
+ /**
869
+ * Returns a list of all supported post types with archives.
870
+ * Memoizes the return value.
871
+ *
872
+ * @since 4.2.0
873
+ *
874
+ * @return string[] Supported post types with post type archive support.
875
+ */
876
+ public function get_supported_post_type_archives() {
877
+ return memo() ?? memo(
878
+ array_values(
879
+ array_filter(
880
+ $this->get_supported_post_types(),
881
+ static function( $post_type ) {
882
+ return \get_post_type_object( $post_type )->has_archive ?? false;
883
+ }
884
+ )
885
+ )
886
+ );
887
+ }
888
+
889
+ /**
890
+ * Gets all post types that have PTA and could possibly support SEO.
891
+ * Memoizes the return value.
892
+ *
893
+ * @since 4.2.0
894
+ *
895
+ * @return string[] Public post types with post type archive support.
896
+ */
897
+ public function get_public_post_type_archives() {
898
+ return memo() ?? memo(
899
+ array_values(
900
+ array_filter(
901
+ $this->get_public_post_types(),
902
+ static function( $post_type ) {
903
+ return \get_post_type_object( $post_type )->has_archive ?? false;
904
+ }
905
+ )
906
+ )
907
+ );
908
  }
909
 
910
  /**
911
  * Returns a list of all supported post types.
912
  *
913
  * @since 3.1.0
 
914
  *
915
+ * @return string[] All supported post types.
916
  */
917
  public function get_supported_post_types() {
918
+ return memo() ?? memo(
919
+ array_values(
920
+ array_filter( $this->get_public_post_types(), [ $this, 'is_post_type_supported' ] )
921
+ )
 
 
 
922
  );
923
  }
924
 
929
  * @since 4.1.0
930
  * @since 4.1.4 Now resets the index keys of the return value.
931
  *
932
+ * @return string[] All public post types.
933
  */
934
  protected function get_public_post_types() {
935
+ return umemo( __METHOD__ )
936
+ ?? umemo(
937
+ __METHOD__,
938
+ /**
939
+ * Do not consider using this filter. Properly register your post type, noob.
940
+ *
941
+ * @since 4.2.0
942
+ * @param string[] $post_types The public post types.
943
+ */
944
+ \apply_filters(
945
+ 'the_seo_framework_public_post_types',
946
+ array_values(
947
+ array_filter(
948
+ array_unique(
949
+ array_merge(
950
+ $this->get_forced_supported_post_types(),
951
+ //? array_values() because get_post_types() gives a sequential array.
952
+ array_keys( (array) \get_post_types( [ 'public' => true ] ) )
953
+ )
954
+ ),
955
+ 'is_post_type_viewable'
956
+ )
957
  )
958
+ )
959
+ );
 
 
960
  }
961
 
962
  /**
963
  * Returns a list of builtin public post types.
 
964
  *
965
  * @since 3.1.0
966
+ * @since 4.2.0 Removed memoization.
967
  *
968
+ * @return string[] Forced supported post types.
969
  */
970
  protected function get_forced_supported_post_types() {
 
 
971
  /**
972
+ * @since 3.1.0
973
+ * @param string[] $forced Forced supported post types
974
+ */
975
+ return (array) \apply_filters_ref_array(
976
  'the_seo_framework_forced_supported_post_types',
977
+ [
978
+ array_values( \get_post_types( [
979
+ 'public' => true,
980
+ '_builtin' => true,
981
+ ] ) ),
982
+ ]
983
+ );
984
+ }
985
+
986
+ /**
987
+ * Returns a list of all supported taxonomies.
988
+ *
989
+ * @since 4.2.0
990
+ *
991
+ * @return string[] All supported taxonomies.
992
+ */
993
+ public function get_supported_taxonomies() {
994
+ return memo() ?? memo(
995
+ array_values(
996
+ array_filter( $this->get_public_taxonomies(), [ $this, 'is_taxonomy_supported' ] )
997
+ )
998
  );
999
  }
1000
 
1004
  *
1005
  * @since 4.1.0
1006
  *
1007
+ * @return string[] The taxonomies that are public.
1008
  */
1009
  protected function get_public_taxonomies() {
1010
+ return umemo( __METHOD__ )
1011
+ ?? umemo(
1012
+ __METHOD__,
1013
+ /**
1014
+ * Do not consider using this filter. Properly register your taxonomy, noob.
1015
+ *
1016
+ * @since 4.2.0
1017
+ * @param string[] $post_types The public post types.
1018
+ */
1019
+ \apply_filters(
1020
+ 'the_seo_framework_public_taxonomies',
1021
+ array_filter(
1022
+ array_unique(
1023
+ array_merge(
1024
+ $this->get_forced_supported_taxonomies(),
1025
+ //? array_values() because get_taxonomies() gives a sequential array.
1026
+ array_values( \get_taxonomies( [
1027
+ 'public' => true,
1028
+ '_builtin' => false,
1029
+ ] ) )
1030
+ )
1031
+ ),
1032
+ 'is_taxonomy_viewable'
1033
+ )
1034
  )
1035
+ );
 
 
1036
  }
1037
 
1038
  /**
1039
  * Returns a list of builtin public taxonomies.
 
1040
  *
1041
  * @since 4.1.0
1042
+ * @since 4.2.0 Removed memoization.
1043
  *
1044
+ * @return string[] Forced supported taxonomies
1045
  */
1046
  protected function get_forced_supported_taxonomies() {
 
 
1047
  /**
1048
  * @since 4.1.0
1049
+ * @param string[] $forced Forced supported post types
1050
  */
1051
+ return (array) \apply_filters_ref_array(
1052
  'the_seo_framework_forced_supported_taxonomies',
1053
+ [
1054
+ array_values( \get_taxonomies( [
1055
+ 'public' => true,
1056
+ '_builtin' => true,
1057
+ ] ) ),
1058
+ ]
1059
  );
1060
  }
1061
 
1079
  * @param bool $disabled
1080
  * @param string $post_type
1081
  */
1082
+ return \apply_filters_ref_array(
1083
+ 'the_seo_framework_post_type_disabled',
1084
+ [
1085
+ isset( $this->get_option( 'disabled_post_types' )[ $post_type ] ),
1086
+ $post_type,
1087
+ ]
1088
  );
1089
  }
1090
 
1105
 
1106
  $disabled = false;
1107
 
1108
+ // First, test pertaining option directly.
1109
+ if ( $taxonomy && isset( $this->get_option( 'disabled_taxonomies' )[ $taxonomy ] ) ) {
1110
  $disabled = true;
1111
  } else {
1112
+ // Then, test some() post types.
1113
+ // Populate $disabled within loop, for the taxonomy might not have post types at all.
1114
  foreach ( $this->get_post_types_from_taxonomy( $taxonomy ) as $type ) {
 
 
1115
  if ( $this->is_post_type_supported( $type ) ) {
1116
  $disabled = false;
1117
  break;
1118
  }
1119
+ $disabled = true;
1120
  }
1121
  }
1122
 
1125
  * @param bool $disabled
1126
  * @param string $taxonomy
1127
  */
1128
+ return \apply_filters_ref_array(
1129
+ 'the_seo_framework_taxonomy_disabled',
1130
+ [
1131
+ $disabled,
1132
+ $taxonomy,
1133
+ ]
1134
+ );
1135
  }
1136
 
1137
  /**
1150
  * Detects if we're on a Gutenberg page.
1151
  *
1152
  * @since 3.1.0
1153
+ * @since 3.2.0 1. Now detects the WP 5.0 block editor.
1154
+ * 2. Method is now public.
1155
  *
1156
  * @return bool
1157
  */
1189
  * @return string URL location of robots.txt. Unescaped.
1190
  */
1191
  public function get_robots_txt_url() {
 
1192
 
1193
+ if ( $GLOBALS['wp_rewrite']->using_permalinks() && ! $this->is_subdirectory_installation() ) {
1194
  $home = \trailingslashit( $this->set_preferred_url_scheme( $this->get_home_host() ) );
1195
  $path = "{$home}robots.txt";
1196
  } elseif ( $this->has_robots_txt() ) {
1212
  * @return bool
1213
  */
1214
  public function is_subdirectory_installation() {
1215
+ return memo() ?? memo(
1216
+ (bool) \strlen(
1217
+ ltrim( parse_url( \get_option( 'home' ), PHP_URL_PATH ), ' \\/' )
1218
+ )
1219
+ );
 
 
 
 
1220
  }
1221
 
1222
  /**
1233
  if ( false === strpos( $text, '%%' ) ) return false;
1234
 
1235
  $tags_simple = [ 'date', 'title', 'parent_title', 'archive_title', 'sitename', 'sitedesc', 'excerpt', 'excerpt_only', 'tag', 'category', 'primary_category', 'category_description', 'tag_description', 'term_description', 'term_title', 'searchphrase', 'sep', 'pt_single', 'pt_plural', 'modified', 'id', 'name', 'user_description', 'page', 'pagetotal', 'pagenumber', 'caption', 'focuskw', 'term404', 'ct_product_cat', 'ct_product_tag', 'wc_shortdesc', 'wc_sku', 'wc_brand', 'wc_price' ];
1236
+ $_regex = sprintf( '%%%s%%', implode( '|', $tags_simple ) );
 
1237
 
1238
  if ( preg_match( "/$_regex/i", $text ) ) return true;
1239
 
1240
  $tags_wildcard_end = [ 'cs_', 'ct_desc_', 'ct_pa_' ];
1241
+ $_regex = sprintf( '%%(%s)[^\s]*?%%', implode( '|', $tags_wildcard_end ) );
 
1242
 
1243
  if ( preg_match( "/$_regex/", $text ) ) return true;
1244
 
inc/classes/generate-description.class.php CHANGED
@@ -39,20 +39,19 @@ class Generate_Description extends Generate {
39
  *
40
  * @since 3.0.6
41
  * @since 3.1.0 The first argument now accepts an array, with "id" and "taxonomy" fields.
 
42
  * @uses $this->get_description_from_custom_field()
43
  * @uses $this->get_generated_description()
44
  *
45
- * @param array|null $args An array of 'id' and 'taxonomy' values.
46
- * Accepts int values for backward compatibility.
47
  * @param bool $escape Whether to escape the description.
48
  * @return string The real description output.
49
  */
50
  public function get_description( $args = null, $escape = true ) {
51
 
52
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
53
  $desc = $this->get_description_from_custom_field( $args, false )
54
  ?: $this->get_generated_description( $args, false );
55
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
56
 
57
  return $escape ? $this->escape_description( $desc ) : $desc;
58
  }
@@ -61,22 +60,21 @@ class Generate_Description extends Generate {
61
  * Returns the Open Graph meta description. Falls back to meta description.
62
  *
63
  * @since 3.0.4
64
- * @since 3.1.0 : 1. Now tries to get the homepage social descriptions.
65
- * 2. The first argument now accepts an array, with "id" and "taxonomy" fields.
 
66
  * @uses $this->get_open_graph_description_from_custom_field()
67
  * @uses $this->get_generated_open_graph_description()
68
  *
69
- * @param array|null $args An array of 'id' and 'taxonomy' values.
70
- * Accepts int values for backward compatibility.
71
  * @param bool $escape Whether to escape the description.
72
  * @return string The real Open Graph description output.
73
  */
74
  public function get_open_graph_description( $args = null, $escape = true ) {
75
 
76
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
77
  $desc = $this->get_open_graph_description_from_custom_field( $args, false )
78
  ?: $this->get_generated_open_graph_description( $args, false );
79
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
80
 
81
  return $escape ? $this->escape_description( $desc ) : $desc;
82
  }
@@ -88,7 +86,7 @@ class Generate_Description extends Generate {
88
  * @since 3.1.0
89
  * @see $this->get_open_graph_description()
90
  *
91
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
92
  * Leave null to autodetermine query.
93
  * @param bool $escape Whether to escape the title.
94
  * @return string TwOpen Graphitter description.
@@ -112,6 +110,8 @@ class Generate_Description extends Generate {
112
  * @since 3.1.0
113
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
114
  * @since 4.0.0 Added term meta item checks.
 
 
115
  * @see $this->get_open_graph_description()
116
  * @see $this->get_open_graph_description_from_custom_field()
117
  *
@@ -120,24 +120,25 @@ class Generate_Description extends Generate {
120
  protected function get_custom_open_graph_description_from_query() {
121
 
122
  $desc = '';
123
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
124
  if ( $this->is_real_front_page() ) {
125
  if ( $this->is_static_frontpage() ) {
126
  $desc = $this->get_option( 'homepage_og_description' )
127
  ?: $this->get_post_meta_item( '_open_graph_description' )
128
- ?: $this->get_description_from_custom_field();
129
  } else {
130
  $desc = $this->get_option( 'homepage_og_description' )
131
- ?: $this->get_description_from_custom_field();
132
  }
133
  } elseif ( $this->is_singular() ) {
134
  $desc = $this->get_post_meta_item( '_open_graph_description' )
135
- ?: $this->get_description_from_custom_field();
136
  } elseif ( $this->is_term_meta_capable() ) {
137
  $desc = $this->get_term_meta_item( 'og_description' )
138
- ?: $this->get_description_from_custom_field();
 
 
 
139
  }
140
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
141
 
142
  return $desc;
143
  }
@@ -147,36 +148,39 @@ class Generate_Description extends Generate {
147
  * Falls back to meta description.
148
  *
149
  * @since 3.1.0
150
- * @since 3.2.2 : 1. Now tests for the homepage as page prior getting custom field data.
151
- * 2. Now obtains custom field data for terms.
152
  * @since 4.0.0 Added term meta item checks.
 
 
153
  * @see $this->get_open_graph_description()
154
  * @see $this->get_open_graph_description_from_custom_field()
155
  *
156
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
157
  * @return string Open Graph description.
158
  */
159
- protected function get_custom_open_graph_description_from_args( array $args ) {
160
 
161
  $desc = '';
162
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
163
  if ( $args['taxonomy'] ) {
164
  $desc = $this->get_term_meta_item( 'og_description', $args['id'] )
165
- ?: $this->get_description_from_custom_field( $args );
 
 
 
166
  } else {
167
  if ( $this->is_static_frontpage( $args['id'] ) ) {
168
  $desc = $this->get_option( 'homepage_og_description' )
169
  ?: $this->get_post_meta_item( '_open_graph_description', $args['id'] )
170
- ?: $this->get_description_from_custom_field( $args );
171
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
172
  $desc = $this->get_option( 'homepage_og_description' )
173
- ?: $this->get_description_from_custom_field( $args );
174
  } else {
175
  $desc = $this->get_post_meta_item( '_open_graph_description', $args['id'] )
176
- ?: $this->get_description_from_custom_field( $args );
177
  }
178
  }
179
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
180
 
181
  return $desc;
182
  }
@@ -186,22 +190,21 @@ class Generate_Description extends Generate {
186
  * Falls back to Open Graph description.
187
  *
188
  * @since 3.0.4
189
- * @since 3.1.0 : 1. Now tries to get the homepage social descriptions.
190
- * 2. The first argument now accepts an array, with "id" and "taxonomy" fields.
 
191
  * @uses $this->get_twitter_description_from_custom_field()
192
  * @uses $this->get_generated_twitter_description()
193
  *
194
- * @param array|null $args An array of 'id' and 'taxonomy' values.
195
- * Accepts int values for backward compatibility.
196
  * @param bool $escape Whether to escape the description.
197
  * @return string The real Twitter description output.
198
  */
199
  public function get_twitter_description( $args = null, $escape = true ) {
200
 
201
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
202
  $desc = $this->get_twitter_description_from_custom_field( $args, false )
203
  ?: $this->get_generated_twitter_description( $args, false );
204
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
205
 
206
  return $escape ? $this->escape_description( $desc ) : $desc;
207
  }
@@ -213,7 +216,7 @@ class Generate_Description extends Generate {
213
  * @since 3.1.0
214
  * @see $this->get_twitter_description()
215
  *
216
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
217
  * Leave null to autodetermine query.
218
  * @param bool $escape Whether to escape the title.
219
  * @return string Twitter description.
@@ -235,9 +238,11 @@ class Generate_Description extends Generate {
235
  * Falls back to Open Graph description.
236
  *
237
  * @since 3.1.0
238
- * @since 3.2.2 : 1. Now tests for the homepage as page prior getting custom field data.
239
- * 2. Now obtains custom field data for terms.
240
  * @since 4.0.0 Added term meta item checks.
 
 
241
  * @see $this->get_twitter_description()
242
  * @see $this->get_twitter_description_from_custom_field()
243
  *
@@ -246,33 +251,36 @@ class Generate_Description extends Generate {
246
  protected function get_custom_twitter_description_from_query() {
247
 
248
  $desc = '';
249
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
250
  if ( $this->is_real_front_page() ) {
251
  if ( $this->is_static_frontpage() ) {
252
  $desc = $this->get_option( 'homepage_twitter_description' )
253
  ?: $this->get_post_meta_item( '_twitter_description' )
254
  ?: $this->get_option( 'homepage_og_description' )
255
  ?: $this->get_post_meta_item( '_open_graph_description' )
256
- ?: $this->get_description_from_custom_field()
257
  ?: '';
258
  } else {
259
  $desc = $this->get_option( 'homepage_twitter_description' )
260
  ?: $this->get_option( 'homepage_og_description' )
261
- ?: $this->get_description_from_custom_field()
262
  ?: '';
263
  }
264
  } elseif ( $this->is_singular() ) {
265
  $desc = $this->get_post_meta_item( '_twitter_description' )
266
  ?: $this->get_post_meta_item( '_open_graph_description' )
267
- ?: $this->get_description_from_custom_field()
268
  ?: '';
269
  } elseif ( $this->is_term_meta_capable() ) {
270
  $desc = $this->get_term_meta_item( 'tw_description' )
271
  ?: $this->get_term_meta_item( 'og_description' )
272
- ?: $this->get_description_from_custom_field()
 
 
 
 
 
273
  ?: '';
274
  }
275
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
276
 
277
  return $desc;
278
  }
@@ -282,22 +290,28 @@ class Generate_Description extends Generate {
282
  * Falls back to Open Graph description.
283
  *
284
  * @since 3.1.0
285
- * @since 3.2.2 : 1. Now tests for the homepage as page prior getting custom field data.
286
- * 2. Now obtains custom field data for terms.
287
  * @since 4.0.0 Added term meta item checks.
 
 
288
  * @see $this->get_twitter_description()
289
  * @see $this->get_twitter_description_from_custom_field()
290
  *
291
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
292
  * @return string Twitter description.
293
  */
294
- protected function get_custom_twitter_description_from_args( array $args ) {
295
 
296
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
297
  if ( $args['taxonomy'] ) {
298
  $desc = $this->get_term_meta_item( 'tw_description', $args['id'] )
299
  ?: $this->get_term_meta_item( 'og_description', $args['id'] )
300
- ?: $this->get_description_from_custom_field( $args )
 
 
 
 
 
301
  ?: '';
302
  } else {
303
  if ( $this->is_static_frontpage( $args['id'] ) ) {
@@ -305,21 +319,20 @@ class Generate_Description extends Generate {
305
  ?: $this->get_post_meta_item( '_twitter_description', $args['id'] )
306
  ?: $this->get_option( 'homepage_og_description' )
307
  ?: $this->get_post_meta_item( '_open_graph_description', $args['id'] )
308
- ?: $this->get_description_from_custom_field( $args )
309
  ?: '';
310
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
311
  $desc = $this->get_option( 'homepage_twitter_description' )
312
  ?: $this->get_option( 'homepage_og_description' )
313
- ?: $this->get_description_from_custom_field( $args )
314
  ?: '';
315
  } else {
316
  $desc = $this->get_post_meta_item( '_twitter_description', $args['id'] )
317
  ?: $this->get_post_meta_item( '_open_graph_description', $args['id'] )
318
- ?: $this->get_description_from_custom_field( $args )
319
  ?: '';
320
  }
321
  }
322
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
323
 
324
  return $desc;
325
  }
@@ -329,9 +342,10 @@ class Generate_Description extends Generate {
329
  *
330
  * @since 3.0.6
331
  * @since 3.1.0 The first argument now accepts an array, with "id" and "taxonomy" fields.
 
332
  *
333
- * @param array|null $args An array of 'id' and 'taxonomy' values.
334
- * Accepts int values for backward compatibility.
335
  * @param bool $escape Whether to escape the description.
336
  * @return string The custom field description.
337
  */
@@ -339,12 +353,6 @@ class Generate_Description extends Generate {
339
 
340
  if ( null === $args ) {
341
  $desc = $this->get_custom_description_from_query();
342
-
343
- // Generated as backward compat for the filter...
344
- $args = [
345
- 'id' => $this->get_the_real_ID(),
346
- 'taxonomy' => $this->get_current_taxonomy(),
347
- ];
348
  } else {
349
  $this->fix_generation_args( $args );
350
  $desc = $this->get_custom_description_from_args( $args );
@@ -354,11 +362,19 @@ class Generate_Description extends Generate {
354
  * @since 2.9.0
355
  * @since 3.0.6 1. Duplicated from $this->generate_description() (deprecated)
356
  * 2. Removed all arguments but the 'id' argument.
 
 
357
  * @param string $desc The custom-field description.
358
- * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
359
  * Is null when query is autodetermined.
360
  */
361
- $desc = (string) \apply_filters( 'the_seo_framework_custom_field_description', $desc, $args );
 
 
 
 
 
 
362
 
363
  return $escape ? $this->escape_description( $desc ) : $desc;
364
  }
@@ -368,6 +384,7 @@ class Generate_Description extends Generate {
368
  *
369
  * @since 3.1.0
370
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
 
371
  * @internal
372
  * @see $this->get_description_from_custom_field()
373
  *
@@ -377,7 +394,6 @@ class Generate_Description extends Generate {
377
 
378
  $desc = '';
379
 
380
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
381
  if ( $this->is_real_front_page() ) {
382
  if ( $this->is_static_frontpage() ) {
383
  $desc = $this->get_option( 'homepage_description' )
@@ -393,11 +409,16 @@ class Generate_Description extends Generate {
393
  } elseif ( \is_post_type_archive() ) {
394
  /**
395
  * @since 4.0.6
 
 
396
  * @param string $desc The post type archive description.
397
  */
398
- $desc = (string) \apply_filters( 'the_seo_framework_pta_description', '' ) ?: '';
 
 
 
 
399
  }
400
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
401
 
402
  return $desc;
403
  }
@@ -407,17 +428,19 @@ class Generate_Description extends Generate {
407
  *
408
  * @since 3.1.0
409
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
 
410
  * @internal
411
  * @see $this->get_description_from_custom_field()
412
  *
413
  * @param array $args Array of 'id' and 'taxonomy' values.
414
  * @return string The custom description.
415
  */
416
- protected function get_custom_description_from_args( array $args ) {
417
 
418
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
419
  if ( $args['taxonomy'] ) {
420
  $desc = $this->get_term_meta_item( 'description', $args['id'] ) ?: '';
 
 
421
  } else {
422
  if ( $this->is_static_frontpage( $args['id'] ) ) {
423
  $desc = $this->get_option( 'homepage_description' )
@@ -429,7 +452,6 @@ class Generate_Description extends Generate {
429
  $desc = $this->get_post_meta_item( '_genesis_description', $args['id'] ) ?: '';
430
  }
431
  }
432
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
433
 
434
  return $desc;
435
  }
@@ -445,10 +467,14 @@ class Generate_Description extends Generate {
445
  * @since 3.1.2 1. Now omits additions when the description will be deemed too short.
446
  * 2. Now no longer converts additions into excerpt when no excerpt is found.
447
  * @since 3.2.2 Now converts HTML characters prior trimming.
 
448
  * @uses $this->generate_description()
 
 
 
449
  *
450
- * @param array|null $args An array of 'id' and 'taxonomy' values.
451
- * Accepts int values for backward compatibility.
452
  * @param bool $escape Whether to escape the description.
453
  * @param string $type Type of description. Accepts 'search', 'opengraph', 'twitter'.
454
  * @return string The generated description output.
@@ -472,18 +498,24 @@ class Generate_Description extends Generate {
472
  * @since 3.1.0 No longer passes 3rd and 4th parameter.
473
  * @since 4.0.0 1. Deprecated second parameter.
474
  * 2. Added third parameter: $args.
 
475
  * @param string $excerpt The excerpt to use.
476
  * @param int $page_id Deprecated.
477
- * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
478
  * Is null when query is autodetermined.
479
  */
480
- $excerpt = (string) \apply_filters( 'the_seo_framework_fetched_description_excerpt', $excerpt, 0, $args );
 
 
 
 
 
 
 
481
 
482
- // TODO Should we enforce a minimum description length, where this result is ignored?
483
- // e.g. use the input guidelines 'lower' value as a minimum, so that TSF won't ever generate "bad" descriptions?
484
  // This page has a generated description that's far too short: https://theseoframework.com/em-changelog/1-0-0-amplified-seo/.
485
  // A direct directory-'site:' query will accept the description outputted--anything else will ignore it...
486
- // We should then create a new method which processes this with a parameter for the minimum length, so we can optimize it for performance.
487
  $excerpt = $this->trim_excerpt(
488
  $excerpt,
489
  0,
@@ -493,11 +525,18 @@ class Generate_Description extends Generate {
493
  /**
494
  * @since 2.9.0
495
  * @since 3.1.0 No longer passes 3rd and 4th parameter.
 
496
  * @param string $desc The generated description.
497
- * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
498
  * Is null when query is autodetermined.
499
  */
500
- $desc = (string) \apply_filters( 'the_seo_framework_generated_description', $excerpt, $args );
 
 
 
 
 
 
501
 
502
  return $escape ? $this->escape_description( $desc ) : $desc;
503
  }
@@ -506,9 +545,10 @@ class Generate_Description extends Generate {
506
  * Returns the autogenerated Twitter meta description. Falls back to meta description.
507
  *
508
  * @since 3.0.4
 
509
  *
510
- * @param array|null $args An array of 'id' and 'taxonomy' values.
511
- * Accepts int values for backward compatibility.
512
  * @param bool $escape Whether to escape the description.
513
  * @return string The generated Twitter description output.
514
  */
@@ -520,10 +560,11 @@ class Generate_Description extends Generate {
520
  * Returns the autogenerated Open Graph meta description. Falls back to meta description.
521
  *
522
  * @since 3.0.4
 
523
  * @uses $this->generate_description()
524
  *
525
- * @param array|null $args An array of 'id' and 'taxonomy' values.
526
- * Accepts int values for backward compatibility.
527
  * @param bool $escape Whether to escape the description.
528
  * @return string The generated Open Graph description output.
529
  */
@@ -535,6 +576,7 @@ class Generate_Description extends Generate {
535
  * Returns a description excerpt for the current query.
536
  *
537
  * @since 3.1.0
 
538
  *
539
  * @return string
540
  */
@@ -547,14 +589,14 @@ class Generate_Description extends Generate {
547
 
548
  $excerpt = '';
549
 
550
- if ( $this->is_blog_page() ) {
551
- $excerpt = $this->get_blog_page_description_excerpt();
552
- } elseif ( $this->is_real_front_page() ) {
553
  $excerpt = $this->get_front_page_description_excerpt();
554
- } elseif ( $this->is_archive() ) {
555
- $excerpt = $this->get_archival_description_excerpt();
556
  } elseif ( $this->is_singular() ) {
557
  $excerpt = $this->get_singular_description_excerpt();
 
 
558
  }
559
 
560
  return $excerpt;
@@ -565,21 +607,24 @@ class Generate_Description extends Generate {
565
  *
566
  * @since 3.1.0
567
  * @since 3.2.2 Fixed front-page as blog logic.
 
568
  *
569
- * @param array|null $args An array of 'id' and 'taxonomy' values.
570
  * @return string
571
  */
572
- protected function get_description_excerpt_from_args( array $args ) {
573
 
574
  $excerpt = '';
575
 
576
  if ( $args['taxonomy'] ) {
577
  $excerpt = $this->get_archival_description_excerpt( \get_term( $args['id'], $args['taxonomy'] ) );
 
 
578
  } else {
579
- if ( $this->is_blog_page_by_id( $args['id'] ) ) {
580
- $excerpt = $this->get_blog_page_description_excerpt();
581
- } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
582
  $excerpt = $this->get_front_page_description_excerpt();
 
 
583
  } else {
584
  $excerpt = $this->get_singular_description_excerpt( $args['id'] );
585
  }
@@ -596,10 +641,7 @@ class Generate_Description extends Generate {
596
  * @return string
597
  */
598
  protected function get_blog_page_description_excerpt() {
599
- return $this->get_description_additions( [
600
- 'id' => (int) \get_option( 'page_for_posts' ),
601
- 'taxonomy' => '',
602
- ] );
603
  }
604
 
605
  /**
@@ -613,14 +655,8 @@ class Generate_Description extends Generate {
613
 
614
  $id = $this->get_the_front_page_ID();
615
 
616
- $excerpt = '';
617
- if ( $id ) {
618
- $excerpt = $this->get_singular_description_excerpt( $id );
619
- }
620
- $excerpt = $excerpt ?: $this->get_description_additions( [
621
- 'id' => $id,
622
- 'taxonomy' => '',
623
- ] );
624
 
625
  return $excerpt;
626
  }
@@ -630,29 +666,37 @@ class Generate_Description extends Generate {
630
  *
631
  * @since 3.1.0
632
  * @since 4.0.0 Now processes HTML tags via s_excerpt_raw() for the author descriptions.
 
 
633
  *
634
- * @param null|\WP_Term $term The term.
635
  * @return string
636
  */
637
- protected function get_archival_description_excerpt( $term = null ) {
638
 
639
- if ( $term && \is_wp_error( $term ) )
640
  return '';
641
 
642
- if ( \is_null( $term ) ) {
643
  $in_the_loop = true;
644
- $term = \get_queried_object();
645
  } else {
646
  $in_the_loop = false;
647
  }
648
 
649
  /**
650
  * @since 3.1.0
651
- * @see `\the_seo_framework()->s_excerpt_raw()` to strip HTML tags neatly.
652
- * @param string $excerpt The short circuit excerpt.
653
- * @param \WP_Term $term The Term object.
654
  */
655
- $excerpt = (string) \apply_filters( 'the_seo_framework_generated_archive_excerpt', '', $term );
 
 
 
 
 
 
656
 
657
  if ( $excerpt ) return $excerpt;
658
 
@@ -662,29 +706,40 @@ class Generate_Description extends Generate {
662
  if ( $this->is_category() || $this->is_tag() || $this->is_tax() ) {
663
  // WordPress DOES NOT allow HTML in term descriptions, not even if you're a super-administrator.
664
  // See https://wpvulndb.com/vulnerabilities/9445. We won't parse HTMl tags unless WordPress adds native support.
665
- $excerpt = ! empty( $term->description ) ? $this->s_description_raw( $term->description ) : '';
666
  } elseif ( $this->is_author() ) {
667
  $excerpt = $this->s_excerpt_raw( \get_the_author_meta( 'description', (int) \get_query_var( 'author' ) ) );
668
  } elseif ( \is_post_type_archive() ) {
669
  /**
670
- * @TODO can we even obtain anything useful ourselves?
671
- *
672
  * @since 4.0.6
 
673
  * @param string $excerpt The archive description excerpt.
674
- * @param mixed $term The queried object.
675
  */
676
- $excerpt = (string) \apply_filters( 'the_seo_framework_pta_description_excerpt', '', $term );
 
 
 
 
 
 
677
  } else {
678
  /**
679
  * @since 4.0.6
680
- * @since 4.1.0 Added the $term object parameter.
681
  * @param string $excerpt The fallback archive description excerpt.
682
- * @param \WP_Term $term The Term object.
683
  */
684
- $excerpt = (string) \apply_filters( 'the_seo_framework_fallback_archive_description_excerpt', '', $term );
 
 
 
 
 
 
685
  }
686
  } else {
687
- $excerpt = ! empty( $term->description ) ? $this->s_description_raw( $term->description ) : '';
688
  }
689
 
690
  return $excerpt;
@@ -700,8 +755,7 @@ class Generate_Description extends Generate {
700
  */
701
  protected function get_singular_description_excerpt( $id = null ) {
702
 
703
- if ( \is_null( $id ) )
704
- $id = $this->get_the_real_ID();
705
 
706
  // If the post is protected, don't generate a description.
707
  if ( $this->is_protected( $id ) ) return '';
@@ -713,36 +767,29 @@ class Generate_Description extends Generate {
713
  * Returns additions for "Title on Site Title".
714
  *
715
  * @since 3.1.0
716
- * @since 3.2.0 : 1. Now no longer listens to options.
717
- * 2. Now only works for the front and blog pages.
718
  * @since 3.2.2 Now works for homepages from external requests.
 
719
  * @see $this->get_generated_description()
720
  *
721
- * @param array|null $args An array of 'id' and 'taxonomy' values.
722
- * Accepts int values for backward compatibility.
723
- * @param bool $forced Whether to force the additions, bypassing options and filters.
724
  * @return string The description additions.
725
  */
726
- protected function get_description_additions( $args, $forced = false ) {
727
 
728
- $this->fix_generation_args( $args );
729
-
730
- if ( $this->is_blog_page_by_id( $args['id'] ) ) {
731
- $title = $this->get_filtered_raw_generated_title( $args );
732
- /* translators: %s = Blog page title. Front-end output. */
733
- $title = sprintf( \__( 'Latest posts: %s', 'autodescription' ), $title );
734
- } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
735
  $title = $this->get_home_title_additions();
 
 
 
 
 
 
736
  }
737
 
738
- if ( empty( $title ) )
739
- return '';
740
-
741
- $on = \_x( 'on', 'Placement. e.g. Post Title "on" Site Title', 'autodescription' );
742
- $blogname = $this->get_blogname();
743
-
744
- /* translators: 1: Title, 2: on, 3: Site Title */
745
- return trim( sprintf( \_x( '%1$s %2$s %3$s', 'blog page description', 'autodescription' ), $title, $on, $blogname ) );
746
  }
747
 
748
  /**
@@ -750,8 +797,8 @@ class Generate_Description extends Generate {
750
  *
751
  * @since 1.0.0
752
  * @since 2.8.2 Added 4th parameter for escaping.
753
- * @since 3.1.0 : 1. No longer returns anything for terms.
754
- * 2. Now strips plausible embeds URLs.
755
  * @since 4.0.1 The second parameter `$id` now defaults to int 0, instead of an empty string.
756
  *
757
  * @param string $excerpt The Excerpt.
@@ -818,33 +865,34 @@ class Generate_Description extends Generate {
818
  * Warning: Returns with entities encoded. The output is not safe for printing.
819
  *
820
  * @since 2.6.0
821
- * @since 3.1.0 : 1. Now uses smarter trimming.
822
- * 2. Deprecated 2nd parameter.
823
- * 3. Now has unicode support for sentence closing.
824
- * 4. Now strips last three words when preceded by a sentence closing separator.
825
- * 5. Now always leads with (inviting) dots, even if the excerpt is shorter than $max_char_length.
826
- * @since 4.0.0 : 1. Now stops parsing earlier on failure.
827
- * 2. Now performs faster queries.
828
- * 3. Now maintains last sentence with closing punctuations.
829
- * @since 4.0.5 : 1. Now decodes the excerpt input, improving accuracy, and so that HTML entities at
830
- * the end won't be transformed into gibberish.
831
- * @since 4.1.0 : 1. Now texturizes the excerpt input, improving accuracy with included closing & final punctuation support.
832
- * 2. Now performs even faster queries, in most situations. (0.2ms/0.02ms total (worst/best) @ PHP 7.3/PCRE 11).
833
- * Mind you, this method probably boots PCRE and wptexturize; so, it'll be slower than what we noted--it's
834
- * overhead that otherwise WP, the theme, or other plugin would cause anyway. So, deduct that.
835
- * 3. Now recognizes connector and final punctuations for preliminary sentence bounding.
836
- * 4. Leading punctuation now excludes symbols, special annotations, opening brackets and quotes,
837
- * and marks used in some latin languages like ¡¿.
838
- * 5. Is now able to always strip leading punctuation.
839
- * 6. It will now strip leading colon characters.
840
- * 7. It will now stop counting trailing words towards new sentences when a connector, dash, mark, or ¡¿ is found.
841
- * 8. Now returns encoded entities once more. So that the return value can be treated the same as anything else
842
- * revolving around descriptions--preventing double transcoding like `&amp;amp; > &amp; > &` instead of `&amp;`.
843
- * @since 4.1.5 : 1. The second parameter now accepts values again. From "current description length" to minimum accepted char length.
844
- * 2. Can now return an empty string when the input string doesn't satisfy the minimum character length.
845
- * 3. The third parameter now defaults to 4096, so no longer unexpected results are created.
846
- * 4. Resolved some backtracking issues.
847
- * 5. Resolved an issue where a character followed by punctuation would cause the match to fail.
 
848
  * @see https://secure.php.net/manual/en/regexp.reference.unicode.php
849
  *
850
  * We use `[^\P{Po}\'\"]` because WordPress texturizes ' and " to fall under `\P{Po}`.
@@ -858,6 +906,9 @@ class Generate_Description extends Generate {
858
  */
859
  public function trim_excerpt( $excerpt, $min_char_length = 1, $max_char_length = 4096 ) {
860
 
 
 
 
861
  // We should _actually_ use mb_strlen, but that's wasteful on resources for something benign.
862
  // We'll rectify that later, somewhat, where characters are transformed.
863
  // We could also use preg_match_all( '/./u' ); or count( preg_split( '/./u', $excerpt, $min_char_length ) );
@@ -869,7 +920,7 @@ class Generate_Description extends Generate {
869
 
870
  // Find all words with $max_char_length, and trim when the last word boundary or punctuation is found.
871
  preg_match( sprintf( '/.{0,%d}([^\P{Po}\'\":]|[\p{Pc}\p{Pd}\p{Pf}\p{Z}]|\Z){1}/su', $max_char_length ), trim( $excerpt ), $matches );
872
- $excerpt = isset( $matches[0] ) ? ( $matches[0] ?: '' ) : '';
873
 
874
  $excerpt = trim( $excerpt );
875
 
@@ -909,12 +960,12 @@ class Generate_Description extends Generate {
909
  );
910
 
911
  if ( isset( $matches[5] ) ) {
912
- $excerpt = $matches[1] . $matches[3] . $matches[4] . $matches[5];
913
  // Skip 4. It's useless content without 5.
914
  } elseif ( isset( $matches[3] ) ) {
915
- $excerpt = $matches[1] . $matches[3];
916
  } elseif ( isset( $matches[2] ) ) {
917
- $excerpt = $matches[1] . $matches[2];
918
  } elseif ( isset( $matches[1] ) ) {
919
  $excerpt = $matches[1];
920
  }
@@ -935,10 +986,10 @@ class Generate_Description extends Generate {
935
  );
936
  // Why can $matches[2] still be populated with 3 set? Does it populate empty results upward to last, always???
937
  if ( isset( $matches[2] ) && \strlen( $matches[2] ) ) {
938
- $excerpt = $matches[1] . $matches[2];
939
  } elseif ( isset( $matches[1] ) && \strlen( $matches[1] ) ) {
940
  // Ignore useless [3], there's no [2], [1] is open-ended; so, add hellip.
941
- $excerpt = $matches[1] . '...'; // This should be texturized later to &hellip;.
942
  } else {
943
  // If there's no matches[1], only some form of non-closing-leading punctuation was left in $excerpt. Empty it.
944
  $excerpt = '';
@@ -953,29 +1004,28 @@ class Generate_Description extends Generate {
953
  * Determines whether automated descriptions are enabled.
954
  *
955
  * @since 3.1.0
 
 
956
  * @access private
957
  * @see $this->get_the_real_ID()
958
  * @see $this->get_current_taxonomy()
959
  *
960
- * @param array|null $args An array of 'id' and 'taxonomy' values.
961
- * Can be null when query is autodetermined.
962
  * @return bool
963
  */
964
- public function is_auto_description_enabled( $args ) {
965
 
966
- if ( \is_null( $args ) ) {
967
- $args = [
968
- 'id' => $this->get_the_real_ID(),
969
- 'taxonomy' => $this->get_current_taxonomy(),
970
- ];
971
- }
972
 
973
  /**
974
  * @since 2.5.0
975
  * @since 3.0.0 Now passes $args as the second parameter.
976
  * @since 3.1.0 Now listens to option.
 
977
  * @param bool $autodescription Enable or disable the automated descriptions.
978
- * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
979
  * Is null when query is autodetermined.
980
  */
981
  return (bool) \apply_filters_ref_array(
39
  *
40
  * @since 3.0.6
41
  * @since 3.1.0 The first argument now accepts an array, with "id" and "taxonomy" fields.
42
+ * @since 4.2.0 Now supports the `$args['pta']` index.
43
  * @uses $this->get_description_from_custom_field()
44
  * @uses $this->get_generated_description()
45
  *
46
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
47
+ * Leave null to autodetermine query.
48
  * @param bool $escape Whether to escape the description.
49
  * @return string The real description output.
50
  */
51
  public function get_description( $args = null, $escape = true ) {
52
 
 
53
  $desc = $this->get_description_from_custom_field( $args, false )
54
  ?: $this->get_generated_description( $args, false );
 
55
 
56
  return $escape ? $this->escape_description( $desc ) : $desc;
57
  }
60
  * Returns the Open Graph meta description. Falls back to meta description.
61
  *
62
  * @since 3.0.4
63
+ * @since 3.1.0 1. Now tries to get the homepage social descriptions.
64
+ * 2. The first argument now accepts an array, with "id" and "taxonomy" fields.
65
+ * @since 4.2.0 Now supports the `$args['pta']` index.
66
  * @uses $this->get_open_graph_description_from_custom_field()
67
  * @uses $this->get_generated_open_graph_description()
68
  *
69
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
70
+ * Leave null to autodetermine query.
71
  * @param bool $escape Whether to escape the description.
72
  * @return string The real Open Graph description output.
73
  */
74
  public function get_open_graph_description( $args = null, $escape = true ) {
75
 
 
76
  $desc = $this->get_open_graph_description_from_custom_field( $args, false )
77
  ?: $this->get_generated_open_graph_description( $args, false );
 
78
 
79
  return $escape ? $this->escape_description( $desc ) : $desc;
80
  }
86
  * @since 3.1.0
87
  * @see $this->get_open_graph_description()
88
  *
89
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
90
  * Leave null to autodetermine query.
91
  * @param bool $escape Whether to escape the title.
92
  * @return string TwOpen Graphitter description.
110
  * @since 3.1.0
111
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
112
  * @since 4.0.0 Added term meta item checks.
113
+ * @since 4.2.0 1. No longer returns an escaped custom field description.
114
+ * 2. Now returns custom descriptions for post type archives.
115
  * @see $this->get_open_graph_description()
116
  * @see $this->get_open_graph_description_from_custom_field()
117
  *
120
  protected function get_custom_open_graph_description_from_query() {
121
 
122
  $desc = '';
 
123
  if ( $this->is_real_front_page() ) {
124
  if ( $this->is_static_frontpage() ) {
125
  $desc = $this->get_option( 'homepage_og_description' )
126
  ?: $this->get_post_meta_item( '_open_graph_description' )
127
+ ?: $this->get_description_from_custom_field( null, false );
128
  } else {
129
  $desc = $this->get_option( 'homepage_og_description' )
130
+ ?: $this->get_description_from_custom_field( null, false );
131
  }
132
  } elseif ( $this->is_singular() ) {
133
  $desc = $this->get_post_meta_item( '_open_graph_description' )
134
+ ?: $this->get_description_from_custom_field( null, false );
135
  } elseif ( $this->is_term_meta_capable() ) {
136
  $desc = $this->get_term_meta_item( 'og_description' )
137
+ ?: $this->get_description_from_custom_field( null, false );
138
+ } elseif ( \is_post_type_archive() ) {
139
+ $desc = $this->get_post_type_archive_meta_item( 'og_description' )
140
+ ?: $this->get_description_from_custom_field( null, false );
141
  }
 
142
 
143
  return $desc;
144
  }
148
  * Falls back to meta description.
149
  *
150
  * @since 3.1.0
151
+ * @since 3.2.2 1. Now tests for the homepage as page prior getting custom field data.
152
+ * 2. Now obtains custom field data for terms.
153
  * @since 4.0.0 Added term meta item checks.
154
+ * @since 4.2.0 1. No longer returns an escaped custom field description.
155
+ * 2. Now supports the `$args['pta']` index.
156
  * @see $this->get_open_graph_description()
157
  * @see $this->get_open_graph_description_from_custom_field()
158
  *
159
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
160
  * @return string Open Graph description.
161
  */
162
+ protected function get_custom_open_graph_description_from_args( $args ) {
163
 
164
  $desc = '';
 
165
  if ( $args['taxonomy'] ) {
166
  $desc = $this->get_term_meta_item( 'og_description', $args['id'] )
167
+ ?: $this->get_description_from_custom_field( $args, false );
168
+ } elseif ( $args['pta'] ) {
169
+ $desc = $this->get_post_type_archive_meta_item( 'og_description', $args['pta'] )
170
+ ?: $this->get_description_from_custom_field( $args, false );
171
  } else {
172
  if ( $this->is_static_frontpage( $args['id'] ) ) {
173
  $desc = $this->get_option( 'homepage_og_description' )
174
  ?: $this->get_post_meta_item( '_open_graph_description', $args['id'] )
175
+ ?: $this->get_description_from_custom_field( $args, false );
176
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
177
  $desc = $this->get_option( 'homepage_og_description' )
178
+ ?: $this->get_description_from_custom_field( $args, false );
179
  } else {
180
  $desc = $this->get_post_meta_item( '_open_graph_description', $args['id'] )
181
+ ?: $this->get_description_from_custom_field( $args, false );
182
  }
183
  }
 
184
 
185
  return $desc;
186
  }
190
  * Falls back to Open Graph description.
191
  *
192
  * @since 3.0.4
193
+ * @since 3.1.0 1. Now tries to get the homepage social descriptions.
194
+ * 2. The first argument now accepts an array, with "id" and "taxonomy" fields.
195
+ * @since 4.2.0 Now supports the `$args['pta']` index.
196
  * @uses $this->get_twitter_description_from_custom_field()
197
  * @uses $this->get_generated_twitter_description()
198
  *
199
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
200
+ * Leave null to autodetermine query.
201
  * @param bool $escape Whether to escape the description.
202
  * @return string The real Twitter description output.
203
  */
204
  public function get_twitter_description( $args = null, $escape = true ) {
205
 
 
206
  $desc = $this->get_twitter_description_from_custom_field( $args, false )
207
  ?: $this->get_generated_twitter_description( $args, false );
 
208
 
209
  return $escape ? $this->escape_description( $desc ) : $desc;
210
  }
216
  * @since 3.1.0
217
  * @see $this->get_twitter_description()
218
  *
219
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
220
  * Leave null to autodetermine query.
221
  * @param bool $escape Whether to escape the title.
222
  * @return string Twitter description.
238
  * Falls back to Open Graph description.
239
  *
240
  * @since 3.1.0
241
+ * @since 3.2.2 1. Now tests for the homepage as page prior getting custom field data.
242
+ * 2. Now obtains custom field data for terms.
243
  * @since 4.0.0 Added term meta item checks.
244
+ * @since 4.2.0 1. No longer returns an escaped custom field description.
245
+ * 2. Now returns custom descriptions for post type archives.
246
  * @see $this->get_twitter_description()
247
  * @see $this->get_twitter_description_from_custom_field()
248
  *
251
  protected function get_custom_twitter_description_from_query() {
252
 
253
  $desc = '';
 
254
  if ( $this->is_real_front_page() ) {
255
  if ( $this->is_static_frontpage() ) {
256
  $desc = $this->get_option( 'homepage_twitter_description' )
257
  ?: $this->get_post_meta_item( '_twitter_description' )
258
  ?: $this->get_option( 'homepage_og_description' )
259
  ?: $this->get_post_meta_item( '_open_graph_description' )
260
+ ?: $this->get_description_from_custom_field( null, false )
261
  ?: '';
262
  } else {
263
  $desc = $this->get_option( 'homepage_twitter_description' )
264
  ?: $this->get_option( 'homepage_og_description' )
265
+ ?: $this->get_description_from_custom_field( null, false )
266
  ?: '';
267
  }
268
  } elseif ( $this->is_singular() ) {
269
  $desc = $this->get_post_meta_item( '_twitter_description' )
270
  ?: $this->get_post_meta_item( '_open_graph_description' )
271
+ ?: $this->get_description_from_custom_field( null, false )
272
  ?: '';
273
  } elseif ( $this->is_term_meta_capable() ) {
274
  $desc = $this->get_term_meta_item( 'tw_description' )
275
  ?: $this->get_term_meta_item( 'og_description' )
276
+ ?: $this->get_description_from_custom_field( null, false )
277
+ ?: '';
278
+ } elseif ( \is_post_type_archive() ) {
279
+ $desc = $this->get_post_type_archive_meta_item( 'tw_description' )
280
+ ?: $this->get_post_type_archive_meta_item( 'og_description' )
281
+ ?: $this->get_description_from_custom_field( null, false )
282
  ?: '';
283
  }
 
284
 
285
  return $desc;
286
  }
290
  * Falls back to Open Graph description.
291
  *
292
  * @since 3.1.0
293
+ * @since 3.2.2 1. Now tests for the homepage as page prior getting custom field data.
294
+ * 2. Now obtains custom field data for terms.
295
  * @since 4.0.0 Added term meta item checks.
296
+ * @since 4.2.0 1. No longer returns an escaped custom field description.
297
+ * 2. Now supports the `$args['pta']` index.
298
  * @see $this->get_twitter_description()
299
  * @see $this->get_twitter_description_from_custom_field()
300
  *
301
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
302
  * @return string Twitter description.
303
  */
304
+ protected function get_custom_twitter_description_from_args( $args ) {
305
 
 
306
  if ( $args['taxonomy'] ) {
307
  $desc = $this->get_term_meta_item( 'tw_description', $args['id'] )
308
  ?: $this->get_term_meta_item( 'og_description', $args['id'] )
309
+ ?: $this->get_description_from_custom_field( $args, false )
310
+ ?: '';
311
+ } elseif ( $args['pta'] ) {
312
+ $desc = $this->get_post_type_archive_meta_item( 'tw_description', $args['pta'] )
313
+ ?: $this->get_post_type_archive_meta_item( 'og_description', $args['pta'] )
314
+ ?: $this->get_description_from_custom_field( $args, false )
315
  ?: '';
316
  } else {
317
  if ( $this->is_static_frontpage( $args['id'] ) ) {
319
  ?: $this->get_post_meta_item( '_twitter_description', $args['id'] )
320
  ?: $this->get_option( 'homepage_og_description' )
321
  ?: $this->get_post_meta_item( '_open_graph_description', $args['id'] )
322
+ ?: $this->get_description_from_custom_field( $args, false )
323
  ?: '';
324
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
325
  $desc = $this->get_option( 'homepage_twitter_description' )
326
  ?: $this->get_option( 'homepage_og_description' )
327
+ ?: $this->get_description_from_custom_field( $args, false )
328
  ?: '';
329
  } else {
330
  $desc = $this->get_post_meta_item( '_twitter_description', $args['id'] )
331
  ?: $this->get_post_meta_item( '_open_graph_description', $args['id'] )
332
+ ?: $this->get_description_from_custom_field( $args, false )
333
  ?: '';
334
  }
335
  }
 
336
 
337
  return $desc;
338
  }
342
  *
343
  * @since 3.0.6
344
  * @since 3.1.0 The first argument now accepts an array, with "id" and "taxonomy" fields.
345
+ * @since 4.2.0 Now supports the `$args['pta']` index.
346
  *
347
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
348
+ * Leave null to autodetermine query.
349
  * @param bool $escape Whether to escape the description.
350
  * @return string The custom field description.
351
  */
353
 
354
  if ( null === $args ) {
355
  $desc = $this->get_custom_description_from_query();
 
 
 
 
 
 
356
  } else {
357
  $this->fix_generation_args( $args );
358
  $desc = $this->get_custom_description_from_args( $args );
362
  * @since 2.9.0
363
  * @since 3.0.6 1. Duplicated from $this->generate_description() (deprecated)
364
  * 2. Removed all arguments but the 'id' argument.
365
+ * @since 4.2.0 1. No longer gets supplied custom query arguments when in the loop.
366
+ * 2. Now supports the `$args['pta']` index.
367
  * @param string $desc The custom-field description.
368
+ * @param array|null $args The query arguments. Contains 'id', 'taxonomy', and 'pta'.
369
  * Is null when query is autodetermined.
370
  */
371
+ $desc = (string) \apply_filters_ref_array(
372
+ 'the_seo_framework_custom_field_description',
373
+ [
374
+ $desc,
375
+ $args,
376
+ ]
377
+ );
378
 
379
  return $escape ? $this->escape_description( $desc ) : $desc;
380
  }
384
  *
385
  * @since 3.1.0
386
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
387
+ * @since 4.2.0 Now returns custom descriptions for post type archives.
388
  * @internal
389
  * @see $this->get_description_from_custom_field()
390
  *
394
 
395
  $desc = '';
396
 
 
397
  if ( $this->is_real_front_page() ) {
398
  if ( $this->is_static_frontpage() ) {
399
  $desc = $this->get_option( 'homepage_description' )
409
  } elseif ( \is_post_type_archive() ) {
410
  /**
411
  * @since 4.0.6
412
+ * @since 4.2.0 Deprecated.
413
+ * @deprecated Use options instead.
414
  * @param string $desc The post type archive description.
415
  */
416
+ $desc = (string) \apply_filters_deprecated(
417
+ 'the_seo_framework_pta_description',
418
+ [ $this->get_post_type_archive_meta_item( 'description' ) ?: '' ],
419
+ '4.2.0 of The SEO Framework'
420
+ ) ?: '';
421
  }
 
422
 
423
  return $desc;
424
  }
428
  *
429
  * @since 3.1.0
430
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
431
+ * @since 4.2.0 Now supports the `$args['pta']` index.
432
  * @internal
433
  * @see $this->get_description_from_custom_field()
434
  *
435
  * @param array $args Array of 'id' and 'taxonomy' values.
436
  * @return string The custom description.
437
  */
438
+ protected function get_custom_description_from_args( $args ) {
439
 
 
440
  if ( $args['taxonomy'] ) {
441
  $desc = $this->get_term_meta_item( 'description', $args['id'] ) ?: '';
442
+ } elseif ( $args['pta'] ) {
443
+ $desc = $this->get_post_type_archive_meta_item( 'description', $args['pta'] ) ?: '';
444
  } else {
445
  if ( $this->is_static_frontpage( $args['id'] ) ) {
446
  $desc = $this->get_option( 'homepage_description' )
452
  $desc = $this->get_post_meta_item( '_genesis_description', $args['id'] ) ?: '';
453
  }
454
  }
 
455
 
456
  return $desc;
457
  }
467
  * @since 3.1.2 1. Now omits additions when the description will be deemed too short.
468
  * 2. Now no longer converts additions into excerpt when no excerpt is found.
469
  * @since 3.2.2 Now converts HTML characters prior trimming.
470
+ * @since 4.2.0 Now supports the `$args['pta']` index.
471
  * @uses $this->generate_description()
472
+ * @TODO Should we enforce a minimum description length, where this result is ignored? e.g., use the input
473
+ * guidelines' 'lower' value as a minimum, so that TSF won't ever generate "bad" descriptions?
474
+ * This isn't truly helpful, since then search engines can truly fetch whatever with zero guidance.
475
  *
476
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
477
+ * Leave null to autodetermine query.
478
  * @param bool $escape Whether to escape the description.
479
  * @param string $type Type of description. Accepts 'search', 'opengraph', 'twitter'.
480
  * @return string The generated description output.
498
  * @since 3.1.0 No longer passes 3rd and 4th parameter.
499
  * @since 4.0.0 1. Deprecated second parameter.
500
  * 2. Added third parameter: $args.
501
+ * @since 4.2.0 Now supports the `$args['pta']` index.
502
  * @param string $excerpt The excerpt to use.
503
  * @param int $page_id Deprecated.
504
+ * @param array|null $args The query arguments. Contains 'id', 'taxonomy', and 'pta'.
505
  * Is null when query is autodetermined.
506
  */
507
+ $excerpt = (string) \apply_filters_ref_array(
508
+ 'the_seo_framework_fetched_description_excerpt',
509
+ [
510
+ $excerpt,
511
+ 0,
512
+ $args,
513
+ ]
514
+ );
515
 
 
 
516
  // This page has a generated description that's far too short: https://theseoframework.com/em-changelog/1-0-0-amplified-seo/.
517
  // A direct directory-'site:' query will accept the description outputted--anything else will ignore it...
518
+ // We should not work around that, because it won't direct in the slightest what to display.
519
  $excerpt = $this->trim_excerpt(
520
  $excerpt,
521
  0,
525
  /**
526
  * @since 2.9.0
527
  * @since 3.1.0 No longer passes 3rd and 4th parameter.
528
+ * @since 4.2.0 Now supports the `$args['pta']` index.
529
  * @param string $desc The generated description.
530
+ * @param array|null $args The query arguments. Contains 'id', 'taxonomy', and 'pta'.
531
  * Is null when query is autodetermined.
532
  */
533
+ $desc = (string) \apply_filters_ref_array(
534
+ 'the_seo_framework_generated_description',
535
+ [
536
+ $excerpt,
537
+ $args,
538
+ ]
539
+ );
540
 
541
  return $escape ? $this->escape_description( $desc ) : $desc;
542
  }
545
  * Returns the autogenerated Twitter meta description. Falls back to meta description.
546
  *
547
  * @since 3.0.4
548
+ * @since 4.2.0 Now supports the `$args['pta']` index.
549
  *
550
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
551
+ * Leave null to autodetermine query.
552
  * @param bool $escape Whether to escape the description.
553
  * @return string The generated Twitter description output.
554
  */
560
  * Returns the autogenerated Open Graph meta description. Falls back to meta description.
561
  *
562
  * @since 3.0.4
563
+ * @since 4.2.0 Now supports the `$args['pta']` index.
564
  * @uses $this->generate_description()
565
  *
566
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
567
+ * Leave null to autodetermine query.
568
  * @param bool $escape Whether to escape the description.
569
  * @return string The generated Open Graph description output.
570
  */
576
  * Returns a description excerpt for the current query.
577
  *
578
  * @since 3.1.0
579
+ * @since 4.2.0 Flipped order of query tests.
580
  *
581
  * @return string
582
  */
589
 
590
  $excerpt = '';
591
 
592
+ if ( $this->is_real_front_page() ) {
 
 
593
  $excerpt = $this->get_front_page_description_excerpt();
594
+ } elseif ( $this->is_home_as_page() ) {
595
+ $excerpt = $this->get_blog_page_description_excerpt();
596
  } elseif ( $this->is_singular() ) {
597
  $excerpt = $this->get_singular_description_excerpt();
598
+ } elseif ( $this->is_archive() ) {
599
+ $excerpt = $this->get_archival_description_excerpt();
600
  }
601
 
602
  return $excerpt;
607
  *
608
  * @since 3.1.0
609
  * @since 3.2.2 Fixed front-page as blog logic.
610
+ * @since 4.2.0 Now supports the `$args['pta']` index.
611
  *
612
+ * @param array $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
613
  * @return string
614
  */
615
+ protected function get_description_excerpt_from_args( $args ) {
616
 
617
  $excerpt = '';
618
 
619
  if ( $args['taxonomy'] ) {
620
  $excerpt = $this->get_archival_description_excerpt( \get_term( $args['id'], $args['taxonomy'] ) );
621
+ } elseif ( $args['pta'] ) {
622
+ $excerpt = $this->get_archival_description_excerpt( \get_post_type_object( $args['pta'] ) );
623
  } else {
624
+ if ( $this->is_real_front_page_by_id( $args['id'] ) ) {
 
 
625
  $excerpt = $this->get_front_page_description_excerpt();
626
+ } elseif ( $this->is_home_as_page( $args['id'] ) ) {
627
+ $excerpt = $this->get_blog_page_description_excerpt();
628
  } else {
629
  $excerpt = $this->get_singular_description_excerpt( $args['id'] );
630
  }
641
  * @return string
642
  */
643
  protected function get_blog_page_description_excerpt() {
644
+ return $this->get_description_additions( [ 'id' => (int) \get_option( 'page_for_posts' ) ] );
 
 
 
645
  }
646
 
647
  /**
655
 
656
  $id = $this->get_the_front_page_ID();
657
 
658
+ $excerpt = ( $id ? $this->get_singular_description_excerpt( $id ) : '' )
659
+ ?: $this->get_description_additions( [ 'id' => $id ] );
 
 
 
 
 
 
660
 
661
  return $excerpt;
662
  }
666
  *
667
  * @since 3.1.0
668
  * @since 4.0.0 Now processes HTML tags via s_excerpt_raw() for the author descriptions.
669
+ * @since 4.2.0 Now uses post type archive descriptions to prefill meta descriptions.
670
+ * @TODO fixme: why don't we parse filters? -> What did I mean when I wrote this?
671
  *
672
+ * @param null|\WP_Term|\WP_Post_Type $object The term or post type object.
673
  * @return string
674
  */
675
+ protected function get_archival_description_excerpt( $object = null ) {
676
 
677
+ if ( $object && \is_wp_error( $object ) )
678
  return '';
679
 
680
+ if ( \is_null( $object ) ) {
681
  $in_the_loop = true;
682
+ $object = \get_queried_object();
683
  } else {
684
  $in_the_loop = false;
685
  }
686
 
687
  /**
688
  * @since 3.1.0
689
+ * @see `\tsf()->s_excerpt_raw()` to strip HTML tags neatly.
690
+ * @param string $excerpt The short circuit excerpt.
691
+ * @param \WP_Term|\WP_Post_Type $object The Term object or post type object.
692
  */
693
+ $excerpt = (string) \apply_filters_ref_array(
694
+ 'the_seo_framework_generated_archive_excerpt',
695
+ [
696
+ '',
697
+ $object,
698
+ ]
699
+ );
700
 
701
  if ( $excerpt ) return $excerpt;
702
 
706
  if ( $this->is_category() || $this->is_tag() || $this->is_tax() ) {
707
  // WordPress DOES NOT allow HTML in term descriptions, not even if you're a super-administrator.
708
  // See https://wpvulndb.com/vulnerabilities/9445. We won't parse HTMl tags unless WordPress adds native support.
709
+ $excerpt = $this->s_description_raw( $object->description ?? '' );
710
  } elseif ( $this->is_author() ) {
711
  $excerpt = $this->s_excerpt_raw( \get_the_author_meta( 'description', (int) \get_query_var( 'author' ) ) );
712
  } elseif ( \is_post_type_archive() ) {
713
  /**
 
 
714
  * @since 4.0.6
715
+ * @since 4.2.0 Now provides the post type object description, if assigned.
716
  * @param string $excerpt The archive description excerpt.
717
+ * @param \WP_Term|\WP_Post_Type $object The post type object.
718
  */
719
+ $excerpt = (string) \apply_filters_ref_array(
720
+ 'the_seo_framework_pta_description_excerpt',
721
+ [
722
+ $this->s_description_raw( $object->description ?? '' ),
723
+ $object,
724
+ ]
725
+ );
726
  } else {
727
  /**
728
  * @since 4.0.6
729
+ * @since 4.1.0 Added the $object object parameter.
730
  * @param string $excerpt The fallback archive description excerpt.
731
+ * @param \WP_Term $object The Term object.
732
  */
733
+ $excerpt = (string) \apply_filters_ref_array(
734
+ 'the_seo_framework_fallback_archive_description_excerpt',
735
+ [
736
+ '',
737
+ $object,
738
+ ]
739
+ );
740
  }
741
  } else {
742
+ $excerpt = $this->s_description_raw( $object->description ?? '' );
743
  }
744
 
745
  return $excerpt;
755
  */
756
  protected function get_singular_description_excerpt( $id = null ) {
757
 
758
+ $id = $id ?? $this->get_the_real_ID();
 
759
 
760
  // If the post is protected, don't generate a description.
761
  if ( $this->is_protected( $id ) ) return '';
767
  * Returns additions for "Title on Site Title".
768
  *
769
  * @since 3.1.0
770
+ * @since 3.2.0 1. Now no longer listens to options.
771
+ * 2. Now only works for the front and blog pages.
772
  * @since 3.2.2 Now works for homepages from external requests.
773
+ * @since 4.2.0 No longer adds "on Blogname".
774
  * @see $this->get_generated_description()
775
  *
776
+ * @param array $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
777
+ * This method should always have 'taxonomy' set to ''.
 
778
  * @return string The description additions.
779
  */
780
+ protected function get_description_additions( $args ) {
781
 
782
+ if ( $this->is_real_front_page_by_id( $args['id'] ) ) {
 
 
 
 
 
 
783
  $title = $this->get_home_title_additions();
784
+ } elseif ( $this->is_home_as_page( $args['id'] ) ) {
785
+ $title = sprintf(
786
+ /* translators: %s = Blog page title. Front-end output. */
787
+ \__( 'Latest posts: %s', 'autodescription' ),
788
+ $this->get_filtered_raw_generated_title( $args )
789
+ );
790
  }
791
 
792
+ return ( $title ?? '' ) ?: '';
 
 
 
 
 
 
 
793
  }
794
 
795
  /**
797
  *
798
  * @since 1.0.0
799
  * @since 2.8.2 Added 4th parameter for escaping.
800
+ * @since 3.1.0 1. No longer returns anything for terms.
801
+ * 2. Now strips plausible embeds URLs.
802
  * @since 4.0.1 The second parameter `$id` now defaults to int 0, instead of an empty string.
803
  *
804
  * @param string $excerpt The Excerpt.
865
  * Warning: Returns with entities encoded. The output is not safe for printing.
866
  *
867
  * @since 2.6.0
868
+ * @since 3.1.0 1. Now uses smarter trimming.
869
+ * 2. Deprecated 2nd parameter.
870
+ * 3. Now has unicode support for sentence closing.
871
+ * 4. Now strips last three words when preceded by a sentence closing separator.
872
+ * 5. Now always leads with (inviting) dots, even if the excerpt is shorter than $max_char_length.
873
+ * @since 4.0.0 1. Now stops parsing earlier on failure.
874
+ * 2. Now performs faster queries.
875
+ * 3. Now maintains last sentence with closing punctuations.
876
+ * @since 4.0.5 1. Now decodes the excerpt input, improving accuracy, and so that HTML entities at
877
+ * the end won't be transformed into gibberish.
878
+ * @since 4.1.0 1. Now texturizes the excerpt input, improving accuracy with included closing & final punctuation support.
879
+ * 2. Now performs even faster queries, in most situations. (0.2ms/0.02ms total (worst/best) @ PHP 7.3/PCRE 11).
880
+ * Mind you, this method probably boots PCRE and wptexturize; so, it'll be slower than what we noted--it's
881
+ * overhead that otherwise WP, the theme, or other plugin would cause anyway. So, deduct that.
882
+ * 3. Now recognizes connector and final punctuations for preliminary sentence bounding.
883
+ * 4. Leading punctuation now excludes symbols, special annotations, opening brackets and quotes,
884
+ * and marks used in some latin languages like ¡¿.
885
+ * 5. Is now able to always strip leading punctuation.
886
+ * 6. It will now strip leading colon characters.
887
+ * 7. It will now stop counting trailing words towards new sentences when a connector, dash, mark, or ¡¿ is found.
888
+ * 8. Now returns encoded entities once more. So that the return value can be treated the same as anything else
889
+ * revolving around descriptions--preventing double transcoding like `&amp;amp; > &amp; > &` instead of `&amp;`.
890
+ * @since 4.1.5 1. The second parameter now accepts values again. From "current description length" to minimum accepted char length.
891
+ * 2. Can now return an empty string when the input string doesn't satisfy the minimum character length.
892
+ * 3. The third parameter now defaults to 4096, so no longer unexpected results are created.
893
+ * 4. Resolved some backtracking issues.
894
+ * 5. Resolved an issue where a character followed by punctuation would cause the match to fail.
895
+ * @since 4.2.0 Now enforces at least a character length of 1. This prevents needless processing.
896
  * @see https://secure.php.net/manual/en/regexp.reference.unicode.php
897
  *
898
  * We use `[^\P{Po}\'\"]` because WordPress texturizes ' and " to fall under `\P{Po}`.
906
  */
907
  public function trim_excerpt( $excerpt, $min_char_length = 1, $max_char_length = 4096 ) {
908
 
909
+ // At least 1.
910
+ $min_char_length = $min_char_length < 1 ? 1 : $min_char_length;
911
+
912
  // We should _actually_ use mb_strlen, but that's wasteful on resources for something benign.
913
  // We'll rectify that later, somewhat, where characters are transformed.
914
  // We could also use preg_match_all( '/./u' ); or count( preg_split( '/./u', $excerpt, $min_char_length ) );
920
 
921
  // Find all words with $max_char_length, and trim when the last word boundary or punctuation is found.
922
  preg_match( sprintf( '/.{0,%d}([^\P{Po}\'\":]|[\p{Pc}\p{Pd}\p{Pf}\p{Z}]|\Z){1}/su', $max_char_length ), trim( $excerpt ), $matches );
923
+ $excerpt = trim( ( $matches[0] ?? '' ) ?: '' );
924
 
925
  $excerpt = trim( $excerpt );
926
 
960
  );
961
 
962
  if ( isset( $matches[5] ) ) {
963
+ $excerpt = "$matches[1]$matches[3]$matches[4]$matches[5]";
964
  // Skip 4. It's useless content without 5.
965
  } elseif ( isset( $matches[3] ) ) {
966
+ $excerpt = "$matches[1]$matches[3]";
967
  } elseif ( isset( $matches[2] ) ) {
968
+ $excerpt = "$matches[1]$matches[2]";
969
  } elseif ( isset( $matches[1] ) ) {
970
  $excerpt = $matches[1];
971
  }
986
  );
987
  // Why can $matches[2] still be populated with 3 set? Does it populate empty results upward to last, always???
988
  if ( isset( $matches[2] ) && \strlen( $matches[2] ) ) {
989
+ $excerpt = "$matches[1]$matches[2]";
990
  } elseif ( isset( $matches[1] ) && \strlen( $matches[1] ) ) {
991
  // Ignore useless [3], there's no [2], [1] is open-ended; so, add hellip.
992
+ $excerpt = "$matches[1]..."; // This should be texturized later to &hellip;.
993
  } else {
994
  // If there's no matches[1], only some form of non-closing-leading punctuation was left in $excerpt. Empty it.
995
  $excerpt = '';
1004
  * Determines whether automated descriptions are enabled.
1005
  *
1006
  * @since 3.1.0
1007
+ * @since 4.2.0 1. Now fixes the input arguments.
1008
+ * 2. Now supports the `$args['pta']` index.
1009
  * @access private
1010
  * @see $this->get_the_real_ID()
1011
  * @see $this->get_current_taxonomy()
1012
  *
1013
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
1014
+ * Leave null to autodetermine query.
1015
  * @return bool
1016
  */
1017
+ public function is_auto_description_enabled( $args = null ) {
1018
 
1019
+ if ( null !== $args )
1020
+ $this->fix_generation_args( $args );
 
 
 
 
1021
 
1022
  /**
1023
  * @since 2.5.0
1024
  * @since 3.0.0 Now passes $args as the second parameter.
1025
  * @since 3.1.0 Now listens to option.
1026
+ * @since 4.2.0 Now supports the `$args['pta']` index.
1027
  * @param bool $autodescription Enable or disable the automated descriptions.
1028
+ * @param array|null $args The query arguments. Contains 'id', 'taxonomy', and 'pta'.
1029
  * Is null when query is autodetermined.
1030
  */
1031
  return (bool) \apply_filters_ref_array(
inc/classes/generate-image.class.php CHANGED
@@ -54,8 +54,7 @@ class Generate_Image extends Generate_Url {
54
  * }
55
  */
56
  public function get_image_details_from_cache( $single = false ) {
57
- static $cache = [];
58
- return isset( $cache[ $single ] ) ? $cache[ $single ] : $cache[ $single ] = $this->get_image_details( null, $single );
59
  }
60
 
61
  /**
@@ -63,8 +62,9 @@ class Generate_Image extends Generate_Url {
63
  *
64
  * @since 4.0.0
65
  * @since 4.0.5 The output is now filterable.
 
66
  *
67
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
68
  * Leave null to autodetermine query.
69
  * @param bool $single Whether to fetch one image, or multiple.
70
  * @param string $context The filter context. Default 'social'.
@@ -94,6 +94,7 @@ class Generate_Image extends Generate_Url {
94
 
95
  /**
96
  * @since 4.0.5
 
97
  * @param array $details The image details array, sequential: int => {
98
  * string url: The image URL,
99
  * int id: The image ID,
@@ -101,7 +102,7 @@ class Generate_Image extends Generate_Url {
101
  * int height: The image height in pixels,
102
  * string alt: The image alt tag,
103
  * }
104
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
105
  * Is null when query is autodetermined.
106
  * @param bool $single Whether to fetch one image, or multiple.
107
  * @param string $context The filter context. Default 'social'.
@@ -123,8 +124,9 @@ class Generate_Image extends Generate_Url {
123
  * Returns single custom field image details.
124
  *
125
  * @since 4.0.0
 
126
  *
127
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
128
  * Leave null to autodetermine query.
129
  * @param bool $single Whether to fetch one image, or multiple. Unused, reserved.
130
  * @param bool $clean Whether to clean the image, like stripping duplicates and erroneous items.
@@ -153,8 +155,9 @@ class Generate_Image extends Generate_Url {
153
  * Returns single or multiple generates image details.
154
  *
155
  * @since 4.0.0
 
156
  *
157
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
158
  * Leave null to autodetermine query.
159
  * @param bool $single Whether to fetch one image, or multiple.
160
  * @param string $context The filter context. Default 'social'.
@@ -184,6 +187,7 @@ class Generate_Image extends Generate_Url {
184
  * Returns single custom field image details from query.
185
  *
186
  * @since 4.0.0
 
187
  *
188
  * @return array The image details array, sequential: int => {
189
  * string url: The image URL,
@@ -223,6 +227,11 @@ class Generate_Image extends Generate_Url {
223
  'url' => $this->get_term_meta_item( 'social_image_url' ),
224
  'id' => $this->get_term_meta_item( 'social_image_id' ),
225
  ];
 
 
 
 
 
226
  } else {
227
  $details = [
228
  'url' => '',
@@ -246,6 +255,7 @@ class Generate_Image extends Generate_Url {
246
  * Returns single custom field image details from arguments.
247
  *
248
  * @since 4.0.0
 
249
  *
250
  * @param array $args The query arguments. Must have 'id' and 'taxonomy'.
251
  * @return array The image details array, sequential: int => {
@@ -263,6 +273,11 @@ class Generate_Image extends Generate_Url {
263
  'url' => $this->get_term_meta_item( 'social_image_url', $args['id'] ),
264
  'id' => $this->get_term_meta_item( 'social_image_id', $args['id'] ),
265
  ];
 
 
 
 
 
266
  } else {
267
  if ( $this->is_static_frontpage( $args['id'] ) ) {
268
  $details = [
@@ -305,8 +320,9 @@ class Generate_Image extends Generate_Url {
305
  *
306
  * @since 4.0.0
307
  * @since 4.1.1 Now only the 'social' context will fetch images from the content.
 
308
  *
309
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
310
  * Leave null to autodetermine query.
311
  * @param string $context The filter context. Default 'social'.
312
  * May be (for example) 'breadcrumb' or 'article' for structured data.
@@ -322,23 +338,20 @@ class Generate_Image extends Generate_Url {
322
  */
323
  public function get_image_generation_params( $args = null, $context = 'social' ) {
324
 
325
- if ( null !== $args )
326
- $this->fix_generation_args( $args );
327
-
328
  $builder = Builders\Images::class;
329
 
330
  if ( null === $args ) {
331
  if ( $this->is_singular() ) {
332
  if ( $this->is_attachment() ) {
333
  $cbs = [
334
- 'attachment' => "$builder::get_attachment_image_details",
335
  ];
336
  } else {
337
  $cbs = [
338
- 'featured' => "$builder::get_featured_image_details",
339
  ];
340
  if ( 'social' === $context ) {
341
- $cbs['content'] = "$builder::get_content_image_details";
342
  }
343
  }
344
  } elseif ( $this->is_term_meta_capable() ) {
@@ -347,19 +360,21 @@ class Generate_Image extends Generate_Url {
347
  $cbs = [];
348
  }
349
  } else {
350
- if ( $args['taxonomy'] ) {
 
 
351
  $cbs = [];
352
  } else {
353
  if ( \wp_attachment_is_image( $args['id'] ) ) {
354
  $cbs = [
355
- 'attachment' => "$builder::get_attachment_image_details",
356
  ];
357
  } else {
358
  $cbs = [
359
- 'featured' => "$builder::get_featured_image_details",
360
  ];
361
  if ( 'social' === $context ) {
362
- $cbs['content'] = "$builder::get_content_image_details";
363
  }
364
  }
365
  }
@@ -367,10 +382,10 @@ class Generate_Image extends Generate_Url {
367
 
368
  if ( 'social' === $context ) {
369
  $fallback = [
370
- 'settings' => "$builder::get_fallback_image_details",
371
- 'header' => "$builder::get_theme_header_image_details",
372
- 'logo' => "$builder::get_site_logo_image_details",
373
- 'icon' => "$builder::get_site_icon_image_details",
374
  ];
375
  } else {
376
  $fallback = [];
@@ -378,13 +393,14 @@ class Generate_Image extends Generate_Url {
378
 
379
  /**
380
  * @since 4.0.0
 
381
  * @param array $params : [
382
  * string size: The image size to use.
383
  * boolean multi: Whether to allow multiple images to be returned. This may be overwritten by generators to 'false'.
384
  * array cbs: The callbacks to parse. Ideally be generators, so we can halt remotely.
385
  * array fallback: The callbacks to parse. Ideally be generators, so we can halt remotely.
386
  * ];
387
- * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
388
  * Is null when query is autodetermined.
389
  * @param string $context The filter context. Default 'social'.
390
  * May be (for example) 'breadcrumb' or 'article' for structured data.
@@ -409,7 +425,7 @@ class Generate_Image extends Generate_Url {
409
  *
410
  * @since 4.0.0
411
  *
412
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
413
  * Leave null to autodetermine query.
414
  * @param bool $single Whether to fetch one image, or multiple.
415
  * @param string $context The context of the image generation, albeit 'social', 'schema', etc.
@@ -438,7 +454,7 @@ class Generate_Image extends Generate_Url {
438
  * @since 4.0.0
439
  *
440
  * @param array $cbs The callbacks to parse. Ideally be generators, so we can halt early.
441
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
442
  * Leave null to autodetermine query.
443
  * @param string $size The image size to use.
444
  * @param bool $single Whether to fetch one image, or multiple.
@@ -459,8 +475,9 @@ class Generate_Image extends Generate_Url {
459
  // This is one of the slowest calls in this plugin on PHP 5.6. However, PHP 7.0 optimized cuf(a). Neglegible.
460
  foreach ( \call_user_func_array( $cb, [ $args, $size ] ) as $details ) {
461
  if ( $details['url'] && $this->s_url_query( $details['url'] ) ) {
462
- $items[ $i++ ] = $this->merge_extra_image_details( $details, $size );
463
  if ( $single ) break 2;
 
464
  }
465
  }
466
  }
@@ -486,7 +503,7 @@ class Generate_Image extends Generate_Url {
486
  * string alt: The image alt tag,
487
  * }
488
  */
489
- public function merge_extra_image_details( array $details, $size = 'full' ) {
490
 
491
  $details += $this->get_image_dimensions( $details['id'], $details['url'], $size );
492
  $details += [ 'alt' => $this->get_image_alt_tag( $details['id'] ) ];
@@ -517,7 +534,7 @@ class Generate_Image extends Generate_Url {
517
  ];
518
 
519
  if ( $image ) {
520
- list( $src, $width, $height ) = $image;
521
 
522
  $test_src = \esc_url_raw( $this->set_url_scheme( $src, 'https' ), [ 'https', 'http' ] );
523
  $test_url = \esc_url_raw( $this->set_url_scheme( $url, 'https' ), [ 'https', 'http' ] );
54
  * }
55
  */
56
  public function get_image_details_from_cache( $single = false ) {
57
+ return memo( null, $single ) ?? memo( $this->get_image_details( null, $single ), $single );
 
58
  }
59
 
60
  /**
62
  *
63
  * @since 4.0.0
64
  * @since 4.0.5 The output is now filterable.
65
+ * @since 4.2.0 Now supports the `$args['pta']` index.
66
  *
67
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
68
  * Leave null to autodetermine query.
69
  * @param bool $single Whether to fetch one image, or multiple.
70
  * @param string $context The filter context. Default 'social'.
94
 
95
  /**
96
  * @since 4.0.5
97
+ * @since 4.2.0 Now supports the `$args['pta']` index.
98
  * @param array $details The image details array, sequential: int => {
99
  * string url: The image URL,
100
  * int id: The image ID,
102
  * int height: The image height in pixels,
103
  * string alt: The image alt tag,
104
  * }
105
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
106
  * Is null when query is autodetermined.
107
  * @param bool $single Whether to fetch one image, or multiple.
108
  * @param string $context The filter context. Default 'social'.
124
  * Returns single custom field image details.
125
  *
126
  * @since 4.0.0
127
+ * @since 4.2.0 Now supports the `$args['pta']` index.
128
  *
129
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
130
  * Leave null to autodetermine query.
131
  * @param bool $single Whether to fetch one image, or multiple. Unused, reserved.
132
  * @param bool $clean Whether to clean the image, like stripping duplicates and erroneous items.
155
  * Returns single or multiple generates image details.
156
  *
157
  * @since 4.0.0
158
+ * @since 4.2.0 Now supports the `$args['pta']` index.
159
  *
160
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
161
  * Leave null to autodetermine query.
162
  * @param bool $single Whether to fetch one image, or multiple.
163
  * @param string $context The filter context. Default 'social'.
187
  * Returns single custom field image details from query.
188
  *
189
  * @since 4.0.0
190
+ * @since 4.2.0 Can now return post type archive images from settings.
191
  *
192
  * @return array The image details array, sequential: int => {
193
  * string url: The image URL,
227
  'url' => $this->get_term_meta_item( 'social_image_url' ),
228
  'id' => $this->get_term_meta_item( 'social_image_id' ),
229
  ];
230
+ } elseif ( \is_post_type_archive() ) {
231
+ $details = [
232
+ 'url' => $this->get_post_type_archive_meta_item( 'social_image_url' ),
233
+ 'id' => $this->get_post_type_archive_meta_item( 'social_image_id' ),
234
+ ];
235
  } else {
236
  $details = [
237
  'url' => '',
255
  * Returns single custom field image details from arguments.
256
  *
257
  * @since 4.0.0
258
+ * @since 4.2.0 Now supports the `$args['pta']` index.
259
  *
260
  * @param array $args The query arguments. Must have 'id' and 'taxonomy'.
261
  * @return array The image details array, sequential: int => {
273
  'url' => $this->get_term_meta_item( 'social_image_url', $args['id'] ),
274
  'id' => $this->get_term_meta_item( 'social_image_id', $args['id'] ),
275
  ];
276
+ } elseif ( $args['pta'] ) {
277
+ $details = [
278
+ 'url' => $this->get_post_type_archive_meta_item( 'social_image_url', $args['pta'] ),
279
+ 'id' => $this->get_post_type_archive_meta_item( 'social_image_id', $args['pta'] ),
280
+ ];
281
  } else {
282
  if ( $this->is_static_frontpage( $args['id'] ) ) {
283
  $details = [
320
  *
321
  * @since 4.0.0
322
  * @since 4.1.1 Now only the 'social' context will fetch images from the content.
323
+ * @since 4.2.0 Now supports the `$args['pta']` index.
324
  *
325
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
326
  * Leave null to autodetermine query.
327
  * @param string $context The filter context. Default 'social'.
328
  * May be (for example) 'breadcrumb' or 'article' for structured data.
338
  */
339
  public function get_image_generation_params( $args = null, $context = 'social' ) {
340
 
 
 
 
341
  $builder = Builders\Images::class;
342
 
343
  if ( null === $args ) {
344
  if ( $this->is_singular() ) {
345
  if ( $this->is_attachment() ) {
346
  $cbs = [
347
+ 'attachment' => [ $builder, 'get_attachment_image_details' ],
348
  ];
349
  } else {
350
  $cbs = [
351
+ 'featured' => [ $builder, 'get_featured_image_details' ],
352
  ];
353
  if ( 'social' === $context ) {
354
+ $cbs['content'] = [ $builder, 'get_content_image_details' ];
355
  }
356
  }
357
  } elseif ( $this->is_term_meta_capable() ) {
360
  $cbs = [];
361
  }
362
  } else {
363
+ $this->fix_generation_args( $args );
364
+
365
+ if ( $args['taxonomy'] || $args['pta'] ) {
366
  $cbs = [];
367
  } else {
368
  if ( \wp_attachment_is_image( $args['id'] ) ) {
369
  $cbs = [
370
+ 'attachment' => [ $builder, 'get_attachment_image_details' ],
371
  ];
372
  } else {
373
  $cbs = [
374
+ 'featured' => [ $builder, 'get_featured_image_details' ],
375
  ];
376
  if ( 'social' === $context ) {
377
+ $cbs['content'] = [ $builder, 'get_content_image_details' ];
378
  }
379
  }
380
  }
382
 
383
  if ( 'social' === $context ) {
384
  $fallback = [
385
+ 'settings' => [ $builder, 'get_fallback_image_details' ],
386
+ 'header' => [ $builder, 'get_theme_header_image_details' ],
387
+ 'logo' => [ $builder, 'get_site_logo_image_details' ],
388
+ 'icon' => [ $builder, 'get_site_icon_image_details' ],
389
  ];
390
  } else {
391
  $fallback = [];
393
 
394
  /**
395
  * @since 4.0.0
396
+ * @since 4.2.0 Now supports the `$args['pta']` index.
397
  * @param array $params : [
398
  * string size: The image size to use.
399
  * boolean multi: Whether to allow multiple images to be returned. This may be overwritten by generators to 'false'.
400
  * array cbs: The callbacks to parse. Ideally be generators, so we can halt remotely.
401
  * array fallback: The callbacks to parse. Ideally be generators, so we can halt remotely.
402
  * ];
403
+ * @param array|null $args The query arguments. Contains 'id', 'taxonomy', and 'pta'.
404
  * Is null when query is autodetermined.
405
  * @param string $context The filter context. Default 'social'.
406
  * May be (for example) 'breadcrumb' or 'article' for structured data.
425
  *
426
  * @since 4.0.0
427
  *
428
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
429
  * Leave null to autodetermine query.
430
  * @param bool $single Whether to fetch one image, or multiple.
431
  * @param string $context The context of the image generation, albeit 'social', 'schema', etc.
454
  * @since 4.0.0
455
  *
456
  * @param array $cbs The callbacks to parse. Ideally be generators, so we can halt early.
457
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
458
  * Leave null to autodetermine query.
459
  * @param string $size The image size to use.
460
  * @param bool $single Whether to fetch one image, or multiple.
475
  // This is one of the slowest calls in this plugin on PHP 5.6. However, PHP 7.0 optimized cuf(a). Neglegible.
476
  foreach ( \call_user_func_array( $cb, [ $args, $size ] ) as $details ) {
477
  if ( $details['url'] && $this->s_url_query( $details['url'] ) ) {
478
+ $items[ $i ] = $this->merge_extra_image_details( $details, $size );
479
  if ( $single ) break 2;
480
+ $i++;
481
  }
482
  }
483
  }
503
  * string alt: The image alt tag,
504
  * }
505
  */
506
+ public function merge_extra_image_details( $details, $size = 'full' ) {
507
 
508
  $details += $this->get_image_dimensions( $details['id'], $details['url'], $size );
509
  $details += [ 'alt' => $this->get_image_alt_tag( $details['id'] ) ];
534
  ];
535
 
536
  if ( $image ) {
537
+ [ $src, $width, $height ] = $image;
538
 
539
  $test_src = \esc_url_raw( $this->set_url_scheme( $src, 'https' ), [ 'https', 'http' ] );
540
  $test_url = \esc_url_raw( $this->set_url_scheme( $url, 'https' ), [ 'https', 'http' ] );
inc/classes/generate-ldjson.class.php CHANGED
@@ -45,10 +45,10 @@ class Generate_Ldjson extends Generate_Image {
45
  * @see $this->receive_json_data()
46
  * @uses $this->build_json_data_cache()
47
  *
48
- * @param string $key The JSON data key.
49
- * @param array $data The JSON data.
50
  */
51
- public function build_json_data( $key, array $data ) {
52
 
53
  $key = \sanitize_key( $key );
54
  $data = array_filter( $data );
@@ -107,7 +107,7 @@ class Generate_Ldjson extends Generate_Image {
107
  * @param string $key The JSON data key.
108
  * @param array $entry The JSON data entry.
109
  */
110
- protected function build_json_data_cache( $key, array $entry ) {
111
  $this->cache_json_data( false, $key, $entry );
112
  }
113
 
@@ -123,7 +123,7 @@ class Generate_Ldjson extends Generate_Image {
123
  * @param array $entry The JSON data entry.
124
  * @return array The JSON data for $key.
125
  */
126
- protected function cache_json_data( $get = true, $key = '', array $entry = [] ) {
127
 
128
  static $data = [];
129
 
@@ -147,13 +147,13 @@ class Generate_Ldjson extends Generate_Image {
147
  public function render_ld_json_scripts() {
148
 
149
  if ( $this->is_real_front_page() ) {
150
- //= Homepage Schema.
151
  $output = '';
152
 
153
  $output .= $this->get_ld_json_website() ?: '';
154
  $output .= $this->get_ld_json_links() ?: '';
155
  } else {
156
- //= All other pages' Schema.
157
  $output = $this->get_ld_json_breadcrumbs() ?: '';
158
  }
159
 
@@ -180,7 +180,7 @@ class Generate_Ldjson extends Generate_Image {
180
  'url' => $this->get_homepage_permalink(),
181
  ];
182
 
183
- //= The name part.
184
  $blogname = $this->get_blogname();
185
  $kname = $this->get_option( 'knowledge_name' );
186
 
@@ -191,7 +191,7 @@ class Generate_Ldjson extends Generate_Image {
191
  'alternateName' => \strlen( $alternate_name ) ? $this->escape_title( $alternate_name ) : '',
192
  ];
193
 
194
- //= The searchbox part.
195
  $pattern = '%s{%s}';
196
  $action_name = 'search_term_string';
197
  $search_link = $this->pretty_permalinks ? \trailingslashit( \get_search_link() ) : \get_search_link();
@@ -215,7 +215,7 @@ class Generate_Ldjson extends Generate_Image {
215
  ],
216
  ];
217
 
218
- //= Building
219
  $key = 'website';
220
  $this->build_json_data( $key, $data );
221
  $json = $this->receive_json_data( $key );
@@ -325,35 +325,6 @@ class Generate_Ldjson extends Generate_Image {
325
  );
326
  }
327
 
328
- /**
329
- * Returns image URL suitable for Schema items.
330
- *
331
- * These are images that are strictly assigned to the Post or Page, fallbacks are omitted.
332
- * Themes should compliment these. If not, then Open Graph should at least compliment these.
333
- * If that's not even true, then I don't know what happens. But then you're in a grey area...
334
- *
335
- * @since 4.0.0
336
- * @uses $this->get_image_details()
337
- * @api Not used internally, only externally.
338
- *
339
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
340
- * Leave null to autodetermine query.
341
- * @param bool $details Whether to return all details, or just a simple URL.
342
- * @return string|array $url The Schema.org safe image.
343
- */
344
- public function get_safe_schema_image( $args = null, $details = false ) {
345
-
346
- static $image_details = null;
347
-
348
- if ( ! isset( $image_details ) )
349
- $image_details = current( $this->get_image_details( $args, true, 'schema' ) );
350
-
351
- if ( $details )
352
- return $image_details;
353
-
354
- return isset( $image_details['url'] ) ? $image_details['url'] : '';
355
- }
356
-
357
  /**
358
  * Generates LD+JSON Breadcrumbs script.
359
  *
@@ -398,10 +369,7 @@ class Generate_Ldjson extends Generate_Image {
398
  foreach ( $parents as $parent_id ) {
399
  ++$position;
400
 
401
- $_generator_args = [
402
- 'id' => $parent_id,
403
- 'taxonomy' => '',
404
- ];
405
 
406
  if ( $this->ld_json_breadcrumbs_use_seo_title() ) {
407
  $parent_name = $this->get_filtered_raw_custom_field_title( $_generator_args )
@@ -436,8 +404,8 @@ class Generate_Ldjson extends Generate_Image {
436
  * Generates LD+JSON Breadcrumbs script for Posts.
437
  *
438
  * @since 2.9.3
439
- * @since 3.0.0 : 1. Now only returns one crumb.
440
- * 2. Now listens to primary term ID.
441
  *
442
  * @return string LD+JSON breadcrumbs script for Posts on success. Empty string on failure.
443
  */
@@ -504,14 +472,14 @@ class Generate_Ldjson extends Generate_Image {
504
  // Check if they have parents (gets them all).
505
  $ancestors = \get_ancestors( $term_id, $taxonomy );
506
  if ( $ancestors ) {
507
- //= Save parents to find duplicates.
508
  $parents[ $term_id ] = $ancestors;
509
  } else {
510
- //= Save current only with empty parent id..
511
  $parents[ $term_id ] = [];
512
  }
513
  endforeach;
514
- //= Circle of life...
515
  unset( $terms );
516
 
517
  if ( ! $parents )
@@ -548,7 +516,7 @@ class Generate_Ldjson extends Generate_Image {
548
  }
549
  }
550
  if ( ! $filtered ) {
551
- //= Only get the first tree through numeric ordering.
552
  ksort( $assigned_ids, SORT_NUMERIC );
553
  $tree_ids = $this->filter_ld_json_breadcrumb_trees( $tree_ids, key( $assigned_ids ) );
554
  }
@@ -566,7 +534,6 @@ class Generate_Ldjson extends Generate_Image {
566
  'taxonomy' => $taxonomy,
567
  ];
568
 
569
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
570
  if ( $this->ld_json_breadcrumbs_use_seo_title() ) {
571
  $cat_name = $this->get_filtered_raw_custom_field_title( $_generator_args )
572
  ?: $this->get_generated_single_term_title( \get_term( $child_id, $taxonomy ) )
@@ -575,7 +542,6 @@ class Generate_Ldjson extends Generate_Image {
575
  $cat_name = $this->get_generated_single_term_title( \get_term( $child_id, $taxonomy ) )
576
  ?: $this->get_static_untitled_title();
577
  }
578
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
579
 
580
  // Store in cache.
581
  $items[] = [
@@ -610,7 +576,7 @@ class Generate_Ldjson extends Generate_Image {
610
  * @param array $previous_tree A previous set tree to compare to, if set.
611
  * @return array Trees in order.
612
  */
613
- protected function build_ld_json_breadcrumb_trees( $cats, array $previous_tree = [] ) {
614
 
615
  $trees = $previous_tree;
616
 
@@ -682,40 +648,36 @@ class Generate_Ldjson extends Generate_Image {
682
  * Memoizes the return value.
683
  *
684
  * @since 2.9.3
685
- * @since 3.2.2 : 1. The title now works for the homepage as blog.
686
- * 2. The image has been disabled for the homepage as blog.
687
- * i. I couldn't fix it without evading the API, which is bad.
688
  * @since 4.0.0 Removed the image input requirement.
689
  *
690
  * @return array The HomePage crumb entry.
691
  */
692
  public function get_ld_json_breadcrumb_home_crumb() {
693
 
694
- static $crumb = null;
695
- if ( isset( $crumb ) )
696
- return $crumb;
697
 
698
- $_generator_args = [
699
- 'id' => $this->get_the_front_page_ID(),
700
- 'taxonomy' => '',
701
- ];
702
 
703
  if ( $this->ld_json_breadcrumbs_use_seo_title() ) {
704
- $title = $this->get_filtered_raw_custom_field_title( $_generator_args ) ?: $this->get_blogname();
 
705
  } else {
706
- $title = $this->get_filtered_raw_generated_title( $_generator_args ) ?: $this->get_blogname();
 
707
  }
708
 
709
- $crumb = [
710
  '@type' => 'ListItem',
711
  'position' => 1,
712
  'item' => [
713
  '@id' => $this->get_schema_url_id( 'breadcrumb', 'homepage' ),
714
  'name' => $this->escape_title( $title ),
715
  ],
716
- ];
717
-
718
- return $crumb;
719
  }
720
 
721
  /**
@@ -741,12 +703,8 @@ class Generate_Ldjson extends Generate_Image {
741
  }
742
 
743
  $post_id = $this->get_the_real_ID();
744
- $_generator_args = [
745
- 'id' => $post_id,
746
- 'taxonomy' => '',
747
- ];
748
 
749
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
750
  if ( $this->ld_json_breadcrumbs_use_seo_title() ) {
751
  $name = $this->get_filtered_raw_custom_field_title( $_generator_args )
752
  ?: $this->get_generated_single_post_title( $post_id )
@@ -755,7 +713,6 @@ class Generate_Ldjson extends Generate_Image {
755
  $name = $this->get_generated_single_post_title( $post_id )
756
  ?: $this->get_static_untitled_title();
757
  }
758
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
759
 
760
  $crumb = [
761
  '@type' => 'ListItem',
@@ -786,7 +743,7 @@ class Generate_Ldjson extends Generate_Image {
786
 
787
  static $it = 0;
788
 
789
- $key = 'breadcrumbs_' . $it;
790
 
791
  $data = [
792
  '@context' => 'https://schema.org',
@@ -847,88 +804,58 @@ class Generate_Ldjson extends Generate_Image {
847
  * @return bool
848
  */
849
  public function ld_json_breadcrumbs_use_seo_title() {
850
-
851
- static $cache = null;
852
-
853
  /**
854
  * @since 2.9.0
855
  * @param bool $use_seo_title Whether to use the SEO title.
856
  */
857
- return isset( $cache ) ? $cache : $cache = (bool) \apply_filters( 'the_seo_framework_use_breadcrumb_seo_title', true );
858
  }
859
 
860
  /**
861
  * Determines if breadcrumbs scripts are enabled.
862
- * Memoizes the return value.
863
  *
864
  * @since 2.6.0
 
865
  *
866
  * @return bool
867
  */
868
  public function enable_ld_json_breadcrumbs() {
869
-
870
- static $cache = null;
871
-
872
- if ( isset( $cache ) )
873
- return $cache;
874
-
875
  /**
876
  * @since 2.4.2
877
- * @param bool $filter Whether to force disable Schema.org breadcrumbs.
878
  */
879
- $filter = (bool) \apply_filters( 'the_seo_framework_json_breadcrumb_output', true );
880
- $option = $this->get_option( 'ld_json_breadcrumbs' );
881
-
882
- return $cache = $filter && $option;
883
  }
884
 
885
  /**
886
  * Determines if searchbox script is enabled.
887
- * Memoizes the return value.
888
  *
889
  * @since 2.6.0
 
890
  *
891
  * @return bool
892
  */
893
  public function enable_ld_json_searchbox() {
894
-
895
- static $cache = null;
896
-
897
- if ( isset( $cache ) )
898
- return $cache;
899
-
900
  /**
901
  * @since 2.3.9
902
- * @param bool $filter Whether to force disable Schema.org searchbox.
903
  */
904
- $filter = (bool) \apply_filters( 'the_seo_framework_json_search_output', true );
905
- $option = $this->get_option( 'ld_json_searchbox' );
906
-
907
- return $cache = $filter && $option;
908
  }
909
 
910
  /**
911
  * Determines if Knowledge Graph Script is enabled.
912
- * Memoizes the return value.
913
  *
914
  * @since 2.6.5
 
915
  *
916
  * @return bool
917
  */
918
  public function enable_ld_json_knowledge() {
919
-
920
- static $cache = null;
921
-
922
- if ( isset( $cache ) )
923
- return $cache;
924
-
925
  /**
926
- * @since 2.6.5
927
- * @param bool $filter Whether to force disable Schema.org knowledge.
928
  */
929
- $filter = (bool) \apply_filters( 'the_seo_framework_json_knowledge_output', true );
930
- $option = $this->get_option( 'knowledge_output' );
931
-
932
- return $cache = $filter && $option;
933
  }
934
  }
45
  * @see $this->receive_json_data()
46
  * @uses $this->build_json_data_cache()
47
  *
48
+ * @param string $key The JSON data key.
49
+ * @param iterable $data The JSON data.
50
  */
51
+ public function build_json_data( $key, $data ) {
52
 
53
  $key = \sanitize_key( $key );
54
  $data = array_filter( $data );
107
  * @param string $key The JSON data key.
108
  * @param array $entry The JSON data entry.
109
  */
110
+ protected function build_json_data_cache( $key, $entry ) {
111
  $this->cache_json_data( false, $key, $entry );
112
  }
113
 
123
  * @param array $entry The JSON data entry.
124
  * @return array The JSON data for $key.
125
  */
126
+ protected function cache_json_data( $get = true, $key = '', $entry = [] ) {
127
 
128
  static $data = [];
129
 
147
  public function render_ld_json_scripts() {
148
 
149
  if ( $this->is_real_front_page() ) {
150
+ // Homepage Schema.
151
  $output = '';
152
 
153
  $output .= $this->get_ld_json_website() ?: '';
154
  $output .= $this->get_ld_json_links() ?: '';
155
  } else {
156
+ // All other pages' Schema.
157
  $output = $this->get_ld_json_breadcrumbs() ?: '';
158
  }
159
 
180
  'url' => $this->get_homepage_permalink(),
181
  ];
182
 
183
+ // The name part.
184
  $blogname = $this->get_blogname();
185
  $kname = $this->get_option( 'knowledge_name' );
186
 
191
  'alternateName' => \strlen( $alternate_name ) ? $this->escape_title( $alternate_name ) : '',
192
  ];
193
 
194
+ // The searchbox part.
195
  $pattern = '%s{%s}';
196
  $action_name = 'search_term_string';
197
  $search_link = $this->pretty_permalinks ? \trailingslashit( \get_search_link() ) : \get_search_link();
215
  ],
216
  ];
217
 
218
+ // Building
219
  $key = 'website';
220
  $this->build_json_data( $key, $data );
221
  $json = $this->receive_json_data( $key );
325
  );
326
  }
327
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  /**
329
  * Generates LD+JSON Breadcrumbs script.
330
  *
369
  foreach ( $parents as $parent_id ) {
370
  ++$position;
371
 
372
+ $_generator_args = [ 'id' => $parent_id ];
 
 
 
373
 
374
  if ( $this->ld_json_breadcrumbs_use_seo_title() ) {
375
  $parent_name = $this->get_filtered_raw_custom_field_title( $_generator_args )
404
  * Generates LD+JSON Breadcrumbs script for Posts.
405
  *
406
  * @since 2.9.3
407
+ * @since 3.0.0 1. Now only returns one crumb.
408
+ * 2. Now listens to primary term ID.
409
  *
410
  * @return string LD+JSON breadcrumbs script for Posts on success. Empty string on failure.
411
  */
472
  // Check if they have parents (gets them all).
473
  $ancestors = \get_ancestors( $term_id, $taxonomy );
474
  if ( $ancestors ) {
475
+ // Save parents to find duplicates.
476
  $parents[ $term_id ] = $ancestors;
477
  } else {
478
+ // Save current only with empty parent id..
479
  $parents[ $term_id ] = [];
480
  }
481
  endforeach;
482
+ // Circle of life...
483
  unset( $terms );
484
 
485
  if ( ! $parents )
516
  }
517
  }
518
  if ( ! $filtered ) {
519
+ // Only get the first tree through numeric ordering.
520
  ksort( $assigned_ids, SORT_NUMERIC );
521
  $tree_ids = $this->filter_ld_json_breadcrumb_trees( $tree_ids, key( $assigned_ids ) );
522
  }
534
  'taxonomy' => $taxonomy,
535
  ];
536
 
 
537
  if ( $this->ld_json_breadcrumbs_use_seo_title() ) {
538
  $cat_name = $this->get_filtered_raw_custom_field_title( $_generator_args )
539
  ?: $this->get_generated_single_term_title( \get_term( $child_id, $taxonomy ) )
542
  $cat_name = $this->get_generated_single_term_title( \get_term( $child_id, $taxonomy ) )
543
  ?: $this->get_static_untitled_title();
544
  }
 
545
 
546
  // Store in cache.
547
  $items[] = [
576
  * @param array $previous_tree A previous set tree to compare to, if set.
577
  * @return array Trees in order.
578
  */
579
+ protected function build_ld_json_breadcrumb_trees( $cats, $previous_tree = [] ) {
580
 
581
  $trees = $previous_tree;
582
 
648
  * Memoizes the return value.
649
  *
650
  * @since 2.9.3
651
+ * @since 3.2.2 1. The title now works for the homepage as blog.
652
+ * 2. The image has been disabled for the homepage as blog.
653
+ * i. I couldn't fix it without evading the API, which is bad.
654
  * @since 4.0.0 Removed the image input requirement.
655
  *
656
  * @return array The HomePage crumb entry.
657
  */
658
  public function get_ld_json_breadcrumb_home_crumb() {
659
 
660
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
661
+ if ( null !== $memo = memo() ) return $memo;
 
662
 
663
+ $_generator_args = [ 'id' => $this->get_the_front_page_ID() ];
 
 
 
664
 
665
  if ( $this->ld_json_breadcrumbs_use_seo_title() ) {
666
+ $title = $this->get_filtered_raw_custom_field_title( $_generator_args )
667
+ ?: $this->get_blogname();
668
  } else {
669
+ $title = $this->get_filtered_raw_generated_title( $_generator_args )
670
+ ?: $this->get_blogname();
671
  }
672
 
673
+ return memo( [
674
  '@type' => 'ListItem',
675
  'position' => 1,
676
  'item' => [
677
  '@id' => $this->get_schema_url_id( 'breadcrumb', 'homepage' ),
678
  'name' => $this->escape_title( $title ),
679
  ],
680
+ ] );
 
 
681
  }
682
 
683
  /**
703
  }
704
 
705
  $post_id = $this->get_the_real_ID();
706
+ $_generator_args = [ 'id' => $post_id ];
 
 
 
707
 
 
708
  if ( $this->ld_json_breadcrumbs_use_seo_title() ) {
709
  $name = $this->get_filtered_raw_custom_field_title( $_generator_args )
710
  ?: $this->get_generated_single_post_title( $post_id )
713
  $name = $this->get_generated_single_post_title( $post_id )
714
  ?: $this->get_static_untitled_title();
715
  }
 
716
 
717
  $crumb = [
718
  '@type' => 'ListItem',
743
 
744
  static $it = 0;
745
 
746
+ $key = "breadcrumbs_{$it}";
747
 
748
  $data = [
749
  '@context' => 'https://schema.org',
804
  * @return bool
805
  */
806
  public function ld_json_breadcrumbs_use_seo_title() {
 
 
 
807
  /**
808
  * @since 2.9.0
809
  * @param bool $use_seo_title Whether to use the SEO title.
810
  */
811
+ return memo() ?? memo( (bool) \apply_filters( 'the_seo_framework_use_breadcrumb_seo_title', true ) );
812
  }
813
 
814
  /**
815
  * Determines if breadcrumbs scripts are enabled.
 
816
  *
817
  * @since 2.6.0
818
+ * @since 4.2.0 No longer memoizes the return value.
819
  *
820
  * @return bool
821
  */
822
  public function enable_ld_json_breadcrumbs() {
 
 
 
 
 
 
823
  /**
824
  * @since 2.4.2
825
+ * @param bool $enable Whether to force disable Schema.org breadcrumbs.
826
  */
827
+ return (bool) \apply_filters( 'the_seo_framework_json_breadcrumb_output', $this->get_option( 'ld_json_breadcrumbs' ) );
 
 
 
828
  }
829
 
830
  /**
831
  * Determines if searchbox script is enabled.
 
832
  *
833
  * @since 2.6.0
834
+ * @since 4.2.0 No longer memoizes the return value.
835
  *
836
  * @return bool
837
  */
838
  public function enable_ld_json_searchbox() {
 
 
 
 
 
 
839
  /**
840
  * @since 2.3.9
841
+ * @param bool $enable Whether to force disable Schema.org searchbox.
842
  */
843
+ return (bool) \apply_filters( 'the_seo_framework_json_search_output', $this->get_option( 'ld_json_searchbox' ) );
 
 
 
844
  }
845
 
846
  /**
847
  * Determines if Knowledge Graph Script is enabled.
 
848
  *
849
  * @since 2.6.5
850
+ * @since 4.2.0 No longer memoizes the return value.
851
  *
852
  * @return bool
853
  */
854
  public function enable_ld_json_knowledge() {
 
 
 
 
 
 
855
  /**
856
+ * @since 2.3.9
857
+ * @param bool $enable Whether to force disable Schema.org knowledge.
858
  */
859
+ return (bool) \apply_filters( 'the_seo_framework_json_knowledge_output', $this->get_option( 'knowledge_output' ) );
 
 
 
860
  }
861
  }
inc/classes/generate-title.class.php CHANGED
@@ -43,10 +43,11 @@ class Generate_Title extends Generate_Description {
43
  * @since 3.1.0
44
  * @since 3.2.2 No longer double-escapes the custom field title.
45
  * @since 4.1.0 Added the third $social parameter.
 
46
  * @uses $this->get_custom_field_title()
47
  * @uses $this->get_generated_title()
48
  *
49
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
50
  * Leave null to autodetermine query.
51
  * @param bool $escape Whether to escape the title.
52
  * @param bool $social Whether the title is meant for social display.
@@ -54,10 +55,8 @@ class Generate_Title extends Generate_Description {
54
  */
55
  public function get_title( $args = null, $escape = true, $social = false ) {
56
 
57
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
58
  $title = $this->get_custom_field_title( $args, false, $social )
59
  ?: $this->get_generated_title( $args, false, $social );
60
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
61
 
62
  return $escape ? $this->escape_title( $title ) : $title;
63
  }
@@ -68,8 +67,9 @@ class Generate_Title extends Generate_Description {
68
  * @since 3.1.0
69
  * @since 4.0.0 Moved the filter to a separated method.
70
  * @since 4.1.0 Added the third $social parameter.
 
71
  *
72
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
73
  * Leave null to autodetermine query.
74
  * @param bool $escape Whether to escape the title.
75
  * @param bool $social Whether the title is meant for social display.
@@ -101,10 +101,11 @@ class Generate_Title extends Generate_Description {
101
  * 2. Moved check for title pagination.
102
  * @since 4.0.0 Moved the filter to a separated method.
103
  * @since 4.1.0 Added the third $social parameter.
 
104
  * @uses $this->s_title_raw() : This is the same method used to prepare custom title on save.
105
  * @uses $this->get_filtered_raw_generated_title()
106
  *
107
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
108
  * Leave null to autodetermine query.
109
  * @param bool $escape Whether to escape the title.
110
  * @param bool $social Whether the title is meant for social display.
@@ -132,19 +133,27 @@ class Generate_Title extends Generate_Description {
132
  * Returns the raw filtered custom field meta title.
133
  *
134
  * @since 4.0.0
 
 
 
135
  *
136
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
137
  * Leave null to autodetermine query.
138
  * @return string The raw generated title output.
139
  */
140
- public function get_filtered_raw_custom_field_title( $args ) {
 
 
 
 
141
  /**
142
  * Filters the title from custom field, if any.
143
  *
144
  * @since 3.1.0
 
145
  *
146
  * @param string $title The title.
147
- * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
148
  * Is null when query is autodetermined.
149
  */
150
  return (string) \apply_filters_ref_array(
@@ -160,20 +169,28 @@ class Generate_Title extends Generate_Description {
160
  * Returns the raw filtered autogenerated meta title.
161
  *
162
  * @since 4.0.0
 
 
 
163
  *
164
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
165
  * Leave null to autodetermine query.
166
  * @return string The raw generated title output.
167
  */
168
- public function get_filtered_raw_generated_title( $args ) {
 
 
 
 
169
  /**
170
  * Filters the title from query.
171
  *
172
  * @NOTE: This filter doesn't consistently run on the SEO Settings page.
173
- * You may want to avoid this filter for the homepage, by returning the default value.
174
  * @since 3.1.0
 
175
  * @param string $title The title.
176
- * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
177
  * Is null when query is autodetermined.
178
  */
179
  return (string) \apply_filters_ref_array(
@@ -190,20 +207,19 @@ class Generate_Title extends Generate_Description {
190
  * Falls back to Open Graph title.
191
  *
192
  * @since 3.0.4
193
- * @since 3.1.0 : 1. The first parameter now expects an array.
194
- * 2. Now tries to get the homepage social titles.
 
195
  *
196
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
197
  * Leave null to autodetermine query.
198
  * @param bool $escape Whether to escape the title.
199
  * @return string Twitter Title.
200
  */
201
  public function get_twitter_title( $args = null, $escape = true ) {
202
 
203
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
204
  $title = $this->get_twitter_title_from_custom_field( $args, false )
205
  ?: $this->get_generated_twitter_title( $args, false );
206
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
207
 
208
  return $escape ? $this->escape_title( $title ) : $title;
209
  }
@@ -215,7 +231,7 @@ class Generate_Title extends Generate_Description {
215
  * @since 3.1.0
216
  * @see $this->get_twitter_title()
217
  *
218
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
219
  * @param bool $escape Whether to escape the title.
220
  * @return string Twitter Title.
221
  */
@@ -238,6 +254,7 @@ class Generate_Title extends Generate_Description {
238
  * @since 3.1.0
239
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
240
  * @since 4.0.0 Added term meta item checks.
 
241
  * @see $this->get_twitter_title()
242
  * @see $this->get_twitter_title_from_custom_field()
243
  *
@@ -246,7 +263,7 @@ class Generate_Title extends Generate_Description {
246
  protected function get_custom_twitter_title_from_query() {
247
 
248
  $title = '';
249
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
250
  if ( $this->is_real_front_page() ) {
251
  if ( $this->is_static_frontpage() ) {
252
  $title = $this->get_option( 'homepage_twitter_title' )
@@ -267,8 +284,11 @@ class Generate_Title extends Generate_Description {
267
  $title = $this->get_term_meta_item( 'tw_title' )
268
  ?: $this->get_term_meta_item( 'og_title' )
269
  ?: '';
 
 
 
 
270
  }
271
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
272
 
273
  return $title;
274
  }
@@ -280,19 +300,23 @@ class Generate_Title extends Generate_Description {
280
  * @since 3.1.0
281
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
282
  * @since 4.0.0 Added term meta item checks.
 
283
  * @see $this->get_twitter_title()
284
  * @see $this->get_twitter_title_from_custom_field()
285
  *
286
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
287
  * @return string Twitter Title.
288
  */
289
- protected function get_custom_twitter_title_from_args( array $args ) {
290
 
291
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
292
  if ( $args['taxonomy'] ) {
293
  $title = $this->get_term_meta_item( 'tw_title', $args['id'] )
294
  ?: $this->get_term_meta_item( 'og_title', $args['id'] )
295
  ?: '';
 
 
 
 
296
  } else {
297
  if ( $this->is_static_frontpage( $args['id'] ) ) {
298
  $title = $this->get_option( 'homepage_twitter_title' )
@@ -310,7 +334,6 @@ class Generate_Title extends Generate_Description {
310
  ?: '';
311
  }
312
  }
313
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
314
 
315
  return $title;
316
  }
@@ -322,9 +345,10 @@ class Generate_Title extends Generate_Description {
322
  * @since 3.0.4
323
  * @since 3.1.0 The first parameter now expects an array.
324
  * @since 4.1.0 Now appends the "social" argument when getting the title.
 
325
  * @uses $this->get_title()
326
  *
327
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
328
  * Leave null to autodetermine query.
329
  * @param bool $escape Whether to escape the title.
330
  * @return string The generated Twitter Title.
@@ -338,21 +362,20 @@ class Generate_Title extends Generate_Description {
338
  * Falls back to meta title.
339
  *
340
  * @since 3.0.4
341
- * @since 3.1.0 : 1. The first parameter now expects an array.
342
- * 2. Now tries to get the homepage social title.
 
343
  * @uses $this->get_generated_open_graph_title()
344
  *
345
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
346
  * Leave null to autodetermine query.
347
  * @param bool $escape Whether to escape the title.
348
  * @return string Open Graph Title.
349
  */
350
  public function get_open_graph_title( $args = null, $escape = true ) {
351
 
352
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
353
  $title = $this->get_open_graph_title_from_custom_field( $args, false )
354
  ?: $this->get_generated_open_graph_title( $args, false );
355
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
356
 
357
  return $escape ? $this->escape_title( $title ) : $title;
358
  }
@@ -364,7 +387,7 @@ class Generate_Title extends Generate_Description {
364
  * @since 3.1.0
365
  * @see $this->get_open_graph_title()
366
  *
367
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
368
  * @param bool $escape Whether to escape the title.
369
  * @return string Open Graph Title.
370
  */
@@ -387,6 +410,7 @@ class Generate_Title extends Generate_Description {
387
  * @since 3.1.0
388
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
389
  * @since 4.0.0 Added term meta item checks.
 
390
  * @see $this->get_open_graph_title()
391
  * @see $this->get_open_graph_title_from_custom_field()
392
  *
@@ -395,7 +419,6 @@ class Generate_Title extends Generate_Description {
395
  protected function get_custom_open_graph_title_from_query() {
396
 
397
  $title = '';
398
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
399
  if ( $this->is_real_front_page() ) {
400
  if ( $this->is_static_frontpage() ) {
401
  $title = $this->get_option( 'homepage_og_title' )
@@ -408,8 +431,9 @@ class Generate_Title extends Generate_Description {
408
  $title = $this->get_post_meta_item( '_open_graph_title' ) ?: '';
409
  } elseif ( $this->is_term_meta_capable() ) {
410
  $title = $this->get_term_meta_item( 'og_title' ) ?: '';
 
 
411
  }
412
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
413
 
414
  return $title;
415
  }
@@ -421,18 +445,19 @@ class Generate_Title extends Generate_Description {
421
  * @since 3.1.0
422
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
423
  * @since 4.0.0 Added term meta item checks.
 
424
  * @see $this->get_open_graph_title()
425
  * @see $this->get_open_graph_title_from_custom_field()
426
  *
427
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
428
  * @return string Open Graph Title.
429
  */
430
- protected function get_custom_open_graph_title_from_args( array $args ) {
431
 
432
- $title = '';
433
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
434
  if ( $args['taxonomy'] ) {
435
  $title = $this->get_term_meta_item( 'og_title', $args['id'] ) ?: '';
 
 
436
  } else {
437
  if ( $this->is_static_frontpage( $args['id'] ) ) {
438
  $title = $this->get_option( 'homepage_og_title' )
@@ -444,7 +469,6 @@ class Generate_Title extends Generate_Description {
444
  $title = $this->get_post_meta_item( '_open_graph_title', $args['id'] ) ?: '';
445
  }
446
  }
447
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
448
 
449
  return $title;
450
  }
@@ -456,9 +480,10 @@ class Generate_Title extends Generate_Description {
456
  * @since 3.0.4
457
  * @since 3.1.0 The first parameter now expects an array.
458
  * @since 4.1.0 Now appends the "social" argument when getting the title.
 
459
  * @uses $this->get_title()
460
  *
461
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
462
  * Leave null to autodetermine query.
463
  * @param bool $escape Whether to escape the title.
464
  * @return string The generated Open Graph Title.
@@ -474,9 +499,10 @@ class Generate_Title extends Generate_Description {
474
  * finally admits through their code that terms can be queried using only IDs.
475
  *
476
  * @since 3.1.0
 
477
  * @internal But, feel free to use it.
478
  *
479
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
480
  * Leave null to autodetermine query.
481
  * @return string The custom field title, if it exists.
482
  */
@@ -499,6 +525,7 @@ class Generate_Title extends Generate_Description {
499
  *
500
  * @since 3.1.0
501
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
 
502
  * @internal
503
  * @see $this->get_raw_custom_field_title()
504
  *
@@ -507,7 +534,7 @@ class Generate_Title extends Generate_Description {
507
  protected function get_custom_field_title_from_query() {
508
 
509
  $title = '';
510
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
511
  if ( $this->is_real_front_page() ) {
512
  if ( $this->is_static_frontpage() ) {
513
  $title = $this->get_option( 'homepage_title' )
@@ -523,11 +550,16 @@ class Generate_Title extends Generate_Description {
523
  } elseif ( \is_post_type_archive() ) {
524
  /**
525
  * @since 4.0.6
 
 
526
  * @param string $title The post type archive title.
527
  */
528
- $title = (string) \apply_filters( 'the_seo_framework_pta_title', '' ) ?: '';
 
 
 
 
529
  }
530
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
531
 
532
  return $title;
533
  }
@@ -538,18 +570,19 @@ class Generate_Title extends Generate_Description {
538
  * @since 3.1.0
539
  * @since 3.1.4 Now uses the 'id' to get custom singular title.
540
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
 
541
  * @internal
542
  * @see $this->get_raw_custom_field_title()
543
  *
544
- * @param array $args The query arguments. Accepts 'id' and 'taxonomy'.
545
  * @return string The custom title.
546
  */
547
- protected function get_custom_field_title_from_args( array $args ) {
548
 
549
- $title = '';
550
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
551
  if ( $args['taxonomy'] ) {
552
  $title = $this->get_term_meta_item( 'doctitle', $args['id'] ) ?: '';
 
 
553
  } else {
554
  if ( $this->is_static_frontpage( $args['id'] ) ) {
555
  $title = $this->get_option( 'homepage_title' )
@@ -561,7 +594,6 @@ class Generate_Title extends Generate_Description {
561
  $title = $this->get_post_meta_item( '_genesis_title', $args['id'] ) ?: '';
562
  }
563
  }
564
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
565
 
566
  return $title;
567
  }
@@ -570,15 +602,20 @@ class Generate_Title extends Generate_Description {
570
  * Generates a title, based on expected or current query, without additions or prefixes.
571
  *
572
  * @since 3.1.0
 
 
573
  * @uses $this->generate_title_from_query()
574
  * @uses $this->generate_title_from_args()
575
  *
576
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
577
  * Leave null to autodetermine query.
578
  * @return string The generated title.
579
  */
580
  public function get_raw_generated_title( $args = null ) {
581
 
 
 
 
582
  $this->remove_default_title_filters( false, $args );
583
 
584
  if ( null === $args ) {
@@ -590,7 +627,7 @@ class Generate_Title extends Generate_Description {
590
 
591
  $this->reset_default_title_filters();
592
 
593
- return $title ?: $this->get_static_untitled_title();
594
  }
595
 
596
  /**
@@ -604,7 +641,7 @@ class Generate_Title extends Generate_Description {
604
  * @internal Only to be used within $this->get_raw_generated_title()
605
  *
606
  * @param bool $reset Whether to reset the removed filters.
607
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
608
  * Leave null to autodetermine query.
609
  */
610
  protected function remove_default_title_filters( $reset = false, $args = null ) {
@@ -624,6 +661,7 @@ class Generate_Title extends Generate_Description {
624
  $filters = [ 'single_post_title', 'single_cat_title', 'single_tag_title' ];
625
  } else {
626
  $this->fix_generation_args( $args );
 
627
  if ( 'category' === $args['taxonomy'] ) {
628
  $filters = [ 'single_cat_title' ];
629
  } elseif ( 'post_tag' === $args['taxonomy'] ) {
@@ -674,6 +712,7 @@ class Generate_Title extends Generate_Description {
674
  * Generates a title, based on current query, without additions or prefixes.
675
  *
676
  * @since 3.1.0
 
677
  * @internal
678
  * @see $this->get_raw_generated_title()
679
  *
@@ -689,10 +728,10 @@ class Generate_Title extends Generate_Description {
689
  $title = $this->get_generated_search_query_title();
690
  } elseif ( $this->is_real_front_page() ) {
691
  $title = $this->get_static_front_page_title();
692
- } elseif ( $this->is_archive() ) {
693
- $title = $this->get_generated_archive_title();
694
  } elseif ( $this->is_singular() ) {
695
  $title = $this->get_generated_single_post_title();
 
 
696
  }
697
 
698
  return $title;
@@ -702,18 +741,21 @@ class Generate_Title extends Generate_Description {
702
  * Generates a title, based on expected query, without additions or prefixes.
703
  *
704
  * @since 3.1.0
 
705
  * @internal
706
  * @see $this->get_raw_generated_title()
707
  *
708
- * @param array $args The query arguments. Required. Accepts 'id' and 'taxonomy'.
709
  * @return string The generated title. Empty if query can't be replicated.
710
  */
711
- protected function generate_title_from_args( array $args ) {
712
 
713
  $title = '';
714
 
715
  if ( $args['taxonomy'] ) {
716
  $title = $this->get_generated_archive_title( \get_term( $args['id'], $args['taxonomy'] ) );
 
 
717
  } else {
718
  if ( $this->is_real_front_page_by_id( $args['id'] ) ) {
719
  $title = $this->get_static_front_page_title();
@@ -728,32 +770,17 @@ class Generate_Title extends Generate_Description {
728
  /**
729
  * Generates front page title.
730
  *
 
 
 
731
  * @since 3.1.0
732
- * @TODO figure out why we didn't choose to use $this->get_blogname()?
 
733
  *
734
  * @return string The generated front page title.
735
  */
736
  public function get_static_front_page_title() {
737
- return \get_bloginfo( 'name', 'raw' );
738
- }
739
-
740
- /**
741
- * Combobulates archive title prefixes for WP 5.5+.
742
- *
743
- * @since 4.1.2
744
- * @TEMP
745
- *
746
- * @param string $prefix The archive prefix.
747
- * @param string $title The archive title.
748
- * @return string The archive title.
749
- */
750
- protected function _combobulate_wp550_archive_title( $prefix, $title ) {
751
- return sprintf(
752
- /* translators: 1: Title prefix. 2: Title. */
753
- \_x( '%1$s %2$s', 'archive title', 'default' ),
754
- $prefix,
755
- $title
756
- );
757
  }
758
 
759
  /**
@@ -769,207 +796,231 @@ class Generate_Title extends Generate_Description {
769
  * 2: The first parameter now accepts `\WP_User` objects.
770
  * @since 4.1.2 Now supports WP 5.5 archive titles.
771
  *
772
- * @param \WP_Term|\WP_User|\WP_Error|null $term The Term object or error. Leave null to autodetermine query.
773
- * @return string The generated archive title, not escaped.
 
 
774
  */
775
- public function get_generated_archive_title( $term = null ) {
776
 
777
- if ( $term && \is_wp_error( $term ) )
778
  return '';
779
 
780
- if ( \is_null( $term ) ) {
781
- $_query = true;
782
- $term = \get_queried_object();
783
- } else {
784
- $_query = false;
785
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
786
 
787
  /**
788
  * @since 2.6.0
789
  *
790
- * @param string $title The short circuit title.
791
- * @param \WP_Term|\WP_User $term The Term object.
792
  */
793
- $title = (string) \apply_filters( 'the_seo_framework_the_archive_title', '', $term );
 
 
 
 
 
 
 
 
794
 
795
  if ( $title )
796
- return $title;
797
-
798
- $_tax = isset( $term->taxonomy ) ? $term->taxonomy : '';
799
- $use_prefix = $this->use_generated_archive_prefix( $term );
800
-
801
- // TEMP hack. Let's clean this up later.
802
- $use_new_string_prefixes = $use_prefix && version_compare( \get_bloginfo( 'version' ), '5.5', '>=' );
803
-
804
- if ( ! $_query ) {
805
- if ( $_tax ) {
806
- if ( 'category' === $_tax ) {
807
- $title = $this->get_generated_single_term_title( $term );
808
-
809
- if ( $use_new_string_prefixes ) {
810
- $title = $this->_combobulate_wp550_archive_title(
811
- \_x( 'Category:', 'category archive title prefix', 'default' ),
812
- $title
813
- );
814
- } else {
815
- /* translators: Category archive title. 1: Category name */
816
- $title = $use_prefix ? sprintf( \__( 'Category: %s', 'default' ), $title ) : $title;
817
- }
818
- } elseif ( 'post_tag' === $_tax ) {
819
- $title = $this->get_generated_single_term_title( $term );
820
-
821
- if ( $use_new_string_prefixes ) {
822
- $title = $this->_combobulate_wp550_archive_title(
823
- \_x( 'Tag:', 'tag archive title prefix', 'default' ),
824
- $title
825
- );
826
- } else {
827
- /* translators: Tag archive title. 1: Tag name */
828
- $title = $use_prefix ? sprintf( \__( 'Tag: %s', 'default' ), $title ) : $title;
829
- }
830
- } else {
831
- $title = $this->get_generated_single_term_title( $term );
832
- $title = $use_prefix ? $this->prepend_tax_label_prefix( $title, $_tax ) : $title;
833
- }
834
- } elseif ( $term instanceof \WP_User && isset( $term->display_name ) ) {
835
- $title = $term->display_name;
836
 
837
- if ( $use_new_string_prefixes ) {
838
- $title = $this->_combobulate_wp550_archive_title(
839
- \_x( 'Author:', 'author archive title prefix', 'default' ),
840
- $title
841
- );
842
- } else {
843
- /* translators: Author archive title. 1: Author name */
844
- $title = $use_prefix ? sprintf( \__( 'Author: %s', 'default' ), $title ) : $title;
845
- }
846
- } else {
847
- $title = \__( 'Archives', 'default' );
848
- }
849
- } else {
850
- if ( $this->is_category() ) {
851
- $title = $this->get_generated_single_term_title( $term );
852
-
853
- if ( $use_new_string_prefixes ) {
854
- $title = $this->_combobulate_wp550_archive_title(
855
- \_x( 'Category:', 'category archive title prefix', 'default' ),
856
- $title
857
- );
858
- } else {
859
- /* translators: Category archive title. 1: Category name */
860
- $title = $use_prefix ? sprintf( \__( 'Category: %s', 'default' ), $title ) : $title;
861
- }
862
- } elseif ( $this->is_tag() ) {
863
- $title = $this->get_generated_single_term_title( $term );
864
 
865
- if ( $use_new_string_prefixes ) {
866
- $title = $this->_combobulate_wp550_archive_title(
867
- \_x( 'Tag:', 'tag archive title prefix', 'default' ),
868
- $title
869
- );
870
- } else {
871
- /* translators: Tag archive title. 1: Tag name */
872
- $title = $use_prefix ? sprintf( \__( 'Tag: %s', 'default' ), $title ) : $title;
873
- }
874
- } elseif ( $this->is_author() ) {
875
- $title = isset( $term->display_name ) ? $term->display_name : '';
876
 
877
- if ( $use_new_string_prefixes ) {
878
- $title = $this->_combobulate_wp550_archive_title(
879
- \_x( 'Author:', 'author archive title prefix', 'default' ),
880
- $title
881
- );
882
- } else {
883
- /* translators: Author archive title. 1: Author name */
884
- $title = $use_prefix ? sprintf( \__( 'Author: %s', 'default' ), $title ) : $title;
885
- }
886
- } elseif ( $this->is_date() ) {
887
- if ( $this->is_year() ) {
888
- $title = \get_the_date( \_x( 'Y', 'yearly archives date format', 'default' ) );
889
-
890
- if ( $use_new_string_prefixes ) {
891
- $title = $this->_combobulate_wp550_archive_title(
892
- \_x( 'Year:', 'date archive title prefix', 'default' ),
893
- $title
894
- );
895
- } else {
896
- /* translators: Yearly archive title. 1: Year */
897
- $title = $use_prefix ? sprintf( \__( 'Year: %s', 'default' ), $title ) : $title;
898
- }
899
- } elseif ( $this->is_month() ) {
900
- $title = \get_the_date( \_x( 'F Y', 'monthly archives date format', 'default' ) );
901
-
902
- if ( $use_new_string_prefixes ) {
903
- $title = $this->_combobulate_wp550_archive_title(
904
- \_x( 'Month:', 'date archive title prefix', 'default' ),
905
- $title
906
- );
907
- } else {
908
- /* translators: Monthly archive title. 1: Month name and year */
909
- $title = $use_prefix ? sprintf( \__( 'Month: %s', 'default' ), $title ) : $title;
910
- }
911
- } elseif ( $this->is_day() ) {
912
- $title = \get_the_date( \_x( 'F j, Y', 'daily archives date format', 'default' ) );
913
-
914
- if ( $use_new_string_prefixes ) {
915
- $title = $this->_combobulate_wp550_archive_title(
916
- \_x( 'Day:', 'date archive title prefix', 'default' ),
917
- $title
918
- );
919
- } else {
920
- /* translators: Daily archive title. 1: Date */
921
- $title = $use_prefix ? sprintf( \__( 'Day: %s', 'default' ), $title ) : $title;
922
- }
923
- }
924
- } elseif ( \is_tax( 'post_format' ) ) {
925
- if ( \is_tax( 'post_format', 'post-format-aside' ) ) {
926
- $title = \_x( 'Asides', 'post format archive title', 'default' );
927
- } elseif ( \is_tax( 'post_format', 'post-format-gallery' ) ) {
928
- $title = \_x( 'Galleries', 'post format archive title', 'default' );
929
- } elseif ( \is_tax( 'post_format', 'post-format-image' ) ) {
930
- $title = \_x( 'Images', 'post format archive title', 'default' );
931
- } elseif ( \is_tax( 'post_format', 'post-format-video' ) ) {
932
- $title = \_x( 'Videos', 'post format archive title', 'default' );
933
- } elseif ( \is_tax( 'post_format', 'post-format-quote' ) ) {
934
- $title = \_x( 'Quotes', 'post format archive title', 'default' );
935
- } elseif ( \is_tax( 'post_format', 'post-format-link' ) ) {
936
- $title = \_x( 'Links', 'post format archive title', 'default' );
937
- } elseif ( \is_tax( 'post_format', 'post-format-status' ) ) {
938
- $title = \_x( 'Statuses', 'post format archive title', 'default' );
939
- } elseif ( \is_tax( 'post_format', 'post-format-audio' ) ) {
940
- $title = \_x( 'Audio', 'post format archive title', 'default' );
941
- } elseif ( \is_tax( 'post_format', 'post-format-chat' ) ) {
942
- $title = \_x( 'Chats', 'post format archive title', 'default' );
943
- }
944
- } elseif ( \is_post_type_archive() ) {
945
- $title = $this->get_generated_post_type_archive_title() ?: $this->get_tax_type_label( $_tax, false );
946
 
947
- if ( $use_new_string_prefixes ) {
948
- $title = $this->_combobulate_wp550_archive_title(
949
- \_x( 'Archives:', 'post type archive title prefix', 'default' ),
950
- $title
951
- );
952
- } else {
953
- /* translators: Post type archive title. 1: Post type name */
954
- $title = $use_prefix ? sprintf( \__( 'Archives: %s', 'default' ), $title ) : $title;
955
- }
956
- } elseif ( $this->is_tax() ) {
957
- $title = $this->get_generated_single_term_title( $term );
958
- $title = $use_prefix ? $this->prepend_tax_label_prefix( $title, $_tax ) : $title;
959
- } else {
960
- $title = \__( 'Archives', 'default' );
961
  }
962
  }
963
 
964
  /**
965
  * Filters the archive title.
 
 
966
  *
967
  * @since 3.0.4
 
968
  *
969
- * @param string $title Archive title to be displayed.
970
- * @param \WP_Term $term The term object.
 
 
971
  */
972
- return \apply_filters( 'the_seo_framework_generated_archive_title', $title, $term );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
973
  }
974
 
975
  /**
@@ -987,7 +1038,6 @@ class Generate_Title extends Generate_Description {
987
 
988
  //? Home queries can be tricky. Use get_the_real_ID to be certain.
989
  $_post = \get_post( $id ?: $this->get_the_real_ID() );
990
- $title = '';
991
 
992
  if ( isset( $_post->post_title ) ) {
993
  /**
@@ -1001,7 +1051,7 @@ class Generate_Title extends Generate_Description {
1001
  $title = \apply_filters( 'single_post_title', $_post->post_title, $_post );
1002
  }
1003
 
1004
- return $title;
1005
  }
1006
 
1007
  /**
@@ -1009,10 +1059,9 @@ class Generate_Title extends Generate_Description {
1009
  *
1010
  * It can autodetermine the term; so, perform your checks prior calling.
1011
  *
1012
- * @NOTE Taken from WordPress core. Altered to work in the Admin area.
 
1013
  * @see WP Core single_term_title()
1014
- * TODO Term names may not be empty. But, if you insert illegal characters when updating/creating a term (e.g. `<tag>`)
1015
- * the term name will be empty. When prefixes are added to the term (e.g. `Category:`), only that will be shown.
1016
  *
1017
  * @since 3.1.0
1018
  * @since 4.0.0 No longer redundantly tests the query, now only uses the term input or queried object.
@@ -1026,10 +1075,10 @@ class Generate_Title extends Generate_Description {
1026
  if ( \is_null( $term ) )
1027
  $term = \get_queried_object();
1028
 
1029
- $term_name = '';
1030
 
1031
- if ( isset( $term->name ) ) {
1032
- if ( 'category' === $term->taxonomy ) {
1033
  /**
1034
  * Filter the category archive page title.
1035
  *
@@ -1038,7 +1087,8 @@ class Generate_Title extends Generate_Description {
1038
  * @param string $term_name Category name for archive being displayed.
1039
  */
1040
  $term_name = \apply_filters( 'single_cat_title', $term->name );
1041
- } elseif ( 'post_tag' === $term->taxonomy ) {
 
1042
  /**
1043
  * Filter the tag archive page title.
1044
  *
@@ -1047,7 +1097,8 @@ class Generate_Title extends Generate_Description {
1047
  * @param string $term_name Tag name for archive being displayed.
1048
  */
1049
  $term_name = \apply_filters( 'single_tag_title', $term->name );
1050
- } else {
 
1051
  /**
1052
  * Filter the custom taxonomy archive page title.
1053
  *
@@ -1056,12 +1107,9 @@ class Generate_Title extends Generate_Description {
1056
  * @param string $term_name Term name for archive being displayed.
1057
  */
1058
  $term_name = \apply_filters( 'single_term_title', $term->name );
1059
- }
1060
- }
1061
 
1062
- // Store the prefix sprintf at get_generated_archive_title() instead and set this on title capture failure?
1063
- // We're working around a bug in WordPress here. This should be fixed inside WordPress! Forgo.
1064
- // return strlen( $term_name ) ? $term_name : $this->get_static_untitled_title();
1065
  return $term_name;
1066
  }
1067
 
@@ -1072,21 +1120,23 @@ class Generate_Title extends Generate_Description {
1072
  * @see WP Core post_type_archive_title()
1073
  *
1074
  * @since 3.1.0
 
1075
  *
1076
  * @param string $post_type The post type.
1077
  * @return string The generated post type archive title.
1078
  */
1079
  public function get_generated_post_type_archive_title( $post_type = '' ) {
1080
 
1081
- $post_type = $post_type ?: \get_query_var( 'post_type' );
1082
-
1083
- if ( ! \is_post_type_archive( $post_type ) )
1084
  return '';
1085
 
 
 
1086
  if ( \is_array( $post_type ) )
1087
  $post_type = reset( $post_type );
1088
 
1089
- $post_type_obj = \get_post_type_object( $post_type );
 
1090
 
1091
  /**
1092
  * Filters the post type archive title.
@@ -1096,7 +1146,13 @@ class Generate_Title extends Generate_Description {
1096
  * @param string $post_type_name Post type 'name' label.
1097
  * @param string $post_type Post type.
1098
  */
1099
- $title = \apply_filters( 'post_type_archive_title', $post_type_obj->labels->name, $post_type );
 
 
 
 
 
 
1100
 
1101
  return $title;
1102
  }
@@ -1146,11 +1202,12 @@ class Generate_Title extends Generate_Description {
1146
  * @since 3.1.0
1147
  * @since 3.1.2 Added strict taxonomical check.
1148
  * @since 3.1.3 Fixed conditional logic.
 
1149
  * @uses $this->get_title_branding_from_query()
1150
  * @uses $this->get_title_branding_from_args()
1151
  *
1152
  * @param string $title The title. Passed by reference.
1153
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
1154
  * Leave null to autodetermine query.
1155
  */
1156
  public function merge_title_branding( &$title, $args = null ) {
@@ -1201,14 +1258,15 @@ class Generate_Title extends Generate_Description {
1201
  * Returns the addition and seplocation from arguments.
1202
  *
1203
  * @since 3.2.2
 
1204
  * @see $this->merge_title_branding();
1205
  *
1206
- * @param array $args The query arguments. Accepts 'id' and 'taxonomy'.
1207
  * @return array { 'addition', 'seplocation' }
1208
  */
1209
- protected function get_title_branding_from_args( array $args ) {
1210
 
1211
- if ( ! $args['taxonomy'] && $this->is_real_front_page_by_id( $args['id'] ) ) {
1212
  $addition = $this->get_home_title_additions();
1213
  $seplocation = $this->get_home_title_seplocation();
1214
  } else {
@@ -1252,10 +1310,11 @@ class Generate_Title extends Generate_Description {
1252
  * @since 3.1.0
1253
  * @since 3.1.2 Added strict taxonomical checks for title protection.
1254
  * @since 3.1.3 Fixed conditional logic.
 
1255
  * @see $this->merge_title_prefixes()
1256
  *
1257
  * @param string $title The title. Passed by reference.
1258
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
1259
  * Leave null to autodetermine query.
1260
  * @return void
1261
  */
@@ -1263,14 +1322,14 @@ class Generate_Title extends Generate_Description {
1263
 
1264
  if ( null === $args ) {
1265
  $id = $this->get_the_real_ID();
1266
- $tax = $this->get_current_taxonomy();
1267
  } else {
1268
  $this->fix_generation_args( $args );
1269
  $id = $args['id'];
1270
- $tax = $args['taxonomy'];
1271
  }
1272
 
1273
- if ( $tax ) return;
1274
 
1275
  $post = $id ? \get_post( $id ) : null;
1276
 
@@ -1356,50 +1415,15 @@ class Generate_Title extends Generate_Description {
1356
  return $this->get_title_seplocation( true );
1357
  }
1358
 
1359
- /**
1360
- * Prepends the taxonomy label to the title.
1361
- *
1362
- * @since 4.1.0
1363
- * @since 4.1.2 Now supports WP 5.5 archive titles.
1364
- *
1365
- * @param string $title The title to prepend taxonomy label to.
1366
- * @param string $taxonomy The taxonomy to get label from.
1367
- * @return string The title with possibly prepended tax-label.
1368
- */
1369
- public function prepend_tax_label_prefix( $title, $taxonomy ) {
1370
-
1371
- $prefix = $this->get_tax_type_label( $taxonomy ) ?: '';
1372
-
1373
- if ( $prefix ) {
1374
- $use_new_string_prefixes = version_compare( \get_bloginfo( 'version' ), '5.5', '>=' );
1375
-
1376
- if ( $use_new_string_prefixes ) {
1377
- $title = $this->_combobulate_wp550_archive_title(
1378
- /* translators: %s: Taxonomy singular name. */
1379
- sprintf( \_x( '%s:', 'taxonomy term archive title prefix', 'default' ), $prefix ),
1380
- $title
1381
- );
1382
- } else {
1383
- $title = sprintf(
1384
- /* translators: Taxonomy term archive title. 1: Taxonomy singular name, 2: Current taxonomy term. */
1385
- \__( '%1$s: %2$s', 'default' ),
1386
- $prefix,
1387
- $title
1388
- );
1389
- }
1390
- }
1391
-
1392
- return $title;
1393
- }
1394
-
1395
  /**
1396
  * Determines whether to add or remove title protection prefixes.
1397
  *
1398
  * @since 3.2.4
 
1399
  * NOTE: This does not guarantee that protection is to be added. Only that it will be considered. Bad method name.
1400
  * @see $this->merge_title_protection()
1401
  *
1402
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
1403
  * Leave null to autodetermine query.
1404
  * @return bool True when prefixes are allowed.
1405
  */
@@ -1408,8 +1432,8 @@ class Generate_Title extends Generate_Description {
1408
  if ( null === $args ) {
1409
  $use = $this->is_singular();
1410
  } else {
1411
- $this->fix_generation_args( $args ); // redundant since we only check for a non-autofillable value... use empty( $args['tax..] ) instead?
1412
- $use = $args && ! $args['taxonomy'];
1413
  }
1414
 
1415
  return $use;
@@ -1422,7 +1446,7 @@ class Generate_Title extends Generate_Description {
1422
  * NOTE: This does not guarantee that pagination is to be added. Only that it will be considered. Bad method name.
1423
  * @see $this->merge_title_pagination()
1424
  *
1425
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
1426
  * Leave null to autodetermine query.
1427
  * @return bool True when additions are allowed.
1428
  */
@@ -1446,28 +1470,27 @@ class Generate_Title extends Generate_Description {
1446
  * Determines whether to add or remove title branding additions.
1447
  *
1448
  * @since 3.1.0
1449
- * @since 3.1.2 : 1. Added filter.
1450
- * 2. Added strict taxonomical check.
1451
  * @since 3.2.2 Now differentiates from query and parameter input.
1452
  * @since 4.1.0 Added the second $social parameter.
 
1453
  * @see $this->merge_title_branding()
1454
  * @uses $this->use_title_branding_from_query()
1455
  * @uses $this->use_title_branding_from_args()
1456
  *
1457
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
1458
  * Leave null to autodetermine query.
1459
- * @param bool $social Whether the title is meant for social display.
 
1460
  * @return bool True when additions are allowed.
1461
  */
1462
  public function use_title_branding( $args = null, $social = false ) {
1463
 
1464
- $use = true;
1465
-
1466
- if ( $social ) {
1467
- $use = ! $this->get_option( 'social_title_rem_additions' );
1468
- }
1469
 
1470
- // When social titles tend to use it, evaluate again from general title settings.
1471
  if ( $use ) {
1472
  if ( null === $args ) {
1473
  $use = $this->use_title_branding_from_query();
@@ -1481,11 +1504,18 @@ class Generate_Title extends Generate_Description {
1481
  * @since 3.1.2
1482
  * @since 4.1.0 Added the third $social parameter.
1483
  * @param string $use Whether to use branding.
1484
- * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
1485
  * Is null when query is autodetermined.
1486
  * @param bool $social Whether the title is meant for social display.
1487
  */
1488
- return \apply_filters_ref_array( 'the_seo_framework_use_title_branding', [ $use, $args, $social ] );
 
 
 
 
 
 
 
1489
  }
1490
 
1491
  /**
@@ -1494,6 +1524,7 @@ class Generate_Title extends Generate_Description {
1494
  * @since 3.2.2
1495
  * @since 4.0.0 Added use_taxonomical_title_branding() check.
1496
  * @since 4.0.2 Removed contemned \is_post_type_archive() check for taxonomical branding.
 
1497
  * @see $this->use_title_branding()
1498
  *
1499
  * @return bool
@@ -1506,6 +1537,8 @@ class Generate_Title extends Generate_Description {
1506
  $use = $this->use_singular_title_branding();
1507
  } elseif ( $this->is_term_meta_capable() ) {
1508
  $use = $this->use_taxonomical_title_branding();
 
 
1509
  } else {
1510
  $use = ! $this->get_option( 'title_rem_additions' );
1511
  }
@@ -1518,15 +1551,19 @@ class Generate_Title extends Generate_Description {
1518
  *
1519
  * @since 3.2.2
1520
  * @since 4.0.0 Added use_taxonomical_title_branding() check.
 
 
1521
  * @see $this->use_title_branding()
1522
  *
1523
- * @param array $args The query arguments. Accepts 'id' and 'taxonomy'.
1524
  * @return bool
1525
  */
1526
- protected function use_title_branding_from_args( array $args ) {
1527
 
1528
  if ( $args['taxonomy'] ) {
1529
  $use = $this->use_taxonomical_title_branding( $args['id'] );
 
 
1530
  } else {
1531
  if ( $this->is_real_front_page_by_id( $args['id'] ) ) {
1532
  $use = $this->use_home_page_title_tagline();
@@ -1545,18 +1582,18 @@ class Generate_Title extends Generate_Description {
1545
  * @since 4.0.5 1: Added first parameter `$term`.
1546
  * 2: Added filter.
1547
  *
1548
- * @param \WP_Term|\WP_User|null $term The Term object. Leave null to autodermine query.
1549
  * @return bool
1550
  */
1551
  public function use_generated_archive_prefix( $term = null ) {
1552
 
1553
- $term = isset( $term ) ? $term : \get_queried_object();
1554
  $use = ! $this->get_option( 'title_rem_prefixes' );
1555
 
1556
  /**
1557
  * @since 4.0.5
1558
- * @param string $use Whether to use branding.
1559
- * @param \WP_Term|\WP_User $term The current term.
1560
  */
1561
  return \apply_filters_ref_array( 'the_seo_framework_use_archive_prefix', [ $use, $term ] );
1562
  }
@@ -1597,6 +1634,18 @@ class Generate_Title extends Generate_Description {
1597
  return ! $this->get_term_meta_item( 'title_no_blog_name', $id ) && ! $this->get_option( 'title_rem_additions' );
1598
  }
1599
 
 
 
 
 
 
 
 
 
 
 
 
 
1600
  /**
1601
  * Returns the homepage additions (tagline) from option or bloginfo, when set.
1602
  * Memoizes the return value.
@@ -1607,11 +1656,12 @@ class Generate_Title extends Generate_Description {
1607
  * @return string The trimmed tagline.
1608
  */
1609
  public function get_home_title_additions() {
1610
- static $cache;
1611
- return isset( $cache ) ? $cache : $cache = $this->s_title_raw(
1612
- trim( $this->get_option( 'homepage_title_tagline' ) )
1613
- ?: $this->get_blogdescription()
1614
- ?: ''
 
1615
  );
1616
  }
1617
  }
43
  * @since 3.1.0
44
  * @since 3.2.2 No longer double-escapes the custom field title.
45
  * @since 4.1.0 Added the third $social parameter.
46
+ * @since 4.2.0 Now supports the `$args['pta']` index.
47
  * @uses $this->get_custom_field_title()
48
  * @uses $this->get_generated_title()
49
  *
50
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
51
  * Leave null to autodetermine query.
52
  * @param bool $escape Whether to escape the title.
53
  * @param bool $social Whether the title is meant for social display.
55
  */
56
  public function get_title( $args = null, $escape = true, $social = false ) {
57
 
 
58
  $title = $this->get_custom_field_title( $args, false, $social )
59
  ?: $this->get_generated_title( $args, false, $social );
 
60
 
61
  return $escape ? $this->escape_title( $title ) : $title;
62
  }
67
  * @since 3.1.0
68
  * @since 4.0.0 Moved the filter to a separated method.
69
  * @since 4.1.0 Added the third $social parameter.
70
+ * @since 4.2.0 Now supports the `$args['pta']` index.
71
  *
72
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
73
  * Leave null to autodetermine query.
74
  * @param bool $escape Whether to escape the title.
75
  * @param bool $social Whether the title is meant for social display.
101
  * 2. Moved check for title pagination.
102
  * @since 4.0.0 Moved the filter to a separated method.
103
  * @since 4.1.0 Added the third $social parameter.
104
+ * @since 4.2.0 Now supports the `$args['pta']` index.
105
  * @uses $this->s_title_raw() : This is the same method used to prepare custom title on save.
106
  * @uses $this->get_filtered_raw_generated_title()
107
  *
108
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
109
  * Leave null to autodetermine query.
110
  * @param bool $escape Whether to escape the title.
111
  * @param bool $social Whether the title is meant for social display.
133
  * Returns the raw filtered custom field meta title.
134
  *
135
  * @since 4.0.0
136
+ * @since 4.2.0 1. The first parameter can now be voided.
137
+ * 2. The first parameter is now rectified, so you can leave out indexes.
138
+ * 3. Now supports the `$args['pta']` index.
139
  *
140
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
141
  * Leave null to autodetermine query.
142
  * @return string The raw generated title output.
143
  */
144
+ public function get_filtered_raw_custom_field_title( $args = null ) {
145
+
146
+ if ( null !== $args )
147
+ $this->fix_generation_args( $args );
148
+
149
  /**
150
  * Filters the title from custom field, if any.
151
  *
152
  * @since 3.1.0
153
+ * @since 4.2.0 Now supports the `$args['pta']` index.
154
  *
155
  * @param string $title The title.
156
+ * @param array|null $args The query arguments. Contains 'id', 'taxonomy', and 'pta'.
157
  * Is null when query is autodetermined.
158
  */
159
  return (string) \apply_filters_ref_array(
169
  * Returns the raw filtered autogenerated meta title.
170
  *
171
  * @since 4.0.0
172
+ * @since 4.2.0 1. The first parameter can now be voided.
173
+ * 2. The first parameter is now rectified, so you can leave out indexes.
174
+ * 3. Now supports the `$args['pta']` index.
175
  *
176
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
177
  * Leave null to autodetermine query.
178
  * @return string The raw generated title output.
179
  */
180
+ public function get_filtered_raw_generated_title( $args = null ) {
181
+
182
+ if ( null !== $args )
183
+ $this->fix_generation_args( $args );
184
+
185
  /**
186
  * Filters the title from query.
187
  *
188
  * @NOTE: This filter doesn't consistently run on the SEO Settings page.
189
+ * You may want to avoid this filter for the homepage and pta, by returning the default value.
190
  * @since 3.1.0
191
+ * @since 4.2.0 Now supports the `$args['pta']` index.
192
  * @param string $title The title.
193
+ * @param array|null $args The query arguments. Contains 'id', 'taxonomy', and 'pta'.
194
  * Is null when query is autodetermined.
195
  */
196
  return (string) \apply_filters_ref_array(
207
  * Falls back to Open Graph title.
208
  *
209
  * @since 3.0.4
210
+ * @since 3.1.0 1. The first parameter now expects an array.
211
+ * 2. Now tries to get the homepage social titles.
212
+ * @since 4.2.0 Now supports the `$args['pta']` index.
213
  *
214
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
215
  * Leave null to autodetermine query.
216
  * @param bool $escape Whether to escape the title.
217
  * @return string Twitter Title.
218
  */
219
  public function get_twitter_title( $args = null, $escape = true ) {
220
 
 
221
  $title = $this->get_twitter_title_from_custom_field( $args, false )
222
  ?: $this->get_generated_twitter_title( $args, false );
 
223
 
224
  return $escape ? $this->escape_title( $title ) : $title;
225
  }
231
  * @since 3.1.0
232
  * @see $this->get_twitter_title()
233
  *
234
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
235
  * @param bool $escape Whether to escape the title.
236
  * @return string Twitter Title.
237
  */
254
  * @since 3.1.0
255
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
256
  * @since 4.0.0 Added term meta item checks.
257
+ * @since 4.2.0 Can now return custom post type archive titles.
258
  * @see $this->get_twitter_title()
259
  * @see $this->get_twitter_title_from_custom_field()
260
  *
263
  protected function get_custom_twitter_title_from_query() {
264
 
265
  $title = '';
266
+
267
  if ( $this->is_real_front_page() ) {
268
  if ( $this->is_static_frontpage() ) {
269
  $title = $this->get_option( 'homepage_twitter_title' )
284
  $title = $this->get_term_meta_item( 'tw_title' )
285
  ?: $this->get_term_meta_item( 'og_title' )
286
  ?: '';
287
+ } elseif ( \is_post_type_archive() ) {
288
+ $title = $this->get_post_type_archive_meta_item( 'tw_title' )
289
+ ?: $this->get_post_type_archive_meta_item( 'og_title' )
290
+ ?: '';
291
  }
 
292
 
293
  return $title;
294
  }
300
  * @since 3.1.0
301
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
302
  * @since 4.0.0 Added term meta item checks.
303
+ * @since 4.2.0 Now supports the `$args['pta']` index.
304
  * @see $this->get_twitter_title()
305
  * @see $this->get_twitter_title_from_custom_field()
306
  *
307
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
308
  * @return string Twitter Title.
309
  */
310
+ protected function get_custom_twitter_title_from_args( $args ) {
311
 
 
312
  if ( $args['taxonomy'] ) {
313
  $title = $this->get_term_meta_item( 'tw_title', $args['id'] )
314
  ?: $this->get_term_meta_item( 'og_title', $args['id'] )
315
  ?: '';
316
+ } elseif ( $args['pta'] ) {
317
+ $title = $this->get_post_type_archive_meta_item( 'tw_title', $args['pta'] )
318
+ ?: $this->get_post_type_archive_meta_item( 'og_title', $args['pta'] )
319
+ ?: '';
320
  } else {
321
  if ( $this->is_static_frontpage( $args['id'] ) ) {
322
  $title = $this->get_option( 'homepage_twitter_title' )
334
  ?: '';
335
  }
336
  }
 
337
 
338
  return $title;
339
  }
345
  * @since 3.0.4
346
  * @since 3.1.0 The first parameter now expects an array.
347
  * @since 4.1.0 Now appends the "social" argument when getting the title.
348
+ * @since 4.2.0 Now supports the `$args['pta']` index.
349
  * @uses $this->get_title()
350
  *
351
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
352
  * Leave null to autodetermine query.
353
  * @param bool $escape Whether to escape the title.
354
  * @return string The generated Twitter Title.
362
  * Falls back to meta title.
363
  *
364
  * @since 3.0.4
365
+ * @since 3.1.0 1. The first parameter now expects an array.
366
+ * 2. Now tries to get the homepage social title.
367
+ * @since 4.2.0 Now supports the `$args['pta']` index.
368
  * @uses $this->get_generated_open_graph_title()
369
  *
370
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
371
  * Leave null to autodetermine query.
372
  * @param bool $escape Whether to escape the title.
373
  * @return string Open Graph Title.
374
  */
375
  public function get_open_graph_title( $args = null, $escape = true ) {
376
 
 
377
  $title = $this->get_open_graph_title_from_custom_field( $args, false )
378
  ?: $this->get_generated_open_graph_title( $args, false );
 
379
 
380
  return $escape ? $this->escape_title( $title ) : $title;
381
  }
387
  * @since 3.1.0
388
  * @see $this->get_open_graph_title()
389
  *
390
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
391
  * @param bool $escape Whether to escape the title.
392
  * @return string Open Graph Title.
393
  */
410
  * @since 3.1.0
411
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
412
  * @since 4.0.0 Added term meta item checks.
413
+ * @since 4.2.0 Can now return custom post type archive titles.
414
  * @see $this->get_open_graph_title()
415
  * @see $this->get_open_graph_title_from_custom_field()
416
  *
419
  protected function get_custom_open_graph_title_from_query() {
420
 
421
  $title = '';
 
422
  if ( $this->is_real_front_page() ) {
423
  if ( $this->is_static_frontpage() ) {
424
  $title = $this->get_option( 'homepage_og_title' )
431
  $title = $this->get_post_meta_item( '_open_graph_title' ) ?: '';
432
  } elseif ( $this->is_term_meta_capable() ) {
433
  $title = $this->get_term_meta_item( 'og_title' ) ?: '';
434
+ } elseif ( \is_post_type_archive() ) {
435
+ $title = $this->get_post_type_archive_meta_item( 'og_title' ) ?: '';
436
  }
 
437
 
438
  return $title;
439
  }
445
  * @since 3.1.0
446
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
447
  * @since 4.0.0 Added term meta item checks.
448
+ * @since 4.2.0 Now supports the `$args['pta']` index.
449
  * @see $this->get_open_graph_title()
450
  * @see $this->get_open_graph_title_from_custom_field()
451
  *
452
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
453
  * @return string Open Graph Title.
454
  */
455
+ protected function get_custom_open_graph_title_from_args( $args ) {
456
 
 
 
457
  if ( $args['taxonomy'] ) {
458
  $title = $this->get_term_meta_item( 'og_title', $args['id'] ) ?: '';
459
+ } elseif ( $args['pta'] ) {
460
+ $title = $this->get_post_type_archive_meta_item( 'og_title', $args['pta'] ) ?: '';
461
  } else {
462
  if ( $this->is_static_frontpage( $args['id'] ) ) {
463
  $title = $this->get_option( 'homepage_og_title' )
469
  $title = $this->get_post_meta_item( '_open_graph_title', $args['id'] ) ?: '';
470
  }
471
  }
 
472
 
473
  return $title;
474
  }
480
  * @since 3.0.4
481
  * @since 3.1.0 The first parameter now expects an array.
482
  * @since 4.1.0 Now appends the "social" argument when getting the title.
483
+ * @since 4.2.0 Now supports the `$args['pta']` index.
484
  * @uses $this->get_title()
485
  *
486
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
487
  * Leave null to autodetermine query.
488
  * @param bool $escape Whether to escape the title.
489
  * @return string The generated Open Graph Title.
499
  * finally admits through their code that terms can be queried using only IDs.
500
  *
501
  * @since 3.1.0
502
+ * @since 4.2.0 Now supports the `$args['pta']` index.
503
  * @internal But, feel free to use it.
504
  *
505
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
506
  * Leave null to autodetermine query.
507
  * @return string The custom field title, if it exists.
508
  */
525
  *
526
  * @since 3.1.0
527
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
528
+ * @since 4.2.0 Can now return custom post type archive titles.
529
  * @internal
530
  * @see $this->get_raw_custom_field_title()
531
  *
534
  protected function get_custom_field_title_from_query() {
535
 
536
  $title = '';
537
+
538
  if ( $this->is_real_front_page() ) {
539
  if ( $this->is_static_frontpage() ) {
540
  $title = $this->get_option( 'homepage_title' )
550
  } elseif ( \is_post_type_archive() ) {
551
  /**
552
  * @since 4.0.6
553
+ * @since 4.2.0 Deprecated.
554
+ * @deprecated Use options instead.
555
  * @param string $title The post type archive title.
556
  */
557
+ $title = (string) \apply_filters_deprecated(
558
+ 'the_seo_framework_pta_title',
559
+ [ $this->get_post_type_archive_meta_item( 'doctitle' ) ],
560
+ '4.2.0 of The SEO Framework'
561
+ ) ?: '';
562
  }
 
563
 
564
  return $title;
565
  }
570
  * @since 3.1.0
571
  * @since 3.1.4 Now uses the 'id' to get custom singular title.
572
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
573
+ * @since 4.2.0 Now supports the `$args['pta']` index.
574
  * @internal
575
  * @see $this->get_raw_custom_field_title()
576
  *
577
+ * @param array $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
578
  * @return string The custom title.
579
  */
580
+ protected function get_custom_field_title_from_args( $args ) {
581
 
 
 
582
  if ( $args['taxonomy'] ) {
583
  $title = $this->get_term_meta_item( 'doctitle', $args['id'] ) ?: '';
584
+ } elseif ( $args['pta'] ) {
585
+ $title = $this->get_post_type_archive_meta_item( 'doctitle', $args['pta'] ) ?: '';
586
  } else {
587
  if ( $this->is_static_frontpage( $args['id'] ) ) {
588
  $title = $this->get_option( 'homepage_title' )
594
  $title = $this->get_post_meta_item( '_genesis_title', $args['id'] ) ?: '';
595
  }
596
  }
 
597
 
598
  return $title;
599
  }
602
  * Generates a title, based on expected or current query, without additions or prefixes.
603
  *
604
  * @since 3.1.0
605
+ * @since 4.2.0 1. Added memoization.
606
+ * 2. Now supports the `$args['pta']` index.
607
  * @uses $this->generate_title_from_query()
608
  * @uses $this->generate_title_from_args()
609
  *
610
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
611
  * Leave null to autodetermine query.
612
  * @return string The generated title.
613
  */
614
  public function get_raw_generated_title( $args = null ) {
615
 
616
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
617
+ if ( null !== $memo = memo( null, $args ) ) return $memo;
618
+
619
  $this->remove_default_title_filters( false, $args );
620
 
621
  if ( null === $args ) {
627
 
628
  $this->reset_default_title_filters();
629
 
630
+ return memo( $title ?: $this->get_static_untitled_title(), $args );
631
  }
632
 
633
  /**
641
  * @internal Only to be used within $this->get_raw_generated_title()
642
  *
643
  * @param bool $reset Whether to reset the removed filters.
644
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
645
  * Leave null to autodetermine query.
646
  */
647
  protected function remove_default_title_filters( $reset = false, $args = null ) {
661
  $filters = [ 'single_post_title', 'single_cat_title', 'single_tag_title' ];
662
  } else {
663
  $this->fix_generation_args( $args );
664
+
665
  if ( 'category' === $args['taxonomy'] ) {
666
  $filters = [ 'single_cat_title' ];
667
  } elseif ( 'post_tag' === $args['taxonomy'] ) {
712
  * Generates a title, based on current query, without additions or prefixes.
713
  *
714
  * @since 3.1.0
715
+ * @since 4.2.0 Flipped order of query tests.
716
  * @internal
717
  * @see $this->get_raw_generated_title()
718
  *
728
  $title = $this->get_generated_search_query_title();
729
  } elseif ( $this->is_real_front_page() ) {
730
  $title = $this->get_static_front_page_title();
 
 
731
  } elseif ( $this->is_singular() ) {
732
  $title = $this->get_generated_single_post_title();
733
+ } elseif ( $this->is_archive() ) {
734
+ $title = $this->get_generated_archive_title();
735
  }
736
 
737
  return $title;
741
  * Generates a title, based on expected query, without additions or prefixes.
742
  *
743
  * @since 3.1.0
744
+ * @since 4.2.0 Now supports the `$args['pta']` index.
745
  * @internal
746
  * @see $this->get_raw_generated_title()
747
  *
748
+ * @param array $args The query arguments. Required. Accepts 'id', 'taxonomy', and 'pta'.
749
  * @return string The generated title. Empty if query can't be replicated.
750
  */
751
+ protected function generate_title_from_args( $args ) {
752
 
753
  $title = '';
754
 
755
  if ( $args['taxonomy'] ) {
756
  $title = $this->get_generated_archive_title( \get_term( $args['id'], $args['taxonomy'] ) );
757
+ } elseif ( $args['pta'] ) {
758
+ $title = $this->get_generated_archive_title( \get_post_type_object( $args['pta'] ) );
759
  } else {
760
  if ( $this->is_real_front_page_by_id( $args['id'] ) ) {
761
  $title = $this->get_static_front_page_title();
770
  /**
771
  * Generates front page title.
772
  *
773
+ * This is an alias of get_blogname(). The difference is that this is used for
774
+ * the front-page title output solely, whereas the other one has a mixed usage.
775
+ *
776
  * @since 3.1.0
777
+ * @since 4.2.0 1. Now listens to the new `site_title` option.
778
+ * 2. Now applies filters.
779
  *
780
  * @return string The generated front page title.
781
  */
782
  public function get_static_front_page_title() {
783
+ return $this->get_blogname();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
784
  }
785
 
786
  /**
796
  * 2: The first parameter now accepts `\WP_User` objects.
797
  * @since 4.1.2 Now supports WP 5.5 archive titles.
798
  *
799
+ * @param \WP_Term|\WP_User|\WP_Post_Type|\WP_Error|null $object The Term object or error.
800
+ * Leave null to autodetermine query.
801
+ * @return string|string[] The generated archive title, not escaped.
802
+ * When $get is 'admin', it returns an map of [prefix,title].
803
  */
804
+ public function get_generated_archive_title( $object = null ) {
805
 
806
+ if ( $object && \is_wp_error( $object ) )
807
  return '';
808
 
809
+ [ $title ] = $this->get_raw_generated_archive_title_items( $object );
810
+
811
+ return $title;
812
+ }
813
+
814
+ /**
815
+ * Returns the archive title items. Also works in admin.
816
+ *
817
+ * @NOTE Taken from WordPress core. Altered to work for metadata.
818
+ * @see WP Core get_the_archive_title()
819
+ *
820
+ * @since 4.2.0
821
+ *
822
+ * @param \WP_Term|\WP_User|\WP_Post_Type|null $object The Term object.
823
+ * Leave null to autodetermine query.
824
+ * @return String[$title,$prefix,$title_without_prefix] The generated archive title items, not escaped.
825
+ */
826
+ public function get_raw_generated_archive_title_items( $object = null ) {
827
+
828
+ $filterobject = $object ?? \get_queried_object();
829
 
830
  /**
831
  * @since 2.6.0
832
  *
833
+ * @param string $title The short circuit title.
834
+ * @param \WP_Term|\WP_User|\WP_Post_Type $object The archive object.
835
  */
836
+ $title = (string) \apply_filters_deprecated(
837
+ 'the_seo_framework_the_archive_title',
838
+ [
839
+ '',
840
+ $filterobject,
841
+ ],
842
+ '4.2.0 of The SEO Framework',
843
+ 'the_seo_framework_generated_archive_title_prefix and the_seo_framework_generated_archive_title'
844
+ );
845
 
846
  if ( $title )
847
+ return [ $title, '', $title ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
848
 
849
+ [ $title, $prefix ] = $object
850
+ ? $this->get_generate_archive_title_from_term( $object )
851
+ : $this->get_generate_archive_title_from_query();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
852
 
853
+ $title_without_prefix = $title;
 
 
 
 
 
 
 
 
 
 
854
 
855
+ if ( $this->use_generated_archive_prefix( $object ) ) {
856
+ /**
857
+ * Filters the archive title prefix.
858
+ * This is a sibling of WordPress's `get_the_archive_title_prefix`,
859
+ * but then without the HTML, and runs optionally, based on site-settings,
860
+ * and then with the second paramter: `$object`.
861
+ *
862
+ * @since 4.2.0
863
+ *
864
+ * @param string $prefix Archive title prefix.
865
+ * @param \WP_Term|\WP_User|\WP_Post_Type $object The archive object.
866
+ */
867
+ $prefix = \apply_filters_ref_array(
868
+ 'the_seo_framework_generated_archive_title_prefix',
869
+ [
870
+ $prefix,
871
+ $filterobject,
872
+ ]
873
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
874
 
875
+ if ( $prefix ) {
876
+ $title = sprintf(
877
+ /* translators: 1: Title prefix. 2: Title. */
878
+ \_x( '%1$s %2$s', 'archive title', 'default' ),
879
+ $prefix,
880
+ $title
881
+ );
 
 
 
 
 
 
 
882
  }
883
  }
884
 
885
  /**
886
  * Filters the archive title.
887
+ * This is a sibling of WordPress's `get_the_archive_title`,
888
+ * but then without the HTML.
889
  *
890
  * @since 3.0.4
891
+ * @since 4.2.0 Added the `$prefix` and `$origintitle_without_prefixal_title` parameters.
892
  *
893
+ * @param string $title Archive title to be displayed.
894
+ * @param \WP_Term|\WP_User|\WP_Post_Type $object The archive object.
895
+ * @param string $title_without_prefix Archive title without prefix.
896
+ * @param string $prefix Archive title prefix.
897
  */
898
+ $title = \apply_filters_ref_array(
899
+ 'the_seo_framework_generated_archive_title',
900
+ [
901
+ $title,
902
+ $filterobject,
903
+ $title_without_prefix,
904
+ $prefix,
905
+ ]
906
+ );
907
+
908
+ return [ $title, $prefix, $title_without_prefix ];
909
+ }
910
+
911
+ /**
912
+ * Returns the generated archive title by evaluating the input Term only.
913
+ *
914
+ * @since 4.2.0
915
+ * @see $this->get_generate_archive_title_from_term() which evaluates from arguments.
916
+ *
917
+ * @return string[$title,$prefix] The title and prefix.
918
+ */
919
+ protected function get_generate_archive_title_from_query() {
920
+
921
+ $title = \__( 'Archives', 'default' );
922
+ $prefix = '';
923
+
924
+ if ( $this->is_category() ) {
925
+ $title = $this->get_generated_single_term_title( \get_queried_object() );
926
+ $prefix = \_x( 'Category:', 'category archive title prefix', 'default' );
927
+ } elseif ( $this->is_tag() ) {
928
+ $title = $this->get_generated_single_term_title( \get_queried_object() );
929
+ $prefix = \_x( 'Tag:', 'tag archive title prefix', 'default' );
930
+ } elseif ( $this->is_author() ) {
931
+ $title = \get_queried_object()->display_name ?? '';
932
+ $prefix = \_x( 'Author:', 'author archive title prefix', 'default' );
933
+ } elseif ( $this->is_date() ) {
934
+ if ( $this->is_year() ) {
935
+ $title = \get_the_date( \_x( 'Y', 'yearly archives date format', 'default' ) );
936
+ $prefix = \_x( 'Year:', 'date archive title prefix', 'default' );
937
+ } elseif ( $this->is_month() ) {
938
+ $title = \get_the_date( \_x( 'F Y', 'monthly archives date format', 'default' ) );
939
+ $prefix = \_x( 'Month:', 'date archive title prefix', 'default' );
940
+ } elseif ( $this->is_day() ) {
941
+ $title = \get_the_date( \_x( 'F j, Y', 'daily archives date format', 'default' ) );
942
+ $prefix = \_x( 'Day:', 'date archive title prefix', 'default' );
943
+ }
944
+ } elseif ( \is_tax( 'post_format' ) ) {
945
+ if ( \is_tax( 'post_format', 'post-format-aside' ) ) {
946
+ $title = \_x( 'Asides', 'post format archive title', 'default' );
947
+ } elseif ( \is_tax( 'post_format', 'post-format-gallery' ) ) {
948
+ $title = \_x( 'Galleries', 'post format archive title', 'default' );
949
+ } elseif ( \is_tax( 'post_format', 'post-format-image' ) ) {
950
+ $title = \_x( 'Images', 'post format archive title', 'default' );
951
+ } elseif ( \is_tax( 'post_format', 'post-format-video' ) ) {
952
+ $title = \_x( 'Videos', 'post format archive title', 'default' );
953
+ } elseif ( \is_tax( 'post_format', 'post-format-quote' ) ) {
954
+ $title = \_x( 'Quotes', 'post format archive title', 'default' );
955
+ } elseif ( \is_tax( 'post_format', 'post-format-link' ) ) {
956
+ $title = \_x( 'Links', 'post format archive title', 'default' );
957
+ } elseif ( \is_tax( 'post_format', 'post-format-status' ) ) {
958
+ $title = \_x( 'Statuses', 'post format archive title', 'default' );
959
+ } elseif ( \is_tax( 'post_format', 'post-format-audio' ) ) {
960
+ $title = \_x( 'Audio', 'post format archive title', 'default' );
961
+ } elseif ( \is_tax( 'post_format', 'post-format-chat' ) ) {
962
+ $title = \_x( 'Chats', 'post format archive title', 'default' );
963
+ }
964
+ } elseif ( \is_post_type_archive() ) {
965
+ $title = $this->get_generated_post_type_archive_title();
966
+ $prefix = \_x( 'Archives:', 'post type archive title prefix', 'default' );
967
+ } elseif ( $this->is_tax() ) {
968
+ $term = \get_queried_object();
969
+
970
+ if ( $term ) {
971
+ $title = $this->get_generated_single_term_title( $term );
972
+ $prefix = sprintf(
973
+ /* translators: %s: Taxonomy singular name. */
974
+ \_x( '%s:', 'taxonomy term archive title prefix', 'default' ),
975
+ $this->get_tax_type_label( $term->taxonomy ?? '' )
976
+ );
977
+ }
978
+ }
979
+
980
+ return [ $title, $prefix ];
981
+ }
982
+
983
+ /**
984
+ * Returns the generated archive title by evaluating the input Term only.
985
+ *
986
+ * @since 4.2.0
987
+ * @see $this->get_generate_archive_title_from_query() which evalutates the query only.
988
+ *
989
+ * @param \WP_Term|\WP_User $term The Term object.
990
+ * @return string[$title,$prefix] The title and prefix.
991
+ */
992
+ protected function get_generate_archive_title_from_term( $term ) {
993
+
994
+ $title = \__( 'Archives', 'default' );
995
+ $prefix = '';
996
+
997
+ if ( ! empty( $term->taxonomy ) ) {
998
+ $title = $this->get_generated_single_term_title( $term );
999
+
1000
+ switch ( $term->taxonomy ) :
1001
+ case 'category':
1002
+ $prefix = \_x( 'Category:', 'category archive title prefix', 'default' );
1003
+ break;
1004
+ case 'post_tag':
1005
+ $prefix = \_x( 'Tag:', 'tag archive title prefix', 'default' );
1006
+ break;
1007
+ default:
1008
+ $prefix = sprintf(
1009
+ /* translators: %s: Taxonomy singular name. */
1010
+ \_x( '%s:', 'taxonomy term archive title prefix', 'default' ),
1011
+ $this->get_tax_type_label( $term->taxonomy )
1012
+ );
1013
+ break;
1014
+ endswitch;
1015
+ } elseif ( $term instanceof \WP_User && isset( $term->display_name ) ) {
1016
+ $title = $term->display_name;
1017
+ $prefix = \_x( 'Author:', 'author archive title prefix', 'default' );
1018
+ } elseif ( $term instanceof \WP_Post_Type && isset( $term->name ) ) {
1019
+ $title = $this->get_generated_post_type_archive_title( $term->name );
1020
+ $prefix = \_x( 'Archives:', 'post type archive title prefix', 'default' );
1021
+ }
1022
+
1023
+ return [ $title, $prefix ];
1024
  }
1025
 
1026
  /**
1038
 
1039
  //? Home queries can be tricky. Use get_the_real_ID to be certain.
1040
  $_post = \get_post( $id ?: $this->get_the_real_ID() );
 
1041
 
1042
  if ( isset( $_post->post_title ) ) {
1043
  /**
1051
  $title = \apply_filters( 'single_post_title', $_post->post_title, $_post );
1052
  }
1053
 
1054
+ return $title ?? '';
1055
  }
1056
 
1057
  /**
1059
  *
1060
  * It can autodetermine the term; so, perform your checks prior calling.
1061
  *
1062
+ * Taken from WordPress core. Altered to work in the Admin area.
1063
+ *
1064
  * @see WP Core single_term_title()
 
 
1065
  *
1066
  * @since 3.1.0
1067
  * @since 4.0.0 No longer redundantly tests the query, now only uses the term input or queried object.
1075
  if ( \is_null( $term ) )
1076
  $term = \get_queried_object();
1077
 
1078
+ if ( ! isset( $term->name ) ) return '';
1079
 
1080
+ switch ( $term->category ) :
1081
+ case 'category':
1082
  /**
1083
  * Filter the category archive page title.
1084
  *
1087
  * @param string $term_name Category name for archive being displayed.
1088
  */
1089
  $term_name = \apply_filters( 'single_cat_title', $term->name );
1090
+ break;
1091
+ case 'post_tag':
1092
  /**
1093
  * Filter the tag archive page title.
1094
  *
1097
  * @param string $term_name Tag name for archive being displayed.
1098
  */
1099
  $term_name = \apply_filters( 'single_tag_title', $term->name );
1100
+ break;
1101
+ default:
1102
  /**
1103
  * Filter the custom taxonomy archive page title.
1104
  *
1107
  * @param string $term_name Term name for archive being displayed.
1108
  */
1109
  $term_name = \apply_filters( 'single_term_title', $term->name );
1110
+ break;
1111
+ endswitch;
1112
 
 
 
 
1113
  return $term_name;
1114
  }
1115
 
1120
  * @see WP Core post_type_archive_title()
1121
  *
1122
  * @since 3.1.0
1123
+ * @since 4.2.0 Now actually works in the admin area, provided you forward $post_type.
1124
  *
1125
  * @param string $post_type The post type.
1126
  * @return string The generated post type archive title.
1127
  */
1128
  public function get_generated_post_type_archive_title( $post_type = '' ) {
1129
 
1130
+ if ( ! $post_type && ! \is_post_type_archive() )
 
 
1131
  return '';
1132
 
1133
+ $post_type = $post_type ?: $this->get_current_post_type();
1134
+
1135
  if ( \is_array( $post_type ) )
1136
  $post_type = reset( $post_type );
1137
 
1138
+ if ( ! \in_array( $post_type, $this->get_public_post_type_archives(), true ) )
1139
+ return '';
1140
 
1141
  /**
1142
  * Filters the post type archive title.
1146
  * @param string $post_type_name Post type 'name' label.
1147
  * @param string $post_type Post type.
1148
  */
1149
+ $title = \apply_filters_ref_array(
1150
+ 'post_type_archive_title',
1151
+ [
1152
+ $this->get_post_type_label( $post_type, false ),
1153
+ $post_type,
1154
+ ]
1155
+ );
1156
 
1157
  return $title;
1158
  }
1202
  * @since 3.1.0
1203
  * @since 3.1.2 Added strict taxonomical check.
1204
  * @since 3.1.3 Fixed conditional logic.
1205
+ * @since 4.2.0 Now supports the `$args['pta']` index.
1206
  * @uses $this->get_title_branding_from_query()
1207
  * @uses $this->get_title_branding_from_args()
1208
  *
1209
  * @param string $title The title. Passed by reference.
1210
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
1211
  * Leave null to autodetermine query.
1212
  */
1213
  public function merge_title_branding( &$title, $args = null ) {
1258
  * Returns the addition and seplocation from arguments.
1259
  *
1260
  * @since 3.2.2
1261
+ * @since 4.2.0 Now supports the `$args['pta']` index.
1262
  * @see $this->merge_title_branding();
1263
  *
1264
+ * @param array $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
1265
  * @return array { 'addition', 'seplocation' }
1266
  */
1267
+ protected function get_title_branding_from_args( $args ) {
1268
 
1269
+ if ( ! $args['taxonomy'] && ! $args['pta'] && $this->is_real_front_page_by_id( $args['id'] ) ) {
1270
  $addition = $this->get_home_title_additions();
1271
  $seplocation = $this->get_home_title_seplocation();
1272
  } else {
1310
  * @since 3.1.0
1311
  * @since 3.1.2 Added strict taxonomical checks for title protection.
1312
  * @since 3.1.3 Fixed conditional logic.
1313
+ * @since 4.2.0 Now supports the `$args['pta']` index.
1314
  * @see $this->merge_title_prefixes()
1315
  *
1316
  * @param string $title The title. Passed by reference.
1317
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
1318
  * Leave null to autodetermine query.
1319
  * @return void
1320
  */
1322
 
1323
  if ( null === $args ) {
1324
  $id = $this->get_the_real_ID();
1325
+ $run = $this->is_singular();
1326
  } else {
1327
  $this->fix_generation_args( $args );
1328
  $id = $args['id'];
1329
+ $run = ! $args['taxonomy'] && ! $args['pta'];
1330
  }
1331
 
1332
+ if ( $run ) return;
1333
 
1334
  $post = $id ? \get_post( $id ) : null;
1335
 
1415
  return $this->get_title_seplocation( true );
1416
  }
1417
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1418
  /**
1419
  * Determines whether to add or remove title protection prefixes.
1420
  *
1421
  * @since 3.2.4
1422
+ * @since 4.2.0 Now supports the `$args['pta']` index.
1423
  * NOTE: This does not guarantee that protection is to be added. Only that it will be considered. Bad method name.
1424
  * @see $this->merge_title_protection()
1425
  *
1426
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
1427
  * Leave null to autodetermine query.
1428
  * @return bool True when prefixes are allowed.
1429
  */
1432
  if ( null === $args ) {
1433
  $use = $this->is_singular();
1434
  } else {
1435
+ $this->fix_generation_args( $args );
1436
+ $use = $args && ! $args['taxonomy'] && ! $args['pta'];
1437
  }
1438
 
1439
  return $use;
1446
  * NOTE: This does not guarantee that pagination is to be added. Only that it will be considered. Bad method name.
1447
  * @see $this->merge_title_pagination()
1448
  *
1449
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
1450
  * Leave null to autodetermine query.
1451
  * @return bool True when additions are allowed.
1452
  */
1470
  * Determines whether to add or remove title branding additions.
1471
  *
1472
  * @since 3.1.0
1473
+ * @since 3.1.2 1. Added filter.
1474
+ * 2. Added strict taxonomical check.
1475
  * @since 3.2.2 Now differentiates from query and parameter input.
1476
  * @since 4.1.0 Added the second $social parameter.
1477
+ * @since 4.2.0 Now supports the `$args['pta']` index.
1478
  * @see $this->merge_title_branding()
1479
  * @uses $this->use_title_branding_from_query()
1480
  * @uses $this->use_title_branding_from_args()
1481
  *
1482
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
1483
  * Leave null to autodetermine query.
1484
+ * @param bool|string $social Whether the title is meant for social display.
1485
+ * Also accepts string 'og' and 'twitter' for future proofing.
1486
  * @return bool True when additions are allowed.
1487
  */
1488
  public function use_title_branding( $args = null, $social = false ) {
1489
 
1490
+ // If social, test its option first.
1491
+ $use = $social ? ! $this->get_option( 'social_title_rem_additions' ) : true;
 
 
 
1492
 
1493
+ // Reevaluate from general title settings, overriding social.
1494
  if ( $use ) {
1495
  if ( null === $args ) {
1496
  $use = $this->use_title_branding_from_query();
1504
  * @since 3.1.2
1505
  * @since 4.1.0 Added the third $social parameter.
1506
  * @param string $use Whether to use branding.
1507
+ * @param array|null $args The query arguments. Contains 'id', 'taxonomy', and 'pta'.
1508
  * Is null when query is autodetermined.
1509
  * @param bool $social Whether the title is meant for social display.
1510
  */
1511
+ return \apply_filters_ref_array(
1512
+ 'the_seo_framework_use_title_branding',
1513
+ [
1514
+ $use,
1515
+ $args,
1516
+ (bool) $social,
1517
+ ]
1518
+ );
1519
  }
1520
 
1521
  /**
1524
  * @since 3.2.2
1525
  * @since 4.0.0 Added use_taxonomical_title_branding() check.
1526
  * @since 4.0.2 Removed contemned \is_post_type_archive() check for taxonomical branding.
1527
+ * @since 4.2.0 Can now test for custom post type archive branding.
1528
  * @see $this->use_title_branding()
1529
  *
1530
  * @return bool
1537
  $use = $this->use_singular_title_branding();
1538
  } elseif ( $this->is_term_meta_capable() ) {
1539
  $use = $this->use_taxonomical_title_branding();
1540
+ } elseif ( \is_post_type_archive() ) {
1541
+ $use = $this->use_post_type_archive_title_branding();
1542
  } else {
1543
  $use = ! $this->get_option( 'title_rem_additions' );
1544
  }
1551
  *
1552
  * @since 3.2.2
1553
  * @since 4.0.0 Added use_taxonomical_title_branding() check.
1554
+ * @since 4.2.0 1. Now supports the `$args['pta']` index.
1555
+ * 2. Now tests for custom post type archive branding.
1556
  * @see $this->use_title_branding()
1557
  *
1558
+ * @param array $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
1559
  * @return bool
1560
  */
1561
+ protected function use_title_branding_from_args( $args ) {
1562
 
1563
  if ( $args['taxonomy'] ) {
1564
  $use = $this->use_taxonomical_title_branding( $args['id'] );
1565
+ } elseif ( $args['pta'] ) {
1566
+ $use = $this->use_post_type_archive_title_branding( $args['pta'] );
1567
  } else {
1568
  if ( $this->is_real_front_page_by_id( $args['id'] ) ) {
1569
  $use = $this->use_home_page_title_tagline();
1582
  * @since 4.0.5 1: Added first parameter `$term`.
1583
  * 2: Added filter.
1584
  *
1585
+ * @param \WP_Term|\WP_User|\WP_Post_Type|null $term The Term object. Leave null to autodermine query.
1586
  * @return bool
1587
  */
1588
  public function use_generated_archive_prefix( $term = null ) {
1589
 
1590
+ $term = $term ?? \get_queried_object();
1591
  $use = ! $this->get_option( 'title_rem_prefixes' );
1592
 
1593
  /**
1594
  * @since 4.0.5
1595
+ * @param string $use Whether to use branding.
1596
+ * @param \WP_Term|\WP_User|\WP_Post_Type $term The current term.
1597
  */
1598
  return \apply_filters_ref_array( 'the_seo_framework_use_archive_prefix', [ $use, $term ] );
1599
  }
1634
  return ! $this->get_term_meta_item( 'title_no_blog_name', $id ) && ! $this->get_option( 'title_rem_additions' );
1635
  }
1636
 
1637
+ /**
1638
+ * Determines whether to add the title tagline for the pta.
1639
+ *
1640
+ * @since 4.2.0
1641
+ *
1642
+ * @param string $pta The post type archive. Optional.
1643
+ * @return bool
1644
+ */
1645
+ public function use_post_type_archive_title_branding( $pta = '' ) {
1646
+ return ! $this->get_post_type_archive_meta_item( 'title_no_blog_name', $pta ) && ! $this->get_option( 'title_rem_additions' );
1647
+ }
1648
+
1649
  /**
1650
  * Returns the homepage additions (tagline) from option or bloginfo, when set.
1651
  * Memoizes the return value.
1656
  * @return string The trimmed tagline.
1657
  */
1658
  public function get_home_title_additions() {
1659
+ return memo() ?? memo(
1660
+ $this->s_title_raw(
1661
+ $this->get_option( 'homepage_title_tagline' )
1662
+ ?: $this->get_blogdescription()
1663
+ ?: ''
1664
+ )
1665
  );
1666
  }
1667
  }
inc/classes/generate-url.class.php CHANGED
@@ -38,25 +38,34 @@ class Generate_Url extends Generate_Title {
38
  * Determines if the given page has a custom canonical URL.
39
  *
40
  * @since 3.2.4
 
 
 
41
  *
42
  * @param null|array $args The canonical URL arguments, leave null to autodetermine query : {
43
- * int $id The Post, Page or Term ID to generate the URL for.
44
- * string $taxonomy The taxonomy.
45
  * }
46
  * @return bool
47
  */
48
  public function has_custom_canonical_url( $args = null ) {
49
 
50
- if ( ! $args ) {
51
  if ( $this->is_singular() ) {
52
- $has = $this->get_singular_custom_canonical_url( $this->get_the_real_ID() );
 
 
 
 
53
  } else {
54
  $has = false;
55
  }
56
  } else {
57
  $this->fix_generation_args( $args );
58
- if ( ! $args || $args['taxonomy'] ) {
59
- $has = false;
 
 
60
  } else {
61
  $has = $this->get_singular_custom_canonical_url( $args['id'] );
62
  }
@@ -74,8 +83,7 @@ class Generate_Url extends Generate_Title {
74
  * @return string The current URL.
75
  */
76
  public function get_current_canonical_url() {
77
- static $cache;
78
- return isset( $cache ) ? $cache : $cache = $this->get_canonical_url();
79
  }
80
 
81
  /**
@@ -91,11 +99,12 @@ class Generate_Url extends Generate_Title {
91
  * @return string The current permalink.
92
  */
93
  public function get_current_permalink() {
94
- static $cache;
95
- return isset( $cache ) ? $cache : $cache = $this->create_canonical_url( [
96
- 'id' => $this->get_the_real_ID(),
97
- 'taxonomy' => $this->get_current_taxonomy(),
98
- ] );
 
99
  }
100
 
101
  /**
@@ -107,11 +116,9 @@ class Generate_Url extends Generate_Title {
107
  * @return string The home URL.
108
  */
109
  public function get_homepage_permalink() {
110
- static $cache;
111
- return isset( $cache ) ? $cache : $cache = $this->create_canonical_url( [
112
- 'id' => $this->get_the_front_page_ID(),
113
- 'taxonomy' => '',
114
- ] );
115
  }
116
 
117
  /**
@@ -120,27 +127,27 @@ class Generate_Url extends Generate_Title {
120
  *
121
  * @since 3.0.0
122
  * @since 4.0.0 Now preemptively fixes the generation arguments, for easier implementation.
 
123
  * @uses $this->get_canonical_url()
124
  *
125
  * @param array $args The canonical URL arguments : {
126
  * int $id The Post, Page or Term ID to generate the URL for.
127
  * string $taxonomy The taxonomy.
 
128
  * bool $get_custom_field Whether to get custom canonical URLs from user settings.
129
  * }
130
  * @return string The canonical URL, if any.
131
  */
132
  public function create_canonical_url( $args = [] ) {
133
 
134
- $this->fix_generation_args( $args );
135
- $args = $args ?: [];
136
-
137
- $defaults = [
138
  'id' => 0,
139
  'taxonomy' => '',
 
140
  'get_custom_field' => false,
141
  ];
142
 
143
- return $this->get_canonical_url( array_merge( $defaults, $args ) );
144
  }
145
 
146
  /**
@@ -148,9 +155,10 @@ class Generate_Url extends Generate_Title {
148
  * Removes pagination if the URL isn't obtained via the query.
149
  *
150
  * @since 3.0.0
 
151
  * @see $this->create_canonical_url()
152
  *
153
- * @param array|null $args : Private variable. Use $this->create_canonical_url() instead.
154
  * @return string The canonical URL, if any.
155
  */
156
  public function get_canonical_url( $args = null ) {
@@ -167,12 +175,11 @@ class Generate_Url extends Generate_Title {
167
  if ( ! $canonical_url )
168
  return '';
169
 
170
- if ( ! $query && $args['id'] === $this->get_the_real_ID() ) {
171
  $canonical_url = $this->remove_pagination_from_url( $canonical_url );
172
- }
173
- if ( $this->matches_this_domain( $canonical_url ) ) {
174
  $canonical_url = $this->set_preferred_url_scheme( $canonical_url );
175
- }
176
 
177
  return $this->clean_canonical_url( $canonical_url );
178
  }
@@ -183,33 +190,43 @@ class Generate_Url extends Generate_Title {
183
  * @since 3.0.0
184
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
185
  * @since 4.0.0 Can now fetch custom canonical URL for terms.
 
186
  * @see $this->create_canonical_url()
187
  *
188
- * @param array $args Use $this->create_canonical_url().
189
  * @return string The canonical URL.
190
  */
191
- protected function build_canonical_url( array $args ) {
192
 
193
  $url = '';
194
 
195
  if ( $args['taxonomy'] ) {
196
- if ( $args['get_custom_field'] ) {
197
- $url = $this->get_taxonomical_custom_canonical_url( $args['id'] );
198
- }
199
- $url = $url ?: $this->get_taxonomical_canonical_url( $args['id'], $args['taxonomy'] );
 
 
 
 
 
 
 
200
  } else {
201
  if ( $this->is_static_frontpage( $args['id'] ) ) {
202
- if ( $args['get_custom_field'] ) {
203
- $url = $this->get_singular_custom_canonical_url( $args['id'] );
204
- }
205
- $url = $url ?: $this->get_home_canonical_url();
 
206
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
207
- $url = $this->get_home_canonical_url();
208
  } elseif ( $args['id'] ) {
209
- if ( $args['get_custom_field'] ) {
210
- $url = $this->get_singular_custom_canonical_url( $args['id'] );
211
- }
212
- $url = $url ?: $this->get_singular_canonical_url( $args['id'] );
 
213
  }
214
  }
215
 
@@ -221,34 +238,33 @@ class Generate_Url extends Generate_Title {
221
  *
222
  * @since 3.0.0
223
  * @since 4.0.0 Can now fetch custom canonical URL for terms.
224
- * @TODO Remove the $id passthrough requirement? Methods lower than this pass it to the query handler...
 
225
  * @see $this->get_canonical_url()
226
  *
227
  * @return string The canonical URL.
228
  */
229
  protected function generate_canonical_url() {
230
 
231
- $id = $this->get_the_real_ID();
232
- $url = '';
233
-
234
  if ( $this->is_real_front_page() ) {
235
  if ( $this->has_page_on_front() ) {
236
- $url = $this->get_singular_custom_canonical_url( $id )
237
  ?: $this->get_home_canonical_url();
238
  } else {
239
  $url = $this->get_home_canonical_url();
240
  }
241
  } elseif ( $this->is_singular() ) {
242
- $url = $this->get_singular_custom_canonical_url( $id )
243
- ?: $this->get_singular_canonical_url( $id );
244
  } elseif ( $this->is_archive() ) {
245
  if ( $this->is_term_meta_capable() ) {
246
- $url = $this->get_taxonomical_custom_canonical_url( $id )
247
- ?: $this->get_taxonomical_canonical_url( $id, $this->get_current_taxonomy() );
248
  } elseif ( \is_post_type_archive() ) {
249
- $url = $this->get_post_type_archive_canonical_url();
 
250
  } elseif ( $this->is_author() ) {
251
- $url = $this->get_author_canonical_url( $id );
252
  } elseif ( $this->is_date() ) {
253
  if ( $this->is_day() ) {
254
  $url = $this->get_date_canonical_url( \get_query_var( 'year' ), \get_query_var( 'monthnum' ), \get_query_var( 'day' ) );
@@ -262,7 +278,7 @@ class Generate_Url extends Generate_Title {
262
  $url = $this->get_search_canonical_url();
263
  }
264
 
265
- return $url;
266
  }
267
 
268
  /**
@@ -276,14 +292,11 @@ class Generate_Url extends Generate_Title {
276
  */
277
  public function clean_canonical_url( $url ) {
278
 
279
- if ( $this->pretty_permalinks ) {
280
- $url = \esc_url( $url, [ 'https', 'http' ] );
281
- } else {
282
- //= Keep the &'s more readable.
283
- $url = \esc_url_raw( $url, [ 'https', 'http' ] );
284
- }
285
 
286
- return $url;
 
287
  }
288
 
289
  /**
@@ -299,8 +312,7 @@ class Generate_Url extends Generate_Title {
299
  */
300
  public function get_home_canonical_url() {
301
 
302
- //= Prevent admin bias by passing preferred scheme.
303
- $url = \get_home_url( null, '', $this->get_preferred_scheme() );
304
 
305
  if ( ! $url ) return '';
306
 
@@ -329,15 +341,27 @@ class Generate_Url extends Generate_Title {
329
  return $url;
330
  }
331
 
 
 
 
 
 
 
 
 
 
 
 
332
  /**
333
  * Returns singular custom field's canonical URL.
334
  *
335
  * @since 3.0.0
 
336
  *
337
- * @param int $id The page ID.
338
  * @return string The custom canonical URL, if any.
339
  */
340
- public function get_singular_custom_canonical_url( $id ) {
341
  return $this->get_post_meta_item( '_genesis_canonical_uri', $id ) ?: '';
342
  }
343
 
@@ -348,74 +372,108 @@ class Generate_Url extends Generate_Title {
348
  * @since 3.1.0 Added WC Shop and WP Blog (as page) pagination integration via $this->paged().
349
  * @since 3.2.4 Removed pagination support for singular posts, as the SEO attack is now mitigated via WordPress.
350
  * @since 4.0.5 Now passes the `$id` to `is_singular_archive()`
 
 
351
  *
352
- * @param int|null $id The page ID.
353
  * @return string The custom canonical URL, if any.
354
  */
355
  public function get_singular_canonical_url( $id = null ) {
356
 
357
- $url = \wp_get_canonical_url( $id ) ?: '';
 
358
 
359
- $_page = \get_query_var( 'page', 1 ) ?: 1; // WP_Query tests isset, not empty.
360
- if ( $url && $_page !== $this->page() ) {
361
- /** @link https://core.trac.wordpress.org/ticket/37505 */
362
- $url = $this->remove_pagination_from_url( $url, $_page, false );
363
- }
364
 
365
- if ( $url && $this->is_singular_archive( $id ) ) {
366
- // Singular archives, like blog pages and shop pages, use the pagination base with paged.
367
- $url = $this->add_url_pagination( $url, $this->paged(), true );
 
 
 
 
 
 
 
 
 
 
 
368
  }
369
 
370
- return $url;
371
  }
372
 
373
  /**
374
  * Returns taxonomical custom field's canonical URL.
375
  *
376
  * @since 4.0.0
 
377
  *
378
  * @param int $term_id The term ID.
379
  * @return string The custom canonical URL, if any.
380
  */
381
- public function get_taxonomical_custom_canonical_url( $term_id ) {
382
  return $this->get_term_meta_item( 'canonical', $term_id ) ?: '';
383
  }
384
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  /**
386
  * Returns taxonomical canonical URL.
387
  * Automatically adds pagination if the ID matches the query.
388
  *
389
  * @since 3.0.0
390
- * @since 4.0.0 1. Renamed from "get_taxonomical_canonical_url" (note the typo)
391
  * 2. Now works on the admin-screens.
 
 
392
  *
393
- * @param int $term_id The term ID.
394
- * @param string $taxonomy The taxonomy.
395
  * @return string The taxonomical canonical URL, if any.
396
  */
397
- public function get_taxonomical_canonical_url( $term_id, $taxonomy ) {
398
 
399
- $term = \get_term( $term_id, $taxonomy );
400
- $link = \get_term_link( $term, $taxonomy );
 
401
 
402
- if ( \is_wp_error( $link ) )
403
- return '';
404
 
405
- if ( $term_id === $this->get_the_real_ID() ) {
406
- //= Adds pagination if ID matches query.
407
- $link = $this->add_url_pagination( $link, $this->paged(), true );
 
 
 
 
 
408
  }
409
 
410
- return $link;
411
  }
412
 
413
  /**
414
  * Returns post type archive canonical URL.
415
  *
416
  * @since 3.0.0
417
- * @since 4.0.0 : 1. Deprecated first parameter as integer. Use strings or null.
418
- * 2. Now forwards post type object calling to WordPress's function.
 
 
419
  *
420
  * @param null|string $post_type The post type archive's post type.
421
  * Leave null to use query, and allow pagination.
@@ -423,26 +481,19 @@ class Generate_Url extends Generate_Title {
423
  */
424
  public function get_post_type_archive_canonical_url( $post_type = null ) {
425
 
426
- if ( \is_int( $post_type ) ) {
427
- $this->_doing_it_wrong( __METHOD__, 'Only send strings or null in the first parameter.', '4.0.0' );
428
- $post_type = '';
429
- }
430
-
431
- $query = true;
432
-
433
- if ( null === $post_type ) {
434
- $post_type = \get_query_var( 'post_type' );
435
- $post_type = \is_array( $post_type ) ? reset( $post_type ) : $post_type;
436
-
437
- $query = false;
438
  }
439
 
440
- $link = \get_post_type_archive_link( $post_type ) ?: '';
441
 
442
- if ( $query && $link )
443
- $link = $this->add_url_pagination( $link, $this->paged(), true );
444
 
445
- return $link;
446
  }
447
 
448
  /**
@@ -450,23 +501,21 @@ class Generate_Url extends Generate_Title {
450
  * Automatically adds pagination if the ID matches the query.
451
  *
452
  * @since 3.0.0
 
 
453
  *
454
- * @param int $author_id The author ID.
455
  * @return string The author canonical URL, if any.
456
  */
457
- public function get_author_canonical_url( $author_id ) {
458
-
459
- $link = \get_author_posts_url( $author_id );
460
 
461
- if ( ! $link )
462
- return '';
463
 
464
- if ( $author_id === $this->get_the_real_ID() ) {
465
- //= Adds pagination if ID matches query.
466
- $link = $this->add_url_pagination( $link, $this->paged(), true );
467
- }
468
 
469
- return $link;
 
 
470
  }
471
 
472
  /**
@@ -514,7 +563,7 @@ class Generate_Url extends Generate_Title {
514
  }
515
 
516
  if ( $_paginate ) {
517
- //= Adds pagination if input matches query.
518
  $link = $this->add_url_pagination( $link, $this->paged(), true );
519
  }
520
 
@@ -526,9 +575,9 @@ class Generate_Url extends Generate_Title {
526
  * Automatically adds pagination if the input matches the query.
527
  *
528
  * @since 3.0.0
529
- * @since 3.1.0 : 1. The first parameter now defaults to null.
530
- * 2. The search term is now matched with the input query if not set,
531
- * instead of it being empty.
532
  *
533
  * @param string $query The search query. Mustn't be escaped.
534
  * When left empty, the current query will be used.
@@ -546,7 +595,7 @@ class Generate_Url extends Generate_Title {
546
  $link = \get_search_link( $query );
547
 
548
  if ( $_paginate ) {
549
- //= Adds pagination if input query isn't null.
550
  $link = $this->add_url_pagination( $link, $this->paged(), true );
551
  }
552
 
@@ -565,10 +614,8 @@ class Generate_Url extends Generate_Title {
565
  */
566
  public function get_preferred_scheme() {
567
 
568
- static $scheme;
569
-
570
- if ( isset( $scheme ) )
571
- return $scheme;
572
 
573
  switch ( $this->get_option( 'canonical_scheme' ) ) :
574
  case 'https':
@@ -589,9 +636,7 @@ class Generate_Url extends Generate_Title {
589
  * @since 2.8.0
590
  * @param string $scheme The current URL scheme.
591
  */
592
- $scheme = (string) \apply_filters( 'the_seo_framework_preferred_url_scheme', $scheme );
593
-
594
- return $scheme;
595
  }
596
 
597
  /**
@@ -610,7 +655,12 @@ class Generate_Url extends Generate_Title {
610
  * @return string The detected URl scheme, lowercase.
611
  */
612
  public function detect_site_url_scheme() {
613
- return strtolower( parse_url( \get_home_url(), PHP_URL_SCHEME ) ) ?: ( $this->is_ssl() ? 'https' : 'http' );
 
 
 
 
 
614
  }
615
 
616
  /**
@@ -669,6 +719,8 @@ class Generate_Url extends Generate_Title {
669
  * @since 3.0.0
670
  * @since 3.2.4 1. Now considers query arguments when using pretty permalinks.
671
  * 2. The second and third parameters are now optional.
 
 
672
  *
673
  * @param string $url The fully qualified URL.
674
  * @param int $page The page number. Should be bigger than 1 to paginate.
@@ -680,13 +732,14 @@ class Generate_Url extends Generate_Title {
680
  */
681
  public function add_url_pagination( $url, $page = null, $use_base = null ) {
682
 
683
- $_page = isset( $page ) ? $page : max( $this->paged(), $this->page() );
684
 
685
  if ( $_page < 2 )
686
  return $url;
687
 
688
- $_use_base = isset( $use_base ) ? $use_base :
689
- $this->is_archive() || $this->is_real_front_page() || $this->is_singular_archive();
 
690
 
691
  if ( $this->pretty_permalinks ) {
692
 
@@ -699,7 +752,7 @@ class Generate_Url extends Generate_Title {
699
  $base = $base ?: $GLOBALS['wp_rewrite']->pagination_base;
700
 
701
  if ( $_use_base ) {
702
- $url = \user_trailingslashit( \trailingslashit( $url ) . $base . '/' . $_page, 'paged' );
703
  } else {
704
  $url = \user_trailingslashit( \trailingslashit( $url ) . $_page, 'single_paged' );
705
  }
@@ -728,6 +781,7 @@ class Generate_Url extends Generate_Title {
728
  * 4. Now supports pretty permalinks with query parameters.
729
  * 5. Is now public.
730
  * @since 4.1.2 Now correctly reappends query when pagination isn't removed.
 
731
  *
732
  * @param string $url The fully qualified URL to remove pagination from.
733
  * @param int|null $page The page number to remove. If null, it will get number from query.
@@ -740,24 +794,22 @@ class Generate_Url extends Generate_Title {
740
  public function remove_pagination_from_url( $url, $page = null, $use_base = null ) {
741
 
742
  if ( $this->pretty_permalinks ) {
743
- // Defensive programming...
744
- static $user_slash, $base;
745
- $user_slash = isset( $user_slash ) ? $user_slash :
746
- ( $GLOBALS['wp_rewrite']->use_trailing_slashes ? '/' : '' );
747
- $base = isset( $base ) ? $base : $GLOBALS['wp_rewrite']->pagination_base;
748
 
749
- $_page = isset( $page ) ? $page : max( $this->paged(), $this->page() );
750
 
751
  if ( $_page > 1 ) {
752
  $_url = $url;
753
 
754
- $_use_base = isset( $use_base ) ? $use_base
755
- : $this->is_archive() || $this->is_real_front_page() || $this->is_singular_archive();
 
 
 
756
 
757
  if ( $_use_base ) {
758
- $find = '/' . $base . '/' . $_page . $user_slash;
759
  } else {
760
- $find = '/' . $_page . $user_slash;
761
  }
762
 
763
  $_query = parse_url( $_url, PHP_URL_QUERY );
@@ -838,9 +890,9 @@ class Generate_Url extends Generate_Title {
838
  // FIXME: Core Report: WP doesn't accept paged parameters w/ date parameters. It'll lead to the homepage.
839
  $_query = $GLOBALS['wp_query']->query;
840
  $_date = [
841
- 'y' => isset( $_query['year'] ) ? $_query['year'] : '',
842
- 'm' => isset( $_query['monthnum'] ) ? $_query['monthnum'] : '',
843
- 'd' => isset( $_query['day'] ) ? $_query['day'] : '',
844
  ];
845
 
846
  $url = \add_query_arg( [ 'm' => implode( '', $_date ) ], $home );
@@ -850,12 +902,11 @@ class Generate_Url extends Generate_Title {
850
  // Generate shortlink for object type and slug.
851
  $object = \get_queried_object();
852
 
853
- $tax = isset( $object->taxonomy ) ? $object->taxonomy : '';
854
- $slug = isset( $object->slug ) ? $object->slug : '';
855
 
856
- if ( $tax && $slug ) {
857
  $url = \add_query_arg( [ $tax => $slug ], $home );
858
- }
859
  }
860
  } elseif ( $this->is_search() ) {
861
  $url = \add_query_arg( [ 's' => \get_search_query( false ) ], $home );
@@ -918,15 +969,13 @@ class Generate_Url extends Generate_Title {
918
  */
919
  public function get_paged_urls() {
920
 
921
- static $prev, $next;
922
-
923
- if ( isset( $prev, $next ) ) goto end;
924
 
925
  $prev = $next = '';
926
 
927
  if ( $this->has_custom_canonical_url() ) goto end;
928
 
929
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
930
  if ( $this->is_singular() && ! $this->is_singular_archive() && $this->is_multipage() ) {
931
  $_run = $this->is_real_front_page()
932
  ? $this->get_option( 'prev_next_frontpage' )
@@ -948,25 +997,27 @@ class Generate_Url extends Generate_Title {
948
  } else {
949
  goto end;
950
  }
951
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
952
 
953
  // See if-statements below.
954
  if ( ! ( $page + 1 <= $_numpages || $page > 1 ) ) goto end;
955
 
956
- $canonical = $this->remove_pagination_from_url( $this->get_current_canonical_url() );
 
 
 
 
957
 
958
  // If this page is not the last, create a next-URL.
959
- if ( $page + 1 <= $_numpages ) {
960
- $next = $this->add_url_pagination( $canonical, $page + 1 );
961
- }
962
  // If this page is not the first, create a prev-URL.
963
- if ( $page > 1 ) {
964
- $prev = $this->add_url_pagination( $canonical, $page - 1 );
965
- }
966
 
967
  end:;
968
 
969
- return compact( 'next', 'prev' );
970
  }
971
 
972
  /**
@@ -975,26 +1026,26 @@ class Generate_Url extends Generate_Title {
975
  * Memoizes the return value.
976
  *
977
  * @since 2.7.0
978
- * @since 2.9.2 : 1. Now considers port too.
979
- * 2. Now uses get_home_url(), rather than get_option('home').
980
  *
981
  * @return string The home URL host.
982
  */
983
  public function get_home_host() {
984
 
985
- static $cache = null;
986
-
987
- if ( isset( $cache ) )
988
- return $cache;
989
 
990
- $parsed_url = parse_url( \get_home_url() );
 
 
991
 
992
- $host = isset( $parsed_url['host'] ) ? $parsed_url['host'] : '';
993
 
994
  if ( $host && isset( $parsed_url['port'] ) )
995
- $host .= ':' . $parsed_url['port'];
996
 
997
- return $cache = $host;
998
  }
999
 
1000
  /**
@@ -1039,9 +1090,9 @@ class Generate_Url extends Generate_Title {
1039
  public function make_fully_qualified_url( $url ) {
1040
 
1041
  if ( '//' === substr( $url, 0, 2 ) ) {
1042
- $url = 'http:' . $url;
1043
  } elseif ( 'http' !== substr( $url, 0, 4 ) ) {
1044
- $url = 'http://' . $url;
1045
  }
1046
 
1047
  return $url;
@@ -1089,7 +1140,7 @@ class Generate_Url extends Generate_Title {
1089
  $url = \add_query_arg( $results, $url );
1090
 
1091
  if ( $_fragment )
1092
- $url .= '#' . $_fragment;
1093
 
1094
  return $url;
1095
  }
@@ -1108,26 +1159,37 @@ class Generate_Url extends Generate_Title {
1108
  if ( ! $url )
1109
  return false;
1110
 
1111
- static $home_domain;
1112
-
1113
- if ( ! $home_domain ) {
1114
- $home_domain = \esc_url_raw( \get_home_url(), [ 'https', 'http' ] );
1115
- //= Simply convert to HTTPS/HTTP based on is_ssl()
1116
- $home_domain = $this->set_url_scheme( $home_domain );
1117
- }
1118
 
1119
  // Test for likely match early, before transforming.
1120
  if ( 0 === stripos( $url, $home_domain ) )
1121
  return true;
1122
 
1123
- $url = \esc_url_raw( $url, [ 'https', 'http' ] );
1124
- //= Simply convert to HTTPS/HTTP based on is_ssl()
1125
- $url = $this->set_url_scheme( $url );
 
1126
 
1127
- //= If they start with the same, we can assume it's the same domain.
1128
- if ( 0 === stripos( $url, $home_domain ) )
1129
- return true;
1130
 
1131
- return false;
 
 
 
 
 
 
 
 
 
 
 
1132
  }
1133
  }
38
  * Determines if the given page has a custom canonical URL.
39
  *
40
  * @since 3.2.4
41
+ * @since 4.2.0 1. Now also detects canonical URLs for taxonomies.
42
+ * 2. Now also detects canonical URLs for PTAs.
43
+ * 3. Now supports the `$args['pta']` index.
44
  *
45
  * @param null|array $args The canonical URL arguments, leave null to autodetermine query : {
46
+ * int $id The Post, Page or Term ID to generate the URL for.
47
+ * string $taxonomy The taxonomy.
48
  * }
49
  * @return bool
50
  */
51
  public function has_custom_canonical_url( $args = null ) {
52
 
53
+ if ( null === $args ) {
54
  if ( $this->is_singular() ) {
55
+ $has = $this->get_singular_custom_canonical_url();
56
+ } elseif ( $this->is_term_meta_capable() ) {
57
+ $has = $this->get_taxonomical_custom_canonical_url();
58
+ } elseif ( \is_post_type_archive() ) {
59
+ $has = $this->get_post_type_archive_custom_canonical_url();
60
  } else {
61
  $has = false;
62
  }
63
  } else {
64
  $this->fix_generation_args( $args );
65
+ if ( $args['taxonomy'] ) {
66
+ $has = $this->get_taxonomical_custom_canonical_url( $args['id'] );
67
+ } elseif ( $args['pta'] ) {
68
+ $has = $this->get_post_type_archive_custom_canonical_url( $args['pta'] );
69
  } else {
70
  $has = $this->get_singular_custom_canonical_url( $args['id'] );
71
  }
83
  * @return string The current URL.
84
  */
85
  public function get_current_canonical_url() {
86
+ return memo() ?? memo( $this->get_canonical_url() );
 
87
  }
88
 
89
  /**
99
  * @return string The current permalink.
100
  */
101
  public function get_current_permalink() {
102
+ return memo() ?? memo(
103
+ $this->create_canonical_url( [
104
+ 'id' => $this->get_the_real_ID(),
105
+ 'taxonomy' => $this->get_current_taxonomy(),
106
+ ] )
107
+ );
108
  }
109
 
110
  /**
116
  * @return string The home URL.
117
  */
118
  public function get_homepage_permalink() {
119
+ return memo() ?? memo(
120
+ $this->create_canonical_url( [ 'id' => $this->get_the_front_page_ID() ] )
121
+ );
 
 
122
  }
123
 
124
  /**
127
  *
128
  * @since 3.0.0
129
  * @since 4.0.0 Now preemptively fixes the generation arguments, for easier implementation.
130
+ * @since 4.2.0 Now supports the `$args['pta']` index.
131
  * @uses $this->get_canonical_url()
132
  *
133
  * @param array $args The canonical URL arguments : {
134
  * int $id The Post, Page or Term ID to generate the URL for.
135
  * string $taxonomy The taxonomy.
136
+ * string $pta The pta.
137
  * bool $get_custom_field Whether to get custom canonical URLs from user settings.
138
  * }
139
  * @return string The canonical URL, if any.
140
  */
141
  public function create_canonical_url( $args = [] ) {
142
 
143
+ $args += [
 
 
 
144
  'id' => 0,
145
  'taxonomy' => '',
146
+ 'pta' => '',
147
  'get_custom_field' => false,
148
  ];
149
 
150
+ return $this->get_canonical_url( $args );
151
  }
152
 
153
  /**
155
  * Removes pagination if the URL isn't obtained via the query.
156
  *
157
  * @since 3.0.0
158
+ * @since 4.2.0 Now supports the `$args['pta']` index.
159
  * @see $this->create_canonical_url()
160
  *
161
+ * @param array|null $args Private variable! Use `$this->create_canonical_url()` instead.
162
  * @return string The canonical URL, if any.
163
  */
164
  public function get_canonical_url( $args = null ) {
175
  if ( ! $canonical_url )
176
  return '';
177
 
178
+ if ( ! $query && $args['id'] === $this->get_the_real_ID() )
179
  $canonical_url = $this->remove_pagination_from_url( $canonical_url );
180
+
181
+ if ( $this->matches_this_domain( $canonical_url ) )
182
  $canonical_url = $this->set_preferred_url_scheme( $canonical_url );
 
183
 
184
  return $this->clean_canonical_url( $canonical_url );
185
  }
190
  * @since 3.0.0
191
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
192
  * @since 4.0.0 Can now fetch custom canonical URL for terms.
193
+ * @since 4.2.0 Now supports the `$args['pta']` index.
194
  * @see $this->create_canonical_url()
195
  *
196
+ * @param array $args Required. Use $this->create_canonical_url().
197
  * @return string The canonical URL.
198
  */
199
+ protected function build_canonical_url( $args ) {
200
 
201
  $url = '';
202
 
203
  if ( $args['taxonomy'] ) {
204
+ $url = (
205
+ $args['get_custom_field']
206
+ ? $this->get_taxonomical_custom_canonical_url( $args['id'] )
207
+ : ''
208
+ ) ?: $this->get_taxonomical_canonical_url( $args['id'], $args['taxonomy'] );
209
+ } elseif ( $args['pta'] ) {
210
+ $url = (
211
+ $args['get_custom_field']
212
+ ? $this->get_post_type_archive_custom_canonical_url( $args['pta'] )
213
+ : ''
214
+ ) ?: $this->get_post_type_archive_canonical_url( $args['pta'] );
215
  } else {
216
  if ( $this->is_static_frontpage( $args['id'] ) ) {
217
+ $url = (
218
+ $args['get_custom_field']
219
+ ? $this->get_singular_custom_canonical_url( $args['id'] )
220
+ : ''
221
+ ) ?: $this->get_raw_home_canonical_url();
222
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
223
+ $url = $this->get_raw_home_canonical_url();
224
  } elseif ( $args['id'] ) {
225
+ $url = (
226
+ $args['get_custom_field']
227
+ ? $this->get_singular_custom_canonical_url( $args['id'] )
228
+ : ''
229
+ ) ?: $this->get_singular_canonical_url( $args['id'] );
230
  }
231
  }
232
 
238
  *
239
  * @since 3.0.0
240
  * @since 4.0.0 Can now fetch custom canonical URL for terms.
241
+ * @since 4.2.0 1. No longer relies on passing through the page ID.
242
+ * 2. Can now return custom canonical URLs for post type archives.
243
  * @see $this->get_canonical_url()
244
  *
245
  * @return string The canonical URL.
246
  */
247
  protected function generate_canonical_url() {
248
 
 
 
 
249
  if ( $this->is_real_front_page() ) {
250
  if ( $this->has_page_on_front() ) {
251
+ $url = $this->get_singular_custom_canonical_url()
252
  ?: $this->get_home_canonical_url();
253
  } else {
254
  $url = $this->get_home_canonical_url();
255
  }
256
  } elseif ( $this->is_singular() ) {
257
+ $url = $this->get_singular_custom_canonical_url()
258
+ ?: $this->get_singular_canonical_url();
259
  } elseif ( $this->is_archive() ) {
260
  if ( $this->is_term_meta_capable() ) {
261
+ $url = $this->get_taxonomical_custom_canonical_url()
262
+ ?: $this->get_taxonomical_canonical_url();
263
  } elseif ( \is_post_type_archive() ) {
264
+ $url = $this->get_post_type_archive_custom_canonical_url()
265
+ ?: $this->get_post_type_archive_canonical_url();
266
  } elseif ( $this->is_author() ) {
267
+ $url = $this->get_author_canonical_url();
268
  } elseif ( $this->is_date() ) {
269
  if ( $this->is_day() ) {
270
  $url = $this->get_date_canonical_url( \get_query_var( 'year' ), \get_query_var( 'monthnum' ), \get_query_var( 'day' ) );
278
  $url = $this->get_search_canonical_url();
279
  }
280
 
281
+ return $url ?? '';
282
  }
283
 
284
  /**
292
  */
293
  public function clean_canonical_url( $url ) {
294
 
295
+ if ( $this->pretty_permalinks )
296
+ return \esc_url( $url, [ 'https', 'http' ] );
 
 
 
 
297
 
298
+ // Keep the &'s more readable when using query-parameters.
299
+ return \esc_url_raw( $url, [ 'https', 'http' ] );
300
  }
301
 
302
  /**
312
  */
313
  public function get_home_canonical_url() {
314
 
315
+ $url = $this->get_raw_home_canonical_url();
 
316
 
317
  if ( ! $url ) return '';
318
 
341
  return $url;
342
  }
343
 
344
+ /**
345
+ * Returns home canonical URL without pagination or slashing.
346
+ *
347
+ * @since 4.2.0
348
+ *
349
+ * @return string The home canonical URL without pagination.
350
+ */
351
+ public function get_raw_home_canonical_url() {
352
+ return $this->set_preferred_url_scheme( $this->get_home_url() );
353
+ }
354
+
355
  /**
356
  * Returns singular custom field's canonical URL.
357
  *
358
  * @since 3.0.0
359
+ * @since 4.2.0 The first parameter is now optional.
360
  *
361
+ * @param int|null $id The page ID.
362
  * @return string The custom canonical URL, if any.
363
  */
364
+ public function get_singular_custom_canonical_url( $id = null ) {
365
  return $this->get_post_meta_item( '_genesis_canonical_uri', $id ) ?: '';
366
  }
367
 
372
  * @since 3.1.0 Added WC Shop and WP Blog (as page) pagination integration via $this->paged().
373
  * @since 3.2.4 Removed pagination support for singular posts, as the SEO attack is now mitigated via WordPress.
374
  * @since 4.0.5 Now passes the `$id` to `is_singular_archive()`
375
+ * @since 4.2.0 1. Added memoization.
376
+ * 2. When the $id isn't set, the URL won't get tested for pagination issues.
377
  *
378
+ * @param int|null $id The page ID. Leave null to autodetermine.
379
  * @return string The custom canonical URL, if any.
380
  */
381
  public function get_singular_canonical_url( $id = null ) {
382
 
383
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
384
+ if ( null !== $memo = umemo( __METHOD__, null, $id ) ) return $memo;
385
 
386
+ $url = \wp_get_canonical_url(
387
+ $id ?? $this->get_the_real_ID()
388
+ ) ?: '';
 
 
389
 
390
+ if ( ! $url ) return '';
391
+
392
+ if ( null !== $id ) {
393
+ $_page = \get_query_var( 'page', 1 ) ?: 1;
394
+
395
+ if ( $_page > 1 && $_page !== $this->page() ) {
396
+ /** @link https://core.trac.wordpress.org/ticket/37505 */
397
+ $url = $this->remove_pagination_from_url( $url, $_page, false );
398
+ }
399
+
400
+ if ( $this->is_singular_archive( $id ) ) {
401
+ // Singular archives, like blog pages and shop pages, use the pagination base with paged.
402
+ $url = $this->add_url_pagination( $url, $this->paged(), true );
403
+ }
404
  }
405
 
406
+ return umemo( __METHOD__, $url, $id );
407
  }
408
 
409
  /**
410
  * Returns taxonomical custom field's canonical URL.
411
  *
412
  * @since 4.0.0
413
+ * @since 4.2.0 The first parameter is now optional.
414
  *
415
  * @param int $term_id The term ID.
416
  * @return string The custom canonical URL, if any.
417
  */
418
+ public function get_taxonomical_custom_canonical_url( $term_id = null ) {
419
  return $this->get_term_meta_item( 'canonical', $term_id ) ?: '';
420
  }
421
 
422
+ /**
423
+ * Returns post type archive custom field's canonical URL.
424
+ *
425
+ * @since 4.2.0
426
+ *
427
+ * @param string $pta The post type.
428
+ * @return string The custom canonical URL, if any.
429
+ */
430
+ public function get_post_type_archive_custom_canonical_url( $pta = '' ) {
431
+ return $this->get_post_type_archive_meta_item( 'canonical', $pta ) ?: '';
432
+ }
433
+
434
  /**
435
  * Returns taxonomical canonical URL.
436
  * Automatically adds pagination if the ID matches the query.
437
  *
438
  * @since 3.0.0
439
+ * @since 4.0.0 1. Renamed from "get_taxonomial_canonical_url" (note the typo)
440
  * 2. Now works on the admin-screens.
441
+ * @since 4.2.0 1. Added memoization.
442
+ * 2. The parameters are now optional.
443
  *
444
+ * @param int|null $term_id The term ID. Leave null to autodetermine.
445
+ * @param string $taxonomy The taxonomy. Leave empty to autodetermine.
446
  * @return string The taxonomical canonical URL, if any.
447
  */
448
+ public function get_taxonomical_canonical_url( $term_id = null, $taxonomy = '' ) {
449
 
450
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
451
+ if ( null !== $memo = umemo( __METHOD__, null, $term_id, $taxonomy ) )
452
+ return $memo;
453
 
454
+ $url = \get_term_link( $term_id ?: $this->get_the_real_ID(), $taxonomy );
 
455
 
456
+ if ( \is_wp_error( $url ) )
457
+ return umemo( __METHOD__, '', $term_id, $taxonomy );
458
+
459
+ if ( null !== $term_id ) {
460
+ $paged = $this->paged();
461
+
462
+ if ( $paged > 1 )
463
+ $url = $this->add_url_pagination( $url, $paged, true );
464
  }
465
 
466
+ return umemo( __METHOD__, $url, $term_id, $taxonomy );
467
  }
468
 
469
  /**
470
  * Returns post type archive canonical URL.
471
  *
472
  * @since 3.0.0
473
+ * @since 4.0.0 1. Deprecated first parameter as integer. Use strings or null.
474
+ * 2. Now forwards post type object calling to WordPress's function.
475
+ * @since 4.2.0 1. Now correctly adds pagination to the URL.
476
+ * 2. Removed argument type deprecation doing it wrong warning.
477
  *
478
  * @param null|string $post_type The post type archive's post type.
479
  * Leave null to use query, and allow pagination.
481
  */
482
  public function get_post_type_archive_canonical_url( $post_type = null ) {
483
 
484
+ if ( ! $post_type ) {
485
+ $post_type = $this->get_current_post_type();
486
+ $is_query = true;
487
+ } else {
488
+ $is_query = false;
 
 
 
 
 
 
 
489
  }
490
 
491
+ $url = \get_post_type_archive_link( $post_type ) ?: '';
492
 
493
+ if ( $is_query && $url )
494
+ $url = $this->add_url_pagination( $url, $this->paged(), true );
495
 
496
+ return $url;
497
  }
498
 
499
  /**
501
  * Automatically adds pagination if the ID matches the query.
502
  *
503
  * @since 3.0.0
504
+ * @since 4.2.0 1. The first parameter is now optional.
505
+ * 2. When the $id isn't set, the URL won't get tested for pagination issues.
506
  *
507
+ * @param int|null $id The author ID. Leave null to autodetermine.
508
  * @return string The author canonical URL, if any.
509
  */
510
+ public function get_author_canonical_url( $id = null ) {
 
 
511
 
512
+ $url = \get_author_posts_url( $id ?? $this->get_the_real_ID() );
 
513
 
514
+ if ( ! $url ) return '';
 
 
 
515
 
516
+ return null === $id
517
+ ? $this->add_url_pagination( $url, $this->paged(), true )
518
+ : $url;
519
  }
520
 
521
  /**
563
  }
564
 
565
  if ( $_paginate ) {
566
+ // Adds pagination if input matches query.
567
  $link = $this->add_url_pagination( $link, $this->paged(), true );
568
  }
569
 
575
  * Automatically adds pagination if the input matches the query.
576
  *
577
  * @since 3.0.0
578
+ * @since 3.1.0 1. The first parameter now defaults to null.
579
+ * 2. The search term is now matched with the input query if not set,
580
+ * instead of it being empty.
581
  *
582
  * @param string $query The search query. Mustn't be escaped.
583
  * When left empty, the current query will be used.
595
  $link = \get_search_link( $query );
596
 
597
  if ( $_paginate ) {
598
+ // Adds pagination if input query isn't null.
599
  $link = $this->add_url_pagination( $link, $this->paged(), true );
600
  }
601
 
614
  */
615
  public function get_preferred_scheme() {
616
 
617
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
618
+ if ( null !== $memo = memo() ) return $memo;
 
 
619
 
620
  switch ( $this->get_option( 'canonical_scheme' ) ) :
621
  case 'https':
636
  * @since 2.8.0
637
  * @param string $scheme The current URL scheme.
638
  */
639
+ return memo( (string) \apply_filters( 'the_seo_framework_preferred_url_scheme', $scheme ) );
 
 
640
  }
641
 
642
  /**
655
  * @return string The detected URl scheme, lowercase.
656
  */
657
  public function detect_site_url_scheme() {
658
+ return strtolower( parse_url(
659
+ $this->get_home_url(),
660
+ PHP_URL_SCHEME
661
+ ) ) ?: (
662
+ $this->is_ssl() ? 'https' : 'http'
663
+ );
664
  }
665
 
666
  /**
719
  * @since 3.0.0
720
  * @since 3.2.4 1. Now considers query arguments when using pretty permalinks.
721
  * 2. The second and third parameters are now optional.
722
+ * @since 4.2.0 Now properly adds pagination to search links.
723
+ * @TODO call this add_pagination_to_url()? Or call remove_pagination_from_url() remove_url_pagination()?
724
  *
725
  * @param string $url The fully qualified URL.
726
  * @param int $page The page number. Should be bigger than 1 to paginate.
732
  */
733
  public function add_url_pagination( $url, $page = null, $use_base = null ) {
734
 
735
+ $_page = $page ?? max( $this->paged(), $this->page() );
736
 
737
  if ( $_page < 2 )
738
  return $url;
739
 
740
+ $_use_base = $use_base ?? (
741
+ $this->is_real_front_page() || $this->is_archive() || $this->is_singular_archive() || $this->is_search()
742
+ );
743
 
744
  if ( $this->pretty_permalinks ) {
745
 
752
  $base = $base ?: $GLOBALS['wp_rewrite']->pagination_base;
753
 
754
  if ( $_use_base ) {
755
+ $url = \user_trailingslashit( \trailingslashit( $url ) . "$base/$_page", 'paged' );
756
  } else {
757
  $url = \user_trailingslashit( \trailingslashit( $url ) . $_page, 'single_paged' );
758
  }
781
  * 4. Now supports pretty permalinks with query parameters.
782
  * 5. Is now public.
783
  * @since 4.1.2 Now correctly reappends query when pagination isn't removed.
784
+ * @since 4.2.0 Now properly removes pagination from search links.
785
  *
786
  * @param string $url The fully qualified URL to remove pagination from.
787
  * @param int|null $page The page number to remove. If null, it will get number from query.
794
  public function remove_pagination_from_url( $url, $page = null, $use_base = null ) {
795
 
796
  if ( $this->pretty_permalinks ) {
 
 
 
 
 
797
 
798
+ $_page = $page ?? max( $this->paged(), $this->page() );
799
 
800
  if ( $_page > 1 ) {
801
  $_url = $url;
802
 
803
+ $_use_base = $use_base ?? (
804
+ $this->is_real_front_page() || $this->is_archive() || $this->is_singular_archive() || $this->is_search()
805
+ );
806
+
807
+ $user_slash = ( $GLOBALS['wp_rewrite']->use_trailing_slashes ? '/' : '' );
808
 
809
  if ( $_use_base ) {
810
+ $find = "/{$GLOBALS['wp_rewrite']->pagination_base}/{$_page}{$user_slash}";
811
  } else {
812
+ $find = "/{$_page}{$user_slash}";
813
  }
814
 
815
  $_query = parse_url( $_url, PHP_URL_QUERY );
890
  // FIXME: Core Report: WP doesn't accept paged parameters w/ date parameters. It'll lead to the homepage.
891
  $_query = $GLOBALS['wp_query']->query;
892
  $_date = [
893
+ 'y' => $_query['year'] ?? '',
894
+ 'm' => $_query['monthnum'] ?? '',
895
+ 'd' => $_query['day'] ?? '',
896
  ];
897
 
898
  $url = \add_query_arg( [ 'm' => implode( '', $_date ) ], $home );
902
  // Generate shortlink for object type and slug.
903
  $object = \get_queried_object();
904
 
905
+ $tax = $object->taxonomy ?? '';
906
+ $slug = $object->slug ?? '';
907
 
908
+ if ( $tax && $slug )
909
  $url = \add_query_arg( [ $tax => $slug ], $home );
 
910
  }
911
  } elseif ( $this->is_search() ) {
912
  $url = \add_query_arg( [ 's' => \get_search_query( false ) ], $home );
969
  */
970
  public function get_paged_urls() {
971
 
972
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
973
+ if ( null !== $memo = memo() ) return $memo;
 
974
 
975
  $prev = $next = '';
976
 
977
  if ( $this->has_custom_canonical_url() ) goto end;
978
 
 
979
  if ( $this->is_singular() && ! $this->is_singular_archive() && $this->is_multipage() ) {
980
  $_run = $this->is_real_front_page()
981
  ? $this->get_option( 'prev_next_frontpage' )
997
  } else {
998
  goto end;
999
  }
 
1000
 
1001
  // See if-statements below.
1002
  if ( ! ( $page + 1 <= $_numpages || $page > 1 ) ) goto end;
1003
 
1004
+ $canonical_url = memo( null, 'canonical' )
1005
+ ?? memo(
1006
+ $this->remove_pagination_from_url( $this->get_current_canonical_url() ),
1007
+ 'canonical'
1008
+ );
1009
 
1010
  // If this page is not the last, create a next-URL.
1011
+ if ( $page + 1 <= $_numpages )
1012
+ $next = $this->add_url_pagination( $canonical_url, $page + 1 );
1013
+
1014
  // If this page is not the first, create a prev-URL.
1015
+ if ( $page > 1 )
1016
+ $prev = $this->add_url_pagination( $canonical_url, $page - 1 );
 
1017
 
1018
  end:;
1019
 
1020
+ return memo( compact( 'next', 'prev' ) );
1021
  }
1022
 
1023
  /**
1026
  * Memoizes the return value.
1027
  *
1028
  * @since 2.7.0
1029
+ * @since 2.9.2 1. Now considers port too.
1030
+ * 2. Now uses get_home_url(), rather than get_option('home').
1031
  *
1032
  * @return string The home URL host.
1033
  */
1034
  public function get_home_host() {
1035
 
1036
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
1037
+ if ( null !== $memo = memo() ) return $memo;
 
 
1038
 
1039
+ $parsed_url = parse_url(
1040
+ $this->get_home_url()
1041
+ );
1042
 
1043
+ $host = $parsed_url['host'] ?? '';
1044
 
1045
  if ( $host && isset( $parsed_url['port'] ) )
1046
+ $host .= ":{$parsed_url['port']}";
1047
 
1048
+ return memo( $host );
1049
  }
1050
 
1051
  /**
1090
  public function make_fully_qualified_url( $url ) {
1091
 
1092
  if ( '//' === substr( $url, 0, 2 ) ) {
1093
+ $url = "http:$url";
1094
  } elseif ( 'http' !== substr( $url, 0, 4 ) ) {
1095
+ $url = "http://{$url}";
1096
  }
1097
 
1098
  return $url;
1140
  $url = \add_query_arg( $results, $url );
1141
 
1142
  if ( $_fragment )
1143
+ $url .= "#$_fragment";
1144
 
1145
  return $url;
1146
  }
1159
  if ( ! $url )
1160
  return false;
1161
 
1162
+ $home_domain = memo() ?? memo(
1163
+ $this->set_url_scheme( \esc_url_raw(
1164
+ $this->get_home_url(),
1165
+ [ 'https', 'http' ]
1166
+ ) )
1167
+ );
 
1168
 
1169
  // Test for likely match early, before transforming.
1170
  if ( 0 === stripos( $url, $home_domain ) )
1171
  return true;
1172
 
1173
+ $url = $this->set_url_scheme( \esc_url_raw(
1174
+ $url,
1175
+ [ 'https', 'http' ]
1176
+ ) );
1177
 
1178
+ // If they start with the same, we can assume it's the same domain.
1179
+ return 0 === stripos( $url, $home_domain );
1180
+ }
1181
 
1182
+ /**
1183
+ * Returns the home URL. Created for the WordPress method is slow for it
1184
+ * performs "set_url_scheme" calls slowly. We rely on this method for some
1185
+ * plugins filter `home_url`.
1186
+ * Memoized.
1187
+ *
1188
+ * @since 4.2.0
1189
+ *
1190
+ * @return string The home URL.
1191
+ */
1192
+ public function get_home_url() {
1193
+ return umemo( __METHOD__ ) ?? umemo( __METHOD__, \get_home_url() );
1194
  }
1195
  }
inc/classes/generate.class.php CHANGED
@@ -38,8 +38,11 @@ class Generate extends User_Data {
38
  * Fixes generation arguments, to prevent ID conflicts with taxonomies.
39
  *
40
  * @since 3.1.0
41
- * @since 4.1.0 1: Improved performance by testing for null first.
42
- * 2: Improved performance by testing argument keys prior array merge.
 
 
 
43
  * @internal
44
  *
45
  * @param array|int|null $args The arguments, passed by reference.
@@ -49,72 +52,72 @@ class Generate extends User_Data {
49
  if ( null === $args ) return;
50
 
51
  if ( \is_array( $args ) ) {
52
- if ( ! isset( $args['id'], $args['taxonomy'] ) ) {
53
- $args = array_merge(
54
- [
55
- 'id' => 0,
56
- 'taxonomy' => '',
57
- ],
58
- $args
59
- );
60
- }
61
  } elseif ( is_numeric( $args ) ) {
62
  $args = [
63
  'id' => (int) $args,
64
  'taxonomy' => '',
 
65
  ];
66
  } else {
67
  $args = null;
68
  }
69
  }
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  /**
72
  * Returns the `noindex`, `nofollow`, `noarchive` robots meta code array.
73
  *
74
  * @since 4.1.4
 
 
75
  *
76
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
77
- * @param null $get Reserved.
78
- * @param int <bit> $ignore The ignore level. {
79
- * 0 = 0b00: Ignore nothing.
80
- * 1 = 0b01: Ignore protection. (\The_SEO_Framework\ROBOTS_IGNORE_PROTECTION)
81
- * 2 = 0b10: Ignore post/term setting. (\The_SEO_Framework\ROBOTS_IGNORE_SETTINGS)
82
- * 3 = 0b11: Ignore protection and post/term setting.
 
 
83
  * }
84
  * @return array Only values actualized for display: {
85
  * string index : string value
86
  * }
87
  */
88
- public function generate_robots_meta( $args = null, $get = null, $ignore = 0b00 ) {
89
 
90
- if ( null === $args ) {
91
- $_meta = $this->get_robots_meta_by_query( $ignore );
92
-
93
- if ( $this->is_query_exploited() ) {
94
- $_meta['noindex'] = true;
95
- $_meta['nofollow'] = true;
96
- }
97
- } else {
98
- $this->fix_generation_args( $args );
99
- $_meta = $this->get_robots_meta_by_args( $args, $ignore );
100
- }
101
 
102
- $meta = [
103
- 'noindex' => '',
104
- 'nofollow' => '',
105
- 'noarchive' => '',
106
- 'max_snippet' => '',
107
- 'max_image_preview' => '',
108
- 'max_video_preview' => '',
109
- ];
110
 
 
111
  foreach (
112
- array_intersect_key( $_meta, array_flip( [ 'noindex', 'nofollow', 'noarchive' ] ) )
113
  as $k => $v
114
  ) $v and $meta[ $k ] = $k;
115
 
 
116
  foreach (
117
- array_intersect_key( $_meta, array_flip( [ 'max_snippet', 'max_image_preview', 'max_video_preview' ] ) )
118
  as $k => $v
119
  ) false !== $v and $meta[ $k ] = str_replace( '_', '-', $k ) . ":$v";
120
 
@@ -125,6 +128,7 @@ class Generate extends User_Data {
125
  * @since 4.0.0 Added two parameters ($args and $ignore).
126
  * @since 4.0.2 Now contains the copyright diretive values.
127
  * @since 4.0.3 Changed `$meta` key `max_snippet_length` to `max_snippet`
 
128
  *
129
  * @param array $meta The current robots meta. {
130
  * 'noindex' : 'noindex'|''
@@ -134,13 +138,13 @@ class Generate extends User_Data {
134
  * 'max_image_preview' : 'max-image-preview:<string>'|''
135
  * 'max_video_preview' : 'max-video-preview:<string>'|''
136
  * }
137
- * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
138
  * Is null when query is autodetermined.
139
- * @param int <bit> $ignore The ignore level. {
140
- * 0 = 0b00: Ignore nothing.
141
- * 1 = 0b01: Ignore protection. (\The_SEO_Framework\ROBOTS_IGNORE_PROTECTION)
142
- * 2 = 0b10: Ignore post/term setting. (\The_SEO_Framework\ROBOTS_IGNORE_SETTINGS)
143
- * 3 = 0b11: Ignore protection and post/term setting.
144
  * }
145
  */
146
  return array_filter(
@@ -149,320 +153,12 @@ class Generate extends User_Data {
149
  [
150
  $meta,
151
  $args,
152
- $ignore,
153
  ]
154
  )
155
  );
156
  }
157
 
158
- /**
159
- * Generates the `noindex`, `nofollow`, `noarchive` robots meta code array from query.
160
- *
161
- * @since 4.0.0
162
- * @since 4.0.2 Added new copyright directive tags.
163
- * @since 4.0.3 Changed `max_snippet_length` to `max_snippet`
164
- * @since 4.0.5 1. The `$post_type` test now uses a real query ID, instead of `$GLOBALS['post']`;
165
- * mitigating issues with singular-archives pages (blog, shop, etc.).
166
- * 2. Now disregards empty blog pages for automatic `noindex`; although this protection is necessary,
167
- * it can not be reflected in the SEO Bar.
168
- * @since 4.1.0 Now uses the new taxonomy robots settings.
169
- * @global \WP_Query $wp_query
170
- *
171
- * @param int <bit> $ignore The ignore level. {
172
- * 0 = 0b00: Ignore nothing.
173
- * 1 = 0b01: Ignore protection. (\The_SEO_Framework\ROBOTS_IGNORE_PROTECTION)
174
- * 2 = 0b10: Ignore post/term meta settings. (\The_SEO_Framework\ROBOTS_IGNORE_SETTINGS)
175
- * 3 = 0b11: Ignore protection and post/term meta setting.
176
- * }
177
- * @return array|null robots : {
178
- * bool 'noindex'
179
- * bool 'nofollow'
180
- * bool 'noarchive'
181
- * false|int <R>=-1<=600> 'max_snippet'
182
- * false|string 'max_image_preview'
183
- * fasle|int <R>=-1<=600> 'max_video_preview'
184
- * }
185
- */
186
- protected function get_robots_meta_by_query( $ignore = 0b00 ) {
187
-
188
- $noindex = (bool) $this->get_option( 'site_noindex' );
189
- $nofollow = (bool) $this->get_option( 'site_nofollow' );
190
- $noarchive = (bool) $this->get_option( 'site_noarchive' );
191
-
192
- $max_snippet = $max_image_preview = $max_video_preview = false;
193
-
194
- if ( $this->get_option( 'set_copyright_directives' ) ) {
195
- $max_snippet = $this->get_option( 'max_snippet_length' );
196
- $max_image_preview = $this->get_option( 'max_image_preview' );
197
- $max_video_preview = $this->get_option( 'max_video_preview' );
198
- }
199
-
200
- // Check homepage SEO settings, set noindex, nofollow and noarchive
201
- if ( $this->is_real_front_page() ) {
202
- foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r )
203
- $$r = $$r || $this->get_option( "homepage_$r" );
204
-
205
- if ( ! ( $ignore & ROBOTS_IGNORE_PROTECTION ) ) :
206
- $noindex = $noindex
207
- || ( $this->get_option( 'home_paged_noindex' ) && ( $this->page() > 1 || $this->paged() > 1 ) );
208
- endif;
209
- } else {
210
- global $wp_query;
211
-
212
- if ( $this->is_singular_archive() ) {
213
- /**
214
- * Pagination overflow protection via 404 test.
215
- *
216
- * When there are no posts, the first page will NOT relay 404;
217
- * which is exactly as intended. All other pages will relay 404.
218
- *
219
- * We do not test the post_count here, because we want to have
220
- * the first page indexable via user-intent only.
221
- */
222
- $noindex = $noindex || $this->is_404();
223
- } else {
224
- /**
225
- * Check for 404, or if archive is empty: set noindex for those.
226
- *
227
- * Don't check this on the homepage. The homepage is sacred in this regard,
228
- * because page builders and templates can and will take over.
229
- *
230
- * Don't use empty(), null is regarded as indexable.
231
- */
232
- if ( isset( $wp_query->post_count ) && ! $wp_query->post_count ) {
233
- /**
234
- * We recommend using this filter ONLY for archives that have useful content but no "posts" attached.
235
- * For example: a specially custom-developed author page for an author that never published a post.
236
- *
237
- * This filter won't run when a few other conditions for noindex have been met.
238
- *
239
- * @since 4.1.4
240
- * @link <https://github.com/sybrew/the-seo-framework/issues/194#issuecomment-864298702>
241
- * @param bool $noindex Whether to enable no posts protection.
242
- */
243
- $noindex = $noindex || \apply_filters( 'the_seo_framework_enable_noindex_no_posts', true );
244
- }
245
- }
246
-
247
- if (
248
- ! $noindex
249
- && $this->get_option( 'paged_noindex' )
250
- && ( $this->is_archive() || $this->is_singular_archive() )
251
- && $this->paged() > 1
252
- ) {
253
- $noindex = true;
254
- }
255
-
256
- if ( $this->is_archive() ) {
257
- if ( $this->is_author() ) {
258
- foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r )
259
- $$r = $$r || $this->get_option( "author_$r" );
260
- } elseif ( $this->is_date() ) {
261
- foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r )
262
- $$r = $$r || $this->get_option( "date_$r" );
263
- }
264
- } elseif ( $this->is_search() ) {
265
- foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r )
266
- $$r = $$r || $this->get_option( "search_$r" );
267
- }
268
- }
269
-
270
- if ( $this->is_archive() ) {
271
- $taxonomy = $this->get_current_taxonomy();
272
-
273
- $_post_type_meta = [];
274
- // Store values from each post type bound to the taxonomy.
275
- foreach ( $this->get_post_types_from_taxonomy() as $post_type ) {
276
- foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) {
277
- // SECURITY: Put in array to circumvent GLOBALS injection in sequenting loop.
278
- $_post_type_meta[ $r ][] = $$r || $this->is_post_type_robots_set( $r, $post_type );
279
- }
280
- }
281
- // Only enable if all post types have the value ticked.
282
- foreach ( $_post_type_meta as $r => $_values )
283
- $$r = $$r || ! \in_array( false, $_values, true );
284
-
285
- foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r )
286
- $$r = $$r || $this->is_taxonomy_robots_set( $r, $taxonomy );
287
-
288
- if ( ! ( $ignore & ROBOTS_IGNORE_SETTINGS ) ) :
289
- $term_meta = $this->get_current_term_meta();
290
-
291
- foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) {
292
- if ( isset( $term_meta[ $r ] ) ) {
293
- // Test qubit
294
- $$r = ( $$r | (int) $term_meta[ $r ] ) > .33;
295
- }
296
- }
297
- endif;
298
- } elseif ( $this->is_singular() ) {
299
- $post_type = $this->get_current_post_type();
300
-
301
- foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r )
302
- $$r = $$r || $this->is_post_type_robots_set( $r, $post_type );
303
-
304
- if ( ! ( $ignore & ROBOTS_IGNORE_SETTINGS ) ) :
305
- $post_meta = [
306
- 'noindex' => $this->get_post_meta_item( '_genesis_noindex' ),
307
- 'nofollow' => $this->get_post_meta_item( '_genesis_nofollow' ),
308
- 'noarchive' => $this->get_post_meta_item( '_genesis_noarchive' ),
309
- ];
310
-
311
- foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) {
312
- // Test qubit
313
- $$r = ( $$r | (int) $post_meta[ $r ] ) > .33;
314
- }
315
- endif;
316
-
317
- // I hate this reiteration of the very same code as above... but, homepage may not always be singular.
318
- // The conditions below MUST overwrite this, too. So, this is the perfect placement.
319
- if ( $this->is_real_front_page() ) {
320
- if ( ! ( $ignore & ROBOTS_IGNORE_PROTECTION ) ) :
321
- $noindex = $noindex
322
- || ( $this->get_option( 'home_paged_noindex' ) && ( $this->page() > 1 || $this->paged() > 1 ) );
323
- endif;
324
- }
325
-
326
- // Overwrite and ignore the user's settings, regardless; unless ignore is set.
327
- if ( ! ( $ignore & ROBOTS_IGNORE_PROTECTION ) ) :
328
- $noindex = $noindex || $this->is_protected( $this->get_the_real_ID() );
329
- endif;
330
-
331
- /**
332
- * Noindex on comment pagination.
333
- * Overwrites and ignores the user's settings, always.
334
- *
335
- * N.B. WordPress protects this query variable with options 'page_comments'
336
- * and 'default_comments_page' via `redirect_canonical()`, so we don't have to.
337
- * For reference, it fires `remove_query_arg( 'cpage', $redirect['query'] )`;
338
- */
339
- if ( (int) \get_query_var( 'cpage', 0 ) > 0 ) {
340
- /**
341
- * We do not recommend using this filter as it'll likely get those pages flagged as
342
- * duplicated by Google anyway; unless the theme strips or trims the content.
343
- *
344
- * This filter won't run when other conditions for noindex have been met.
345
- *
346
- * @since 4.0.5
347
- * @param bool $noindex Whether to enable comment pagination protection.
348
- */
349
- $noindex = $noindex || \apply_filters( 'the_seo_framework_enable_noindex_comment_pagination', true );
350
- }
351
- }
352
-
353
- return compact( 'noindex', 'nofollow', 'noarchive', 'max_snippet', 'max_image_preview', 'max_video_preview' );
354
- }
355
-
356
- /**
357
- * Generates the `noindex`, `nofollow`, `noarchive` robots meta code array from arguments.
358
- *
359
- * Note that the home-as-blog page can be used for this method.
360
- *
361
- * @since 4.0.0
362
- * @since 4.0.2 Added new copyright directive tags.
363
- * @since 4.0.3 Changed `max_snippet_length` to `max_snippet`
364
- * @since 4.1.0 Now uses the new taxonomy robots settings.
365
- *
366
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
367
- * @param int <bit> $ignore The ignore level. {
368
- * 0 = 0b00: Ignore nothing.
369
- * 1 = 0b01: Ignore protection. (\The_SEO_Framework\ROBOTS_IGNORE_PROTECTION)
370
- * 2 = 0b10: Ignore post/term meta setting. (\The_SEO_Framework\ROBOTS_IGNORE_SETTINGS)
371
- * 3 = 0b11: Ignore protection and post/term meta setting.
372
- * }
373
- * @return array|null robots : {
374
- * bool 'noindex'
375
- * bool 'nofollow'
376
- * bool 'noarchive'
377
- * false|int <R>=-1> 'max_snippet'
378
- * false|string 'max_image_preview'
379
- * fasle|int <R>=-1> 'max_video_preview'
380
- * }
381
- */
382
- protected function get_robots_meta_by_args( $args, $ignore = 0b00 ) {
383
-
384
- $noindex = (bool) $this->get_option( 'site_noindex' );
385
- $nofollow = (bool) $this->get_option( 'site_nofollow' );
386
- $noarchive = (bool) $this->get_option( 'site_noarchive' );
387
-
388
- $max_snippet = $max_image_preview = $max_video_preview = false;
389
-
390
- if ( $this->get_option( 'set_copyright_directives' ) ) {
391
- $max_snippet = $this->get_option( 'max_snippet_length' );
392
- $max_image_preview = $this->get_option( 'max_image_preview' );
393
- $max_video_preview = $this->get_option( 'max_video_preview' );
394
- }
395
-
396
- // Put outside the loop, id=0 may be used here for home-as-blog.
397
- if ( ! $args['taxonomy'] && $this->is_real_front_page_by_id( $args['id'] ) ) {
398
- $noindex = $noindex || $this->get_option( 'homepage_noindex' );
399
- $nofollow = $nofollow || $this->get_option( 'homepage_nofollow' );
400
- $noarchive = $noarchive || $this->get_option( 'homepage_noarchive' );
401
- }
402
-
403
- if ( $args['taxonomy'] ) {
404
- $term = \get_term( $args['id'], $args['taxonomy'] );
405
- /**
406
- * Check if archive is empty: set noindex for those.
407
- */
408
- if ( empty( $term->count ) )
409
- $noindex = true;
410
-
411
- $_post_type_meta = [];
412
- // Store values from each post type bound to the taxonomy.
413
- foreach ( $this->get_post_types_from_taxonomy( $args['taxonomy'] ) as $post_type ) {
414
- foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) {
415
- // SECURITY: Put in array to circumvent GLOBALS injection.
416
- $_post_type_meta[ $r ][] = $$r || $this->is_post_type_robots_set( $r, $post_type );
417
- }
418
- }
419
- // Only enable if all post types have the value ticked.
420
- foreach ( $_post_type_meta as $r => $_values ) {
421
- $$r = $$r || ! \in_array( false, $_values, true );
422
- }
423
-
424
- foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) {
425
- $$r = $$r || $this->is_taxonomy_robots_set( $r, $args['taxonomy'] );
426
- }
427
-
428
- if ( ! ( $ignore & ROBOTS_IGNORE_SETTINGS ) ) :
429
- $term_meta = $this->get_term_meta( $args['id'] );
430
-
431
- foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) {
432
- if ( isset( $term_meta[ $r ] ) ) {
433
- // Test qubit
434
- $$r = ( $$r | (int) $term_meta[ $r ] ) > .33;
435
- }
436
- }
437
- endif;
438
- } elseif ( $args['id'] ) {
439
- $post_type = \get_post_type( $args['id'] );
440
- foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) {
441
- $$r = $$r || $this->is_post_type_robots_set( $r, $post_type );
442
- }
443
-
444
- if ( ! ( $ignore & ROBOTS_IGNORE_SETTINGS ) ) :
445
- $post_meta = [
446
- 'noindex' => $this->get_post_meta_item( '_genesis_noindex', $args['id'] ),
447
- 'nofollow' => $this->get_post_meta_item( '_genesis_nofollow', $args['id'] ),
448
- 'noarchive' => $this->get_post_meta_item( '_genesis_noarchive', $args['id'] ),
449
- ];
450
-
451
- foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) {
452
- // Test qubit
453
- $$r = ( $$r | (int) $post_meta[ $r ] ) > .33;
454
- }
455
- endif;
456
-
457
- // Overwrite and ignore the user's settings, regardless; unless ignore is set.
458
- if ( ! ( $ignore & ROBOTS_IGNORE_PROTECTION ) ) :
459
- $noindex = $noindex || $this->is_protected( $args['id'] );
460
- endif;
461
- }
462
-
463
- return compact( 'noindex', 'nofollow', 'noarchive', 'max_snippet', 'max_image_preview', 'max_video_preview' );
464
- }
465
-
466
  /**
467
  * Determines if the post type has a robots value set.
468
  *
@@ -515,39 +211,63 @@ class Generate extends User_Data {
515
  * @return string The separator.
516
  */
517
  public function get_separator( $type = 'title' ) {
518
-
519
- $sep_option = $this->get_option( $type . '_separator' );
520
- $sep_list = $this->get_separator_list();
521
-
522
- return isset( $sep_list[ $sep_option ] ) ? $sep_list[ $sep_option ] : '&#x2d;';
523
  }
524
 
525
  /**
526
- * Fetches blogname (site title).
527
  * Memoizes the return value.
528
  *
 
 
529
  * @since 2.5.2
530
- * TODO add filter?
 
531
  *
532
- * @return string $blogname The escaped and sanitized blogname.
533
  */
534
  public function get_blogname() {
535
- static $blogname;
536
- return isset( $blogname ) ? $blogname : $blogname = trim( \get_bloginfo( 'name', 'display' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
537
  }
538
 
539
  /**
540
  * Fetch blog description.
541
  * Memoizes the return value.
542
  *
 
 
 
 
543
  * @since 2.5.2
544
  * @since 3.0.0 No longer returns untitled when empty, instead, it just returns an empty string.
545
  *
546
- * @return string $blogname The escaped and sanitized blog description.
547
  */
548
  public function get_blogdescription() {
549
- static $description;
550
- return isset( $description ) ? $description : $description = trim( \get_bloginfo( 'description', 'display' ) );
551
  }
552
 
553
  /**
@@ -565,18 +285,17 @@ class Generate extends User_Data {
565
  $match = \get_locale();
566
 
567
  $match_len = \strlen( $match );
568
- $valid_locales = $this->fb_locales();
569
 
570
  if ( $match_len > 5 ) {
571
  $match_len = 5;
572
- // More than standard-full locale ID is used. Make it just full.
573
  $match = substr( $match, 0, $match_len );
574
  }
575
 
576
  if ( 5 === $match_len ) {
577
- // Full locale is used.
578
-
579
- if ( \in_array( $match, $valid_locales, true ) )
580
  return $match;
581
 
582
  // Convert to only language portion.
@@ -585,17 +304,14 @@ class Generate extends User_Data {
585
  }
586
 
587
  if ( 2 === $match_len ) {
588
- // Only a language key is provided.
589
-
590
- // Find first matching key.
591
- $key = array_search( $match, $this->language_keys(), true );
592
 
593
- if ( $key ) {
594
- return $valid_locales[ $key ];
595
- }
596
  }
597
 
598
- // Return default locale.
599
  return 'en_US';
600
  }
601
 
@@ -630,24 +346,20 @@ class Generate extends User_Data {
630
  * @return string
631
  */
632
  public function get_og_type() {
633
-
634
- static $type = null;
635
-
636
- if ( isset( $type ) )
637
- return $type;
638
-
639
- /**
640
- * @since 2.3.0
641
- * @since 2.7.0 Added output within filter.
642
- * @param string $type The OG type.
643
- * @param int $id The page/term/object ID.
644
- */
645
- return $type = (string) \apply_filters_ref_array(
646
- 'the_seo_framework_ogtype_output',
647
- [
648
- $this->generate_og_type(),
649
- $this->get_the_real_ID(),
650
- ]
651
  );
652
  }
653
 
@@ -661,42 +373,33 @@ class Generate extends User_Data {
661
  */
662
  public function get_modified_time() {
663
 
664
- static $time = null;
665
-
666
- if ( isset( $time ) )
667
- return $time;
668
 
669
  $id = $this->get_the_real_ID();
670
 
671
  $post = \get_post( $id );
672
  $post_modified_gmt = $post->post_modified_gmt;
673
 
674
- if ( '0000-00-00 00:00:00' === $post_modified_gmt )
675
- return $time = '';
676
-
677
- /**
678
- * @since 2.3.0
679
- * @since 2.7.0 Added output within filter.
680
- * @param string $time The article modified time.
681
- * @param int $id The current page or term ID.
682
- */
683
- return $time = (string) \apply_filters_ref_array(
684
- 'the_seo_framework_modifiedtime_output',
685
- [
686
- $this->gmt2date( $this->get_timestamp_format(), $post_modified_gmt ),
687
- $id,
688
- ]
 
689
  );
690
  }
691
 
692
- /**
693
- * @since 3.1.0
694
- * @TODO use this
695
- * @see get_available_twitter_cards
696
- * @ignore
697
- */
698
- public function get_available_open_graph_types() { }
699
-
700
  /**
701
  * Generates the Twitter Card type.
702
  *
@@ -717,15 +420,7 @@ class Generate extends User_Data {
717
  $option = $this->get_option( 'twitter_card' );
718
 
719
  // Option is equal to found cards. Output option.
720
- if ( \in_array( $option, $available_cards, true ) ) {
721
- if ( 'summary_large_image' === $option ) {
722
- $type = 'summary_large_image';
723
- } elseif ( 'summary' === $option ) {
724
- $type = 'summary';
725
- }
726
- } else {
727
- $type = 'summary';
728
- }
729
 
730
  /**
731
  * @since 2.3.0
@@ -744,34 +439,24 @@ class Generate extends User_Data {
744
 
745
  /**
746
  * Determines which Twitter cards can be used.
747
- * Memoizes the return value.
748
  *
749
  * @since 2.9.0
750
  * @since 4.0.0 1. Now only asserts the social titles as required.
751
  * 2. Now always returns an array, instead of a boolean (false) on failure.
 
 
752
  *
753
  * @return array False when it shouldn't be used. Array of available cards otherwise.
754
  */
755
  public function get_available_twitter_cards() {
756
-
757
- static $cache = null;
758
-
759
- if ( isset( $cache ) )
760
- return $cache;
761
-
762
- if ( ! $this->get_twitter_title() ) {
763
- $retval = [];
764
- } else {
765
- $retval = [ 'summary_large_image', 'summary' ];
766
- }
767
-
768
  /**
769
  * @since 2.9.0
770
- * @param array $retval The available Twitter cards. Use empty array to invalidate Twitter card.
771
  */
772
- $retval = (array) \apply_filters( 'the_seo_framework_available_twitter_cards', $retval );
773
-
774
- return $cache = $retval ?: [];
 
775
  }
776
 
777
  /**
@@ -833,10 +518,12 @@ class Generate extends User_Data {
833
  * Returns the redirect URL, if any.
834
  *
835
  * @since 4.1.4
 
 
836
  *
837
  * @param null|array $args The redirect URL arguments, leave null to autodetermine query : {
838
- * int $id The Post, Page or Term ID to generate the URL for.
839
- * string $taxonomy The taxonomy.
840
  * }
841
  * @return string The canonical URL if found, empty string otherwise.
842
  */
@@ -849,13 +536,17 @@ class Generate extends User_Data {
849
  $url = $this->get_post_meta_item( 'redirect' ) ?: '';
850
  } elseif ( $this->is_term_meta_capable() ) {
851
  $url = $this->get_term_meta_item( 'redirect' ) ?: '';
 
 
852
  }
853
  } else {
854
  $this->fix_generation_args( $args );
855
- if ( ! $args['taxonomy'] ) {
856
- $url = $this->get_post_meta_item( 'redirect', $args['id'] ) ?: '';
857
- } else {
858
  $url = $this->get_term_meta_item( 'redirect', $args['id'] ) ?: '';
 
 
 
 
859
  }
860
  }
861
 
38
  * Fixes generation arguments, to prevent ID conflicts with taxonomies.
39
  *
40
  * @since 3.1.0
41
+ * @since 4.1.0 1. Improved performance by testing for null first.
42
+ * 2. Improved performance by testing argument keys prior array merge.
43
+ * @since 4.2.0 1. Added support for the 'pta' index.
44
+ * 2. Removed isset() check -- we now expect incomplete $args, always.
45
+ * 3. Improved performance by 60% switching from array_merge to array_union.
46
  * @internal
47
  *
48
  * @param array|int|null $args The arguments, passed by reference.
52
  if ( null === $args ) return;
53
 
54
  if ( \is_array( $args ) ) {
55
+ $args += [
56
+ 'id' => 0,
57
+ 'taxonomy' => '',
58
+ 'pta' => '',
59
+ ];
 
 
 
 
60
  } elseif ( is_numeric( $args ) ) {
61
  $args = [
62
  'id' => (int) $args,
63
  'taxonomy' => '',
64
+ 'pta' => '',
65
  ];
66
  } else {
67
  $args = null;
68
  }
69
  }
70
 
71
+ /**
72
+ * Returns an array of the collected robots meta assertions.
73
+ *
74
+ * This only works when generate_robots_meta()'s $options value was given:
75
+ * The_SEO_Framework\ROBOTS_ASSERT (0b100);
76
+ *
77
+ * @since 4.2.0
78
+ *
79
+ * @return array
80
+ */
81
+ public function retrieve_robots_meta_assertions() {
82
+ return Builders\Robots\Main::instance()->collect_assertions();
83
+ }
84
+
85
  /**
86
  * Returns the `noindex`, `nofollow`, `noarchive` robots meta code array.
87
  *
88
  * @since 4.1.4
89
+ * @since 4.2.0 1. Now offloads metadata generation to an actual generator.
90
+ * 2. Now supports the `$args['pta']` index.
91
  *
92
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
93
+ * @param null|array $get The robots types to retrieve. Leave null to get all. Set array to pick: {
94
+ * 'noindex', 'nofollow', 'noarchive', 'max_snippet', 'max_image_preview', 'max_video_preview'
95
+ * }
96
+ * @param int <bit> $options The options level. {
97
+ * 0 = 0b000: Ignore nothing. Collect no assertions. (Default front-end.)
98
+ * 1 = 0b001: Ignore protection. (\The_SEO_Framework\ROBOTS_IGNORE_PROTECTION)
99
+ * 2 = 0b010: Ignore post/term setting. (\The_SEO_Framework\ROBOTS_IGNORE_SETTINGS)
100
+ * 4 = 0b100: Collect assertions. (\The_SEO_Framework\ROBOTS_ASSERT)
101
  * }
102
  * @return array Only values actualized for display: {
103
  * string index : string value
104
  * }
105
  */
106
+ public function generate_robots_meta( $args = null, $get = null, $options = 0b00 ) {
107
 
108
+ $this->fix_generation_args( $args );
 
 
 
 
 
 
 
 
 
 
109
 
110
+ $meta = Builders\Robots\Main::instance()->set( $args, $options )->get( $get );
 
 
 
 
 
 
 
111
 
112
+ // Convert the [ 'noindex' => true ] to [ 'noindex' => 'noindex' ]
113
  foreach (
114
+ array_intersect_key( $meta, array_flip( [ 'noindex', 'nofollow', 'noarchive' ] ) )
115
  as $k => $v
116
  ) $v and $meta[ $k ] = $k;
117
 
118
+ // Convert the [ 'max_snippet' => x ] to [ 'max-snippet' => 'max-snippet:x' ]
119
  foreach (
120
+ array_intersect_key( $meta, array_flip( [ 'max_snippet', 'max_image_preview', 'max_video_preview' ] ) )
121
  as $k => $v
122
  ) false !== $v and $meta[ $k ] = str_replace( '_', '-', $k ) . ":$v";
123
 
128
  * @since 4.0.0 Added two parameters ($args and $ignore).
129
  * @since 4.0.2 Now contains the copyright diretive values.
130
  * @since 4.0.3 Changed `$meta` key `max_snippet_length` to `max_snippet`
131
+ * @since 4.2.0 Now supports the `$args['pta']` index.
132
  *
133
  * @param array $meta The current robots meta. {
134
  * 'noindex' : 'noindex'|''
138
  * 'max_image_preview' : 'max-image-preview:<string>'|''
139
  * 'max_video_preview' : 'max-video-preview:<string>'|''
140
  * }
141
+ * @param array|null $args The query arguments. Contains 'id', 'taxonomy', and 'pta'.
142
  * Is null when query is autodetermined.
143
+ * @param int <bit> $options The ignore level. {
144
+ * 0 = 0b000: Ignore nothing. Collect nothing. (Default front-end.)
145
+ * 1 = 0b001: Ignore protection. (\The_SEO_Framework\ROBOTS_IGNORE_PROTECTION)
146
+ * 2 = 0b010: Ignore post/term setting. (\The_SEO_Framework\ROBOTS_IGNORE_SETTINGS)
147
+ * 4 = 0b100: Collect assertions.
148
  * }
149
  */
150
  return array_filter(
153
  [
154
  $meta,
155
  $args,
156
+ $options,
157
  ]
158
  )
159
  );
160
  }
161
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  /**
163
  * Determines if the post type has a robots value set.
164
  *
211
  * @return string The separator.
212
  */
213
  public function get_separator( $type = 'title' ) {
214
+ return $this->get_separator_list()[ $this->get_option( $type . '_separator' ) ] ?? '&#x2d;';
 
 
 
 
215
  }
216
 
217
  /**
218
+ * Fetches public blogname (site title).
219
  * Memoizes the return value.
220
  *
221
+ * Do not consider this function safe for printing!
222
+ *
223
  * @since 2.5.2
224
+ * @since 4.2.0 1. Now listens to the new `site_title` option.
225
+ * 2. Now applies filters.
226
  *
227
+ * @return string $blogname The sanitized blogname.
228
  */
229
  public function get_blogname() {
230
+ return memo()
231
+ ?? memo( $this->get_option( 'site_title' ) ?: $this->get_filtered_raw_blogname() );
232
+ }
233
+
234
+ /**
235
+ * Fetches blogname (site title).
236
+ *
237
+ * Do not consider this function safe for printing!
238
+ *
239
+ * We use get_bloginfo( ..., 'display' ), even though it escapes needlessly, because it applies filters.
240
+ *
241
+ * @since 4.2.0
242
+ *
243
+ * @return string $blogname The sanitized blogname.
244
+ */
245
+ public function get_filtered_raw_blogname() {
246
+ /**
247
+ * @since 4.2.0
248
+ * @param string The blog name.
249
+ */
250
+ return (string) \apply_filters(
251
+ 'the_seo_framework_blog_name',
252
+ trim( \get_bloginfo( 'name', 'display' ) )
253
+ );
254
  }
255
 
256
  /**
257
  * Fetch blog description.
258
  * Memoizes the return value.
259
  *
260
+ * Do not consider this function safe for printing!
261
+ *
262
+ * We use get_bloginfo( ..., 'display' ), even though it escapes needlessly, because it applies filters.
263
+ *
264
  * @since 2.5.2
265
  * @since 3.0.0 No longer returns untitled when empty, instead, it just returns an empty string.
266
  *
267
+ * @return string $blogname The sanitized blog description.
268
  */
269
  public function get_blogdescription() {
270
+ return memo() ?? memo( trim( \get_bloginfo( 'description', 'display' ) ) );
 
271
  }
272
 
273
  /**
285
  $match = \get_locale();
286
 
287
  $match_len = \strlen( $match );
288
+ $valid_locales = $this->supported_social_locales(); // [ ll_LL => ll ]
289
 
290
  if ( $match_len > 5 ) {
291
  $match_len = 5;
292
+ // More than standard-full locale type is used. Make it just full.
293
  $match = substr( $match, 0, $match_len );
294
  }
295
 
296
  if ( 5 === $match_len ) {
297
+ // Full locale is used. See if it's valid and return it.
298
+ if ( isset( $valid_locales[ $match ] ) )
 
299
  return $match;
300
 
301
  // Convert to only language portion.
304
  }
305
 
306
  if ( 2 === $match_len ) {
307
+ // Only two letters of the lang are provided. Find first match and return it.
308
+ $key = array_search( $match, $valid_locales, true );
 
 
309
 
310
+ if ( $key )
311
+ return $key;
 
312
  }
313
 
314
+ // Return default WordPress locale.
315
  return 'en_US';
316
  }
317
 
346
  * @return string
347
  */
348
  public function get_og_type() {
349
+ return memo() ?? memo(
350
+ /**
351
+ * @since 2.3.0
352
+ * @since 2.7.0 Added output within filter.
353
+ * @param string $type The OG type.
354
+ * @param int $id The page/term/object ID.
355
+ */
356
+ (string) \apply_filters_ref_array(
357
+ 'the_seo_framework_ogtype_output',
358
+ [
359
+ $this->generate_og_type(),
360
+ $this->get_the_real_ID(),
361
+ ]
362
+ )
 
 
 
 
363
  );
364
  }
365
 
373
  */
374
  public function get_modified_time() {
375
 
376
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
377
+ if ( null !== $memo = memo() ) return $memo;
 
 
378
 
379
  $id = $this->get_the_real_ID();
380
 
381
  $post = \get_post( $id );
382
  $post_modified_gmt = $post->post_modified_gmt;
383
 
384
+ return memo(
385
+ '0000-00-00 00:00:00' === $post_modified_gmt
386
+ ? ''
387
+ /**
388
+ * @since 2.3.0
389
+ * @since 2.7.0 Added output within filter.
390
+ * @param string $time The article modified time.
391
+ * @param int $id The current page or term ID.
392
+ */
393
+ : (string) \apply_filters_ref_array(
394
+ 'the_seo_framework_modifiedtime_output',
395
+ [
396
+ $this->gmt2date( $this->get_timestamp_format(), $post_modified_gmt ),
397
+ $id,
398
+ ]
399
+ )
400
  );
401
  }
402
 
 
 
 
 
 
 
 
 
403
  /**
404
  * Generates the Twitter Card type.
405
  *
420
  $option = $this->get_option( 'twitter_card' );
421
 
422
  // Option is equal to found cards. Output option.
423
+ $type = \in_array( $option, $available_cards, true ) ? $option : 'summary';
 
 
 
 
 
 
 
 
424
 
425
  /**
426
  * @since 2.3.0
439
 
440
  /**
441
  * Determines which Twitter cards can be used.
 
442
  *
443
  * @since 2.9.0
444
  * @since 4.0.0 1. Now only asserts the social titles as required.
445
  * 2. Now always returns an array, instead of a boolean (false) on failure.
446
+ * @since 4.2.0 1. No longer memoizes the return value.
447
+ * 2. No longer tests for the Twitter title.
448
  *
449
  * @return array False when it shouldn't be used. Array of available cards otherwise.
450
  */
451
  public function get_available_twitter_cards() {
 
 
 
 
 
 
 
 
 
 
 
 
452
  /**
453
  * @since 2.9.0
454
+ * @param array $cards The available Twitter cards. Use empty array to invalidate Twitter card.
455
  */
456
+ return (array) \apply_filters(
457
+ 'the_seo_framework_available_twitter_cards',
458
+ [ 'summary_large_image', 'summary' ]
459
+ );
460
  }
461
 
462
  /**
518
  * Returns the redirect URL, if any.
519
  *
520
  * @since 4.1.4
521
+ * @since 4.2.0 1. Now supports the `$args['pta']` index.
522
+ * 2. Now redirects post type archives.
523
  *
524
  * @param null|array $args The redirect URL arguments, leave null to autodetermine query : {
525
+ * int $id The Post, Page or Term ID to generate the URL for.
526
+ * string $taxonomy The taxonomy.
527
  * }
528
  * @return string The canonical URL if found, empty string otherwise.
529
  */
536
  $url = $this->get_post_meta_item( 'redirect' ) ?: '';
537
  } elseif ( $this->is_term_meta_capable() ) {
538
  $url = $this->get_term_meta_item( 'redirect' ) ?: '';
539
+ } elseif ( \is_post_type_archive() ) {
540
+ $url = $this->get_post_type_archive_meta_item( 'redirect' ) ?: '';
541
  }
542
  } else {
543
  $this->fix_generation_args( $args );
544
+ if ( $args['taxonomy'] ) {
 
 
545
  $url = $this->get_term_meta_item( 'redirect', $args['id'] ) ?: '';
546
+ } elseif ( $args['pta'] ) {
547
+ $url = $this->get_post_type_archive_meta_item( 'redirect', $args['pta'] ) ?: '';
548
+ } else {
549
+ $url = $this->get_post_meta_item( 'redirect', $args['id'] ) ?: '';
550
  }
551
  }
552
 
inc/classes/index.php CHANGED
@@ -4,16 +4,6 @@
4
  *
5
  * # Namespace: The_SEO_Framework
6
  *
7
- * ## Separated:
8
- * - Deprecated
9
- * |-> Final
10
- * - Debug
11
- * |-> Final
12
- *
13
- * ## Failsafe:
14
- * - Silencer
15
- * |-> Final
16
- *
17
  * ## Façade (bottom is called first):
18
  * - | Core
19
  * | Query
@@ -37,5 +27,5 @@
37
  * | Cache
38
  * | Load
39
  * |-> Final
40
- * |-> Instanced in function `the_seo_framework()`
41
  */
4
  *
5
  * # Namespace: The_SEO_Framework
6
  *
 
 
 
 
 
 
 
 
 
 
7
  * ## Façade (bottom is called first):
8
  * - | Core
9
  * | Query
27
  * | Cache
28
  * | Load
29
  * |-> Final
30
+ * |-> Instanced in function `tsf()`|`the_seo_framework()`|`The_SEO_Framework\_init_tsf()`
31
  */
inc/classes/init.class.php CHANGED
@@ -106,19 +106,20 @@ class Init extends Query {
106
  * @since 2.8.0
107
  * @since 4.1.2 1. Added hook for sitemap prerender.
108
  * 2. Added hook for ping retry.
109
- * TODO make protected?
 
110
  */
111
- public function init_cron_actions() {
112
  // Init post update/delete caching actions which may occur during cronjobs.
113
  $this->init_post_cache_actions();
114
 
115
  // Ping searchengines.
116
  if ( $this->get_option( 'ping_use_cron' ) ) {
117
  if ( $this->get_option( 'sitemaps_output' ) && $this->get_option( 'ping_use_cron_prerender' ) )
118
- \add_action( 'tsf_sitemap_cron_hook_before', [ new Builders\Sitemap_Base, 'prerender_sitemap' ] );
119
 
120
- \add_action( 'tsf_sitemap_cron_hook', Bridges\Ping::class . '::ping_search_engines' );
121
- \add_action( 'tsf_sitemap_cron_hook_retry', Bridges\Ping::class . '::retry_ping_search_engines' );
122
  }
123
  }
124
 
@@ -130,16 +131,16 @@ class Init extends Query {
130
  protected function init_ajax_actions() {
131
 
132
  // Admin AJAX for notice dismissal.
133
- \add_action( 'wp_ajax_tsf_dismiss_notice', '\The_SEO_Framework\Bridges\AJAX::_wp_ajax_dismiss_notice' );
134
 
135
  // Admin AJAX for TSF Cropper
136
- \add_action( 'wp_ajax_tsf_crop_image', '\The_SEO_Framework\Bridges\AJAX::_wp_ajax_crop_image' );
137
 
138
  // Admin AJAX for counter options.
139
- \add_action( 'wp_ajax_tsf_update_counter', '\The_SEO_Framework\Bridges\AJAX::_wp_ajax_update_counter_type' );
140
 
141
  // Admin AJAX for Gutenberg SEO Bar update.
142
- \add_action( 'wp_ajax_tsf_update_post_data', '\The_SEO_Framework\Bridges\AJAX::_wp_ajax_get_post_data' );
143
  }
144
 
145
  /**
@@ -155,14 +156,14 @@ class Init extends Query {
155
  */
156
  \do_action( 'the_seo_framework_admin_init' );
157
 
158
- //= Initialize caching actions.
159
  $this->init_admin_caching_actions();
160
 
161
  if ( ! $this->is_headless['meta'] ) {
162
- //= Initialize term meta filters and actions.
163
  $this->init_term_meta();
164
 
165
- //= Initialize term meta filters and actions.
166
  $this->init_post_meta();
167
 
168
  // Enqueue Post meta boxes.
@@ -176,15 +177,15 @@ class Init extends Query {
176
 
177
  // Initialize the SEO Bar for tables.
178
  \add_action( 'admin_init', [ $this, '_init_seo_bar_tables' ] );
 
 
 
179
  }
180
 
181
  if ( ! $this->is_headless['settings'] ) {
182
  // Set up site settings and allow saving resetting them.
183
  \add_action( 'admin_init', [ $this, 'register_settings' ], 5 );
184
 
185
- // Initialize List Edit for tables.
186
- \add_action( 'admin_init', [ $this, '_init_list_edit' ] );
187
-
188
  // Loads setting notices.
189
  \add_action( 'the_seo_framework_setting_notices', [ $this, '_do_settings_page_notices' ] );
190
 
@@ -193,7 +194,7 @@ class Init extends Query {
193
  }
194
 
195
  if ( ! $this->is_headless['user'] ) {
196
- //= Initialize user meta filters and actions.
197
  $this->init_user_meta();
198
 
199
  // Enqueue user meta output.
@@ -212,8 +213,18 @@ class Init extends Query {
212
  }
213
 
214
  // Add plugin links to the plugin activation page.
215
- \add_filter( 'plugin_action_links_' . THE_SEO_FRAMEWORK_PLUGIN_BASENAME, '\The_SEO_Framework\Bridges\PluginTable::_add_plugin_action_links', 10, 2 );
216
- \add_filter( 'plugin_row_meta', '\The_SEO_Framework\Bridges\PluginTable::_add_plugin_row_meta', 10, 2 );
 
 
 
 
 
 
 
 
 
 
217
 
218
  /**
219
  * @since 2.9.4
@@ -253,7 +264,6 @@ class Init extends Query {
253
 
254
  // Prepares sitemap or stylesheet output.
255
  if ( $this->can_run_sitemap() ) {
256
- // We can use action `set_404` when we support WP 5.5+...?
257
  \add_action( 'template_redirect', [ $this, '_init_sitemap' ], 1 );
258
  \add_filter( 'wp_sitemaps_enabled', '__return_false' );
259
  } else {
@@ -267,6 +277,9 @@ class Init extends Query {
267
  // Prepares requisite robots headers to avoid low-quality content penalties.
268
  $this->prepare_robots_headers();
269
 
 
 
 
270
  // Output meta tags.
271
  \add_action( 'wp_head', [ $this, 'html_output' ], 1 );
272
 
@@ -311,17 +324,20 @@ class Init extends Query {
311
 
312
  // New WordPress 4.4.0 filter. Hurray! It's also much faster :)
313
  \add_filter( 'pre_get_document_title', [ $this, 'get_document_title' ], 10 );
314
- // Override WooThemes Title TODO move this to wc compat file.
315
- \add_filter( 'woo_title', [ $this, 'get_document_title' ], 99 );
316
 
317
  /**
318
  * @since 2.4.1
319
  * @param bool $overwrite_titles Whether to enable legacy title overwriting.
 
 
 
320
  */
321
  if ( \apply_filters( 'the_seo_framework_manipulate_title', true ) ) {
322
  \remove_all_filters( 'wp_title', false );
323
  // Override WordPress Title
324
  \add_filter( 'wp_title', [ $this, 'get_wp_title' ], 9 );
 
 
325
  }
326
  }
327
 
@@ -331,6 +347,8 @@ class Init extends Query {
331
  */
332
  if ( \apply_filters( 'the_seo_framework_kill_core_robots', true ) ) {
333
  \remove_filter( 'wp_robots', 'wp_robots_max_image_preview_large' );
 
 
334
  }
335
 
336
  if ( $this->get_option( 'og_tags' ) ) { // independent from filter at use_og_tags--let that be deciding later.
@@ -364,35 +382,89 @@ class Init extends Query {
364
  }
365
 
366
  /**
367
- * Runs header actions.
368
- *
369
- * @since 3.1.0
370
  *
371
- * @param string $location Either 'before' or 'after'.
372
- * @return string The filter output.
 
373
  */
374
- public function get_legacy_header_filters_output( $location = 'before' ) {
375
-
376
- $output = '';
 
 
 
 
 
 
 
 
 
 
377
 
378
  /**
379
  * @since 2.2.6
 
380
  * @param array $functions {
381
  * 'callback' => string|array The function to call.
382
  * 'args' => scalar|array Arguments. When array, each key is a new argument.
383
  * }
384
  */
385
- $functions = (array) \apply_filters( "the_seo_framework_{$location}_output", [] );
 
 
 
 
 
386
 
387
  foreach ( $functions as $function ) {
388
- if ( ! empty( $function['callback'] ) ) {
389
- $args = isset( $function['args'] ) ? $function['args'] : '';
 
 
 
390
 
391
- $output .= \call_user_func_array( $function['callback'], (array) $args );
392
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
  }
394
 
395
- return $output;
 
 
 
 
 
 
 
 
 
 
 
396
  }
397
 
398
  /**
@@ -406,43 +478,40 @@ class Init extends Query {
406
  * @since 4.0.0 Now no longer outputs anything on Customizer.
407
  * @since 4.0.4 1. Now sets timezone to UTC to fix WP 5.3 bug <https://core.trac.wordpress.org/ticket/48623>
408
  * 2. Now always sets timezone regardless of settings, because, again, bug.
 
409
  * @access private
410
  */
411
  public function html_output() {
412
 
413
  if ( $this->is_preview() || $this->is_customize_preview() || ! $this->query_supports_seo() ) return;
414
 
415
- /**
416
- * We added this filter a second time, for this method is conditional (see two lines above).
417
- * When the query doesn't support TSF's SEO, we want default behavior to ensue.
418
- *
419
- * @since 4.1.4
420
- * @param bool $kill_core_robots Whether you feel sympathy for rocks tricked to think.
421
- */
422
- if ( \apply_filters( 'the_seo_framework_kill_core_robots', true ) ) {
423
- \remove_filter( 'wp_robots', 'wp_robots_noindex_search' );
424
- }
425
-
426
  /**
427
  * @since 2.6.0
428
  */
429
  \do_action( 'the_seo_framework_do_before_output' );
430
 
431
  /**
432
- * Start the timer here. I know it doesn't calculate the initiation of
433
- * the plugin, but it will make the code smelly if I were to do so.
434
- * A static array cache counter function would make it possible, but meh.
435
- * This function presumably takes the most time anyway.
 
 
 
 
436
  */
437
  $init_start = microtime( true );
438
 
439
  // phpcs:disable, WordPress.Security.EscapeOutput -- Output is escaped.
440
- // phpcs:ignore Squiz.WhiteSpace.LanguageConstructSpacing -- We're fancy here.
441
  echo PHP_EOL, $this->get_plugin_indicator( 'before' );
442
 
443
  $this->do_meta_output();
444
 
445
- echo $this->get_plugin_indicator( 'after', $init_start ), PHP_EOL;
 
 
 
 
446
  // phpcs:enable, WordPress.Security.EscapeOutput
447
 
448
  /**
@@ -455,23 +524,17 @@ class Init extends Query {
455
  * Outputs all meta tags for the current query.
456
  *
457
  * @since 4.1.4
 
 
458
  */
459
  public function do_meta_output() {
460
 
461
- // phpcs:disable, WordPress.Security.EscapeOutput -- Everything we produce is escaped.
462
-
463
- $get = [ 'robots' ];
464
-
465
- /** @since 4.0.4 Added as WP 5.3 patch. */
466
- $this->set_timezone( 'UTC' );
467
-
468
  /**
469
- * @since 2.6.0
470
- * @param string $before The content before the SEO output.
471
  */
472
- echo \apply_filters( 'the_seo_framework_pre', '' );
473
 
474
- echo $this->get_legacy_header_filters_output( 'before' );
475
 
476
  // Limit processing and redundant tags on 404 and search.
477
  if ( $this->is_search() ) :
@@ -546,34 +609,26 @@ class Init extends Query {
546
  );
547
  endif;
548
 
549
- // TODO add filter? It won't last a few major updates though...
550
  // But that's why I created this method like so... anyway... tough luck.
551
- foreach ( $get as $method )
552
- echo $this->{$method}();
553
-
554
- echo $this->get_legacy_header_filters_output( 'after' );
555
 
556
  /**
557
- * @since 2.6.0
558
- * @param string $after The content after the SEO output.
559
  */
560
- echo \apply_filters( 'the_seo_framework_pro', '' );
561
-
562
- /** @since 4.0.4 Added as WP 5.3 patch. */
563
- $this->reset_timezone();
564
-
565
- // phpcs:enable, WordPress.Security.EscapeOutput
566
  }
567
 
568
  /**
569
  * Redirects singular page to an alternate URL.
570
  *
571
  * @since 2.9.0
572
- * @since 3.1.0 : 1. Now no longer redirects on preview.
573
- * 2. Now listens to post type settings.
574
- * @since 4.0.0 : 1. No longer tries to redirect on "search".
575
- * 2. Added term redirect support.
576
- * 3. No longer redirects on Customizer.
577
  * @access private
578
  *
579
  * @return void early on non-singular pages.
@@ -590,6 +645,7 @@ class Init extends Query {
590
  * @param string $url The URL we're redirecting to.
591
  */
592
  \do_action( 'the_seo_framework_before_redirect', $url );
 
593
  $this->do_redirect( $url );
594
  }
595
  }
@@ -609,7 +665,7 @@ class Init extends Query {
609
  return;
610
  }
611
 
612
- //= All WP defined protocols are allowed.
613
  $url = \esc_url_raw( $url );
614
 
615
  if ( empty( $url ) ) {
@@ -627,7 +683,7 @@ class Init extends Query {
627
  $this->_doing_it_wrong( __METHOD__, 'You should use 3xx HTTP Status Codes. Recommended 301 and 302.', '2.8.0' );
628
 
629
  if ( ! $this->allow_external_redirect() ) {
630
- //= Only HTTP/HTTPS and home URLs are allowed.
631
  $path = $this->set_url_scheme( $url, 'relative' );
632
  $url = \trailingslashit( $this->get_home_host() ) . ltrim( $path, ' /' );
633
 
@@ -660,11 +716,9 @@ class Init extends Query {
660
  * @access private
661
  */
662
  public function _init_core_sitemap() {
663
- // It's not a bridge, don't treat it like one: Submit hooks here. Clean me up?
664
- $builder_class = Builders\CoreSitemaps\Main::class;
665
-
666
- \add_filter( 'wp_sitemaps_add_provider', "{$builder_class}::_filter_add_provider", 9, 2 );
667
- \add_filter( 'wp_sitemaps_max_urls', "{$builder_class}::_filter_max_urls", 9 );
668
  }
669
 
670
  /**
@@ -689,10 +743,10 @@ class Init extends Query {
689
  *
690
  * @since 2.2.9
691
  * @since 2.9.3 Casts $public to string for check.
692
- * @since 4.0.5 : 1. The output is now filterable.
693
- * 2. Improved invalid location test.
694
- * 3. No longer shortcircuits on non-public sites.
695
- * 4. Now marked as private. Will be renamed to `_robots_txt()` in the future.
696
  * @since 4.1.0 Now adds the WordPress Core sitemap URL.
697
  * @since 4.1.2 Now only adds the WP Core sitemap URL when the provider tells us it's enabled.
698
  * @since 4.1.4 Removed object caching support.
@@ -726,9 +780,8 @@ class Init extends Query {
726
  * @since 2.5.0
727
  * @param bool $disallow Whether to disallow robots queries.
728
  */
729
- if ( \apply_filters( 'the_seo_framework_robots_disallow_queries', false ) ) {
730
  $output .= "Disallow: /*?*\r\n";
731
- }
732
 
733
  /**
734
  * @since 2.5.0
@@ -741,11 +794,11 @@ class Init extends Query {
741
  if ( $this->get_option( 'sitemaps_robots' ) ) {
742
  if ( $this->get_option( 'sitemaps_output' ) ) {
743
  $sitemaps = Bridges\Sitemap::get_instance();
744
- foreach ( $sitemaps->get_sitemap_endpoint_list() as $id => $data ) {
745
- if ( ! empty( $data['robots'] ) ) {
 
746
  $output .= sprintf( "\r\nSitemap: %s", \esc_url( $sitemaps->get_expected_sitemap_endpoint_url( $id ) ) );
747
- }
748
- }
749
  $output .= "\r\n";
750
  } elseif ( ! $this->detect_sitemap_plugin() ) { // detect_sitemap_plugin() temp backward compat.
751
  if ( $this->use_core_sitemaps() ) {
@@ -771,7 +824,7 @@ class Init extends Query {
771
  '# This is an invalid robots.txt location.',
772
  '# Please visit: ' . \esc_url( \trailingslashit( $this->set_preferred_url_scheme( $this->get_home_host() ) ) . 'robots.txt' )
773
  );
774
- $output = $error . $output;
775
  }
776
 
777
  /**
@@ -896,8 +949,9 @@ class Init extends Query {
896
  $post__not_in = $wp_query->get( 'post__not_in' );
897
 
898
  if ( ! empty( $post__not_in ) ) {
899
- $excluded = array_merge( (array) $post__not_in, $excluded );
900
- $excluded = array_unique( $excluded );
 
901
  }
902
 
903
  $wp_query->set( 'post__not_in', $excluded );
@@ -929,8 +983,9 @@ class Init extends Query {
929
  $post__not_in = $wp_query->get( 'post__not_in' );
930
 
931
  if ( ! empty( $post__not_in ) ) {
932
- $excluded = array_merge( (array) $post__not_in, $excluded );
933
- $excluded = array_unique( $excluded );
 
934
  }
935
 
936
  $wp_query->set( 'post__not_in', $excluded );
@@ -957,7 +1012,7 @@ class Init extends Query {
957
  if ( $this->get_post_meta_item( 'exclude_local_search', $post->ID ) )
958
  unset( $posts[ $n ] );
959
  }
960
- //= Reset numeric index.
961
  $posts = array_values( $posts );
962
  }
963
 
@@ -984,7 +1039,7 @@ class Init extends Query {
984
  if ( $this->get_post_meta_item( 'exclude_from_archive', $post->ID ) )
985
  unset( $posts[ $n ] );
986
  }
987
- //= Reset numeric index.
988
  $posts = array_values( $posts );
989
  }
990
 
@@ -1010,6 +1065,7 @@ class Init extends Query {
1010
  * @since 4.1.4 1. Renamed from `is_archive_query_adjustment_blocked()`
1011
  * 2. Added taxonomy-supported lookups.
1012
  * 3. Added WP Rest checks for the Block Editor.
 
1013
  *
1014
  * @param \WP_Query $wp_query WP_Query object.
1015
  * @return bool
@@ -1018,9 +1074,9 @@ class Init extends Query {
1018
 
1019
  static $has_filter = null;
1020
 
1021
- if ( null === $has_filter ) {
1022
  $has_filter = \has_filter( 'the_seo_framework_do_adjust_archive_query' );
1023
- }
1024
  if ( $has_filter ) {
1025
  /**
1026
  * This filter affects both 'search-"archives"' and terms/taxonomies.
@@ -1051,15 +1107,17 @@ class Init extends Query {
1051
 
1052
  // This primarily affects 'terms'.
1053
  if ( ! empty( $wp_query->tax_query->queries ) ) :
1054
- $unsupported = [];
1055
 
1056
  foreach ( $wp_query->tax_query->queries as $_query ) {
1057
- if ( isset( $_query['taxonomy'] ) )
1058
- $unsupported[] = ! $this->is_taxonomy_supported( $_query['taxonomy'] );
 
 
 
1059
  }
1060
 
1061
- // Only block when taxonomies were found and all of them are unsupported.
1062
- if ( $unsupported && ! \in_array( false, $unsupported, true ) )
1063
  return true;
1064
  endif;
1065
 
@@ -1084,21 +1142,13 @@ class Init extends Query {
1084
  public function _alter_oembed_response_data( $data = [], $post = null, $width = 0, $height = 0 ) {
1085
 
1086
  // Don't use cache. See @WARNING in doc comment.
1087
- if ( $this->get_option( 'oembed_use_og_title', false ) ) {
1088
- $data['title'] = $this->get_open_graph_title(
1089
- [
1090
- 'id' => $post->ID,
1091
- 'taxonomy' => '',
1092
- ]
1093
- ) ?: $data['title'];
1094
- }
1095
  // Don't use cache. See @WARNING in doc comment.
1096
  if ( $this->get_option( 'oembed_use_social_image', false ) ) {
1097
  $image_details = current( $this->get_image_details(
1098
- [
1099
- 'id' => $post->ID,
1100
- 'taxonomy' => '',
1101
- ],
1102
  true,
1103
  'oembed',
1104
  true
@@ -1111,10 +1161,10 @@ class Init extends Query {
1111
  $data['thumbnail_height'] = $image_details['height'];
1112
  }
1113
  }
 
1114
  // Don't use cache. See @WARNING in doc comment.
1115
- if ( $this->get_option( 'oembed_remove_author', false ) ) {
1116
  unset( $data['author_url'], $data['author_name'] );
1117
- }
1118
 
1119
  return $data;
1120
  }
106
  * @since 2.8.0
107
  * @since 4.1.2 1. Added hook for sitemap prerender.
108
  * 2. Added hook for ping retry.
109
+ * @since 4.2.0 Is now protexted
110
+ * @access protected
111
  */
112
+ protected function init_cron_actions() {
113
  // Init post update/delete caching actions which may occur during cronjobs.
114
  $this->init_post_cache_actions();
115
 
116
  // Ping searchengines.
117
  if ( $this->get_option( 'ping_use_cron' ) ) {
118
  if ( $this->get_option( 'sitemaps_output' ) && $this->get_option( 'ping_use_cron_prerender' ) )
119
+ \add_action( 'tsf_sitemap_cron_hook_before', [ new Builders\Sitemap\Base, 'prerender_sitemap' ] );
120
 
121
+ \add_action( 'tsf_sitemap_cron_hook', [ Bridges\Ping::class, 'ping_search_engines' ] );
122
+ \add_action( 'tsf_sitemap_cron_hook_retry', [ Bridges\Ping::class, 'retry_ping_search_engines' ] );
123
  }
124
  }
125
 
131
  protected function init_ajax_actions() {
132
 
133
  // Admin AJAX for notice dismissal.
134
+ \add_action( 'wp_ajax_tsf_dismiss_notice', [ Bridges\AJAX::class, '_wp_ajax_dismiss_notice' ] );
135
 
136
  // Admin AJAX for TSF Cropper
137
+ \add_action( 'wp_ajax_tsf_crop_image', [ Bridges\AJAX::class, '_wp_ajax_crop_image' ] );
138
 
139
  // Admin AJAX for counter options.
140
+ \add_action( 'wp_ajax_tsf_update_counter', [ Bridges\AJAX::class, '_wp_ajax_update_counter_type' ] );
141
 
142
  // Admin AJAX for Gutenberg SEO Bar update.
143
+ \add_action( 'wp_ajax_tsf_update_post_data', [ Bridges\AJAX::class, '_wp_ajax_get_post_data' ] );
144
  }
145
 
146
  /**
156
  */
157
  \do_action( 'the_seo_framework_admin_init' );
158
 
159
+ // Initialize caching actions.
160
  $this->init_admin_caching_actions();
161
 
162
  if ( ! $this->is_headless['meta'] ) {
163
+ // Initialize term meta filters and actions.
164
  $this->init_term_meta();
165
 
166
+ // Initialize term meta filters and actions.
167
  $this->init_post_meta();
168
 
169
  // Enqueue Post meta boxes.
177
 
178
  // Initialize the SEO Bar for tables.
179
  \add_action( 'admin_init', [ $this, '_init_seo_bar_tables' ] );
180
+
181
+ // Initialize List Edit for tables.
182
+ \add_action( 'admin_init', [ $this, '_init_list_edit' ] );
183
  }
184
 
185
  if ( ! $this->is_headless['settings'] ) {
186
  // Set up site settings and allow saving resetting them.
187
  \add_action( 'admin_init', [ $this, 'register_settings' ], 5 );
188
 
 
 
 
189
  // Loads setting notices.
190
  \add_action( 'the_seo_framework_setting_notices', [ $this, '_do_settings_page_notices' ] );
191
 
194
  }
195
 
196
  if ( ! $this->is_headless['user'] ) {
197
+ // Initialize user meta filters and actions.
198
  $this->init_user_meta();
199
 
200
  // Enqueue user meta output.
213
  }
214
 
215
  // Add plugin links to the plugin activation page.
216
+ \add_filter(
217
+ 'plugin_action_links_' . THE_SEO_FRAMEWORK_PLUGIN_BASENAME,
218
+ [ '\The_SEO_Framework\Bridges\PluginTable', '_add_plugin_action_links' ],
219
+ 10,
220
+ 2
221
+ );
222
+ \add_filter(
223
+ 'plugin_row_meta',
224
+ [ '\The_SEO_Framework\Bridges\PluginTable', '_add_plugin_row_meta' ],
225
+ 10,
226
+ 2
227
+ );
228
 
229
  /**
230
  * @since 2.9.4
264
 
265
  // Prepares sitemap or stylesheet output.
266
  if ( $this->can_run_sitemap() ) {
 
267
  \add_action( 'template_redirect', [ $this, '_init_sitemap' ], 1 );
268
  \add_filter( 'wp_sitemaps_enabled', '__return_false' );
269
  } else {
277
  // Prepares requisite robots headers to avoid low-quality content penalties.
278
  $this->prepare_robots_headers();
279
 
280
+ \add_action( 'the_seo_framework_before_meta_output', [ $this, '_do_deprecated_output_hooks_before' ], 5 );
281
+ \add_action( 'the_seo_framework_after_meta_output', [ $this, '_do_deprecated_output_hooks_after' ], 15 );
282
+
283
  // Output meta tags.
284
  \add_action( 'wp_head', [ $this, 'html_output' ], 1 );
285
 
324
 
325
  // New WordPress 4.4.0 filter. Hurray! It's also much faster :)
326
  \add_filter( 'pre_get_document_title', [ $this, 'get_document_title' ], 10 );
 
 
327
 
328
  /**
329
  * @since 2.4.1
330
  * @param bool $overwrite_titles Whether to enable legacy title overwriting.
331
+ *
332
+ * TODO remove this block? -- it's been 6 years...
333
+ * <https://make.wordpress.org/core/2015/10/20/document-title-in-4-4/>
334
  */
335
  if ( \apply_filters( 'the_seo_framework_manipulate_title', true ) ) {
336
  \remove_all_filters( 'wp_title', false );
337
  // Override WordPress Title
338
  \add_filter( 'wp_title', [ $this, 'get_wp_title' ], 9 );
339
+ // Override WooThemes Title TODO move this to wc compat file.
340
+ \add_filter( 'woo_title', [ $this, 'get_document_title' ], 99 );
341
  }
342
  }
343
 
347
  */
348
  if ( \apply_filters( 'the_seo_framework_kill_core_robots', true ) ) {
349
  \remove_filter( 'wp_robots', 'wp_robots_max_image_preview_large' );
350
+ // Reconsider readding this to "supported" queries only?
351
+ \remove_filter( 'wp_robots', 'wp_robots_noindex_search' );
352
  }
353
 
354
  if ( $this->get_option( 'og_tags' ) ) { // independent from filter at use_og_tags--let that be deciding later.
382
  }
383
 
384
  /**
385
+ * Outputs deprecated output hooks.
 
 
386
  *
387
+ * @since 4.2.0
388
+ * @access private
389
+ * @TODO delete me. v5.0.0+
390
  */
391
+ public function _do_deprecated_output_hooks_before() {
392
+ // phpcs:disable, WordPress.Security.EscapeOutput -- Everything we produce is escaped.
393
+ /**
394
+ * @since 2.6.0
395
+ * @since 4.2.0 Deprecated.
396
+ * @param string $before The content before the SEO output.
397
+ */
398
+ echo \apply_filters_deprecated(
399
+ 'the_seo_framework_pre',
400
+ [ '' ],
401
+ '4.2.0 of The SEO Framework',
402
+ 'Action the_seo_framework_before_meta_output'
403
+ );
404
 
405
  /**
406
  * @since 2.2.6
407
+ * @since 4.2.0 Deprecated
408
  * @param array $functions {
409
  * 'callback' => string|array The function to call.
410
  * 'args' => scalar|array Arguments. When array, each key is a new argument.
411
  * }
412
  */
413
+ $functions = (array) \apply_filters_deprecated(
414
+ 'the_seo_framework_before_output',
415
+ [ [] ],
416
+ '4.2.0 of The SEO Framework',
417
+ 'Action the_seo_framework_before_meta_output'
418
+ );
419
 
420
  foreach ( $functions as $function ) {
421
+ if ( ! empty( $function['callback'] ) )
422
+ echo \call_user_func_array( $function['callback'], [ ( $function['args'] ?? null ) ] );
423
+ }
424
+ // phpcs:enable, WordPress.Security.EscapeOutput
425
+ }
426
 
427
+ /**
428
+ * Outputs deprecated output hooks.
429
+ *
430
+ * @since 4.2.0
431
+ * @access private
432
+ * @TODO delete me. v5.0.0+
433
+ */
434
+ public function _do_deprecated_output_hooks_after() {
435
+ // phpcs:disable, WordPress.Security.EscapeOutput -- Everything we produce is escaped.
436
+ /**
437
+ * @since 2.2.6
438
+ * @since 4.2.0 Deprecated
439
+ * @param array $functions {
440
+ * 'callback' => string|array The function to call.
441
+ * 'args' => scalar|array Arguments. When array, each key is a new argument.
442
+ * }
443
+ */
444
+ $functions = (array) \apply_filters_deprecated(
445
+ 'the_seo_framework_after_output',
446
+ [ [] ],
447
+ '4.2.0 of The SEO Framework',
448
+ 'Action the_seo_framework_after_meta_output'
449
+ );
450
+
451
+ foreach ( $functions as $function ) {
452
+ if ( ! empty( $function['callback'] ) )
453
+ echo \call_user_func_array( $function['callback'], [ ( $function['args'] ?? null ) ] );
454
  }
455
 
456
+ /**
457
+ * @since 2.6.0
458
+ * @since 4.2.0 Deprecated.
459
+ * @param string $after The content after the SEO output.
460
+ */
461
+ echo \apply_filters_deprecated(
462
+ 'the_seo_framework_pro',
463
+ [ '' ],
464
+ '4.2.0 of The SEO Framework',
465
+ 'Action the_seo_framework_after_meta_output'
466
+ );
467
+ // phpcs:enable, WordPress.Security.EscapeOutput
468
  }
469
 
470
  /**
478
  * @since 4.0.0 Now no longer outputs anything on Customizer.
479
  * @since 4.0.4 1. Now sets timezone to UTC to fix WP 5.3 bug <https://core.trac.wordpress.org/ticket/48623>
480
  * 2. Now always sets timezone regardless of settings, because, again, bug.
481
+ * @since 4.2.0 No longer sets timezone.
482
  * @access private
483
  */
484
  public function html_output() {
485
 
486
  if ( $this->is_preview() || $this->is_customize_preview() || ! $this->query_supports_seo() ) return;
487
 
 
 
 
 
 
 
 
 
 
 
 
488
  /**
489
  * @since 2.6.0
490
  */
491
  \do_action( 'the_seo_framework_do_before_output' );
492
 
493
  /**
494
+ * The bootstrap timer keeps adding when metadata is strapping.
495
+ * This causes both timers to increase simultaneously.
496
+ * We catch the bootstrap here, and let the meta-timer take over.
497
+ */
498
+ $bootstrap_timer = _bootstrap_timer();
499
+ /**
500
+ * Start the meta timer here. This also catches file inclusions,
501
+ * which is also caught by the _bootstrap_timer().
502
  */
503
  $init_start = microtime( true );
504
 
505
  // phpcs:disable, WordPress.Security.EscapeOutput -- Output is escaped.
 
506
  echo PHP_EOL, $this->get_plugin_indicator( 'before' );
507
 
508
  $this->do_meta_output();
509
 
510
+ echo $this->get_plugin_indicator(
511
+ 'after',
512
+ microtime( true ) - $init_start,
513
+ $bootstrap_timer
514
+ ), PHP_EOL;
515
  // phpcs:enable, WordPress.Security.EscapeOutput
516
 
517
  /**
524
  * Outputs all meta tags for the current query.
525
  *
526
  * @since 4.1.4
527
+ * @since 4.2.0 1. Now invokes two actions before and after output.
528
+ * 2. No longer rectifies timezones.
529
  */
530
  public function do_meta_output() {
531
 
 
 
 
 
 
 
 
532
  /**
533
+ * @since 4.2.0
 
534
  */
535
+ \do_action( 'the_seo_framework_before_meta_output' );
536
 
537
+ $get = [ 'robots' ];
538
 
539
  // Limit processing and redundant tags on 404 and search.
540
  if ( $this->is_search() ) :
609
  );
610
  endif;
611
 
612
+ // TODO add filter to $get? It won't last a few major updates though...
613
  // But that's why I created this method like so... anyway... tough luck.
614
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- Everything we produce is escaped.
615
+ foreach ( $get as $method ) echo $this->{$method}();
 
 
616
 
617
  /**
618
+ * @since 4.2.0
 
619
  */
620
+ \do_action( 'the_seo_framework_after_meta_output' );
 
 
 
 
 
621
  }
622
 
623
  /**
624
  * Redirects singular page to an alternate URL.
625
  *
626
  * @since 2.9.0
627
+ * @since 3.1.0 1. Now no longer redirects on preview.
628
+ * 2. Now listens to post type settings.
629
+ * @since 4.0.0 1. No longer tries to redirect on "search".
630
+ * 2. Added term redirect support.
631
+ * 3. No longer redirects on Customizer.
632
  * @access private
633
  *
634
  * @return void early on non-singular pages.
645
  * @param string $url The URL we're redirecting to.
646
  */
647
  \do_action( 'the_seo_framework_before_redirect', $url );
648
+
649
  $this->do_redirect( $url );
650
  }
651
  }
665
  return;
666
  }
667
 
668
+ // All WP defined protocols are allowed.
669
  $url = \esc_url_raw( $url );
670
 
671
  if ( empty( $url ) ) {
683
  $this->_doing_it_wrong( __METHOD__, 'You should use 3xx HTTP Status Codes. Recommended 301 and 302.', '2.8.0' );
684
 
685
  if ( ! $this->allow_external_redirect() ) {
686
+ // Only HTTP/HTTPS and home URLs are allowed.
687
  $path = $this->set_url_scheme( $url, 'relative' );
688
  $url = \trailingslashit( $this->get_home_host() ) . ltrim( $path, ' /' );
689
 
716
  * @access private
717
  */
718
  public function _init_core_sitemap() {
719
+ // It's not a bridge, don't treat it like one: So, submit hooks here... But, clean me up?
720
+ \add_filter( 'wp_sitemaps_add_provider', [ Builders\CoreSitemaps\Main::class, '_filter_add_provider' ], 9, 2 );
721
+ \add_filter( 'wp_sitemaps_max_urls', [ Builders\CoreSitemaps\Main::class, '_filter_max_urls' ], 9 );
 
 
722
  }
723
 
724
  /**
743
  *
744
  * @since 2.2.9
745
  * @since 2.9.3 Casts $public to string for check.
746
+ * @since 4.0.5 1. The output is now filterable.
747
+ * 2. Improved invalid location test.
748
+ * 3. No longer shortcircuits on non-public sites.
749
+ * 4. Now marked as private. Will be renamed to `_robots_txt()` in the future.
750
  * @since 4.1.0 Now adds the WordPress Core sitemap URL.
751
  * @since 4.1.2 Now only adds the WP Core sitemap URL when the provider tells us it's enabled.
752
  * @since 4.1.4 Removed object caching support.
780
  * @since 2.5.0
781
  * @param bool $disallow Whether to disallow robots queries.
782
  */
783
+ if ( \apply_filters( 'the_seo_framework_robots_disallow_queries', false ) )
784
  $output .= "Disallow: /*?*\r\n";
 
785
 
786
  /**
787
  * @since 2.5.0
794
  if ( $this->get_option( 'sitemaps_robots' ) ) {
795
  if ( $this->get_option( 'sitemaps_output' ) ) {
796
  $sitemaps = Bridges\Sitemap::get_instance();
797
+
798
+ foreach ( $sitemaps->get_sitemap_endpoint_list() as $id => $data )
799
+ if ( ! empty( $data['robots'] ) )
800
  $output .= sprintf( "\r\nSitemap: %s", \esc_url( $sitemaps->get_expected_sitemap_endpoint_url( $id ) ) );
801
+
 
802
  $output .= "\r\n";
803
  } elseif ( ! $this->detect_sitemap_plugin() ) { // detect_sitemap_plugin() temp backward compat.
804
  if ( $this->use_core_sitemaps() ) {
824
  '# This is an invalid robots.txt location.',
825
  '# Please visit: ' . \esc_url( \trailingslashit( $this->set_preferred_url_scheme( $this->get_home_host() ) ) . 'robots.txt' )
826
  );
827
+ $output = "$error$output";
828
  }
829
 
830
  /**
949
  $post__not_in = $wp_query->get( 'post__not_in' );
950
 
951
  if ( ! empty( $post__not_in ) ) {
952
+ $excluded = array_unique(
953
+ array_merge( (array) $post__not_in, $excluded )
954
+ );
955
  }
956
 
957
  $wp_query->set( 'post__not_in', $excluded );
983
  $post__not_in = $wp_query->get( 'post__not_in' );
984
 
985
  if ( ! empty( $post__not_in ) ) {
986
+ $excluded = array_unique(
987
+ array_merge( (array) $post__not_in, $excluded )
988
+ );
989
  }
990
 
991
  $wp_query->set( 'post__not_in', $excluded );
1012
  if ( $this->get_post_meta_item( 'exclude_local_search', $post->ID ) )
1013
  unset( $posts[ $n ] );
1014
  }
1015
+ // Reset numeric index.
1016
  $posts = array_values( $posts );
1017
  }
1018
 
1039
  if ( $this->get_post_meta_item( 'exclude_from_archive', $post->ID ) )
1040
  unset( $posts[ $n ] );
1041
  }
1042
+ // Reset numeric index.
1043
  $posts = array_values( $posts );
1044
  }
1045
 
1065
  * @since 4.1.4 1. Renamed from `is_archive_query_adjustment_blocked()`
1066
  * 2. Added taxonomy-supported lookups.
1067
  * 3. Added WP Rest checks for the Block Editor.
1068
+ * @since 4.2.0 Improved supported taxonomy loop.
1069
  *
1070
  * @param \WP_Query $wp_query WP_Query object.
1071
  * @return bool
1074
 
1075
  static $has_filter = null;
1076
 
1077
+ if ( null === $has_filter )
1078
  $has_filter = \has_filter( 'the_seo_framework_do_adjust_archive_query' );
1079
+
1080
  if ( $has_filter ) {
1081
  /**
1082
  * This filter affects both 'search-"archives"' and terms/taxonomies.
1107
 
1108
  // This primarily affects 'terms'.
1109
  if ( ! empty( $wp_query->tax_query->queries ) ) :
1110
+ $supported = true;
1111
 
1112
  foreach ( $wp_query->tax_query->queries as $_query ) {
1113
+ if ( isset( $_query['taxonomy'] ) ) {
1114
+ $supported = $this->is_taxonomy_supported( $_query['taxonomy'] );
1115
+ // If just one tax is supported for this query, greenlight it: all must be blocking.
1116
+ if ( $supported ) break;
1117
+ }
1118
  }
1119
 
1120
+ if ( ! $supported )
 
1121
  return true;
1122
  endif;
1123
 
1142
  public function _alter_oembed_response_data( $data = [], $post = null, $width = 0, $height = 0 ) {
1143
 
1144
  // Don't use cache. See @WARNING in doc comment.
1145
+ if ( $this->get_option( 'oembed_use_og_title', false ) )
1146
+ $data['title'] = $this->get_open_graph_title( [ 'id' => $post->ID ] ) ?: $data['title'];
1147
+
 
 
 
 
 
1148
  // Don't use cache. See @WARNING in doc comment.
1149
  if ( $this->get_option( 'oembed_use_social_image', false ) ) {
1150
  $image_details = current( $this->get_image_details(
1151
+ [ 'id' => $post->ID ],
 
 
 
1152
  true,
1153
  'oembed',
1154
  true
1161
  $data['thumbnail_height'] = $image_details['height'];
1162
  }
1163
  }
1164
+
1165
  // Don't use cache. See @WARNING in doc comment.
1166
+ if ( $this->get_option( 'oembed_remove_author', false ) )
1167
  unset( $data['author_url'], $data['author_name'] );
 
1168
 
1169
  return $data;
1170
  }
inc/classes/{debug.class.php → internal/debug.class.php} RENAMED
@@ -1,14 +1,10 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes\Debug
4
  * @subpackage The_SEO_Framework\Debug
5
  */
6
 
7
- namespace The_SEO_Framework;
8
-
9
- \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
10
-
11
- // phpcs:disable, WordPress.PHP.DevelopmentFunctions -- This whole class is meant for development.
12
 
13
  /**
14
  * The SEO Framework plugin
@@ -27,13 +23,20 @@ namespace The_SEO_Framework;
27
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
28
  */
29
 
 
 
 
 
 
 
30
  /**
31
- * Singleton class The_SEO_Framework\Debug
32
  *
33
  * Holds plugin debug functions.
34
  *
35
  * @since 2.8.0
36
  * @since 4.0.0 No longer implements an interface. It's implied.
 
37
  */
38
  final class Debug {
39
 
@@ -64,13 +67,11 @@ final class Debug {
64
  */
65
  public static function _set_instance( $debug = null ) {
66
 
67
- if ( \is_null( static::$instance ) ) {
68
  static::$instance = new static();
69
- }
70
 
71
- if ( isset( $debug ) ) {
72
  static::$instance->the_seo_framework_debug = (bool) $debug;
73
- }
74
  }
75
 
76
  /**
@@ -82,9 +83,8 @@ final class Debug {
82
  */
83
  public static function get_instance() {
84
 
85
- if ( \is_null( static::$instance ) ) {
86
  static::_set_instance();
87
- }
88
 
89
  return static::$instance;
90
  }
@@ -248,7 +248,7 @@ final class Debug {
248
  /* translators: 1: Method or Property name, 2: The SEO Framework class. 3: Message */
249
  \esc_html__( '%1$s is not accessible in %2$s. %3$s', 'autodescription' ),
250
  '<code>' . \esc_html( $p_or_m ) . '</code>',
251
- '<code>the_seo_framework()</code>',
252
  \esc_html( $message )
253
  );
254
 
@@ -287,10 +287,10 @@ final class Debug {
287
  * 1 = Error handler (This class).
288
  * 2 = Error forwarder (TSF class).
289
  */
290
- if ( isset( $backtrace[4]['args'][0][0] ) && is_a( $backtrace[4]['args'][0][0], 'The_SEO_Framework\Deprecated', false ) ) {
291
  /**
292
  * 3 = Deprecated call.
293
- * 4 = TSF deprecation class fowarder.
294
  * 5 = User mistake.
295
  */
296
  $error = $backtrace[5];
@@ -334,9 +334,6 @@ final class Debug {
334
  */
335
  protected function combobulate_error_message( $error, $message, $code ) {
336
 
337
- $file = isset( $error['file'] ) ? $error['file'] : '';
338
- $line = isset( $error['line'] ) ? $error['line'] : '';
339
-
340
  switch ( $code ) :
341
  case E_USER_ERROR:
342
  $type = 'Error';
@@ -356,8 +353,8 @@ final class Debug {
356
  break;
357
  endswitch;
358
 
359
- $file = \esc_html( $file );
360
- $line = \esc_html( $line );
361
 
362
  $_message = "'<span><strong>$type:</strong> $message";
363
  $_message .= $file ? " In $file" : '';
@@ -375,7 +372,7 @@ final class Debug {
375
  * @access private
376
  */
377
  public function _debug_output() {
378
- \the_seo_framework()->get_view( 'debug/output' );
379
  }
380
 
381
  /**
@@ -389,7 +386,7 @@ final class Debug {
389
  * @return string
390
  */
391
  protected function debug_key_wrapper( $key ) {
392
- return '<font color="chucknorris">' . \esc_attr( $key ) . '</font>';
393
  }
394
 
395
  /**
@@ -407,30 +404,7 @@ final class Debug {
407
  if ( ! is_scalar( $value ) )
408
  return '<em>Debug message: not scalar</em>';
409
 
410
- return '<span class="wp-ui-notification">' . \esc_attr( trim( $value ) ) . '</span>';
411
- }
412
-
413
- /**
414
- * Times code until it's called again.
415
- *
416
- * @since 2.6.0
417
- * @since 3.1.0 Now is protected.
418
- *
419
- * @param bool $reset Whether to reset the timer.
420
- * @return float The time it took for code execution.
421
- */
422
- protected function timer( $reset = false ) {
423
-
424
- static $previous = null;
425
-
426
- if ( isset( $previous ) && false === $reset ) {
427
- $output = microtime( true ) - $previous;
428
- $previous = null;
429
- } else {
430
- $output = $previous = microtime( true );
431
- }
432
-
433
- return $output;
434
  }
435
 
436
  /**
@@ -455,7 +429,7 @@ final class Debug {
455
  */
456
  protected function get_debug_header_output() {
457
 
458
- $tsf = \the_seo_framework();
459
 
460
  if ( \is_admin() && ! $tsf->is_term_edit() && ! $tsf->is_post_edit() && ! $tsf->is_seo_settings_page( true ) )
461
  return;
@@ -464,11 +438,14 @@ final class Debug {
464
  \add_filter( 'the_seo_framework_current_object_id', [ $tsf, 'get_the_front_page_ID' ] );
465
 
466
  // Start timer.
467
- $this->timer( true );
468
 
469
- $output = $tsf->get_html_output();
 
 
 
470
 
471
- $timer = '<div style="display:inline-block;width:100%;padding:20px;border-bottom:1px solid #ccc;">Generated in: ' . number_format( $this->timer(), 5 ) . ' seconds</div>';
472
 
473
  $title = \is_admin() ? 'Expected SEO Output' : 'Determined SEO Output';
474
  $title = '<div style="display:inline-block;width:100%;padding:20px;margin:0 auto;border-bottom:1px solid #ccc;"><h2 style="color:#ddd;font-size:22px;padding:0;margin:0">' . $title . '</h2></div>';
@@ -525,13 +502,7 @@ final class Debug {
525
  * @return string Wrapped Query State debug output.
526
  */
527
  protected function get_debug_query_output_from_cache() {
528
-
529
- static $cache = null;
530
-
531
- if ( isset( $cache ) )
532
- return $cache;
533
-
534
- return $cache = $this->get_debug_query_output( 'yup' );
535
  }
536
 
537
  /**
@@ -547,13 +518,12 @@ final class Debug {
547
  protected function get_debug_query_output( $cache_version = 'nope' ) {
548
 
549
  // Start timer.
550
- $this->timer( true );
551
 
552
- $tsf = \the_seo_framework();
553
 
554
  // phpcs:disable, WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase -- Not this file's issue.
555
  // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- get_defined_vars() is used later.
556
- // Only get true/false values.
557
  $page_id = $tsf->get_the_real_ID();
558
  $is_query_exploited = $tsf->is_query_exploited();
559
  $query_supports_seo = $tsf->query_supports_seo() ? 'yes' : 'no';
@@ -565,7 +535,6 @@ final class Debug {
565
  $is_post_edit = $tsf->is_post_edit();
566
  $is_wp_lists_edit = $tsf->is_wp_lists_edit();
567
  $is_author = $tsf->is_author();
568
- $is_blog_page = $tsf->is_blog_page();
569
  $is_category = $tsf->is_category();
570
  $is_date = $tsf->is_date();
571
  $is_year = $tsf->is_year();
@@ -573,8 +542,8 @@ final class Debug {
573
  $is_day = $tsf->is_day();
574
  $is_feed = $tsf->is_feed();
575
  $is_real_front_page = $tsf->is_real_front_page();
576
- $is_front_page_by_id = $tsf->is_front_page_by_id( $page_id );
577
  $is_home = $tsf->is_home();
 
578
  $is_page = $tsf->is_page();
579
  $page = $tsf->page();
580
  $paged = $tsf->paged();
@@ -599,25 +568,27 @@ final class Debug {
599
  $get_post_type_real_ID = $tsf->get_post_type_real_ID();
600
  $admin_post_type = $tsf->get_admin_post_type();
601
  $current_taxonomy = $tsf->get_current_taxonomy();
 
602
  $is_taxonomy_disabled = $tsf->is_taxonomy_disabled();
603
  $is_post_type_archive = \is_post_type_archive();
604
  $is_protected = $tsf->is_protected( $page_id );
605
  // phpcs:enable, WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
606
  // phpcs:enable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
607
 
608
- // Don't debug the class object.
609
- unset( $tsf );
610
 
611
  // Get all above vars, split them in two (true and false) and sort them by key names.
612
- $vars = get_defined_vars();
 
 
 
 
613
  $current = array_filter( $vars );
614
  $not_current = array_diff_key( $vars, $current );
615
 
616
  ksort( $current );
617
  ksort( $not_current );
618
 
619
- $timer = $this->timer();
620
-
621
  $output = '';
622
  foreach ( $current as $name => $value ) {
623
  $type = '(' . \gettype( $value ) . ')';
@@ -625,10 +596,10 @@ final class Debug {
625
  if ( \is_bool( $value ) ) {
626
  $value = $value ? 'true' : 'false';
627
  } else {
628
- $value = \esc_attr( var_export( $value, true ) );
629
  }
630
 
631
- $value = '<font color="harrisonford">' . $type . ' ' . $value . '</font>';
632
  $out = \esc_html( $name ) . ' => ' . $value;
633
  $output .= '<span style="background:#dadada">' . $out . '</span>' . PHP_EOL;
634
  }
@@ -639,10 +610,10 @@ final class Debug {
639
  if ( \is_bool( $value ) ) {
640
  $value = $value ? 'true' : 'false';
641
  } else {
642
- $value = \esc_attr( var_export( $value, true ) );
643
  }
644
 
645
- $value = '<font color="harrisonford">' . $type . ' ' . $value . '</font>';
646
  $out = \esc_html( $name ) . ' => ' . $value;
647
 
648
  $output .= $out . PHP_EOL;
@@ -663,7 +634,7 @@ final class Debug {
663
  ),
664
  sprintf(
665
  '<div style="display:inline-block;width:100%%;padding:20px;border-bottom:1px solid #666;">Generated in: %s seconds</div>',
666
- number_format( $timer, 5 )
667
  ),
668
  sprintf(
669
  '<div style="display:inline-block;width:100%%;padding:20px;font-family:Consolas,Monaco,monospace;font-size:14px;">%s</div>',
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Internal\Debug
4
  * @subpackage The_SEO_Framework\Debug
5
  */
6
 
7
+ namespace The_SEO_Framework\Internal;
 
 
 
 
8
 
9
  /**
10
  * The SEO Framework plugin
23
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
24
  */
25
 
26
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
+
28
+ // phpcs:disable, WordPress.PHP.DevelopmentFunctions -- This whole class is meant for development.
29
+
30
+ use function \The_SEO_Framework\memo;
31
+
32
  /**
33
+ * Singleton class The_SEO_Framework\Internal\Debug
34
  *
35
  * Holds plugin debug functions.
36
  *
37
  * @since 2.8.0
38
  * @since 4.0.0 No longer implements an interface. It's implied.
39
+ * @since 4.2.0 Changed namespace from \The_SEO_Framework to \The_SEO_Framework\Internal
40
  */
41
  final class Debug {
42
 
67
  */
68
  public static function _set_instance( $debug = null ) {
69
 
70
+ if ( \is_null( static::$instance ) )
71
  static::$instance = new static();
 
72
 
73
+ if ( isset( $debug ) )
74
  static::$instance->the_seo_framework_debug = (bool) $debug;
 
75
  }
76
 
77
  /**
83
  */
84
  public static function get_instance() {
85
 
86
+ if ( \is_null( static::$instance ) )
87
  static::_set_instance();
 
88
 
89
  return static::$instance;
90
  }
248
  /* translators: 1: Method or Property name, 2: The SEO Framework class. 3: Message */
249
  \esc_html__( '%1$s is not accessible in %2$s. %3$s', 'autodescription' ),
250
  '<code>' . \esc_html( $p_or_m ) . '</code>',
251
+ '<code>tsf()</code>',
252
  \esc_html( $message )
253
  );
254
 
287
  * 1 = Error handler (This class).
288
  * 2 = Error forwarder (TSF class).
289
  */
290
+ if ( isset( $backtrace[4]['args'][0][0] ) && is_a( $backtrace[4]['args'][0][0], 'The_SEO_Framework\Internal\Deprecated', false ) ) {
291
  /**
292
  * 3 = Deprecated call.
293
+ * 4 = TSF deprecation class forwarder.
294
  * 5 = User mistake.
295
  */
296
  $error = $backtrace[5];
334
  */
335
  protected function combobulate_error_message( $error, $message, $code ) {
336
 
 
 
 
337
  switch ( $code ) :
338
  case E_USER_ERROR:
339
  $type = 'Error';
353
  break;
354
  endswitch;
355
 
356
+ $file = \esc_html( $error['file'] ?? '' );
357
+ $line = \esc_html( $error['line'] ?? '' );
358
 
359
  $_message = "'<span><strong>$type:</strong> $message";
360
  $_message .= $file ? " In $file" : '';
372
  * @access private
373
  */
374
  public function _debug_output() {
375
+ \tsf()->get_view( 'debug/output' );
376
  }
377
 
378
  /**
386
  * @return string
387
  */
388
  protected function debug_key_wrapper( $key ) {
389
+ return '<font color="chucknorris">' . \esc_html( $key ) . '</font>';
390
  }
391
 
392
  /**
404
  if ( ! is_scalar( $value ) )
405
  return '<em>Debug message: not scalar</em>';
406
 
407
+ return '<span class="wp-ui-notification">' . \esc_html( trim( $value ) ) . '</span>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  }
409
 
410
  /**
429
  */
430
  protected function get_debug_header_output() {
431
 
432
+ $tsf = \tsf();
433
 
434
  if ( \is_admin() && ! $tsf->is_term_edit() && ! $tsf->is_post_edit() && ! $tsf->is_seo_settings_page( true ) )
435
  return;
438
  \add_filter( 'the_seo_framework_current_object_id', [ $tsf, 'get_the_front_page_ID' ] );
439
 
440
  // Start timer.
441
+ $t = microtime( true );
442
 
443
+ // I hate ob_*.
444
+ ob_start();
445
+ $tsf->html_output();
446
+ $output = ob_get_clean();
447
 
448
+ $timer = '<div style="display:inline-block;width:100%;padding:20px;border-bottom:1px solid #ccc;">Generated in: ' . number_format( microtime( true ) - $t, 5 ) . ' seconds</div>';
449
 
450
  $title = \is_admin() ? 'Expected SEO Output' : 'Determined SEO Output';
451
  $title = '<div style="display:inline-block;width:100%;padding:20px;margin:0 auto;border-bottom:1px solid #ccc;"><h2 style="color:#ddd;font-size:22px;padding:0;margin:0">' . $title . '</h2></div>';
502
  * @return string Wrapped Query State debug output.
503
  */
504
  protected function get_debug_query_output_from_cache() {
505
+ return memo() ?? memo( $this->get_debug_query_output( 'yup' ) );
 
 
 
 
 
 
506
  }
507
 
508
  /**
518
  protected function get_debug_query_output( $cache_version = 'nope' ) {
519
 
520
  // Start timer.
521
+ $_t = microtime( true );
522
 
523
+ $tsf = \tsf();
524
 
525
  // phpcs:disable, WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase -- Not this file's issue.
526
  // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- get_defined_vars() is used later.
 
527
  $page_id = $tsf->get_the_real_ID();
528
  $is_query_exploited = $tsf->is_query_exploited();
529
  $query_supports_seo = $tsf->query_supports_seo() ? 'yes' : 'no';
535
  $is_post_edit = $tsf->is_post_edit();
536
  $is_wp_lists_edit = $tsf->is_wp_lists_edit();
537
  $is_author = $tsf->is_author();
 
538
  $is_category = $tsf->is_category();
539
  $is_date = $tsf->is_date();
540
  $is_year = $tsf->is_year();
542
  $is_day = $tsf->is_day();
543
  $is_feed = $tsf->is_feed();
544
  $is_real_front_page = $tsf->is_real_front_page();
 
545
  $is_home = $tsf->is_home();
546
+ $is_home_as_page = $tsf->is_home_as_page();
547
  $is_page = $tsf->is_page();
548
  $page = $tsf->page();
549
  $paged = $tsf->paged();
568
  $get_post_type_real_ID = $tsf->get_post_type_real_ID();
569
  $admin_post_type = $tsf->get_admin_post_type();
570
  $current_taxonomy = $tsf->get_current_taxonomy();
571
+ $current_post_type = $tsf->get_current_post_type();
572
  $is_taxonomy_disabled = $tsf->is_taxonomy_disabled();
573
  $is_post_type_archive = \is_post_type_archive();
574
  $is_protected = $tsf->is_protected( $page_id );
575
  // phpcs:enable, WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
576
  // phpcs:enable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
577
 
578
+ $timer = microtime( true ) - $_t;
 
579
 
580
  // Get all above vars, split them in two (true and false) and sort them by key names.
581
+ $vars = get_defined_vars();
582
+
583
+ // Don't debug the class object nor timer.
584
+ unset( $vars['tsf'], $vars['timer'], $vars['_t'] );
585
+
586
  $current = array_filter( $vars );
587
  $not_current = array_diff_key( $vars, $current );
588
 
589
  ksort( $current );
590
  ksort( $not_current );
591
 
 
 
592
  $output = '';
593
  foreach ( $current as $name => $value ) {
594
  $type = '(' . \gettype( $value ) . ')';
596
  if ( \is_bool( $value ) ) {
597
  $value = $value ? 'true' : 'false';
598
  } else {
599
+ $value = \esc_html( var_export( $value, true ) );
600
  }
601
 
602
+ $value = '<font color="harrisonford">' . "$type $value" . '</font>';
603
  $out = \esc_html( $name ) . ' => ' . $value;
604
  $output .= '<span style="background:#dadada">' . $out . '</span>' . PHP_EOL;
605
  }
610
  if ( \is_bool( $value ) ) {
611
  $value = $value ? 'true' : 'false';
612
  } else {
613
+ $value = \esc_html( var_export( $value, true ) );
614
  }
615
 
616
+ $value = '<font color="harrisonford">' . "$type $value" . '</font>';
617
  $out = \esc_html( $name ) . ' => ' . $value;
618
 
619
  $output .= $out . PHP_EOL;
634
  ),
635
  sprintf(
636
  '<div style="display:inline-block;width:100%%;padding:20px;border-bottom:1px solid #666;">Generated in: %s seconds</div>',
637
+ number_format( number_format( $timer, 5 ), 5 )
638
  ),
639
  sprintf(
640
  '<div style="display:inline-block;width:100%%;padding:20px;font-family:Consolas,Monaco,monospace;font-size:14px;">%s</div>',
inc/classes/{deprecated.class.php → internal/deprecated.class.php} RENAMED
@@ -1,12 +1,10 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes\Deprecated
4
  * @subpackage The_SEO_Framework\Debug\Deprecated
5
  */
6
 
7
- namespace The_SEO_Framework;
8
-
9
- \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
10
 
11
  /**
12
  * The SEO Framework plugin
@@ -25,8 +23,15 @@ namespace The_SEO_Framework;
25
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
26
  */
27
 
 
 
 
 
 
 
 
28
  /**
29
- * Class The_SEO_Framework\Deprecated
30
  *
31
  * Contains all deprecated functions.
32
  *
@@ -34,166 +39,68 @@ namespace The_SEO_Framework;
34
  * @since 3.1.0 Removed all methods deprecated in 3.0.0.
35
  * @since 4.0.0 Removed all methods deprecated in 3.1.0.
36
  * @since 4.1.4 Removed all methods deprecated in 4.0.0.
 
 
37
  * @ignore
38
  */
39
  final class Deprecated {
40
 
41
  /**
42
- * Detect if the current screen type is a page or taxonomy.
43
- * Memoizes the return value.
44
  *
45
- * @since 2.3.1
46
- * @since 4.1.0 Deprecated.
 
 
47
  * @deprecated
48
  *
49
- * @param string $type the Screen type
50
- * @return bool true if post type is a page or post
 
51
  */
52
- public function is_post_type_page( $type ) {
53
-
54
- static $is_page = [];
55
-
56
- if ( isset( $is_page[ $type ] ) )
57
- return $is_page[ $type ];
58
-
59
- $tsf = \the_seo_framework();
60
-
61
- $tsf->_deprecated_function( 'the_seo_framework()->is_post_type_page()', '4.1.0' );
62
-
63
- $post_page = (array) \get_post_types( [ 'public' => true ] );
64
-
65
- foreach ( $post_page as $screen ) {
66
- if ( $type === $screen ) {
67
- return $is_page[ $type ] = true;
68
- }
69
- }
70
-
71
- return $is_page[ $type ] = false;
72
  }
73
 
74
  /**
75
- * Checks whether the taxonomy is public and rewritable.
76
  *
77
  * @since 3.1.0
78
- * @since 4.1.0 1: Now returns true on all public taxonomies; not just public taxonomies with rewrite capabilities.
79
- * 2: Deprecated.
80
  * @deprecated
81
  *
82
- * @param string $taxonomy The taxonomy name.
83
- * @return bool
84
- */
85
- public function is_taxonomy_public( $taxonomy = '' ) {
86
-
87
- $tsf = \the_seo_framework();
88
-
89
- $tsf->_deprecated_function( 'the_seo_framework()->is_taxonomy_public()', '4.1.0', 'the_seo_framework()->is_taxonomy_supported()' );
90
-
91
- $taxonomy = $taxonomy ?: $tsf->get_current_taxonomy();
92
- if ( ! $taxonomy ) return false;
93
-
94
- $tax = \get_taxonomy( $taxonomy );
95
-
96
- if ( false === $tax ) return false;
97
-
98
- return ! empty( $tax->public );
99
- }
100
-
101
- /**
102
- * Return option from the options table and cache result.
103
- * Memoizes the return value.
104
- *
105
- * Values pulled from the database are cached on each request, so a second request for the same value won't cause a
106
- * second DB interaction.
107
- *
108
- * @since 2.0.0
109
- * @since 2.8.2 No longer decodes entities on request.
110
- * @since 3.1.0 Now uses the filterable call when caching is disabled.
111
- * @since 4.1.0 Deprecated.
112
- * @thanks StudioPress (http://www.studiopress.com/) for some code.
113
- * @deprecated
114
- *
115
- * @param string $key Option name.
116
- * @param string $setting Optional. Settings field name. Eventually defaults to null if not passed as an argument.
117
- * @param boolean $use_cache Optional. Whether to use the cache value or not.
118
- * @return mixed The value of this $key in the database. Empty string on failure.
119
  */
120
- public function the_seo_framework_get_option( $key, $setting = null, $use_cache = true ) {
121
 
122
- if ( ! $setting ) return '';
123
 
124
- $tsf = \the_seo_framework();
125
-
126
- $tsf->_deprecated_function( 'the_seo_framework()->the_seo_framework_get_option()', '4.1.0', 'the_seo_framework()->get_option()' );
127
-
128
- if ( ! $use_cache ) {
129
- $options = $tsf->get_all_options( $setting, true );
130
- return isset( $options[ $key ] ) ? \stripslashes_deep( $options[ $key ] ) : '';
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  }
132
 
133
- static $cache = [];
134
-
135
- if ( ! isset( $cache[ $setting ] ) )
136
- $cache[ $setting ] = \stripslashes_deep( $tsf->get_all_options( $setting ) );
137
-
138
- return isset( $cache[ $setting ][ $key ] ) ? $cache[ $setting ][ $key ] : '';
139
- }
140
-
141
- /**
142
- * Returns the homepage tagline from option or bloginfo, when set.
143
- *
144
- * @since 3.0.4
145
- * @since 4.0.0 Added caching.
146
- * @since 4.1.0 Deprecated.
147
- * @uses $this->get_blogdescription(), this method already trims.
148
- * @deprecated
149
- *
150
- * @return string The trimmed tagline.
151
- */
152
- public function get_home_page_tagline() {
153
-
154
- $tsf = \the_seo_framework();
155
-
156
- $tsf->_deprecated_function( 'the_seo_framework()->get_home_page_tagline()', '4.1.0', 'the_seo_framework()->get_home_title_additions()' );
157
-
158
- return $tsf->get_home_title_additions();
159
- }
160
-
161
- /**
162
- * Cached WordPress permalink structure settings.
163
- *
164
- * @since 2.6.0
165
- * @since 3.1.0 Removed caching.
166
- * @since 4.1.0 Deprecated.
167
- * @deprecated
168
- *
169
- * @return string permalink structure.
170
- */
171
- public function permalink_structure() {
172
-
173
- $tsf = \the_seo_framework();
174
-
175
- $tsf->_deprecated_function( 'the_seo_framework()->permalink_structure()', '4.1.0', "get_option( 'permalink_structure' )" );
176
-
177
- return \get_option( 'permalink_structure' );
178
- }
179
-
180
- /**
181
- * Appends given query to given URL.
182
- *
183
- * @since 3.0.0
184
- * @since 3.1.0 Now uses parse_str and add_query_arg, preventing duplicated entries.
185
- * @since 4.1.4 Deprecated silently.
186
- * @since 4.2.0 Hard deprecation.
187
- * @deprecated
188
- *
189
- * @param string $url A fully qualified URL.
190
- * @param string $query A fully qualified query taken from parse_url( $url, PHP_URL_QUERY );
191
- * @return string A fully qualified URL with appended $query.
192
- */
193
- public function append_php_query( $url, $query = '' ) {
194
- $tsf = \the_seo_framework();
195
- // $tsf->_deprecated_function( 'the_seo_framework()->append_php_query()', '4.2.0', 'the_seo_framework()->append_url_query()' );
196
- return $tsf->append_url_query( $url, $query );
197
  }
198
 
199
  /**
@@ -208,15 +115,11 @@ final class Deprecated {
208
  */
209
  public function get_html_output() {
210
 
211
- $tsf = \the_seo_framework();
212
-
213
- // $tsf->_deprecated_function( 'the_seo_framework()->get_html_output()', '4.2.0' );
214
 
215
  $robots = $tsf->robots();
216
 
217
- /** @since 4.0.4 Added as WP 5.3 patch. */
218
- $tsf->set_timezone( 'UTC' );
219
-
220
  /**
221
  * @since 2.6.0
222
  * @param string $before The content before the SEO output.
@@ -292,9 +195,6 @@ final class Deprecated {
292
  */
293
  $after = (string) \apply_filters( 'the_seo_framework_pro', '' );
294
 
295
- /** @since 4.0.4 Added as WP 5.3 patch. */
296
- $tsf->reset_timezone();
297
-
298
  return "{$robots}{$before}{$before_legacy}{$output}{$after_legacy}{$after}";
299
  }
300
 
@@ -312,10 +212,11 @@ final class Deprecated {
312
  * @since 4.0.0
313
  * @since 4.1.0 Now uses the new taxonomy robots settings.
314
  * @since 4.1.4 Soft deprecated. Use 'robots_meta' instead.
315
- * @since 4.2.0 Hard deprecation.
 
316
  * @deprecated
317
  *
318
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
319
  * @param int <bit> $ignore The ignore level. {
320
  * 0 = 0b00: Ignore nothing.
321
  * 1 = 0b01: Ignore protection. (\The_SEO_Framework\ROBOTS_IGNORE_PROTECTION)
@@ -325,8 +226,8 @@ final class Deprecated {
325
  * @return bool Whether noindex is set or not
326
  */
327
  public function is_robots_meta_noindex_set_by_args( $args, $ignore = 0b00 ) {
328
- $tsf = \the_seo_framework();
329
- // $tsf->_deprecated_function( 'the_seo_framework()->is_robots_meta_noindex_set_by_args()', '4.2.0', 'the_seo_framework()->robots_meta()' );
330
  $meta = $tsf->generate_robots_meta( $args, null, $ignore );
331
  return isset( $meta['noindex'] ) && 'noindex' === $meta['noindex'];
332
  }
@@ -338,24 +239,26 @@ final class Deprecated {
338
  * @since 2.2.4 Added robots SEO settings check.
339
  * @since 2.2.8 Added check for empty archives.
340
  * @since 2.8.0 Added check for protected/private posts.
341
- * @since 3.0.0 : 1. Removed noodp.
342
- * 2. Improved efficiency by grouping if statements.
343
- * @since 3.1.0 : 1. Simplified statements, often (not always) speeding things up.
344
- * 2. Now checks for wc_shop and blog types for pagination.
345
- * 3. Removed noydir.
346
- * @since 4.0.0 : 1. Now tests for qubit metadata.
347
- * 2. Added custom query support.
348
- * 3. Added two parameters.
349
- * @since 4.0.2 : 1. Added new copyright directive tags.
350
- * 2. Now strictly parses the validity of robots directives via a boolean check.
351
- * @since 4.0.3 : 1. Changed `max_snippet_length` to `max_snippet`
352
- * 2. Changed the copyright directive's spacer from `=` to `:`.
353
- * @since 4.0.5 : 1. Removed copyright directive bug workaround. <https://kb.theseoframework.com/kb/why-is-max-image-preview-none-purged/>
354
- * 2. Now sets noindex and nofollow when queries are exploited (requires option enabled).
355
  * @since 4.1.4 Deprecated silently. Use generate_robots_meta() instead.
356
- * @since 4.2.0 Hard deprecation.
 
 
357
  *
358
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
359
  * @param int <bit> $ignore The ignore level. {
360
  * 0 = 0b00: Ignore nothing.
361
  * 1 = 0b01: Ignore protection. (\The_SEO_Framework\ROBOTS_IGNORE_PROTECTION)
@@ -367,8 +270,8 @@ final class Deprecated {
367
  * }
368
  */
369
  public function robots_meta( $args = null, $ignore = 0b00 ) {
370
- $tsf = \the_seo_framework();
371
- // $tsf->_deprecated_function( 'the_seo_framework()->robots_meta()', '5.0.0', 'the_seo_framework()->generate_robots_meta()' );
372
  return $tsf->generate_robots_meta( $args, null, $ignore );
373
  }
374
 
@@ -381,9 +284,8 @@ final class Deprecated {
381
  * @since 2.9.2 Now also checks for permalinks.
382
  * @since 2.9.3 Now also checks for sitemap_robots option.
383
  * @since 3.1.0 Removed Jetpack's sitemap check -- it's no longer valid.
384
- * @since 4.0.0 : 1. Now uses has_robots_txt()
385
- * : 2. Now uses the get_robots_txt_url() to determine validity.
386
- * FIXME This method also checks for file existence (and location...), but is only used when the file definitely doesn't exist.
387
  * @since 4.1.4 Soft deprecated.
388
  * @since 4.2.0 Hard deprecation.
389
  * @deprecated
@@ -393,9 +295,8 @@ final class Deprecated {
393
  */
394
  public function can_do_sitemap_robots( $check_option = true ) {
395
 
396
- $tsf = \the_seo_framework();
397
-
398
- // $tsf->_deprecated_function( 'the_seo_framework()->is_robots_meta_noindex_set_by_args()', '4.2.0' );
399
 
400
  if ( $check_option ) {
401
  if ( ! $tsf->get_option( 'sitemaps_output' )
@@ -431,7 +332,7 @@ final class Deprecated {
431
  * @param bool $use_tabs Whether to output tabs, only works when $tabs count is greater than 1.
432
  */
433
  public function nav_tab_wrapper( $id, $tabs = [], $depr = null, $use_tabs = true ) {
434
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->nav_tab_wrapper()', '4.2.0', '\The_SEO_Framework\Bridges\PostSettings::_nav_tab_wrapper' );
435
  \The_SEO_Framework\Bridges\SeoSettings::_nav_tab_wrapper( $id, $tabs, $use_tabs );
436
  }
437
 
@@ -458,7 +359,7 @@ final class Deprecated {
458
  * @param bool $use_tabs Whether to output tabs, only works when $tabs count is greater than 1.
459
  */
460
  public function inpost_flex_nav_tab_wrapper( $id, $tabs = [], $_depr = null, $use_tabs = true ) {
461
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->inpost_flex_nav_tab_wrapper()', '4.2.0', '\The_SEO_Framework\Bridges\PostSettings::_flex_nav_tab_wrapper' );
462
  \The_SEO_Framework\Bridges\PostSettings::_flex_nav_tab_wrapper( $id, $tabs, $use_tabs );
463
  }
464
 
@@ -478,7 +379,7 @@ final class Deprecated {
478
  * @return string The image uploader button.
479
  */
480
  public function get_social_image_uploader_form( $input_id ) {
481
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->get_social_image_uploader_form()', '4.2.0', 'The_SEO_Framework\Interpreters\Form::get_image_uploader_form()' );
482
  return \The_SEO_Framework\Interpreters\Form::get_image_uploader_form( [ 'id' => $input_id ] );
483
  }
484
 
@@ -497,7 +398,7 @@ final class Deprecated {
497
  * @return string The image uploader button.
498
  */
499
  public function get_logo_uploader_form( $input_id ) {
500
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->get_logo_uploader_form()', '4.2.0', 'The_SEO_Framework\Interpreters\Form::get_image_uploader_form()' );
501
  return \The_SEO_Framework\Interpreters\Form::get_image_uploader_form( [
502
  'id' => $input_id,
503
  'data' => [
@@ -523,7 +424,8 @@ final class Deprecated {
523
  * @ignore Unused. The relying methods were yeeted off in 4.0.0.
524
  * "We no longer automatically resize images when they’re deemed too large."
525
  * @since 4.1.4 Deprecated silently. Marked for quick deletion.
526
- * @TODO delete me, bypass deprecation? This method makes no sense to the outsider, anyway. -> 4.2.0
 
527
  *
528
  * @param int $i The dimension to resize.
529
  * @param int $r1 The dimension that determines the ratio.
@@ -531,6 +433,8 @@ final class Deprecated {
531
  * @return int The proportional dimension, rounded.
532
  */
533
  public function proportionate_dimensions( $i, $r1, $r2 ) {
 
 
534
  return round( $i / ( $r1 / $r2 ) );
535
  }
536
 
@@ -545,8 +449,8 @@ final class Deprecated {
545
  * @return string The escaped SEO Settings page URL.
546
  */
547
  public function seo_settings_page_url() {
548
- $tsf = \the_seo_framework();
549
- // $tsf->_deprecated_function( 'the_seo_framework()->seo_settings_page_url()', '4.2.0', 'the_seo_framework()->get_seo_settings_page_url()' );
550
  return $tsf->get_seo_settings_page_url();
551
  }
552
 
@@ -556,12 +460,13 @@ final class Deprecated {
556
  * @since 3.0.0
557
  * @since 4.1.4 Deprecated silently. Use `get_user_meta_defaults()` instead.
558
  * @since 4.2.0 Hard deprecation.
 
559
  *
560
  * @return array The default user meta index and values.
561
  */
562
  public function get_default_user_data() {
563
- $tsf = \the_seo_framework();
564
- // $tsf->_deprecated_function( 'the_seo_framework()->get_default_user_data()', '4.2.0', 'the_seo_framework()->get_user_meta_defaults()' );
565
  return $tsf->get_user_meta_defaults();
566
  }
567
 
@@ -578,6 +483,7 @@ final class Deprecated {
578
  * 3. Added not-found cache.
579
  * @since 4.1.4 Deprecated silently. Use `get_user_meta()` instead.
580
  * @since 4.2.0 Hard deprecation.
 
581
  *
582
  * @param int $user_id The user ID. When empty, it will try to fetch the current user.
583
  * @param string $option The option name.
@@ -585,8 +491,8 @@ final class Deprecated {
585
  * @return mixed The metadata value.
586
  */
587
  public function get_user_option( $user_id = 0, $option = '', $default = null ) {
588
- $tsf = \the_seo_framework();
589
- // $tsf->_deprecated_function( 'the_seo_framework()->get_user_option()', '4.2.0', 'the_seo_framework()->get_user_meta_item()' );
590
  return $tsf->get_user_meta_item( $user_id ?: $tsf->get_user_id(), $option ) ?: $default;
591
  }
592
 
@@ -596,6 +502,7 @@ final class Deprecated {
596
  * @since 3.0.0
597
  * @since 4.1.4 Silently deprecated. use `get_current_post_author_id()` instead.
598
  * @since 4.2.0 Hard deprecation.
 
599
  *
600
  * @param int $author_id The author ID. When empty, it will return $default.
601
  * @param string $option The option name. When empty, it will return $default.
@@ -603,8 +510,8 @@ final class Deprecated {
603
  * @return mixed The metadata value
604
  */
605
  public function get_author_option( $author_id, $option, $default = null ) {
606
- $tsf = \the_seo_framework();
607
- // $tsf->_deprecated_function( 'the_seo_framework()->get_author_option()', '4.2.0', 'the_seo_framework()->get_current_post_author_id()' );
608
  return $tsf->get_user_meta_item( $option, $author_id ?: $tsf->get_current_post_author_id() ) ?: $default;
609
  }
610
 
@@ -614,14 +521,15 @@ final class Deprecated {
614
  * @since 3.0.0
615
  * @since 4.1.4 Silently deprecated. Use `get_current_post_author_meta_item()` instead.
616
  * @since 4.2.0 Hard deprecation.
 
617
  *
618
  * @param string $option The option name.
619
  * @param mixed $default The default value to return when the data doesn't exist.
620
  * @return mixed The metadata value
621
  */
622
  public function get_current_author_option( $option, $default = null ) {
623
- $tsf = \the_seo_framework();
624
- // $tsf->_deprecated_function( 'the_seo_framework()->get_current_author_option()', '4.2.0', 'the_seo_framework()->get_current_post_author_meta_item()' );
625
  return $tsf->get_current_post_author_meta_item( $option ) ?: $default;
626
  }
627
 
@@ -642,7 +550,7 @@ final class Deprecated {
642
  */
643
  public function is_wc_shop( $post = null ) {
644
 
645
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->is_wc_shop()', '5.0.0', 'the_seo_framework()->is_shop()' );
646
 
647
  if ( isset( $post ) ) {
648
  $post = \get_post( $post );
@@ -664,8 +572,8 @@ final class Deprecated {
664
  * Determines if the page is the WooCommerce plugin Product page.
665
  *
666
  * @since 2.5.2
667
- * @since 4.0.0 : 1. Added admin support.
668
- * 2. Added parameter for the Post ID or post to test.
669
  * @since 4.0.5 Soft deprecated.
670
  * @since 4.1.4 1. Another silent deprecation. Use `is_product()` instead.
671
  * 2. Removed output memoization.
@@ -678,8 +586,8 @@ final class Deprecated {
678
  */
679
  public function is_wc_product( $post = 0 ) {
680
 
681
- $tsf = \the_seo_framework();
682
- // $tsf->_deprecated_function( 'the_seo_framework()->is_wc_product()', '5.0.0', 'the_seo_framework()->is_product()' );
683
 
684
  if ( \is_admin() )
685
  return $tsf->is_wc_product_admin();
@@ -708,8 +616,8 @@ final class Deprecated {
708
  * @return bool
709
  */
710
  public function is_wc_product_admin() {
711
- $tsf = \the_seo_framework();
712
- // $tsf->_deprecated_function( 'the_seo_framework()->is_wc_product_admin()', '5.0.0', 'the_seo_framework()->is_product_admin()' );
713
  // Checks for "is_singular_admin()" because the post type is non-hierarchical.
714
  return $tsf->is_singular_admin() && 'product' === $tsf->get_admin_post_type();
715
  }
@@ -720,6 +628,8 @@ final class Deprecated {
720
  * @since 2.7.0
721
  * @since 2.8.0 New users now get a new array assigned.
722
  * @since 4.1.4 Deprecated silently. Use `update_single_user_meta_item()` instead.
 
 
723
  *
724
  * @param int $user_id The user ID.
725
  * @param string $option The user's SEO metadata option.
@@ -728,8 +638,8 @@ final class Deprecated {
728
  */
729
  public function update_user_option( $user_id = 0, $option = '', $value = '' ) {
730
 
731
- $tsf = \the_seo_framework();
732
- // $tsf->_deprecated_function( 'the_seo_framework()->update_user_option()', '5.0.0', 'the_seo_framework()->update_single_user_meta_item()' );
733
 
734
  if ( ! $option )
735
  return false;
@@ -760,15 +670,15 @@ final class Deprecated {
760
  *
761
  * @since 2.2.2
762
  * @since 4.1.4 Deprecated silently.
763
- * @since 5.0.0 Hard deprecation.
764
  * @deprecated
765
  *
766
  * @param string $name Field name base
767
  * @return string Full field name
768
  */
769
  public function get_field_name( $name ) {
770
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->get_field_name()', '5.0.0' );
771
- return \The_SEO_Framework\Interpreters\Form::get_field_name( $name );
772
  }
773
 
774
  /**
@@ -776,14 +686,15 @@ final class Deprecated {
776
  *
777
  * @since 2.2.2
778
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
779
- * @since 5.0.0 Hard deprecation.
 
780
  * @uses $this->get_field_name() Construct name attributes for use in form fields.
781
  *
782
  * @param string $name Field name base
783
  */
784
  public function field_name( $name ) {
785
- // $tsf->_deprecated_function( 'the_seo_framework()->field_name()', '5.0.0' );
786
- return \The_SEO_Framework\Interpreters\Form::field_name( $name );
787
  }
788
 
789
  /**
@@ -791,14 +702,15 @@ final class Deprecated {
791
  *
792
  * @since 2.2.2
793
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
794
- * @since 5.0.0 Hard deprecation.
 
795
  *
796
  * @param string $id Field id base
797
  * @return string Full field id
798
  */
799
  public function get_field_id( $id ) {
800
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->get_field_id()', '5.0.0' );
801
- return \The_SEO_Framework\Interpreters\Form::get_field_id( $id );
802
  }
803
 
804
  /**
@@ -806,7 +718,8 @@ final class Deprecated {
806
  *
807
  * @since 2.2.2
808
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
809
- * @since 5.0.0 Hard deprecation.
 
810
  * @uses $this->get_field_id() Constructs id attributes for use in form fields.
811
  *
812
  * @param string $id Field id base.
@@ -814,8 +727,13 @@ final class Deprecated {
814
  * @return string Full field id
815
  */
816
  public function field_id( $id, $echo = true ) {
817
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->field_id()', '5.0.0' );
818
- return \The_SEO_Framework\Interpreters\Form::field_id( $id, $echo );
 
 
 
 
 
819
  }
820
 
821
  /**
@@ -824,13 +742,14 @@ final class Deprecated {
824
  *
825
  * @since 2.0.0
826
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
827
- * @since 5.0.0 Hard deprecation.
 
828
  *
829
  * @param string $content Content to be wrapped in code tags.
830
  * @return string Content wrapped in code tags.
831
  */
832
  public function code_wrap( $content ) {
833
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->code_wrap()', '5.0.0' );
834
  return \The_SEO_Framework\Interpreters\HTML::code_wrap( $content );
835
  }
836
 
@@ -840,13 +759,14 @@ final class Deprecated {
840
  *
841
  * @since 2.2.2
842
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
843
- * @since 5.0.0 Hard deprecation.
 
844
  *
845
  * @param string $content Content to be wrapped in code tags.
846
  * @return string Content wrapped in code tags.
847
  */
848
  public function code_wrap_noesc( $content ) {
849
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->code_wrap_noesc()', '5.0.0' );
850
  return \The_SEO_Framework\Interpreters\HTML::code_wrap_noesc( $content );
851
  }
852
 
@@ -856,13 +776,14 @@ final class Deprecated {
856
  *
857
  * @since 2.7.0
858
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
859
- * @since 5.0.0 Hard deprecation.
 
860
  *
861
  * @param string $content Content to be wrapped in the description wrap.
862
  * @param bool $block Whether to wrap the content in <p> tags.
863
  */
864
  public function description( $content, $block = true ) {
865
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->description()', '5.0.0' );
866
  return \The_SEO_Framework\Interpreters\HTML::description( $content, $block );
867
  }
868
 
@@ -871,13 +792,14 @@ final class Deprecated {
871
  *
872
  * @since 2.7.0
873
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
874
- * @since 5.0.0 Hard deprecation.
 
875
  *
876
  * @param string $content Content to be wrapped in the description wrap. Expected to be escaped.
877
  * @param bool $block Whether to wrap the content in <p> tags.
878
  */
879
  public function description_noesc( $content, $block = true ) {
880
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->description_noesc()', '5.0.0' );
881
  return \The_SEO_Framework\Interpreters\HTML::description_noesc( $content, $block );
882
  }
883
 
@@ -887,13 +809,14 @@ final class Deprecated {
887
  *
888
  * @since 3.1.0
889
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
890
- * @since 5.0.0 Hard deprecation.
 
891
  *
892
  * @param string $content Content to be wrapped in the attention wrap.
893
  * @param bool $block Whether to wrap the content in <p> tags.
894
  */
895
  public function attention( $content, $block = true ) {
896
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->attention()', '5.0.0' );
897
  return \The_SEO_Framework\Interpreters\HTML::attention( $content, $block );
898
  }
899
 
@@ -902,13 +825,14 @@ final class Deprecated {
902
  *
903
  * @since 3.1.0
904
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
905
- * @since 5.0.0 Hard deprecation.
 
906
  *
907
  * @param string $content Content to be wrapped in the attention wrap. Expected to be escaped.
908
  * @param bool $block Whether to wrap the content in <p> tags.
909
  */
910
  public function attention_noesc( $content, $block = true ) {
911
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->attention_noesc()', '5.0.0' );
912
  return \The_SEO_Framework\Interpreters\HTML::attention_noesc( $content, $block );
913
  }
914
 
@@ -918,13 +842,14 @@ final class Deprecated {
918
  *
919
  * @since 3.1.0
920
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
921
- * @since 5.0.0 Hard deprecation.
 
922
  *
923
  * @param string $content Content to be wrapped in the wrap. Expected to be escaped.
924
  * @param bool $block Whether to wrap the content in <p> tags.
925
  */
926
  public function attention_description( $content, $block = true ) {
927
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->attention_description()', '5.0.0' );
928
  return \The_SEO_Framework\Interpreters\HTML::attention_description( $content, $block );
929
  }
930
 
@@ -933,13 +858,14 @@ final class Deprecated {
933
  *
934
  * @since 3.1.0
935
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
936
- * @since 5.0.0 Hard deprecation.
 
937
  *
938
  * @param string $content Content to be wrapped in the wrap. Expected to be escaped.
939
  * @param bool $block Whether to wrap the content in <p> tags.
940
  */
941
  public function attention_description_noesc( $content, $block = true ) {
942
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->attention_description_noesc()', '5.0.0' );
943
  return \The_SEO_Framework\Interpreters\HTML::attention_description_noesc( $content, $block );
944
  }
945
 
@@ -950,14 +876,15 @@ final class Deprecated {
950
  *
951
  * @since 2.6.0
952
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
953
- * @since 5.0.0 Hard deprecation.
 
954
  *
955
  * @param string $input The input to wrap. Should already be escaped.
956
  * @param bool $echo Whether to escape echo or just return.
957
  * @return string|void Wrapped $input.
958
  */
959
  public function wrap_fields( $input = '', $echo = false ) {
960
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->wrap_fields()', '5.0.0' );
961
  return \The_SEO_Framework\Interpreters\HTML::wrap_fields( $input, $echo );
962
  }
963
 
@@ -968,7 +895,8 @@ final class Deprecated {
968
  * @since 3.0.0 Links are now no longer followed, referred or bound to opener.
969
  * @since 4.0.0 Now adds a tabindex to the span tag, so you can focus it using keyboard navigation.
970
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
971
- * @since 5.0.0 Hard deprecation.
 
972
  *
973
  * @param string $description The descriptive on-hover title.
974
  * @param string $link The non-escaped link.
@@ -976,7 +904,7 @@ final class Deprecated {
976
  * @return string HTML checkbox output if $echo is false.
977
  */
978
  public function make_info( $description = '', $link = '', $echo = true ) {
979
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->make_info()', '5.0.0' );
980
  return \The_SEO_Framework\Interpreters\HTML::make_info( $description, $link, $echo );
981
  }
982
 
@@ -986,16 +914,17 @@ final class Deprecated {
986
  * @since 4.0.0
987
  * @since 4.1.0 No longer adds an extra space in front of the return value when no data is generated.
988
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
989
- * @since 5.0.0 Hard deprecation.
990
  * @internal
 
991
  *
992
  * @param array $data : {
993
  * string $k => mixed $v
994
  * }
995
  * @return string The HTML data attributes, with added space to the start.
996
  */
997
- public function make_data_attributes( array $data ) {
998
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->make_data_attributes()', '5.0.0' );
999
  return \The_SEO_Framework\Interpreters\HTML::make_data_attributes( $data );
1000
  }
1001
 
@@ -1006,7 +935,8 @@ final class Deprecated {
1006
  * @since 2.7.0 Added escape parameter. Defaults to true.
1007
  * @since 3.0.3 Added $disabled parameter. Defaults to false.
1008
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
1009
- * @since 5.0.0 Hard deprecation.
 
1010
  *
1011
  * @param string $field_id The option ID. Must be within the Autodescription settings.
1012
  * @param string $label The checkbox description label.
@@ -1016,8 +946,8 @@ final class Deprecated {
1016
  * @return string HTML checkbox output.
1017
  */
1018
  public function make_checkbox( $field_id = '', $label = '', $description = '', $escape = true, $disabled = false ) {
1019
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->make_checkbox()', '5.0.0' );
1020
- return \The_SEO_Framework\Interpreters\Form::make_checkbox( [
1021
  'id' => $field_id,
1022
  'index' => '',
1023
  'label' => $label,
@@ -1033,7 +963,8 @@ final class Deprecated {
1033
  *
1034
  * @since 4.0.0
1035
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
1036
- * @since 5.0.0 Hard deprecation.
 
1037
  *
1038
  * @param array $args : {
1039
  * string $id The select field ID.
@@ -1048,8 +979,8 @@ final class Deprecated {
1048
  * }
1049
  * @return string The option field.
1050
  */
1051
- public function make_single_select_form( array $args ) {
1052
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->make_single_select_form()', '5.0.0' );
1053
  return \The_SEO_Framework\Interpreters\Form::make_single_select_form( $args );
1054
  }
1055
 
@@ -1062,7 +993,9 @@ final class Deprecated {
1062
  * @since 2.2.5
1063
  * @since 3.1.0 Deprecated second parameter.
1064
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
1065
- * @since 5.0.0 Hard deprecation.
 
 
1066
  *
1067
  * @param string $key The option name which returns boolean.
1068
  * @param string $depr Deprecated
@@ -1070,17 +1003,20 @@ final class Deprecated {
1070
  * @param bool $echo Whether to echo or return the output.
1071
  * @return string Empty on echo or the class name with an optional wrapper.
1072
  */
1073
- public function is_default_checked( $key, $depr = '', $wrap = true, $echo = true ) {
1074
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->is_default_checked()', '5.0.0' );
1075
- return \The_SEO_Framework\Interpreters\Form::is_default_checked( $key, $wrap, $echo );
1076
  }
 
1077
  /**
1078
  * Returns the HTML class wrap for warning Checkbox options.
1079
  *
1080
  * @since 2.3.4
1081
  * @since 3.1.0 Deprecated second parameter.
1082
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
1083
- * @since 5.0.0 Hard deprecation.
 
 
1084
  *
1085
  * @param string $key The option name which returns boolean.
1086
  * @param string $deprecated Deprecated.
@@ -1088,24 +1024,27 @@ final class Deprecated {
1088
  * @param bool $echo Whether to echo or return the output.
1089
  * @return string Empty on echo or the class name with an optional wrapper.
1090
  */
1091
- public function is_warning_checked( $key, $deprecated = '', $wrap = true, $echo = true ) {
1092
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->is_warning_checked()', '5.0.0' );
1093
- return \The_SEO_Framework\Interpreters\Form::is_warning_checked( $key, $wrap, $echo );
1094
  }
 
1095
  /**
1096
  * Returns the HTML class wrap for warning/default Checkbox options.
1097
  *
1098
  * @since 2.6.0
1099
  * @since 3.1.0 Added the $wrap parameter.
1100
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
1101
- * @since 5.0.0 Hard deprecation.
 
 
1102
  *
1103
  * @param string $key The option name which returns boolean.
1104
  * @param bool $wrap Whether to wrap the class name in `class="%s"`
1105
  */
1106
- public function get_is_conditional_checked( $key, $wrap = true ) {
1107
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->get_is_conditional_checked()', '5.0.0' );
1108
- return \The_SEO_Framework\Interpreters\Form::get_is_conditional_checked( $key, $wrap );
1109
  }
1110
 
1111
  /**
@@ -1114,7 +1053,9 @@ final class Deprecated {
1114
  * @since 2.3.4
1115
  * @since 3.1.0 Deprecated second parameter.
1116
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
1117
- * @since 5.0.0 Hard deprecation.
 
 
1118
  *
1119
  * @param string $key The option name which returns boolean.
1120
  * @param string $deprecated Deprecated. Used to be the settings field.
@@ -1122,28 +1063,29 @@ final class Deprecated {
1122
  * @param bool $echo Whether to echo or return the output.
1123
  * @return string Empty on echo or the class name with an optional wrapper.
1124
  */
1125
- public function is_conditional_checked( $key, $deprecated = '', $wrap = true, $echo = true ) {
1126
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->is_conditional_checked()', '5.0.0' );
1127
- return \The_SEO_Framework\Interpreters\Form::is_conditional_checked( $key, $wrap, $echo );
1128
  }
1129
 
1130
  /**
1131
  * Outputs character counter wrap for both JavaScript and no-Javascript.
1132
  *
1133
  * @since 3.0.0
1134
- * @since 3.1.0 : 1. Added an "what if you click" onhover-title.
1135
- * 2. Removed second parameter's usage. For passing the expected string.
1136
- * 3. The whole output is now hidden from no-js.
1137
  * @since 4.1.0 No longer marks up the counter with the `description` HTML class.
1138
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
1139
- * @since 5.0.0 Hard deprecation.
 
1140
  *
1141
  * @param string $for The input ID it's for.
1142
  * @param string $depr The initial value for no-JS. Deprecated.
1143
  * @param bool $display Whether to display the counter. (options page gimmick)
1144
  */
1145
  public function output_character_counter_wrap( $for, $depr = '', $display = true ) {
1146
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->output_character_counter_wrap()', '5.0.0' );
1147
  return \The_SEO_Framework\Interpreters\Form::output_character_counter_wrap( $for, $display );
1148
  }
1149
 
@@ -1152,14 +1094,612 @@ final class Deprecated {
1152
  *
1153
  * @since 3.0.0
1154
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
1155
- * @since 5.0.0 Hard deprecation.
 
1156
  *
1157
  * @param string $for The input ID it's for.
1158
  * @param string $type Whether it's a 'title' or 'description' counter.
1159
  * @param bool $display Whether to display the counter. (options page gimmick)
1160
  */
1161
  public function output_pixel_counter_wrap( $for, $type, $display = true ) {
1162
- // \the_seo_framework()->_deprecated_function( 'the_seo_framework()->output_pixel_counter_wrap()', '5.0.0' );
1163
  return \The_SEO_Framework\Interpreters\Form::output_pixel_counter_wrap( $for, $type, $display );
1164
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1165
  }
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Internal\Deprecated
4
  * @subpackage The_SEO_Framework\Debug\Deprecated
5
  */
6
 
7
+ namespace The_SEO_Framework\Internal;
 
 
8
 
9
  /**
10
  * The SEO Framework plugin
23
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
24
  */
25
 
26
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
+
28
+ use function \The_SEO_Framework\{
29
+ memo, // Precautionary.
30
+ umemo, // Precautionary.
31
+ };
32
+
33
  /**
34
+ * Class The_SEO_Framework\Internal\Deprecated
35
  *
36
  * Contains all deprecated functions.
37
  *
39
  * @since 3.1.0 Removed all methods deprecated in 3.0.0.
40
  * @since 4.0.0 Removed all methods deprecated in 3.1.0.
41
  * @since 4.1.4 Removed all methods deprecated in 4.0.0.
42
+ * @since 4.2.0 1. Changed namespace from \The_SEO_Framework to \The_SEO_Framework\Internal
43
+ * 2. Removed all methods deprecated in 4.1.0.
44
  * @ignore
45
  */
46
  final class Deprecated {
47
 
48
  /**
49
+ * Appends given query to given URL.
 
50
  *
51
+ * @since 3.0.0
52
+ * @since 3.1.0 Now uses parse_str and add_query_arg, preventing duplicated entries.
53
+ * @since 4.1.4 Deprecated silently.
54
+ * @since 4.2.0 Hard deprecation.
55
  * @deprecated
56
  *
57
+ * @param string $url A fully qualified URL.
58
+ * @param string $query A fully qualified query taken from parse_url( $url, PHP_URL_QUERY );
59
+ * @return string A fully qualified URL with appended $query.
60
  */
61
+ public function append_php_query( $url, $query = '' ) {
62
+ $tsf = \tsf();
63
+ $tsf->_deprecated_function( 'tsf()->append_php_query()', '4.2.0', 'tsf()->append_url_query()' );
64
+ return $tsf->append_url_query( $url, $query );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  }
66
 
67
  /**
68
+ * Runs header actions.
69
  *
70
  * @since 3.1.0
71
+ * @since 4.2.0 Deprecated
 
72
  * @deprecated
73
  *
74
+ * @param string $location Either 'before' or 'after'.
75
+ * @return string The filter output.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  */
77
+ public function get_legacy_header_filters_output( $location = 'before' ) {
78
 
79
+ $output = '';
80
 
81
+ /**
82
+ * @since 2.2.6
83
+ * @since 4.2.0 Deprecated
84
+ * @deprecated
85
+ * @param array $functions {
86
+ * 'callback' => string|array The function to call.
87
+ * 'args' => scalar|array Arguments. When array, each key is a new argument.
88
+ * }
89
+ */
90
+ $functions = (array) \apply_filters_deprecated(
91
+ "the_seo_framework_{$location}_output",
92
+ [ [] ],
93
+ '4.2.0 of The SEO Framework',
94
+ "Action the_seo_framework_{$location}_meta"
95
+ );
96
+
97
+ foreach ( $functions as $function ) {
98
+ if ( ! empty( $function['callback'] ) ) {
99
+ $output .= \call_user_func_array( $function['callback'], (array) ( $function['args'] ?? '' ) );
100
+ }
101
  }
102
 
103
+ return $output;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  }
105
 
106
  /**
115
  */
116
  public function get_html_output() {
117
 
118
+ $tsf = \tsf();
119
+ $tsf->_deprecated_function( 'tsf()->get_html_output()', '4.2.0' );
 
120
 
121
  $robots = $tsf->robots();
122
 
 
 
 
123
  /**
124
  * @since 2.6.0
125
  * @param string $before The content before the SEO output.
195
  */
196
  $after = (string) \apply_filters( 'the_seo_framework_pro', '' );
197
 
 
 
 
198
  return "{$robots}{$before}{$before_legacy}{$output}{$after_legacy}{$after}";
199
  }
200
 
212
  * @since 4.0.0
213
  * @since 4.1.0 Now uses the new taxonomy robots settings.
214
  * @since 4.1.4 Soft deprecated. Use 'robots_meta' instead.
215
+ * @since 4.2.0 1. Hard deprecation.
216
+ * 2. Now supports the `$args['pta']` index. (inferred)
217
  * @deprecated
218
  *
219
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
220
  * @param int <bit> $ignore The ignore level. {
221
  * 0 = 0b00: Ignore nothing.
222
  * 1 = 0b01: Ignore protection. (\The_SEO_Framework\ROBOTS_IGNORE_PROTECTION)
226
  * @return bool Whether noindex is set or not
227
  */
228
  public function is_robots_meta_noindex_set_by_args( $args, $ignore = 0b00 ) {
229
+ $tsf = \tsf();
230
+ $tsf->_deprecated_function( 'tsf()->is_robots_meta_noindex_set_by_args()', '4.2.0', 'tsf()->generate_robots_meta()' );
231
  $meta = $tsf->generate_robots_meta( $args, null, $ignore );
232
  return isset( $meta['noindex'] ) && 'noindex' === $meta['noindex'];
233
  }
239
  * @since 2.2.4 Added robots SEO settings check.
240
  * @since 2.2.8 Added check for empty archives.
241
  * @since 2.8.0 Added check for protected/private posts.
242
+ * @since 3.0.0 1. Removed noodp.
243
+ * 2. Improved efficiency by grouping if statements.
244
+ * @since 3.1.0 1. Simplified statements, often (not always) speeding things up.
245
+ * 2. Now checks for wc_shop and blog types for pagination.
246
+ * 3. Removed noydir.
247
+ * @since 4.0.0 1. Now tests for qubit metadata.
248
+ * 2. Added custom query support.
249
+ * 3. Added two parameters.
250
+ * @since 4.0.2 1. Added new copyright directive tags.
251
+ * 2. Now strictly parses the validity of robots directives via a boolean check.
252
+ * @since 4.0.3 1. Changed `max_snippet_length` to `max_snippet`
253
+ * 2. Changed the copyright directive's spacer from `=` to `:`.
254
+ * @since 4.0.5 1. Removed copyright directive bug workaround. <https://kb.theseoframework.com/kb/why-is-max-image-preview-none-purged/>
255
+ * 2. Now sets noindex and nofollow when queries are exploited (requires option enabled).
256
  * @since 4.1.4 Deprecated silently. Use generate_robots_meta() instead.
257
+ * @since 4.2.0 1. Hard deprecation.
258
+ * 2. Now supports the `$args['pta']` index. (inferred)
259
+ * @deprecated
260
  *
261
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
262
  * @param int <bit> $ignore The ignore level. {
263
  * 0 = 0b00: Ignore nothing.
264
  * 1 = 0b01: Ignore protection. (\The_SEO_Framework\ROBOTS_IGNORE_PROTECTION)
270
  * }
271
  */
272
  public function robots_meta( $args = null, $ignore = 0b00 ) {
273
+ $tsf = \tsf();
274
+ $tsf->_deprecated_function( 'tsf()->robots_meta()', '4.2.0', 'tsf()->generate_robots_meta()' );
275
  return $tsf->generate_robots_meta( $args, null, $ignore );
276
  }
277
 
284
  * @since 2.9.2 Now also checks for permalinks.
285
  * @since 2.9.3 Now also checks for sitemap_robots option.
286
  * @since 3.1.0 Removed Jetpack's sitemap check -- it's no longer valid.
287
+ * @since 4.0.0 1. Now uses has_robots_txt()
288
+ * 2. Now uses the get_robots_txt_url() to determine validity.
 
289
  * @since 4.1.4 Soft deprecated.
290
  * @since 4.2.0 Hard deprecation.
291
  * @deprecated
295
  */
296
  public function can_do_sitemap_robots( $check_option = true ) {
297
 
298
+ $tsf = \tsf();
299
+ $tsf->_deprecated_function( 'tsf()->can_do_sitemap_robots()', '4.2.0' );
 
300
 
301
  if ( $check_option ) {
302
  if ( ! $tsf->get_option( 'sitemaps_output' )
332
  * @param bool $use_tabs Whether to output tabs, only works when $tabs count is greater than 1.
333
  */
334
  public function nav_tab_wrapper( $id, $tabs = [], $depr = null, $use_tabs = true ) {
335
+ \tsf()->_deprecated_function( 'tsf()->nav_tab_wrapper()', '4.2.0', '\The_SEO_Framework\Bridges\SeoSettings::_nav_tab_wrapper' );
336
  \The_SEO_Framework\Bridges\SeoSettings::_nav_tab_wrapper( $id, $tabs, $use_tabs );
337
  }
338
 
359
  * @param bool $use_tabs Whether to output tabs, only works when $tabs count is greater than 1.
360
  */
361
  public function inpost_flex_nav_tab_wrapper( $id, $tabs = [], $_depr = null, $use_tabs = true ) {
362
+ \tsf()->_deprecated_function( 'tsf()->inpost_flex_nav_tab_wrapper()', '4.2.0', '\The_SEO_Framework\Bridges\PostSettings::_flex_nav_tab_wrapper' );
363
  \The_SEO_Framework\Bridges\PostSettings::_flex_nav_tab_wrapper( $id, $tabs, $use_tabs );
364
  }
365
 
379
  * @return string The image uploader button.
380
  */
381
  public function get_social_image_uploader_form( $input_id ) {
382
+ \tsf()->_deprecated_function( 'tsf()->get_social_image_uploader_form()', '4.2.0', 'The_SEO_Framework\Interpreters\Form::get_image_uploader_form()' );
383
  return \The_SEO_Framework\Interpreters\Form::get_image_uploader_form( [ 'id' => $input_id ] );
384
  }
385
 
398
  * @return string The image uploader button.
399
  */
400
  public function get_logo_uploader_form( $input_id ) {
401
+ \tsf()->_deprecated_function( 'tsf()->get_logo_uploader_form()', '4.2.0', 'The_SEO_Framework\Interpreters\Form::get_image_uploader_form()' );
402
  return \The_SEO_Framework\Interpreters\Form::get_image_uploader_form( [
403
  'id' => $input_id,
404
  'data' => [
424
  * @ignore Unused. The relying methods were yeeted off in 4.0.0.
425
  * "We no longer automatically resize images when they’re deemed too large."
426
  * @since 4.1.4 Deprecated silently. Marked for quick deletion.
427
+ * @since 4.2.0 Hard deprecation.
428
+ * @deprecated
429
  *
430
  * @param int $i The dimension to resize.
431
  * @param int $r1 The dimension that determines the ratio.
433
  * @return int The proportional dimension, rounded.
434
  */
435
  public function proportionate_dimensions( $i, $r1, $r2 ) {
436
+ $tsf = \tsf();
437
+ $tsf->_deprecated_function( 'tsf()->proportionate_dimensions()', '4.2.0' );
438
  return round( $i / ( $r1 / $r2 ) );
439
  }
440
 
449
  * @return string The escaped SEO Settings page URL.
450
  */
451
  public function seo_settings_page_url() {
452
+ $tsf = \tsf();
453
+ $tsf->_deprecated_function( 'tsf()->seo_settings_page_url()', '4.2.0', 'tsf()->get_seo_settings_page_url()' );
454
  return $tsf->get_seo_settings_page_url();
455
  }
456
 
460
  * @since 3.0.0
461
  * @since 4.1.4 Deprecated silently. Use `get_user_meta_defaults()` instead.
462
  * @since 4.2.0 Hard deprecation.
463
+ * @deprecated
464
  *
465
  * @return array The default user meta index and values.
466
  */
467
  public function get_default_user_data() {
468
+ $tsf = \tsf();
469
+ $tsf->_deprecated_function( 'tsf()->get_default_user_data()', '4.2.0', 'tsf()->get_user_meta_defaults()' );
470
  return $tsf->get_user_meta_defaults();
471
  }
472
 
483
  * 3. Added not-found cache.
484
  * @since 4.1.4 Deprecated silently. Use `get_user_meta()` instead.
485
  * @since 4.2.0 Hard deprecation.
486
+ * @deprecated
487
  *
488
  * @param int $user_id The user ID. When empty, it will try to fetch the current user.
489
  * @param string $option The option name.
491
  * @return mixed The metadata value.
492
  */
493
  public function get_user_option( $user_id = 0, $option = '', $default = null ) {
494
+ $tsf = \tsf();
495
+ $tsf->_deprecated_function( 'tsf()->get_user_option()', '4.2.0', 'tsf()->get_user_meta_item()' );
496
  return $tsf->get_user_meta_item( $user_id ?: $tsf->get_user_id(), $option ) ?: $default;
497
  }
498
 
502
  * @since 3.0.0
503
  * @since 4.1.4 Silently deprecated. use `get_current_post_author_id()` instead.
504
  * @since 4.2.0 Hard deprecation.
505
+ * @deprecated
506
  *
507
  * @param int $author_id The author ID. When empty, it will return $default.
508
  * @param string $option The option name. When empty, it will return $default.
510
  * @return mixed The metadata value
511
  */
512
  public function get_author_option( $author_id, $option, $default = null ) {
513
+ $tsf = \tsf();
514
+ $tsf->_deprecated_function( 'tsf()->get_author_option()', '4.2.0', 'tsf()->get_current_post_author_id()' );
515
  return $tsf->get_user_meta_item( $option, $author_id ?: $tsf->get_current_post_author_id() ) ?: $default;
516
  }
517
 
521
  * @since 3.0.0
522
  * @since 4.1.4 Silently deprecated. Use `get_current_post_author_meta_item()` instead.
523
  * @since 4.2.0 Hard deprecation.
524
+ * @deprecated
525
  *
526
  * @param string $option The option name.
527
  * @param mixed $default The default value to return when the data doesn't exist.
528
  * @return mixed The metadata value
529
  */
530
  public function get_current_author_option( $option, $default = null ) {
531
+ $tsf = \tsf();
532
+ $tsf->_deprecated_function( 'tsf()->get_current_author_option()', '4.2.0', 'tsf()->get_current_post_author_meta_item()' );
533
  return $tsf->get_current_post_author_meta_item( $option ) ?: $default;
534
  }
535
 
550
  */
551
  public function is_wc_shop( $post = null ) {
552
 
553
+ \tsf()->_deprecated_function( 'tsf()->is_wc_shop()', '4.2.0', 'tsf()->is_shop()' );
554
 
555
  if ( isset( $post ) ) {
556
  $post = \get_post( $post );
572
  * Determines if the page is the WooCommerce plugin Product page.
573
  *
574
  * @since 2.5.2
575
+ * @since 4.0.0 1. Added admin support.
576
+ * 2. Added parameter for the Post ID or post to test.
577
  * @since 4.0.5 Soft deprecated.
578
  * @since 4.1.4 1. Another silent deprecation. Use `is_product()` instead.
579
  * 2. Removed output memoization.
586
  */
587
  public function is_wc_product( $post = 0 ) {
588
 
589
+ $tsf = \tsf();
590
+ $tsf->_deprecated_function( 'tsf()->is_wc_product()', '4.2.0', 'tsf()->is_product()' );
591
 
592
  if ( \is_admin() )
593
  return $tsf->is_wc_product_admin();
616
  * @return bool
617
  */
618
  public function is_wc_product_admin() {
619
+ $tsf = \tsf();
620
+ $tsf->_deprecated_function( 'tsf()->is_wc_product_admin()', '4.2.0', 'tsf()->is_product_admin()' );
621
  // Checks for "is_singular_admin()" because the post type is non-hierarchical.
622
  return $tsf->is_singular_admin() && 'product' === $tsf->get_admin_post_type();
623
  }
628
  * @since 2.7.0
629
  * @since 2.8.0 New users now get a new array assigned.
630
  * @since 4.1.4 Deprecated silently. Use `update_single_user_meta_item()` instead.
631
+ * @since 4.2.0 Hard deprecation.
632
+ * @deprecated
633
  *
634
  * @param int $user_id The user ID.
635
  * @param string $option The user's SEO metadata option.
638
  */
639
  public function update_user_option( $user_id = 0, $option = '', $value = '' ) {
640
 
641
+ $tsf = \tsf();
642
+ $tsf->_deprecated_function( 'tsf()->update_user_option()', '4.2.0', 'tsf()->update_single_user_meta_item()' );
643
 
644
  if ( ! $option )
645
  return false;
670
  *
671
  * @since 2.2.2
672
  * @since 4.1.4 Deprecated silently.
673
+ * @since 4.2.0 Hard deprecation.
674
  * @deprecated
675
  *
676
  * @param string $name Field name base
677
  * @return string Full field name
678
  */
679
  public function get_field_name( $name ) {
680
+ \tsf()->_deprecated_function( 'tsf()->get_field_name()', '4.2.0' );
681
+ return \The_SEO_Framework\Interpreters\Settings_Input::get_field_name( $name );
682
  }
683
 
684
  /**
686
  *
687
  * @since 2.2.2
688
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
689
+ * @since 4.2.0 Hard deprecation.
690
+ * @deprecated
691
  * @uses $this->get_field_name() Construct name attributes for use in form fields.
692
  *
693
  * @param string $name Field name base
694
  */
695
  public function field_name( $name ) {
696
+ \tsf()->_deprecated_function( 'tsf()->field_name()', '4.2.0' );
697
+ return \The_SEO_Framework\Interpreters\Settings_Input::field_name( $name );
698
  }
699
 
700
  /**
702
  *
703
  * @since 2.2.2
704
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
705
+ * @since 4.2.0 Hard deprecation.
706
+ * @deprecated
707
  *
708
  * @param string $id Field id base
709
  * @return string Full field id
710
  */
711
  public function get_field_id( $id ) {
712
+ \tsf()->_deprecated_function( 'tsf()->get_field_id()', '4.2.0' );
713
+ return \The_SEO_Framework\Interpreters\Settings_Input::get_field_id( $id );
714
  }
715
 
716
  /**
718
  *
719
  * @since 2.2.2
720
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
721
+ * @since 4.2.0 Hard deprecation.
722
+ * @deprecated
723
  * @uses $this->get_field_id() Constructs id attributes for use in form fields.
724
  *
725
  * @param string $id Field id base.
727
  * @return string Full field id
728
  */
729
  public function field_id( $id, $echo = true ) {
730
+ \tsf()->_deprecated_function( 'tsf()->field_id()', '4.2.0' );
731
+ if ( $echo ) {
732
+ // phpcs:ignore, WordPress.Security.EscapeOutput.OutputNotEscaped -- this escapes.
733
+ echo \The_SEO_Framework\Interpreters\Settings_Input::field_id( $id );
734
+ } else {
735
+ return \The_SEO_Framework\Interpreters\Settings_Input::field_id( $id );
736
+ }
737
  }
738
 
739
  /**
742
  *
743
  * @since 2.0.0
744
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
745
+ * @since 4.2.0 Hard deprecation.
746
+ * @deprecated
747
  *
748
  * @param string $content Content to be wrapped in code tags.
749
  * @return string Content wrapped in code tags.
750
  */
751
  public function code_wrap( $content ) {
752
+ \tsf()->_deprecated_function( 'tsf()->code_wrap()', '4.2.0' );
753
  return \The_SEO_Framework\Interpreters\HTML::code_wrap( $content );
754
  }
755
 
759
  *
760
  * @since 2.2.2
761
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
762
+ * @since 4.2.0 Hard deprecation.
763
+ * @deprecated
764
  *
765
  * @param string $content Content to be wrapped in code tags.
766
  * @return string Content wrapped in code tags.
767
  */
768
  public function code_wrap_noesc( $content ) {
769
+ \tsf()->_deprecated_function( 'tsf()->code_wrap_noesc()', '4.2.0' );
770
  return \The_SEO_Framework\Interpreters\HTML::code_wrap_noesc( $content );
771
  }
772
 
776
  *
777
  * @since 2.7.0
778
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
779
+ * @since 4.2.0 Hard deprecation.
780
+ * @deprecated
781
  *
782
  * @param string $content Content to be wrapped in the description wrap.
783
  * @param bool $block Whether to wrap the content in <p> tags.
784
  */
785
  public function description( $content, $block = true ) {
786
+ \tsf()->_deprecated_function( 'tsf()->description()', '4.2.0' );
787
  return \The_SEO_Framework\Interpreters\HTML::description( $content, $block );
788
  }
789
 
792
  *
793
  * @since 2.7.0
794
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
795
+ * @since 4.2.0 Hard deprecation.
796
+ * @deprecated
797
  *
798
  * @param string $content Content to be wrapped in the description wrap. Expected to be escaped.
799
  * @param bool $block Whether to wrap the content in <p> tags.
800
  */
801
  public function description_noesc( $content, $block = true ) {
802
+ \tsf()->_deprecated_function( 'tsf()->description_noesc()', '4.2.0' );
803
  return \The_SEO_Framework\Interpreters\HTML::description_noesc( $content, $block );
804
  }
805
 
809
  *
810
  * @since 3.1.0
811
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
812
+ * @since 4.2.0 Hard deprecation.
813
+ * @deprecated
814
  *
815
  * @param string $content Content to be wrapped in the attention wrap.
816
  * @param bool $block Whether to wrap the content in <p> tags.
817
  */
818
  public function attention( $content, $block = true ) {
819
+ \tsf()->_deprecated_function( 'tsf()->attention()', '4.2.0' );
820
  return \The_SEO_Framework\Interpreters\HTML::attention( $content, $block );
821
  }
822
 
825
  *
826
  * @since 3.1.0
827
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
828
+ * @since 4.2.0 Hard deprecation.
829
+ * @deprecated
830
  *
831
  * @param string $content Content to be wrapped in the attention wrap. Expected to be escaped.
832
  * @param bool $block Whether to wrap the content in <p> tags.
833
  */
834
  public function attention_noesc( $content, $block = true ) {
835
+ \tsf()->_deprecated_function( 'tsf()->attention_noesc()', '4.2.0' );
836
  return \The_SEO_Framework\Interpreters\HTML::attention_noesc( $content, $block );
837
  }
838
 
842
  *
843
  * @since 3.1.0
844
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
845
+ * @since 4.2.0 Hard deprecation.
846
+ * @deprecated
847
  *
848
  * @param string $content Content to be wrapped in the wrap. Expected to be escaped.
849
  * @param bool $block Whether to wrap the content in <p> tags.
850
  */
851
  public function attention_description( $content, $block = true ) {
852
+ \tsf()->_deprecated_function( 'tsf()->attention_description()', '4.2.0' );
853
  return \The_SEO_Framework\Interpreters\HTML::attention_description( $content, $block );
854
  }
855
 
858
  *
859
  * @since 3.1.0
860
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
861
+ * @since 4.2.0 Hard deprecation.
862
+ * @deprecated
863
  *
864
  * @param string $content Content to be wrapped in the wrap. Expected to be escaped.
865
  * @param bool $block Whether to wrap the content in <p> tags.
866
  */
867
  public function attention_description_noesc( $content, $block = true ) {
868
+ \tsf()->_deprecated_function( 'tsf()->attention_description_noesc()', '4.2.0' );
869
  return \The_SEO_Framework\Interpreters\HTML::attention_description_noesc( $content, $block );
870
  }
871
 
876
  *
877
  * @since 2.6.0
878
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
879
+ * @since 4.2.0 Hard deprecation.
880
+ * @deprecated
881
  *
882
  * @param string $input The input to wrap. Should already be escaped.
883
  * @param bool $echo Whether to escape echo or just return.
884
  * @return string|void Wrapped $input.
885
  */
886
  public function wrap_fields( $input = '', $echo = false ) {
887
+ \tsf()->_deprecated_function( 'tsf()->wrap_fields()', '4.2.0' );
888
  return \The_SEO_Framework\Interpreters\HTML::wrap_fields( $input, $echo );
889
  }
890
 
895
  * @since 3.0.0 Links are now no longer followed, referred or bound to opener.
896
  * @since 4.0.0 Now adds a tabindex to the span tag, so you can focus it using keyboard navigation.
897
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
898
+ * @since 4.2.0 Hard deprecation.
899
+ * @deprecated
900
  *
901
  * @param string $description The descriptive on-hover title.
902
  * @param string $link The non-escaped link.
904
  * @return string HTML checkbox output if $echo is false.
905
  */
906
  public function make_info( $description = '', $link = '', $echo = true ) {
907
+ \tsf()->_deprecated_function( 'tsf()->make_info()', '4.2.0', '\The_SEO_Framework\Interpreters\HTML::make_info()' );
908
  return \The_SEO_Framework\Interpreters\HTML::make_info( $description, $link, $echo );
909
  }
910
 
914
  * @since 4.0.0
915
  * @since 4.1.0 No longer adds an extra space in front of the return value when no data is generated.
916
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
917
+ * @since 4.2.0 Hard deprecation.
918
  * @internal
919
+ * @deprecated
920
  *
921
  * @param array $data : {
922
  * string $k => mixed $v
923
  * }
924
  * @return string The HTML data attributes, with added space to the start.
925
  */
926
+ public function make_data_attributes( $data ) {
927
+ \tsf()->_deprecated_function( 'tsf()->make_data_attributes()', '4.2.0' );
928
  return \The_SEO_Framework\Interpreters\HTML::make_data_attributes( $data );
929
  }
930
 
935
  * @since 2.7.0 Added escape parameter. Defaults to true.
936
  * @since 3.0.3 Added $disabled parameter. Defaults to false.
937
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
938
+ * @since 4.2.0 Hard deprecation.
939
+ * @deprecated
940
  *
941
  * @param string $field_id The option ID. Must be within the Autodescription settings.
942
  * @param string $label The checkbox description label.
946
  * @return string HTML checkbox output.
947
  */
948
  public function make_checkbox( $field_id = '', $label = '', $description = '', $escape = true, $disabled = false ) {
949
+ \tsf()->_deprecated_function( 'tsf()->make_checkbox()', '4.2.0' );
950
+ return \The_SEO_Framework\Interpreters\Settings_Input::make_checkbox( [
951
  'id' => $field_id,
952
  'index' => '',
953
  'label' => $label,
963
  *
964
  * @since 4.0.0
965
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
966
+ * @since 4.2.0 Hard deprecation.
967
+ * @deprecated
968
  *
969
  * @param array $args : {
970
  * string $id The select field ID.
979
  * }
980
  * @return string The option field.
981
  */
982
+ public function make_single_select_form( $args ) {
983
+ \tsf()->_deprecated_function( 'tsf()->make_single_select_form()', '4.2.0', 'The_SEO_Framework\Interpreters\Form::make_single_select_form()' );
984
  return \The_SEO_Framework\Interpreters\Form::make_single_select_form( $args );
985
  }
986
 
993
  * @since 2.2.5
994
  * @since 3.1.0 Deprecated second parameter.
995
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
996
+ * @since 4.2.0 1. Hard deprecation.
997
+ * 2. Now always returns an empty string.
998
+ * @deprecated
999
  *
1000
  * @param string $key The option name which returns boolean.
1001
  * @param string $depr Deprecated
1003
  * @param bool $echo Whether to echo or return the output.
1004
  * @return string Empty on echo or the class name with an optional wrapper.
1005
  */
1006
+ public function is_default_checked( $key, $depr = '', $wrap = true, $echo = true ) { // phpcs:ignore, VariableAnalysis.CodeAnalysis
1007
+ \tsf()->_deprecated_function( 'tsf()->is_default_checked()', '4.2.0' );
1008
+ return '';
1009
  }
1010
+
1011
  /**
1012
  * Returns the HTML class wrap for warning Checkbox options.
1013
  *
1014
  * @since 2.3.4
1015
  * @since 3.1.0 Deprecated second parameter.
1016
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
1017
+ * @since 4.2.0 10 Hard deprecation.
1018
+ * 2. Now always returns an empty string.
1019
+ * @deprecated
1020
  *
1021
  * @param string $key The option name which returns boolean.
1022
  * @param string $deprecated Deprecated.
1024
  * @param bool $echo Whether to echo or return the output.
1025
  * @return string Empty on echo or the class name with an optional wrapper.
1026
  */
1027
+ public function is_warning_checked( $key, $deprecated = '', $wrap = true, $echo = true ) { // phpcs:ignore, VariableAnalysis.CodeAnalysis
1028
+ \tsf()->_deprecated_function( 'tsf()->is_warning_checked()', '4.2.0' );
1029
+ return '';
1030
  }
1031
+
1032
  /**
1033
  * Returns the HTML class wrap for warning/default Checkbox options.
1034
  *
1035
  * @since 2.6.0
1036
  * @since 3.1.0 Added the $wrap parameter.
1037
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
1038
+ * @since 4.2.0 1. Hard deprecation.
1039
+ * 2. Now always returns false.
1040
+ * @deprecated
1041
  *
1042
  * @param string $key The option name which returns boolean.
1043
  * @param bool $wrap Whether to wrap the class name in `class="%s"`
1044
  */
1045
+ public function get_is_conditional_checked( $key, $wrap = true ) { // phpcs:ignore, VariableAnalysis.CodeAnalysis
1046
+ \tsf()->_deprecated_function( 'tsf()->get_is_conditional_checked()', '4.2.0' );
1047
+ return false;
1048
  }
1049
 
1050
  /**
1053
  * @since 2.3.4
1054
  * @since 3.1.0 Deprecated second parameter.
1055
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
1056
+ * @since 4.2.0 1. Hard deprecation.
1057
+ * 2. Now always returns false.
1058
+ * @deprecated
1059
  *
1060
  * @param string $key The option name which returns boolean.
1061
  * @param string $deprecated Deprecated. Used to be the settings field.
1063
  * @param bool $echo Whether to echo or return the output.
1064
  * @return string Empty on echo or the class name with an optional wrapper.
1065
  */
1066
+ public function is_conditional_checked( $key, $deprecated = '', $wrap = true, $echo = true ) { // phpcs:ignore, VariableAnalysis.CodeAnalysis
1067
+ \tsf()->_deprecated_function( 'tsf()->is_conditional_checked()', '4.2.0' );
1068
+ return false;
1069
  }
1070
 
1071
  /**
1072
  * Outputs character counter wrap for both JavaScript and no-Javascript.
1073
  *
1074
  * @since 3.0.0
1075
+ * @since 3.1.0 1. Added an "what if you click" onhover-title.
1076
+ * 2. Removed second parameter's usage. For passing the expected string.
1077
+ * 3. The whole output is now hidden from no-js.
1078
  * @since 4.1.0 No longer marks up the counter with the `description` HTML class.
1079
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
1080
+ * @since 4.2.0 Hard deprecation.
1081
+ * @deprecated
1082
  *
1083
  * @param string $for The input ID it's for.
1084
  * @param string $depr The initial value for no-JS. Deprecated.
1085
  * @param bool $display Whether to display the counter. (options page gimmick)
1086
  */
1087
  public function output_character_counter_wrap( $for, $depr = '', $display = true ) {
1088
+ \tsf()->_deprecated_function( 'tsf()->output_character_counter_wrap()', '4.2.0' );
1089
  return \The_SEO_Framework\Interpreters\Form::output_character_counter_wrap( $for, $display );
1090
  }
1091
 
1094
  *
1095
  * @since 3.0.0
1096
  * @since 4.1.4 Deprecated silently. Alternative marked for deletion.
1097
+ * @since 4.2.0 Hard deprecation.
1098
+ * @deprecated
1099
  *
1100
  * @param string $for The input ID it's for.
1101
  * @param string $type Whether it's a 'title' or 'description' counter.
1102
  * @param bool $display Whether to display the counter. (options page gimmick)
1103
  */
1104
  public function output_pixel_counter_wrap( $for, $type, $display = true ) {
1105
+ \tsf()->_deprecated_function( 'tsf()->output_pixel_counter_wrap()', '4.2.0' );
1106
  return \The_SEO_Framework\Interpreters\Form::output_pixel_counter_wrap( $for, $type, $display );
1107
  }
1108
+
1109
+ /**
1110
+ * Determines if WP is above or below a version
1111
+ *
1112
+ * @since 2.2.1
1113
+ * @since 2.3.8 Added caching
1114
+ * @since 2.8.0 No longer overwrites global $wp_version
1115
+ * @since 3.1.0 1. No longer caches.
1116
+ * 2. Removed redundant parameter checks.
1117
+ * 3. Now supports x.yy.zz WordPress versions.
1118
+ * @since 4.2.0 Deprecated. Use your own method instead.
1119
+ *
1120
+ * @param string $version the three part version to compare to WordPress
1121
+ * @param string $compare the comparing operator, default "$version >= Current WP Version"
1122
+ * @return bool True if the WordPress version comparison passes.
1123
+ */
1124
+ public function wp_version( $version = '4.3.0', $compare = '>=' ) {
1125
+
1126
+ \tsf()->_deprecated_function( 'tsf()->wp_version()', '4.2.0' );
1127
+
1128
+ $wp_version = $GLOBALS['wp_version'];
1129
+
1130
+ /**
1131
+ * Add a .0 if WP outputs something like 4.3 instead of 4.3.0
1132
+ * Does consider 4.xx, which will become 4.xx.0.
1133
+ * Does not consider 4.xx-dev, which will become 4.xx-dev.0. Oh well.
1134
+ */
1135
+ if ( 1 === substr_count( $wp_version, '.' ) )
1136
+ $wp_version .= '.0';
1137
+
1138
+ return (bool) version_compare( $wp_version, $version, $compare );
1139
+ }
1140
+
1141
+ /**
1142
+ * Checks for current theme support.
1143
+ *
1144
+ * Maintains detection cache, array and strings are mixed through foreach loops.
1145
+ *
1146
+ * @since 2.2.5
1147
+ * @since 3.1.0 Removed caching
1148
+ * @since 4.2.0 Deprecated. Use WP core `current_theme_supports()` instead.
1149
+ *
1150
+ * @param string|array required $features The features to check for.
1151
+ * @return bool theme support.
1152
+ */
1153
+ public function detect_theme_support( $features ) {
1154
+
1155
+ \tsf()->_deprecated_function( 'tsf()->detect_theme_support()', '4.2.0', 'current_theme_supports()' );
1156
+
1157
+ foreach ( (array) $features as $feature ) {
1158
+ if ( \current_theme_supports( $feature ) ) {
1159
+ return true;
1160
+ }
1161
+ continue;
1162
+ }
1163
+
1164
+ return false;
1165
+ }
1166
+
1167
+ /**
1168
+ * Detects presence of a page builder.
1169
+ * Memoizes the return value.
1170
+ *
1171
+ * Detects the following builders:
1172
+ * - Elementor by Elementor LTD
1173
+ * - Divi Builder by Elegant Themes
1174
+ * - Visual Composer by WPBakery
1175
+ * - Page Builder by SiteOrigin
1176
+ * - Beaver Builder by Fastline Media
1177
+ *
1178
+ * @since 4.0.0
1179
+ * @since 4.0.6 The output is now filterable.
1180
+ * @since 4.2.0 Deprecated
1181
+ * @ignore unused.
1182
+ * @deprecated
1183
+ *
1184
+ * @return bool
1185
+ */
1186
+ public function detect_page_builder() {
1187
+
1188
+ $tsf = \tsf();
1189
+ $tsf->_deprecated_function( 'tsf()->detect_page_builder()', '4.2.0' );
1190
+
1191
+ static $detected = null;
1192
+
1193
+ if ( isset( $detected ) ) return $detected;
1194
+
1195
+ /**
1196
+ * @since 4.0.6
1197
+ * @param bool $detected Whether an active page builder is detected.
1198
+ * @NOTE not to be confused with `the_seo_framework_detect_page_builder`, which tests
1199
+ * the page builder status for each post individually.
1200
+ */
1201
+ return $detected = (bool) \apply_filters(
1202
+ 'the_seo_framework_page_builder_active',
1203
+ $tsf->detect_plugin( [
1204
+ 'constants' => [
1205
+ 'ELEMENTOR_VERSION',
1206
+ 'ET_BUILDER_VERSION',
1207
+ 'WPB_VC_VERSION',
1208
+ 'SITEORIGIN_PANELS_VERSION',
1209
+ 'FL_BUILDER_VERSION',
1210
+ ],
1211
+ ] )
1212
+ );
1213
+ }
1214
+
1215
+ /**
1216
+ * Determines whether the post has a page builder attached to it.
1217
+ * Doesn't use plugin detection features as some builders might be incorporated within themes.
1218
+ *
1219
+ * Detects the following builders:
1220
+ * - Elementor by Elementor LTD
1221
+ * - Divi Builder by Elegant Themes
1222
+ * - Visual Composer by WPBakery
1223
+ * - Page Builder by SiteOrigin
1224
+ * - Beaver Builder by Fastline Media
1225
+ *
1226
+ * @since 2.6.6
1227
+ * @since 3.1.0 Added Elementor detection
1228
+ * @since 4.0.0 Now detects page builders before looping over the meta.
1229
+ * @since 4.2.0 Deprecated.
1230
+ * @TODO -> We may use this data for they have FSE builders. We may want to interface with those, some day.
1231
+ * -> We'd want to return the TYPE of pagebuilder used, if anything. Just deprecate this.
1232
+ * @ignore unused.
1233
+ * @deprecated
1234
+ *
1235
+ * @param int $post_id The post ID to check.
1236
+ * @return bool
1237
+ */
1238
+ public function uses_page_builder( $post_id ) {
1239
+
1240
+ $tsf = \tsf();
1241
+ $tsf->_deprecated_function( 'tsf()->uses_page_builder()', '4.2.0' );
1242
+
1243
+ $meta = \get_post_meta( $post_id );
1244
+
1245
+ /**
1246
+ * @since 2.6.6
1247
+ * @since 3.1.0 1. Now defaults to `null`
1248
+ * 2. Now, when a boolean (either true or false) is defined, it'll short-circuit this function.
1249
+ * @param boolean|null $detected Whether a builder should be detected.
1250
+ * @param int $post_id The current Post ID.
1251
+ * @param array $meta The current post meta.
1252
+ */
1253
+ $detected = \apply_filters( 'the_seo_framework_detect_page_builder', null, $post_id, $meta );
1254
+
1255
+ if ( \is_bool( $detected ) )
1256
+ return $detected;
1257
+
1258
+ if ( ! $tsf->detect_page_builder() )
1259
+ return false;
1260
+
1261
+ if ( empty( $meta ) )
1262
+ return false;
1263
+
1264
+ if ( isset( $meta['_elementor_edit_mode'][0] ) && '' !== $meta['_elementor_edit_mode'][0] && \defined( 'ELEMENTOR_VERSION' ) ) :
1265
+ // Elementor by Elementor LTD
1266
+ return true;
1267
+ elseif ( isset( $meta['_et_pb_use_builder'][0] ) && 'on' === $meta['_et_pb_use_builder'][0] && \defined( 'ET_BUILDER_VERSION' ) ) :
1268
+ // Divi Builder by Elegant Themes
1269
+ return true;
1270
+ elseif ( isset( $meta['_wpb_vc_js_status'][0] ) && 'true' === $meta['_wpb_vc_js_status'][0] && \defined( 'WPB_VC_VERSION' ) ) :
1271
+ // Visual Composer by WPBakery
1272
+ return true;
1273
+ elseif ( isset( $meta['panels_data'][0] ) && '' !== $meta['panels_data'][0] && \defined( 'SITEORIGIN_PANELS_VERSION' ) ) :
1274
+ // Page Builder by SiteOrigin
1275
+ return true;
1276
+ elseif ( isset( $meta['_fl_builder_enabled'][0] ) && '1' === $meta['_fl_builder_enabled'][0] && \defined( 'FL_BUILDER_VERSION' ) ) :
1277
+ // Beaver Builder by Fastline Media...
1278
+ return true;
1279
+ endif;
1280
+
1281
+ return false;
1282
+ }
1283
+
1284
+ /**
1285
+ * Returns Facebook locales array values.
1286
+ *
1287
+ * @since 2.5.2
1288
+ * @TODO deprecate me.
1289
+ *
1290
+ * @see https://www.facebook.com/translations/FacebookLocales.xml (deprecated)
1291
+ * @see https://wordpress.org/support/topic/oglocale-problem/#post-11456346
1292
+ * mirror: http://web.archive.org/web/20190601043836/https://wordpress.org/support/topic/oglocale-problem/
1293
+ * @see $this->language_keys() for the associative array keys.
1294
+ *
1295
+ * @return array Valid Facebook locales
1296
+ */
1297
+ public function fb_locales() {
1298
+ $tsf = \tsf();
1299
+ $tsf->_deprecated_function( 'tsf()->fb_locales()', '4.2.0', 'tsf()->supported_social_locales()' );
1300
+ return \array_keys( $tsf->supported_social_locales() );
1301
+ }
1302
+
1303
+ /**
1304
+ * Returns Facebook locales' associative array keys.
1305
+ *
1306
+ * This is apart from the fb_locales array since there are "duplicated" keys.
1307
+ * Use this to compare the numeric key position.
1308
+ *
1309
+ * @since 2.5.2
1310
+ * @TODO deprecate me.
1311
+ * @see https://www.facebook.com/translations/FacebookLocales.xml (deprecated)
1312
+ * @see https://wordpress.org/support/topic/oglocale-problem/#post-11456346
1313
+ * mirror: http://web.archive.org/web/20190601043836/https://wordpress.org/support/topic/oglocale-problem/
1314
+ *
1315
+ * @return array Valid Facebook locale keys
1316
+ */
1317
+ public function language_keys() {
1318
+ $tsf = \tsf();
1319
+ $tsf->_deprecated_function( 'tsf()->language_keys()', '4.2.0', 'tsf()->supported_social_locales()' );
1320
+ return \array_values( $tsf->supported_social_locales() );
1321
+ }
1322
+
1323
+ /**
1324
+ * Returns the PHP timezone compatible string.
1325
+ * UTC offsets are unreliable.
1326
+ *
1327
+ * @since 2.6.0
1328
+ * @since 4.2.0 Deprecated.
1329
+ * @deprecated
1330
+ *
1331
+ * @param bool $guess If true, the timezone will be guessed from the
1332
+ * WordPress core gmt_offset option.
1333
+ * @return string PHP Timezone String. May be empty (thus invalid).
1334
+ */
1335
+ public function get_timezone_string( $guess = false ) {
1336
+
1337
+ $tsf = \tsf();
1338
+ $tsf->_deprecated_function( 'tsf()->get_timezone_string()', '4.2.0' );
1339
+
1340
+ $tzstring = \get_option( 'timezone_string' );
1341
+
1342
+ if ( false !== strpos( $tzstring, 'Etc/GMT' ) )
1343
+ $tzstring = '';
1344
+
1345
+ if ( $guess && empty( $tzstring ) ) {
1346
+ $tzstring = timezone_name_from_abbr( '', round( \get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ), 1 );
1347
+ }
1348
+
1349
+ return $tzstring;
1350
+ }
1351
+
1352
+ /**
1353
+ * Sets and resets the timezone.
1354
+ *
1355
+ * NOTE: Always call reset_timezone() ASAP. Don't let changes linger, as they can be destructive.
1356
+ *
1357
+ * This exists because WordPress's current_time() adds discrepancies between UTC and GMT.
1358
+ * This is also far more accurate than WordPress's tiny time table.
1359
+ *
1360
+ * @TODO Note that WordPress 5.3 no longer requires this, and that we should rely on wp_date() instead.
1361
+ * So, we should remove this dependency ASAP.
1362
+ *
1363
+ * @since 2.6.0
1364
+ * @since 3.0.6 Now uses the old timezone string when a new one can't be generated.
1365
+ * @since 4.0.4 Now also unsets the stored timezone string on reset.
1366
+ * @since 4.2.0 Deprecated.
1367
+ * @link http://php.net/manual/en/timezones.php
1368
+ * @deprecated
1369
+ *
1370
+ * @param string $tzstring Optional. The PHP Timezone string. Best to leave empty to always get a correct one.
1371
+ * @param bool $reset Whether to reset to default. Ignoring first parameter.
1372
+ * @return bool True on success. False on failure.
1373
+ */
1374
+ public function set_timezone( $tzstring = '', $reset = false ) {
1375
+
1376
+ $tsf = \tsf();
1377
+ $tsf->_deprecated_function( 'tsf()->set_timezone()', '4.2.0' );
1378
+
1379
+ static $old_tz = null;
1380
+
1381
+ $old_tz = $old_tz ?: date_default_timezone_get() ?: 'UTC';
1382
+
1383
+ if ( $reset ) {
1384
+ $_revert_tz = $old_tz;
1385
+ $old_tz = null;
1386
+ // phpcs:ignore, WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set
1387
+ return date_default_timezone_set( $_revert_tz );
1388
+ }
1389
+
1390
+ if ( empty( $tzstring ) )
1391
+ $tzstring = $tsf->get_timezone_string( true ) ?: $old_tz;
1392
+
1393
+ // phpcs:ignore, WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set
1394
+ return date_default_timezone_set( $tzstring );
1395
+ }
1396
+
1397
+ /**
1398
+ * Resets the timezone to default or UTC.
1399
+ *
1400
+ * @since 2.6.0
1401
+ * @since 4.2.0 Deprecated.
1402
+ * @deprecated
1403
+ *
1404
+ * @return bool True on success. False on failure.
1405
+ */
1406
+ public function reset_timezone() {
1407
+ $tsf = \tsf();
1408
+ $tsf->_deprecated_function( 'tsf()->reset_timezone()', '4.2.0' );
1409
+ return $tsf->set_timezone( '', true );
1410
+ }
1411
+
1412
+ /**
1413
+ * Returns and caches term meta for the current query.
1414
+ * Memoizes the return value for the current request.
1415
+ *
1416
+ * @since 3.0.0
1417
+ * @since 4.0.1 Now uses the filterable `get_the_real_ID()`
1418
+ * @since 4.2.0 Deprecated. Use get_term_meta() or get_term_meta_item() instead.
1419
+ * @deprecated
1420
+ *
1421
+ * @return array The current term meta.
1422
+ */
1423
+ public function get_current_term_meta() {
1424
+
1425
+ $tsf = \tsf();
1426
+ $tsf->_deprecated_function( 'tsf()->get_current_term_meta()', '4.2.0', 'tsf()->get_term_meta()' );
1427
+
1428
+ static $cache;
1429
+
1430
+ if ( isset( $cache ) )
1431
+ return $cache;
1432
+
1433
+ if ( $tsf->is_term_meta_capable() ) {
1434
+ $cache = $tsf->get_term_meta( $tsf->get_the_real_ID() ) ?: [];
1435
+ } else {
1436
+ $cache = [];
1437
+ }
1438
+
1439
+ return $cache;
1440
+ }
1441
+
1442
+ /**
1443
+ * Detect the non-home blog page by query (ID).
1444
+ *
1445
+ * @since 2.3.4
1446
+ * @since 4.2.0 Deprecated. Use is_home_as_page() instead.
1447
+ * @deprecated
1448
+ * @see is_wc_shop() -- that's the correct implementation. However, we're dealing with erratic queries here (ET & legacy WP)
1449
+ *
1450
+ * @param int $id the Page ID.
1451
+ * @return bool true if is blog page. Always false if blog page is homepage.
1452
+ */
1453
+ public function is_blog_page( $id = 0 ) {
1454
+
1455
+ $tsf = \tsf();
1456
+ $tsf->_deprecated_function( 'tsf()->is_blog_page()', '4.2.0', 'tsf()->is_home_as_page()' );
1457
+
1458
+ // When the blog page is the front page, treat it as front instead of blog.
1459
+ if ( ! $tsf->has_page_on_front() )
1460
+ return false;
1461
+
1462
+ $id = $id ?: $tsf->get_the_real_ID();
1463
+
1464
+ static $pfp;
1465
+
1466
+ $pfp = $pfp ?? (int) \get_option( 'page_for_posts' );
1467
+
1468
+ return ( $id && $id === $pfp && false === \is_archive() ) || \is_home();
1469
+ }
1470
+
1471
+ /**
1472
+ * Checks blog page by sole ID.
1473
+ *
1474
+ * @since 4.0.0
1475
+ * @since 4.1.4 1. Improved performance by switching the conditional.
1476
+ * 2. Improved performance by adding memoization.
1477
+ * @since 4.2.0 1. Removed memoization.
1478
+ * 2. Deprecated. Use is_home() instead.
1479
+ * @deprecated
1480
+ * @see is_wc_shop() -- that's the correct implementation.
1481
+ *
1482
+ * @param int $id The ID to check
1483
+ * @return bool
1484
+ */
1485
+ public function is_blog_page_by_id( $id ) {
1486
+ \tsf()->_deprecated_function( 'tsf()->is_blog_page()', '4.2.0', 'tsf()->is_home()' );
1487
+
1488
+ // ID 0 cannot be a blog page.
1489
+ if ( ! $id ) return false;
1490
+
1491
+ return (int) \get_option( 'page_for_posts' ) === $id;
1492
+ }
1493
+
1494
+ /**
1495
+ * Checks for front page by input ID.
1496
+ *
1497
+ * NOTE: Doesn't always return true when the ID is 0, although the homepage might be.
1498
+ * This is because it checks for the query, to prevent conflicts.
1499
+ *
1500
+ * @see $this->is_real_front_page_by_id(); Alternative to NOTE.
1501
+ *
1502
+ * @since 2.9.0
1503
+ * @since 2.9.3 Now tests for archive and 404 before testing homepage as blog.
1504
+ * @since 3.2.2 Removed SEO settings page check. This now returns false on that page.
1505
+ * @since 4.2.0 1. No longer casts input $id to integer.
1506
+ * 2. Deprecated.
1507
+ * @deprecated
1508
+ *
1509
+ * @param int $id The page ID, required. Can be 0.
1510
+ * @return bool True if ID if for the homepage.
1511
+ */
1512
+ public function is_front_page_by_id( $id ) {
1513
+ \tsf()->_deprecated_function( 'tsf()->is_front_page_by_id()', '4.2.0', 'tsf()->is_real_front_page_by_id()' );
1514
+
1515
+ $pof = (int) \get_option( 'page_on_front' );
1516
+
1517
+ switch ( \get_option( 'show_on_front' ) ) :
1518
+ case 'page':
1519
+ $is_front_page = $pof === $id;
1520
+ break;
1521
+
1522
+ case 'posts':
1523
+ $is_front_page =
1524
+ ( 0 === $pof && $this->is_home() )
1525
+ || $pof === $id;
1526
+ break;
1527
+
1528
+ default:
1529
+ // Elegant Themes's Extra support
1530
+ $is_front_page = 0 === $id && $this->is_home();
1531
+ break;
1532
+ endswitch;
1533
+
1534
+ return $is_front_page;
1535
+ }
1536
+
1537
+ /**
1538
+ * Prepends the taxonomy label to the title.
1539
+ *
1540
+ * @since 4.1.0
1541
+ * @since 4.1.2 Now supports WP 5.5 archive titles.
1542
+ * @since 4.2.0 Deprecated
1543
+ * @deprecated
1544
+ *
1545
+ * @param string $title The title to prepend taxonomy label to.
1546
+ * @param string $taxonomy The taxonomy to get label from.
1547
+ * @return string The title with possibly prepended tax-label.
1548
+ */
1549
+ public function prepend_tax_label_prefix( $title, $taxonomy ) {
1550
+
1551
+ $tsf = \tsf();
1552
+ \tsf()->_deprecated_function( 'tsf()->prepend_tax_label_prefix()', '4.2.0' );
1553
+
1554
+ $prefix = $tsf->get_tax_type_label( $taxonomy ) ?: '';
1555
+
1556
+ if ( $prefix ) {
1557
+ $title = sprintf(
1558
+ /* translators: 1: Title prefix. 2: Title. */
1559
+ \_x( '%1$s %2$s', 'archive title', 'default' ),
1560
+ /* translators: %s: Taxonomy singular name. */
1561
+ sprintf( \_x( '%s:', 'taxonomy term archive title prefix', 'default' ), $prefix ),
1562
+ $title
1563
+ );
1564
+ }
1565
+
1566
+ return $title;
1567
+ }
1568
+
1569
+ /**
1570
+ * Get the real ID from plugins.
1571
+ *
1572
+ * Only works on front-end as there's no need to check for inconsistent
1573
+ * functions for the current ID in the admin.
1574
+ *
1575
+ * @since 2.5.0
1576
+ * @since 3.1.0 1. Now checks for the feed.
1577
+ * 2. No longer caches.
1578
+ * @since 4.0.5 1. The shop ID is now handled via the filter.
1579
+ * 2. The question ID (AnsPress) is no longer called. This should work out-of-the-box since AnsPress 4.1.
1580
+ * @since 4.2.0 Deprecated
1581
+ * @deprecated
1582
+ *
1583
+ * @return int The admin ID.
1584
+ */
1585
+ public function check_the_real_ID() { // phpcs:ignore -- ID is capitalized because WordPress does that too: get_the_ID().
1586
+
1587
+ \tsf()->_deprecated_function( 'tsf()->check_the_real_ID()', '4.2.0', 'tsf()->get_the_real_ID()' );
1588
+
1589
+ /**
1590
+ * @since 2.5.0
1591
+ * @param int $id
1592
+ */
1593
+ return (int) \apply_filters(
1594
+ 'the_seo_framework_real_id',
1595
+ $this->is_feed() ? \get_the_ID() : 0
1596
+ );
1597
+ }
1598
+
1599
+ /**
1600
+ * Get the default of any of the The SEO Framework settings.
1601
+ *
1602
+ * @since 2.2.4
1603
+ * @since 2.8.2 No longer decodes entities on request.
1604
+ * @since 3.1.0 1. Now returns null if the option doesn't exist, instead of -1.
1605
+ * 2. Is now influenced by filters.
1606
+ * 3. Now also strips slashes when using cache.
1607
+ * 4. The second parameter is deprecated.
1608
+ * @since 4.2.0 Deprecated
1609
+ * @deprecated
1610
+ * @uses $this->get_default_site_options()
1611
+ *
1612
+ * @param string $key Required. The option name.
1613
+ * @param string $depr Deprecated. Leave empty.
1614
+ * @param bool $use_cache Optional. Whether to use the options cache or bypass it.
1615
+ * @return mixed default option
1616
+ * null If option doesn't exist.
1617
+ */
1618
+ public function get_default_settings( $key, $depr = '', $use_cache = true ) {
1619
+
1620
+ $tsf = \tsf();
1621
+ $tsf->_deprecated_function( 'tsf()->get_default_settings()', '4.2.0', 'tsf()->get_default_option()' );
1622
+
1623
+ if ( ! $key ) return false;
1624
+
1625
+ if ( $depr )
1626
+ $tsf->_doing_it_wrong( __METHOD__, 'The second parameter is deprecated.', '3.1.0' );
1627
+
1628
+ if ( ! $use_cache ) {
1629
+ $defaults = $tsf->get_default_site_options();
1630
+ return isset( $defaults[ $key ] ) ? \stripslashes_deep( $defaults[ $key ] ) : null;
1631
+ }
1632
+
1633
+ static $cache;
1634
+
1635
+ return (
1636
+ $cache = $cache ?? \stripslashes_deep( $tsf->get_default_site_options() )
1637
+ )[ $key ] ?? null;
1638
+ }
1639
+
1640
+ /**
1641
+ * Get the warned setting of any of the The SEO Framework settings.
1642
+ *
1643
+ * @since 2.3.4
1644
+ * @since 3.1.0 Now returns 0 if the option doesn't exist, instead of -1.
1645
+ * @since 4.2.0 Deprecated
1646
+ * @deprecated
1647
+ * @uses THE_SEO_FRAMEWORK_SITE_OPTIONS
1648
+ * @uses $this->get_warned_site_options()
1649
+ *
1650
+ * @param string $key Required. The option name.
1651
+ * @param string $depr Deprecated. Leave empty.
1652
+ * @param bool $use_cache Optional. Whether to use the options cache or bypass it.
1653
+ * @return int 0|1 Whether the option is flagged as dangerous for SEO.
1654
+ */
1655
+ public function get_warned_settings( $key, $depr = '', $use_cache = true ) {
1656
+
1657
+ $tsf = \tsf();
1658
+ $tsf->_deprecated_function( 'tsf()->get_warned_settings()', '4.2.0', 'tsf()->get_warned_option()' );
1659
+
1660
+ if ( empty( $key ) )
1661
+ return false;
1662
+
1663
+ if ( $depr )
1664
+ $tsf->_doing_it_wrong( __METHOD__, 'The second parameter is deprecated.', '3.1.0' );
1665
+
1666
+ if ( ! $use_cache )
1667
+ return $tsf->s_one_zero( ! empty( $tsf->get_warned_site_options()[ $key ] ) );
1668
+
1669
+ static $cache;
1670
+
1671
+ if ( ! isset( $cache ) )
1672
+ $cache = $tsf->get_warned_site_options();
1673
+
1674
+ return $tsf->s_one_zero( ! empty( $cache[ $key ] ) );
1675
+ }
1676
+
1677
+ /**
1678
+ * Returns image URL suitable for Schema items.
1679
+ *
1680
+ * These are images that are strictly assigned to the Post or Page, fallbacks are omitted.
1681
+ * Themes should compliment these. If not, then Open Graph should at least compliment these.
1682
+ * If that's not even true, then I don't know what happens. But then you're in a grey area...
1683
+ *
1684
+ * @since 4.0.0
1685
+ * @since 4.2.0 1. Now gets correctly separated results when $args changes.
1686
+ * 2. Now supports the `$args['pta']` index.
1687
+ * 3. Deprecated.
1688
+ * @uses $this->get_image_details()
1689
+ * @deprecated
1690
+ *
1691
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
1692
+ * Leave null to autodetermine query.
1693
+ * @param bool $details Whether to return all details, or just a simple URL.
1694
+ * @return string|array $url The Schema.org safe image.
1695
+ */
1696
+ public function get_safe_schema_image( $args = null, $details = false ) {
1697
+
1698
+ $tsf = \tsf();
1699
+ $tsf->_deprecated_function( 'tsf()->get_safe_schema_image()', '4.2.0', 'tsf()->get_image_details()' );
1700
+
1701
+ $image_details = memo( null, $args ) ?? memo( current( $tsf->get_image_details( $args, true, 'schema' ), $args ) );
1702
+
1703
+ return $details ? $image_details : ( $image_details['url'] ?? '' );
1704
+ }
1705
  }
inc/classes/internal/index.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * They say that God is everywhere,
4
+ * and yet we always think of Him as somewhat of a recluse.
5
+ *
6
+ * - Emily Dickinson
7
+ */
inc/classes/{silencer.class.php → internal/silencer.class.php} RENAMED
@@ -1,12 +1,10 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes\Silencer
4
  * @subpackage The_SEO_Framework\Classes\Facade
5
  */
6
 
7
- namespace The_SEO_Framework;
8
-
9
- \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
10
 
11
  /**
12
  * The SEO Framework plugin
@@ -25,15 +23,18 @@ namespace The_SEO_Framework;
25
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
26
  */
27
 
 
 
28
  // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- That's the whole premise of this file.
29
 
30
  /**
31
- * Class The_SEO_Framework\Silencer
32
  *
33
  * This is an empty class to silence invalid API calls when the plugin is soft-disabled.
34
  * This alleviates redundant checks throughout the plugin API.
35
  *
36
  * @since 3.1.0
 
37
  * @ignore
38
  * @property false $loaded
39
  */
@@ -46,7 +47,7 @@ final class Silencer {
46
  *
47
  * @since 3.1.0
48
  * @access protected
49
- * Don't alter this variable!!!
50
  * @var boolean $loaded
51
  */
52
  public $loaded = false;
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Internal\Silencer
4
  * @subpackage The_SEO_Framework\Classes\Facade
5
  */
6
 
7
+ namespace The_SEO_Framework\Internal;
 
 
8
 
9
  /**
10
  * The SEO Framework plugin
23
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
24
  */
25
 
26
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
+
28
  // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- That's the whole premise of this file.
29
 
30
  /**
31
+ * Class The_SEO_Framework\Internal\Silencer
32
  *
33
  * This is an empty class to silence invalid API calls when the plugin is soft-disabled.
34
  * This alleviates redundant checks throughout the plugin API.
35
  *
36
  * @since 3.1.0
37
+ * @since 4.2.0 Changed namespace from \The_SEO_Framework to \The_SEO_Framework\Internal
38
  * @ignore
39
  * @property false $loaded
40
  */
47
  *
48
  * @since 3.1.0
49
  * @access protected
50
+ * Don't alter this variable.
51
  * @var boolean $loaded
52
  */
53
  public $loaded = false;
inc/classes/interpreters/form.class.php CHANGED
@@ -26,7 +26,7 @@ namespace The_SEO_Framework\Interpreters;
26
  \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
 
28
  /**
29
- * Interprets anything you send here into HTML. Or so it should.
30
  *
31
  * @since 4.1.4
32
  *
@@ -37,184 +37,11 @@ namespace The_SEO_Framework\Interpreters;
37
  */
38
  final class Form {
39
 
40
- /**
41
- * Helper function that constructs header elements.
42
- *
43
- * @since 4.1.4
44
- *
45
- * @param string $title The header title.
46
- */
47
- public static function header_title( $title ) {
48
- printf( '<h4>%s</h4>', \esc_html( $title ) );
49
- }
50
-
51
- /**
52
- * Helper function that constructs name attributes for use in form fields.
53
- *
54
- * Other page implementation classes may wish to construct and use a
55
- * get_field_id() method, if the naming format needs to be different.
56
- *
57
- * @since 4.1.4
58
- *
59
- * @param string $name Field name base
60
- * @return string Full field name
61
- */
62
- public static function get_field_name( $name ) {
63
- return sprintf( '%s[%s]', THE_SEO_FRAMEWORK_SITE_OPTIONS, $name );
64
- }
65
-
66
- /**
67
- * Echo constructed name attributes in form fields.
68
- *
69
- * @since 4.1.4
70
- * @uses static::get_field_name() Construct name attributes for use in form fields.
71
- *
72
- * @param string $name Field name base
73
- */
74
- public static function field_name( $name ) {
75
- echo \esc_attr( static::get_field_name( $name ) );
76
- }
77
-
78
- /**
79
- * Helper function that constructs id attributes for use in form fields.
80
- *
81
- * @since 4.1.4
82
- *
83
- * @param string $id Field id base
84
- * @return string Full field id
85
- */
86
- public static function get_field_id( $id ) {
87
- return sprintf( '%s[%s]', THE_SEO_FRAMEWORK_SITE_OPTIONS, $id );
88
- }
89
-
90
- /**
91
- * Echo constructed id attributes in form fields.
92
- *
93
- * @since 4.1.4
94
- * @uses static::get_field_id() Constructs id attributes for use in form fields.
95
- *
96
- * @param string $id Field id base.
97
- * @param boolean $echo Whether to escape echo or just return.
98
- * @return string Full field id
99
- */
100
- public static function field_id( $id, $echo = true ) {
101
- if ( $echo ) {
102
- echo \esc_attr( static::get_field_id( $id ) );
103
- } else {
104
- return static::get_field_id( $id );
105
- }
106
- }
107
-
108
- /**
109
- * Returns a chechbox wrapper.
110
- *
111
- * @since 4.1.4
112
- *
113
- * @param array $args : {
114
- * string $id The option name, used as field ID.
115
- * string $class The checkbox class.
116
- * string $index The option index, used when the option is an array.
117
- * string $label The checkbox label description, placed inline of the checkbox.
118
- * string $description The checkbox additional description, placed underneat.
119
- * array $data The checkbox field data. Sub-items are expected to be escaped if they're not an array.
120
- * bool $escape Whether to enable escaping of the $label and $description.
121
- * bool $disabled Whether to disable the checkbox field.
122
- * bool $default Whether to display-as-default. This is autodetermined when no $index is set.
123
- * bool $warned Whether to warn the checkbox field value.
124
- * }
125
- * @return string HTML checkbox output.
126
- */
127
- public static function make_checkbox( array $args = [] ) {
128
-
129
- $args = array_merge(
130
- [
131
- 'id' => '',
132
- 'class' => '',
133
- 'index' => '',
134
- 'label' => '',
135
- 'description' => '',
136
- 'data' => [],
137
- 'escape' => true,
138
- 'disabled' => false,
139
- 'default' => false,
140
- 'warned' => false,
141
- ],
142
- $args
143
- );
144
-
145
- if ( $args['escape'] ) {
146
- $args['description'] = \esc_html( $args['description'] );
147
- $args['label'] = \esc_html( $args['label'] );
148
- }
149
-
150
- $tsf = \the_seo_framework();
151
-
152
- $index = $args['index'] ? $tsf->s_field_id( $args['index'] ?: '' ) : '';
153
-
154
- $field_id = $field_name = \esc_attr( sprintf(
155
- '%s%s',
156
- Form::get_field_id( $args['id'] ),
157
- $index ? sprintf( '[%s]', $index ) : ''
158
- ) );
159
-
160
- $value = $tsf->get_option( $args['id'] );
161
- if ( $index ) {
162
- $value = isset( $value[ $index ] ) ? $value[ $index ] : '';
163
- }
164
-
165
- $cb_classes = [];
166
-
167
- if ( $args['class'] ) {
168
- $cb_classes[] = $args['class'];
169
- }
170
-
171
- if ( $args['disabled'] ) {
172
- $cb_classes[] = 'tsf-disabled';
173
- } elseif ( ! $args['index'] ) {
174
- // Can't fetch conditionals in index.
175
- $cb_classes[] = static::get_is_conditional_checked( $args['id'], false );
176
- } else {
177
- if ( $args['default'] ) {
178
- $cb_classes[] = 'tsf-default-selected';
179
- } elseif ( $args['warned'] ) {
180
- $cb_classes[] = 'tsf-warning-selected';
181
- }
182
- }
183
-
184
- $output = sprintf(
185
- '<span class="tsf-toblock">%s</span>',
186
- vsprintf(
187
- '<label for="%s" %s>%s</label>',
188
- [
189
- $field_id,
190
- ( $args['disabled'] ? 'class="tsf-disabled"' : '' ),
191
- vsprintf(
192
- '<input type=checkbox class="%s" name="%s" id="%s" value="1" %s %s %s /> %s',
193
- [
194
- \esc_attr( implode( ' ', $cb_classes ) ),
195
- $field_name,
196
- $field_id,
197
- \checked( $value, true, false ),
198
- ( $args['disabled'] ? 'disabled' : '' ),
199
- $args['data'] ? HTML::make_data_attributes( $args['data'] ) : '',
200
- $args['label'],
201
- ]
202
- ),
203
- ]
204
- )
205
- );
206
-
207
- $output .= $args['description'] ? sprintf( '<p class="description tsf-option-spacer">%s</p>', $args['description'] ) : '';
208
-
209
- return $output;
210
- }
211
-
212
  /**
213
  * Returns a HTML select form elements for qubit options: -1, 0, or 1.
214
  * Does not support "multiple" field selections.
215
  *
216
  * @since 4.1.4
217
- * @TODO allow arrays as index, so we can support multidimensional options easily? @see is_conditional_checked
218
  *
219
  * @param array $args : {
220
  * string $id The select field ID.
@@ -229,18 +56,19 @@ final class Form {
229
  * }
230
  * @return string The option field.
231
  */
232
- public static function make_single_select_form( array $args ) {
233
 
234
  $defaults = [
235
- 'id' => '',
236
- 'class' => '',
237
- 'name' => '',
238
- 'default' => '',
239
- 'options' => [],
240
- 'label' => '',
241
- 'required' => false,
242
- 'data' => [],
243
- 'info' => [],
 
244
  ];
245
 
246
  $args = array_merge( $defaults, $args );
@@ -262,7 +90,7 @@ final class Form {
262
  };
263
  array_walk( $html_options, $create_option, $args['default'] );
264
 
265
- $tsf = \the_seo_framework();
266
 
267
  return vsprintf(
268
  sprintf( '<div class="%s">%s</div>',
@@ -271,22 +99,25 @@ final class Form {
271
  ),
272
  [
273
  $args['label'] ? sprintf(
274
- '<label for=%s>%s</label> ', // NOTE: extra space!
275
  $tsf->s_field_id( $args['id'] ),
276
- \esc_html( $args['label'] )
 
 
 
277
  ) : '',
278
- $args['info'] ? ' ' . HTML::make_info(
279
  $args['info'][0],
280
- isset( $args['info'][1] ) ? $args['info'][1] : '',
281
  false
282
- ) : '',
283
  vsprintf(
284
- '<select id=%s name=%s %s %s>%s</select>',
285
  [
286
  $tsf->s_field_id( $args['id'] ),
287
  \esc_attr( $args['name'] ),
288
- $args['required'] ? 'required' : '',
289
- $args['data'] ? HTML::make_data_attributes( $args['data'] ) : '',
290
  implode( $html_options ),
291
  ]
292
  ),
@@ -294,126 +125,6 @@ final class Form {
294
  );
295
  }
296
 
297
- /**
298
- * Returns the HTML class wrap for default Checkbox options.
299
- *
300
- * This function does nothing special. But is merely a simple wrapper.
301
- * Just like code_wrap.
302
- *
303
- * @since 4.1.4
304
- *
305
- * @param string $key The option name which returns boolean.
306
- * @param bool $wrap Whether to wrap the class name in `class="%s"`
307
- * @param bool $echo Whether to echo or return the output.
308
- * @return string Empty on echo or the class name with an optional wrapper.
309
- */
310
- public static function is_default_checked( $key, $wrap = true, $echo = true ) {
311
-
312
- $class = '';
313
-
314
- $default = \the_seo_framework()->get_default_settings( $key );
315
-
316
- if ( 1 === $default )
317
- $class = 'tsf-default-selected';
318
-
319
- if ( $echo ) {
320
- if ( $wrap ) {
321
- printf( 'class="%s"', \esc_attr( $class ) );
322
- } else {
323
- echo \esc_attr( $class );
324
- }
325
- } else {
326
- if ( $wrap )
327
- return sprintf( 'class="%s"', $class );
328
-
329
- return $class;
330
- }
331
- }
332
-
333
- /**
334
- * Returns the HTML class wrap for warning Checkbox options.
335
- *
336
- * @since 4.1.4
337
- *
338
- * @param string $key The option name which returns boolean.
339
- * @param bool $wrap Whether to wrap the class name in `class="%s"`
340
- * @param bool $echo Whether to echo or return the output.
341
- * @return string Empty on echo or the class name with an optional wrapper.
342
- */
343
- public static function is_warning_checked( $key, $wrap = true, $echo = true ) {
344
-
345
- $class = '';
346
-
347
- $warned = \the_seo_framework()->get_warned_settings( $key );
348
-
349
- if ( 1 === $warned )
350
- $class = 'tsf-warning-selected';
351
-
352
- if ( $echo ) {
353
- if ( $wrap ) {
354
- printf( 'class="%s"', \esc_attr( $class ) );
355
- } else {
356
- echo \esc_attr( $class );
357
- }
358
- } else {
359
- if ( $wrap )
360
- return sprintf( 'class="%s"', $class );
361
-
362
- return $class;
363
- }
364
- }
365
-
366
- /**
367
- * Returns the HTML class wrap for warning/default Checkbox options.
368
- *
369
- * @since 4.1.4
370
- *
371
- * @param string $key The option name which returns boolean.
372
- * @param bool $wrap Whether to wrap the class name in `class="%s"`
373
- */
374
- public static function get_is_conditional_checked( $key, $wrap = true ) {
375
- return static::is_conditional_checked( $key, '', $wrap, false );
376
- }
377
-
378
- /**
379
- * Returns the HTML class wrap for warning/default Checkbox options.
380
- *
381
- * @since 4.1.4
382
- *
383
- * @param string $key The option name which returns boolean.
384
- * @param bool $wrap Whether to wrap the class name in `class="%s"`
385
- * @param bool $echo Whether to echo or return the output.
386
- * @return string Empty on echo or the class name with an optional wrapper.
387
- */
388
- public static function is_conditional_checked( $key, $wrap = true, $echo = true ) {
389
-
390
- $class = '';
391
-
392
- $default = static::is_default_checked( $key, false, false );
393
- $warned = static::is_warning_checked( $key, false, false );
394
-
395
- if ( '' !== $default && '' !== $warned ) {
396
- $class = $default . ' ' . $warned;
397
- } elseif ( '' !== $default ) {
398
- $class = $default;
399
- } elseif ( '' !== $warned ) {
400
- $class = $warned;
401
- }
402
-
403
- if ( $echo ) {
404
- if ( $wrap ) {
405
- printf( 'class="%s"', \esc_attr( $class ) );
406
- } else {
407
- echo \esc_attr( $class );
408
- }
409
- } else {
410
- if ( $wrap )
411
- return sprintf( 'class="%s"', $class );
412
-
413
- return $class;
414
- }
415
- }
416
-
417
  /**
418
  * Outputs character counter wrap for both JavaScript and no-Javascript.
419
  *
@@ -448,7 +159,7 @@ final class Form {
448
  *
449
  * @param string $for The input ID it's for.
450
  * @param string $type Whether it's a 'title' or 'description' counter.
451
- * @param bool $display Whether to display the counter. (options page gimmick)
452
  */
453
  public static function output_pixel_counter_wrap( $for, $type, $display = true ) {
454
  vprintf(
@@ -495,38 +206,37 @@ final class Form {
495
  * }
496
  * @return string The image uploader button.
497
  */
498
- public static function get_image_uploader_form( array $args ) {
499
 
500
- static $image_input_id = 0;
501
- $image_input_id++;
502
 
503
- $tsf = \the_seo_framework();
504
 
505
- $defaults = [
506
- 'id' => null,
507
- 'post_id' => $tsf->get_the_real_ID(),
508
- 'data' => [
509
- 'inputType' => 'social',
510
- 'width' => 1200, // TODO make 1280 - 80px overflow margin? It'd be better for mixed platforms.
511
- 'height' => 630, // TODO make 640 - 80px overflow margin? It'd be better for mixed platforms.
512
- 'minWidth' => 200,
513
- 'minHeight' => 200,
514
- 'flex' => true,
515
- ],
516
- 'i18n' => [
517
- 'button_title' => '',
518
- 'button_text' => \__( 'Select Image', 'autodescription' ),
 
 
519
  ],
520
- ];
521
-
522
- $args = $tsf->array_merge_recursive_distinct( $defaults, $args );
523
-
524
- if ( ! $args['id'] ) return '';
525
 
526
  $content = vsprintf(
527
  '<button type=button data-href="%s" class="tsf-set-image-button button button-primary button-small" title="%s" id="%s-select" %s>%s</button>',
528
  [
529
- \esc_url( \get_upload_iframe_src( 'image', $defaults['post_id'] ) ),
530
  \esc_attr( $args['i18n']['button_title'] ),
531
  \esc_attr( $args['id'] ),
532
  HTML::make_data_attributes(
26
  \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
 
28
  /**
29
+ * Interprets anything you send here into Form HTML. Or so it should.
30
  *
31
  * @since 4.1.4
32
  *
37
  */
38
  final class Form {
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  /**
41
  * Returns a HTML select form elements for qubit options: -1, 0, or 1.
42
  * Does not support "multiple" field selections.
43
  *
44
  * @since 4.1.4
 
45
  *
46
  * @param array $args : {
47
  * string $id The select field ID.
56
  * }
57
  * @return string The option field.
58
  */
59
+ public static function make_single_select_form( $args ) {
60
 
61
  $defaults = [
62
+ 'id' => '',
63
+ 'class' => '',
64
+ 'name' => '',
65
+ 'default' => '',
66
+ 'options' => [],
67
+ 'label' => '',
68
+ 'labelstrong' => false,
69
+ 'required' => false,
70
+ 'data' => [],
71
+ 'info' => [],
72
  ];
73
 
74
  $args = array_merge( $defaults, $args );
90
  };
91
  array_walk( $html_options, $create_option, $args['default'] );
92
 
93
+ $tsf = \tsf();
94
 
95
  return vsprintf(
96
  sprintf( '<div class="%s">%s</div>',
99
  ),
100
  [
101
  $args['label'] ? sprintf(
102
+ '<label for="%s">%s</label> ', // superfluous space!
103
  $tsf->s_field_id( $args['id'] ),
104
+ sprintf(
105
+ $args['labelstrong'] ? '<strong>%s</strong>' : '%s',
106
+ \esc_html( $args['label'] )
107
+ )
108
  ) : '',
109
+ $args['info'] ? HTML::make_info(
110
  $args['info'][0],
111
+ $args['info'][1] ?? '',
112
  false
113
+ ) . ' ' : '',
114
  vsprintf(
115
+ '<select id="%s" name="%s"%s %s>%s</select>',
116
  [
117
  $tsf->s_field_id( $args['id'] ),
118
  \esc_attr( $args['name'] ),
119
+ $args['required'] ? ' required' : '',
120
+ HTML::make_data_attributes( $args['data'] ),
121
  implode( $html_options ),
122
  ]
123
  ),
125
  );
126
  }
127
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  /**
129
  * Outputs character counter wrap for both JavaScript and no-Javascript.
130
  *
159
  *
160
  * @param string $for The input ID it's for.
161
  * @param string $type Whether it's a 'title' or 'description' counter.
162
+ * @param bool $display Whether to display the counter. (Used as options page gimmick)
163
  */
164
  public static function output_pixel_counter_wrap( $for, $type, $display = true ) {
165
  vprintf(
206
  * }
207
  * @return string The image uploader button.
208
  */
209
+ public static function get_image_uploader_form( $args ) {
210
 
211
+ // Required.
212
+ if ( empty( $args['id'] ) ) return '';
213
 
214
+ $tsf = \tsf();
215
 
216
+ $args = $tsf->array_merge_recursive_distinct(
217
+ [
218
+ 'id' => '',
219
+ 'post_id' => $tsf->get_the_real_ID(), // TODO why? Introduced <https://github.com/sybrew/the-seo-framework/commit/6ca4425abf3edafd75d7d47e60e54eb8bca91cc2>
220
+ 'data' => [
221
+ 'inputType' => 'social',
222
+ 'width' => 1200, // TODO make 1280 - 80px overflow margin? It'd be better for mixed platforms.
223
+ 'height' => 630, // TODO make 640 - 80px overflow margin? It'd be better for mixed platforms.
224
+ 'minWidth' => 200,
225
+ 'minHeight' => 200,
226
+ 'flex' => true,
227
+ ],
228
+ 'i18n' => [
229
+ 'button_title' => '', // Redundant.
230
+ 'button_text' => \__( 'Select Image', 'autodescription' ),
231
+ ],
232
  ],
233
+ $args
234
+ );
 
 
 
235
 
236
  $content = vsprintf(
237
  '<button type=button data-href="%s" class="tsf-set-image-button button button-primary button-small" title="%s" id="%s-select" %s>%s</button>',
238
  [
239
+ \esc_url( \get_upload_iframe_src( 'image', $args['post_id'] ) ),
240
  \esc_attr( $args['i18n']['button_title'] ),
241
  \esc_attr( $args['id'] ),
242
  HTML::make_data_attributes(
inc/classes/interpreters/html.class.php CHANGED
@@ -37,6 +37,30 @@ namespace The_SEO_Framework\Interpreters;
37
  */
38
  final class HTML {
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  /**
41
  * Mark up content with code tags.
42
  * Escapes all HTML, so `<` gets changed to `&lt;` and displays correctly.
@@ -60,7 +84,7 @@ final class HTML {
60
  * @return string Content wrapped in code tags.
61
  */
62
  public static function code_wrap_noesc( $content ) {
63
- return '<code>' . $content . '</code>';
64
  }
65
 
66
  /**
@@ -85,9 +109,11 @@ final class HTML {
85
  * @param bool $block Whether to wrap the content in <p> tags.
86
  */
87
  public static function description_noesc( $content, $block = true ) {
88
- $output = '<span class="description">' . $content . '</span>';
89
- // phpcs:ignore, WordPress.Security.EscapeOutput -- Method clearly states it's not escaped.
90
- echo $block ? '<p>' . $output . '</p>' : $output;
 
 
91
  }
92
 
93
  /**
@@ -112,9 +138,11 @@ final class HTML {
112
  * @param bool $block Whether to wrap the content in <p> tags.
113
  */
114
  public static function attention_noesc( $content, $block = true ) {
115
- $output = '<span class="attention">' . $content . '</span>';
116
- // phpcs:ignore, WordPress.Security.EscapeOutput -- Method clearly states it's not escaped.
117
- echo $block ? '<p>' . $output . '</p>' : $output;
 
 
118
  }
119
 
120
  /**
@@ -139,9 +167,11 @@ final class HTML {
139
  * @param bool $block Whether to wrap the content in <p> tags.
140
  */
141
  public static function attention_description_noesc( $content, $block = true ) {
142
- $output = '<span class="description attention">' . $content . '</span>';
143
- // phpcs:ignore, WordPress.Security.EscapeOutput -- Method clearly states it's not escaped.
144
- echo $block ? '<p>' . $output . '</p>' : $output;
 
 
145
  }
146
 
147
  /**
@@ -160,11 +190,13 @@ final class HTML {
160
  if ( \is_array( $input ) )
161
  $input = implode( PHP_EOL, $input );
162
 
 
 
163
  if ( $echo ) {
164
  // phpcs:ignore, WordPress.Security.EscapeOutput -- Escape your $input prior!
165
- echo '<div class="tsf-fields">' . $input . '</div>';
166
  } else {
167
- return '<div class="tsf-fields">' . $input . '</div>';
168
  }
169
  }
170
 
@@ -184,7 +216,7 @@ final class HTML {
184
 
185
  if ( $link ) {
186
  $output = sprintf(
187
- '<a href="%1$s" class="tsf-tooltip-item tsf-help" target="_blank" rel="nofollow noreferrer noopener" title="%2$s" data-desc="%2$s">[?]</a>',
188
  \esc_url( $link, [ 'https', 'http' ] ),
189
  \esc_attr( $description )
190
  );
@@ -195,7 +227,7 @@ final class HTML {
195
  );
196
  }
197
 
198
- $output = sprintf( '<span class="tsf-tooltip-wrap">%s</span>', $output );
199
 
200
  if ( $echo ) {
201
  // phpcs:ignore, WordPress.Security.EscapeOutput
@@ -212,37 +244,25 @@ final class HTML {
212
  * @since 4.1.0 No longer adds an extra space in front of the return value when no data is generated.
213
  * @internal
214
  *
215
- * @param array $data : {
216
  * string $k => mixed $v
217
  * }
218
- * @return string The HTML data attributes, with added space to the start.
219
  */
220
- public static function make_data_attributes( array $data ) {
221
 
222
  $ret = [];
223
 
224
  foreach ( $data as $k => $v ) {
225
- if ( ! is_scalar( $v ) ) {
226
- $ret[] = sprintf(
227
- 'data-%s="%s"',
228
- strtolower( preg_replace(
229
- '/([A-Z])/',
230
- '-$1',
231
- preg_replace( '/[^a-z0-9_\-]/i', '', $k )
232
- ) ), // dash case.
233
- htmlspecialchars( json_encode( $v, JSON_UNESCAPED_SLASHES ), ENT_COMPAT, 'UTF-8' )
234
- );
235
- } else {
236
- $ret[] = sprintf(
237
- 'data-%s="%s"',
238
- strtolower( preg_replace(
239
- '/([A-Z])/',
240
- '-$1',
241
- preg_replace( '/[^a-z0-9_\-]/i', '', $k )
242
- ) ), // dash case.
243
- \esc_attr( $v )
244
- );
245
- }
246
  }
247
 
248
  return $ret ? ' ' . implode( ' ', $ret ) : '';
37
  */
38
  final class HTML {
39
 
40
+ /**
41
+ * Helper function that constructs header elements. Does not escape.
42
+ *
43
+ * @since 4.1.4
44
+ *
45
+ * @param string $title The header title.
46
+ * @return string The header title.
47
+ */
48
+ public static function get_header_title( $title ) {
49
+ return sprintf( '<h4>%s</h4>', $title );
50
+ }
51
+
52
+ /**
53
+ * Helper function that constructs header elements.
54
+ *
55
+ * @since 4.1.4
56
+ *
57
+ * @param string $title The header title.
58
+ */
59
+ public static function header_title( $title ) {
60
+ // phpcs:ignore, WordPress.Security.EscapeOutput.OutputNotEscaped -- it is.
61
+ echo static::get_header_title( \esc_html( $title ) );
62
+ }
63
+
64
  /**
65
  * Mark up content with code tags.
66
  * Escapes all HTML, so `<` gets changed to `&lt;` and displays correctly.
84
  * @return string Content wrapped in code tags.
85
  */
86
  public static function code_wrap_noesc( $content ) {
87
+ return "<code>$content</code>";
88
  }
89
 
90
  /**
109
  * @param bool $block Whether to wrap the content in <p> tags.
110
  */
111
  public static function description_noesc( $content, $block = true ) {
112
+ printf(
113
+ ( $block ? '<p>%s</p>' : '%s' ),
114
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- Method clearly states it's not escaped.
115
+ "<span class=description>$content</span>"
116
+ );
117
  }
118
 
119
  /**
138
  * @param bool $block Whether to wrap the content in <p> tags.
139
  */
140
  public static function attention_noesc( $content, $block = true ) {
141
+ printf(
142
+ ( $block ? '<p>%s</p>' : '%s' ),
143
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- Method clearly states it's not escaped.
144
+ "<span class=attention>$content</span>"
145
+ );
146
  }
147
 
148
  /**
167
  * @param bool $block Whether to wrap the content in <p> tags.
168
  */
169
  public static function attention_description_noesc( $content, $block = true ) {
170
+ printf(
171
+ ( $block ? '<p>%s</p>' : '%s' ),
172
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- Method clearly states it's not escaped.
173
+ "<span class=\"description attention\">$content</span>"
174
+ );
175
  }
176
 
177
  /**
190
  if ( \is_array( $input ) )
191
  $input = implode( PHP_EOL, $input );
192
 
193
+ $output = "<div class=tsf-fields>$input</div>";
194
+
195
  if ( $echo ) {
196
  // phpcs:ignore, WordPress.Security.EscapeOutput -- Escape your $input prior!
197
+ echo $output;
198
  } else {
199
+ return $output;
200
  }
201
  }
202
 
216
 
217
  if ( $link ) {
218
  $output = sprintf(
219
+ '<a href="%1$s" class="tsf-tooltip-item tsf-help" target=_blank rel="nofollow noreferrer noopener" title="%2$s" data-desc="%2$s">[?]</a>',
220
  \esc_url( $link, [ 'https', 'http' ] ),
221
  \esc_attr( $description )
222
  );
227
  );
228
  }
229
 
230
+ $output = sprintf( '<span class=tsf-tooltip-wrap>%s</span>', $output );
231
 
232
  if ( $echo ) {
233
  // phpcs:ignore, WordPress.Security.EscapeOutput
244
  * @since 4.1.0 No longer adds an extra space in front of the return value when no data is generated.
245
  * @internal
246
  *
247
+ * @param iterable $data : {
248
  * string $k => mixed $v
249
  * }
250
+ * @return string The HTML data attributes, with added space to the start if something's created.
251
  */
252
+ public static function make_data_attributes( $data ) {
253
 
254
  $ret = [];
255
 
256
  foreach ( $data as $k => $v ) {
257
+ $ret[] = sprintf(
258
+ 'data-%s="%s"',
259
+ strtolower( preg_replace(
260
+ '/([A-Z])/',
261
+ '-$1',
262
+ preg_replace( '/[^a-z0-9_\-]/i', '', $k )
263
+ ) ), // dash case.
264
+ is_scalar( $v ) ? \esc_attr( $v ) : htmlspecialchars( json_encode( $v, JSON_UNESCAPED_SLASHES ), ENT_COMPAT, 'UTF-8' )
265
+ );
 
 
 
 
 
 
 
 
 
 
 
 
266
  }
267
 
268
  return $ret ? ' ' . implode( ' ', $ret ) : '';
inc/classes/interpreters/markdown.class.php CHANGED
@@ -29,7 +29,9 @@ namespace The_SEO_Framework\Interpreters;
29
  * Interprets anything you send here into Markdown. Or so it should.
30
  *
31
  * @since 4.1.4
32
- * @note Use `the_seo_framework()->convert_markdown() for easy access.
 
 
33
  *
34
  * @access protected
35
  * Everything in this class is subject to change or deletion.
@@ -85,9 +87,8 @@ final class Markdown {
85
 
86
  $md_types = empty( $convert ) ? $conversions : array_intersect( $conversions, $convert );
87
 
88
- if ( 2 === \count( array_intersect( $md_types, [ 'em', 'strong' ] ) ) ) :
89
  $text = static::strong_em( $text );
90
- endif;
91
 
92
  foreach ( $md_types as $type ) :
93
  switch ( $type ) :
@@ -136,6 +137,7 @@ final class Markdown {
136
  private static function strong_em( $text ) {
137
 
138
  $count = preg_match_all( '/(?:\*{3})([^\*{\3}]+)(?:\*{3})/', $text, $matches, PREG_PATTERN_ORDER );
 
139
  for ( $i = 0; $i < $count; $i++ ) {
140
  $text = str_replace(
141
  $matches[0][ $i ],
@@ -259,6 +261,7 @@ final class Markdown {
259
 
260
  $count = preg_match_all( '/(?:(?:\[{1})([^\]]+)(?:\]{1})(?:\({1})([^\)\(]+)(?:\){1}))/', $text, $matches, PREG_PATTERN_ORDER );
261
 
 
262
  $_string = $internal ? '<a href="%s">%s</a>' : '<a href="%s" target="_blank" rel="nofollow noreferrer noopener">%s</a>';
263
 
264
  for ( $i = 0; $i < $count; $i++ ) {
29
  * Interprets anything you send here into Markdown. Or so it should.
30
  *
31
  * @since 4.1.4
32
+ * @note Use `tsf()->convert_markdown() for easy access.
33
+ *
34
+ * @NOTE to self: This is also used in XHTML configurations. Keep it strict!
35
  *
36
  * @access protected
37
  * Everything in this class is subject to change or deletion.
87
 
88
  $md_types = empty( $convert ) ? $conversions : array_intersect( $conversions, $convert );
89
 
90
+ if ( isset( $md_types['*'], $md_types['**'] ) )
91
  $text = static::strong_em( $text );
 
92
 
93
  foreach ( $md_types as $type ) :
94
  switch ( $type ) :
137
  private static function strong_em( $text ) {
138
 
139
  $count = preg_match_all( '/(?:\*{3})([^\*{\3}]+)(?:\*{3})/', $text, $matches, PREG_PATTERN_ORDER );
140
+
141
  for ( $i = 0; $i < $count; $i++ ) {
142
  $text = str_replace(
143
  $matches[0][ $i ],
261
 
262
  $count = preg_match_all( '/(?:(?:\[{1})([^\]]+)(?:\]{1})(?:\({1})([^\)\(]+)(?:\){1}))/', $text, $matches, PREG_PATTERN_ORDER );
263
 
264
+ // Keep this XHTML compatible!
265
  $_string = $internal ? '<a href="%s">%s</a>' : '<a href="%s" target="_blank" rel="nofollow noreferrer noopener">%s</a>';
266
 
267
  for ( $i = 0; $i < $count; $i++ ) {
inc/classes/interpreters/seobar.class.php CHANGED
@@ -1,7 +1,7 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes\Interpreters\SeoBar
4
- * @subpackage The_SEO_Framework\SeoBar
5
  */
6
 
7
  namespace The_SEO_Framework\Interpreters;
@@ -25,23 +25,37 @@ namespace The_SEO_Framework\Interpreters;
25
 
26
  \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
 
 
 
28
  /**
29
  * Interprets the SEO Bar into an HTML item.
30
  *
31
  * @since 4.0.0
32
- * @see \the_seo_framework()->get_generated_seo_bar( $args ) for easy access.
33
  *
34
  * @access public
35
  * Note that you can't instance this class. Only static methods and properties are accessible.
36
  * @final Can't be extended.
37
  */
38
- final class SeoBar {
39
 
40
- const STATE_UNDEFINED = 0b0000;
41
- const STATE_UNKNOWN = 0b0001;
42
- const STATE_BAD = 0b0010;
43
- const STATE_OKAY = 0b0100;
44
- const STATE_GOOD = 0b1000;
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
  /**
47
  * @since 4.0.0
@@ -51,27 +65,23 @@ final class SeoBar {
51
 
52
  /**
53
  * @since 4.0.0
54
- * @var \The_SEO_Framework\Interpreters\SeoBar $instance The instance.
55
  */
56
  private static $instance;
57
 
58
  /**
59
  * @since 4.0.0
60
- * @var array $item The current SEO Bar item list : {
61
- *
 
 
 
 
 
62
  * }
63
  */
64
  private static $items = [];
65
 
66
- /**
67
- * Constructor.
68
- *
69
- * @since 4.0.0
70
- */
71
- private function __construct() {
72
- static::$instance = &$this;
73
- }
74
-
75
  /**
76
  * Returns this instance.
77
  *
@@ -80,8 +90,7 @@ final class SeoBar {
80
  * @return static
81
  */
82
  private static function get_instance() {
83
- static::$instance instanceof static or new static;
84
- return static::$instance;
85
  }
86
 
87
  /**
@@ -93,21 +102,22 @@ final class SeoBar {
93
  * @param array $query : {
94
  * int $id : Required. The current post or term ID.
95
  * string $taxonomy : Optional. If not set, this will interpret it as a post.
 
96
  * string $post_type : Optional. If not set, this will be automatically filled.
97
  * This parameter is ignored for taxonomies.
 
 
98
  * }
99
  * @return string The SEO Bar.
100
  */
101
- public static function generate_bar( array $query ) {
102
-
103
- static::$query = array_merge(
104
- [
105
- 'id' => 0,
106
- 'taxonomy' => '',
107
- 'post_type' => '',
108
- ],
109
- $query
110
- );
111
 
112
  if ( ! static::$query['id'] ) return '';
113
 
@@ -115,13 +125,13 @@ final class SeoBar {
115
  static::$query['post_type'] = static::$query['post_type'] ?: \get_post_type( static::$query['id'] );
116
 
117
  if ( static::$query['taxonomy'] ) {
118
- $builder = \The_SEO_Framework\Builders\SeoBar_Term::get_instance();
119
  } else {
120
- $builder = \The_SEO_Framework\Builders\SeoBar_Page::get_instance();
121
  }
122
 
123
  $instance = static::get_instance();
124
- $instance->store_default_bar_items( $builder );
125
 
126
  /**
127
  * Add or adjust SEO Bar items here, after the tests have run.
@@ -148,7 +158,14 @@ final class SeoBar {
148
  * @since 4.1.1 Is now static.
149
  * @collector
150
  *
151
- * @return array SEO Bar items. Passed by reference.
 
 
 
 
 
 
 
152
  */
153
  public static function &collect_seo_bar_items() {
154
  return static::$items;
@@ -169,7 +186,7 @@ final class SeoBar {
169
  * Does not accept HTML for performant ARIA support.
170
  * }
171
  */
172
- public static function register_seo_bar_item( $key, array $item ) {
173
  static::$items[ $key ] = $item;
174
  }
175
 
@@ -217,9 +234,9 @@ final class SeoBar {
217
  * @since 4.1.4 Offloaded the builder's instantiation.
218
  * @factory
219
  *
220
- * @param \The_SEO_Framework\Builders\SeoBar $builder The builder instance.
221
  */
222
- private function store_default_bar_items( $builder ) {
223
 
224
  /**
225
  * Adjust interpreter and builder items here, before the tests have run.
@@ -230,14 +247,14 @@ final class SeoBar {
230
  * @link Example: https://gist.github.com/sybrew/03dd428deadc860309879e1d5208e1c4
231
  * @see related (recommended) action 'the_seo_framework_seo_bar'
232
  * @since 4.0.0
233
- * @param string $interpreter The current class name.
234
- * @param \The_SEO_Framework\Builders\SeoBar $builder The builder object.
235
  */
236
  \do_action_ref_array( 'the_seo_framework_prepare_seo_bar', [ static::class, $builder ] );
237
 
238
  $items = &$this->collect_seo_bar_items();
239
 
240
- foreach ( $builder->_run_test( $builder::$tests, static::$query ) as $key => $data )
241
  $items[ $key ] = $data;
242
  }
243
 
@@ -246,10 +263,10 @@ final class SeoBar {
246
  *
247
  * @since 4.0.0
248
  *
249
- * @param array $items The SEO Bar items.
250
  * @return string The SEO Bar
251
  */
252
- private function create_seo_bar( array $items ) {
253
 
254
  $blocks = [];
255
 
@@ -274,10 +291,10 @@ final class SeoBar {
274
  * WordPress still hangs on tight to their PHP5.2 roots, where HTML4+ escaping wasn't supported well. Updating that requires
275
  * a whole lot of time, and paves way for potential security issues due to oversight. But, that'd speed up escaping for everyone.
276
  *
277
- * @param array $items The SEO Bar items.
278
  * @yield The SEO Bar HTML item.
279
  */
280
- private function generate_seo_bar_blocks( array $items ) {
281
  foreach ( $items as $item )
282
  yield vsprintf(
283
  '<span class="tsf-seo-bar-section-wrap tsf-tooltip-wrap"><span class="tsf-seo-bar-item tsf-tooltip-item tsf-seo-bar-%1$s" title="%2$s" aria-label="%2$s" data-desc="%3$s" tabindex=0>%4$s</span></span>',
@@ -300,7 +317,7 @@ final class SeoBar {
300
  * @param string $type The description type. Accepts 'html' or 'aria'.
301
  * @return string The SEO Bar item description.
302
  */
303
- private function build_item_description( array $item, $type ) {
304
 
305
  static $gettext = null;
306
  if ( null === $gettext ) {
@@ -341,7 +358,7 @@ final class SeoBar {
341
  * @param array $item See `$this->register_seo_bar_item()`
342
  * @return string The SEO Bar item assessment, in plaintext.
343
  */
344
- private function enumerate_assessment_list( array $item ) {
345
 
346
  $count = \count( $item['assess'] );
347
  $assessments = [];
@@ -425,14 +442,12 @@ final class SeoBar {
425
  * @param array $item See `$this->register_seo_bar_item()`
426
  * @return string The SEO Bar item assessment, in plaintext.
427
  */
428
- private function interpret_status_to_symbol( array $item ) {
429
 
430
- static $use_symbols = null;
431
- if ( null === $use_symbols ) {
432
- $use_symbols = (bool) \the_seo_framework()->get_option( 'seo_bar_symbols' );
433
- }
434
 
435
- if ( $use_symbols && $item['status'] ^ static::STATE_GOOD ) {
436
  switch ( $item['status'] ) :
437
  case static::STATE_OKAY:
438
  $symbol = '!?';
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Interpreters\SEOBar
4
+ * @subpackage The_SEO_Framework\SEOBar
5
  */
6
 
7
  namespace The_SEO_Framework\Interpreters;
25
 
26
  \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
 
28
+ use function \The_SEO_Framework\umemo;
29
+
30
  /**
31
  * Interprets the SEO Bar into an HTML item.
32
  *
33
  * @since 4.0.0
34
+ * @see \tsf()->get_generated_seo_bar( $args ) for easy access.
35
  *
36
  * @access public
37
  * Note that you can't instance this class. Only static methods and properties are accessible.
38
  * @final Can't be extended.
39
  */
40
+ final class SEOBar {
41
 
42
+ /**
43
+ * The recognized SEO Bar item states.
44
+ * Mixed types will fall back to 'undefined'.
45
+ *
46
+ * @since 4.1.0
47
+ * @access public
48
+ * @var int <bit 0> STATE_UNDEFINED
49
+ * @var int <bit 1> STATE_UNKNOWN
50
+ * @var int <bit 10> STATE_BAD
51
+ * @var int <bit 100> STATE_OKAY
52
+ * @var int <bit 1000> STATE_GOOD
53
+ */
54
+ public const STATE_UNDEFINED = 0b0000;
55
+ public const STATE_UNKNOWN = 0b0001;
56
+ public const STATE_BAD = 0b0010;
57
+ public const STATE_OKAY = 0b0100;
58
+ public const STATE_GOOD = 0b1000;
59
 
60
  /**
61
  * @since 4.0.0
65
 
66
  /**
67
  * @since 4.0.0
68
+ * @var \The_SEO_Framework\Interpreters\SEOBar $instance The instance.
69
  */
70
  private static $instance;
71
 
72
  /**
73
  * @since 4.0.0
74
+ * @var array $item The current SEO Bar item list. {
75
+ * string $symbol : The displayed symbol that identifies your bar.
76
+ * string $title : The title of the assessment.
77
+ * string $status : Accepts 'good', 'okay', 'bad', 'unknown'.
78
+ * string $reason : The final assessment: The reason for the $status.
79
+ * string $assess : The assessments on why the reason is set. Keep it short and concise!
80
+ * Does not accept HTML for performant ARIA support.
81
  * }
82
  */
83
  private static $items = [];
84
 
 
 
 
 
 
 
 
 
 
85
  /**
86
  * Returns this instance.
87
  *
90
  * @return static
91
  */
92
  private static function get_instance() {
93
+ return static::$instance ?? ( static::$instance = new static );
 
94
  }
95
 
96
  /**
102
  * @param array $query : {
103
  * int $id : Required. The current post or term ID.
104
  * string $taxonomy : Optional. If not set, this will interpret it as a post.
105
+ * string $pta : Not implemented. Do not populate.
106
  * string $post_type : Optional. If not set, this will be automatically filled.
107
  * This parameter is ignored for taxonomies.
108
+ * This parameter will become obsolete once WP fixes its post cache.
109
+ * <https://core.trac.wordpress.org/ticket/50567>
110
  * }
111
  * @return string The SEO Bar.
112
  */
113
+ public static function generate_bar( $query ) {
114
+
115
+ static::$query = $query + [
116
+ 'id' => 0,
117
+ 'taxonomy' => '',
118
+ 'pta' => '',
119
+ 'post_type' => '',
120
+ ];
 
 
121
 
122
  if ( ! static::$query['id'] ) return '';
123
 
125
  static::$query['post_type'] = static::$query['post_type'] ?: \get_post_type( static::$query['id'] );
126
 
127
  if ( static::$query['taxonomy'] ) {
128
+ $builder = \The_SEO_Framework\Builders\SEOBar\Term::get_instance();
129
  } else {
130
+ $builder = \The_SEO_Framework\Builders\SEOBar\Page::get_instance();
131
  }
132
 
133
  $instance = static::get_instance();
134
+ $instance->store_seo_bar_items( $builder );
135
 
136
  /**
137
  * Add or adjust SEO Bar items here, after the tests have run.
158
  * @since 4.1.1 Is now static.
159
  * @collector
160
  *
161
+ * @return array SEO Bar items. Passed by reference. {
162
+ * string $symbol : The displayed symbol that identifies your bar.
163
+ * string $title : The title of the assessment.
164
+ * string $status : Accepts 'good', 'okay', 'bad', 'unknown'.
165
+ * string $reason : The final assessment: The reason for the $status.
166
+ * string $assess : The assessments on why the reason is set. Keep it short and concise!
167
+ * Does not accept HTML for performant ARIA support.
168
+ * }
169
  */
170
  public static function &collect_seo_bar_items() {
171
  return static::$items;
186
  * Does not accept HTML for performant ARIA support.
187
  * }
188
  */
189
+ public static function register_seo_bar_item( $key, $item ) {
190
  static::$items[ $key ] = $item;
191
  }
192
 
234
  * @since 4.1.4 Offloaded the builder's instantiation.
235
  * @factory
236
  *
237
+ * @param \The_SEO_Framework\Builders\SEOBar\Main $builder The builder instance.
238
  */
239
+ private function store_seo_bar_items( $builder ) {
240
 
241
  /**
242
  * Adjust interpreter and builder items here, before the tests have run.
247
  * @link Example: https://gist.github.com/sybrew/03dd428deadc860309879e1d5208e1c4
248
  * @see related (recommended) action 'the_seo_framework_seo_bar'
249
  * @since 4.0.0
250
+ * @param string $interpreter The current class name.
251
+ * @param \The_SEO_Framework\Builders\SEOBar\Main $builder The builder object.
252
  */
253
  \do_action_ref_array( 'the_seo_framework_prepare_seo_bar', [ static::class, $builder ] );
254
 
255
  $items = &$this->collect_seo_bar_items();
256
 
257
+ foreach ( $builder->_run_all_tests( static::$query ) as $key => $data )
258
  $items[ $key ] = $data;
259
  }
260
 
263
  *
264
  * @since 4.0.0
265
  *
266
+ * @param iterable $items The SEO Bar items.
267
  * @return string The SEO Bar
268
  */
269
+ private function create_seo_bar( $items ) {
270
 
271
  $blocks = [];
272
 
291
  * WordPress still hangs on tight to their PHP5.2 roots, where HTML4+ escaping wasn't supported well. Updating that requires
292
  * a whole lot of time, and paves way for potential security issues due to oversight. But, that'd speed up escaping for everyone.
293
  *
294
+ * @param iterable $items The SEO Bar items.
295
  * @yield The SEO Bar HTML item.
296
  */
297
+ private function generate_seo_bar_blocks( $items ) {
298
  foreach ( $items as $item )
299
  yield vsprintf(
300
  '<span class="tsf-seo-bar-section-wrap tsf-tooltip-wrap"><span class="tsf-seo-bar-item tsf-tooltip-item tsf-seo-bar-%1$s" title="%2$s" aria-label="%2$s" data-desc="%3$s" tabindex=0>%4$s</span></span>',
317
  * @param string $type The description type. Accepts 'html' or 'aria'.
318
  * @return string The SEO Bar item description.
319
  */
320
+ private function build_item_description( $item, $type ) {
321
 
322
  static $gettext = null;
323
  if ( null === $gettext ) {
358
  * @param array $item See `$this->register_seo_bar_item()`
359
  * @return string The SEO Bar item assessment, in plaintext.
360
  */
361
+ private function enumerate_assessment_list( $item ) {
362
 
363
  $count = \count( $item['assess'] );
364
  $assessments = [];
442
  * @param array $item See `$this->register_seo_bar_item()`
443
  * @return string The SEO Bar item assessment, in plaintext.
444
  */
445
+ private function interpret_status_to_symbol( $item ) {
446
 
447
+ $symbols = umemo( __METHOD__ . '/use_symbols' )
448
+ ?? umemo( __METHOD__ . '/use_symbols', (bool) \tsf()->get_option( 'seo_bar_symbols' ) );
 
 
449
 
450
+ if ( $symbols && $item['status'] ^ static::STATE_GOOD ) {
451
  switch ( $item['status'] ) :
452
  case static::STATE_OKAY:
453
  $symbol = '!?';
inc/classes/interpreters/settings-input.class.php ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Classes\Interpreters\Settings_Input
4
+ * @subpackage The_SEO_Framework\Admin\Settings
5
+ */
6
+
7
+ namespace The_SEO_Framework\Interpreters;
8
+
9
+ /**
10
+ * The SEO Framework plugin
11
+ * Copyright (C) 2021 Sybre Waaijer, CyberWire B.V. (https://cyberwire.nl/)
12
+ *
13
+ * This program is free software: you can redistribute it and/or modify
14
+ * it under the terms of the GNU General Public License version 3 as published
15
+ * by the Free Software Foundation.
16
+ *
17
+ * This program is distributed in the hope that it will be useful,
18
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
+ * GNU General Public License for more details.
21
+ *
22
+ * You should have received a copy of the GNU General Public License
23
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
24
+ */
25
+
26
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
+
28
+ /**
29
+ * Interprets anything you send here into Form HTML. Or so it should.
30
+ * Meant for the SEO Settings page, only.
31
+ *
32
+ * @since 4.2.0
33
+ *
34
+ * @access protected
35
+ * Everything in this class is subject to change or deletion.
36
+ * @internal
37
+ * @final Can't be extended.
38
+ */
39
+ final class Settings_Input {
40
+
41
+ /**
42
+ * Helper function that constructs id attributes for use in form fields.
43
+ *
44
+ * One-liner I forwent:
45
+ * return THE_SEO_FRAMEWORK_SITE_OPTIONS . '['. implode( '][', $id ) . ']';
46
+ *
47
+ * @since 4.2.0
48
+ *
49
+ * @param string|string[] $id The field id, or a map of indexes therefor.
50
+ * @return string Full field id
51
+ */
52
+ public static function get_field_id( $id ) {
53
+
54
+ $field_id = THE_SEO_FRAMEWORK_SITE_OPTIONS;
55
+
56
+ foreach ( (array) $id as $subid )
57
+ $field_id .= "[$subid]";
58
+
59
+ return $field_id;
60
+ }
61
+
62
+ /**
63
+ * Echo constructed id attributes in form fields.
64
+ *
65
+ * @since 4.2.0
66
+ * @uses static::get_field_id() Constructs id attributes for use in form fields.
67
+ *
68
+ * @param string|string[] $id The field id, or a map of indexes therefor.
69
+ */
70
+ public static function field_id( $id ) {
71
+ echo \esc_attr( static::get_field_id( $id ) );
72
+ }
73
+
74
+ /**
75
+ * Helper function that constructs name attributes for use in form fields.
76
+ *
77
+ * Alias of field_id.
78
+ *
79
+ * @since 4.2.0
80
+ * @ignore
81
+ *
82
+ * @param string|string[] $name The field name, or a map of indexes therefor.
83
+ * @return string Full field name
84
+ */
85
+ public static function get_field_name( $name ) {
86
+ return static::get_field_id( $name );
87
+ }
88
+
89
+ /**
90
+ * Echo constructed name attributes in form fields.
91
+ *
92
+ * Alias of field_id.
93
+ *
94
+ * @since 4.2.0
95
+ * @uses static::get_field_name() Construct name attributes for use in form fields.
96
+ * @ignore
97
+ *
98
+ * @param string|string[] $name The field name, or a map of indexes therefor.
99
+ */
100
+ public static function field_name( $name ) {
101
+ // phpcs:ignore, WordPress.Security.EscapeOutput.OutputNotEscaped -- field_id escapes.
102
+ echo static::field_id( $name );
103
+ }
104
+
105
+ /**
106
+ * Returns a chechbox wrapper.
107
+ *
108
+ * This is put in this class, instead of Form, because
109
+ * 1) Retrieves the global settings ID from input.
110
+ * 2) Retrieves the default-state of the checkbox via the global settings.
111
+ *
112
+ * @since 4.2.0
113
+ *
114
+ * @param array $args : {
115
+ * string|map $id The option index or map of indexes therefor, used as field ID.
116
+ * string $class The checkbox class.
117
+ * string $label The checkbox label description, placed inline of the checkbox.
118
+ * null|mixed $value The option value. If not set, it'll try to retrieve the value based on $id.
119
+ * string $description The checkbox additional description, placed underneat.
120
+ * array $data The checkbox field data. Sub-items are expected to be escaped if they're not an array.
121
+ * bool $escape Whether to enable escaping of the $label and $description.
122
+ * bool $disabled Whether to disable the checkbox field.
123
+ * }
124
+ * @return string HTML checkbox output.
125
+ */
126
+ public static function make_checkbox( $args = [] ) {
127
+
128
+ $args = array_merge(
129
+ [
130
+ 'id' => '',
131
+ 'class' => '',
132
+ 'label' => '',
133
+ 'value' => null,
134
+ 'description' => '',
135
+ 'data' => [],
136
+ 'escape' => true,
137
+ 'disabled' => false,
138
+ ],
139
+ $args
140
+ );
141
+
142
+ if ( $args['escape'] ) {
143
+ $args['description'] = \esc_html( $args['description'] );
144
+ $args['label'] = \esc_html( $args['label'] );
145
+ }
146
+
147
+ $tsf = \tsf();
148
+
149
+ $field_id = $field_name = static::get_field_id( $args['id'] );
150
+ $value = $args['value'] ?? $tsf->get_option( $args['id'] );
151
+
152
+ $cb_classes = [];
153
+
154
+ if ( $args['class'] )
155
+ $cb_classes[] = $args['class'];
156
+
157
+ if ( $args['disabled'] ) {
158
+ $cb_classes[] = 'tsf-disabled';
159
+ } else {
160
+ array_push( $cb_classes, ...static::get_conditional_checked_classes( $args['id'] ) );
161
+ }
162
+
163
+ $output = sprintf(
164
+ '<span class="tsf-toblock">%s</span>',
165
+ vsprintf(
166
+ '<label for="%s"%s>%s</label>',
167
+ [
168
+ $tsf->s_field_id( $field_id ),
169
+ ( $args['disabled'] ? ' class="tsf-disabled"' : '' ),
170
+ vsprintf(
171
+ '<input type=checkbox class="%s" name="%s" id="%s" value="1" %s%s %s /> %s',
172
+ [
173
+ \esc_attr( implode( ' ', array_filter( $cb_classes ) ) ),
174
+ $tsf->s_field_id( $field_name ),
175
+ $tsf->s_field_id( $field_id ),
176
+ \checked( $value, true, false ),
177
+ ( $args['disabled'] ? ' disabled' : '' ),
178
+ HTML::make_data_attributes( $args['data'] ),
179
+ $args['label'],
180
+ ]
181
+ ),
182
+ ]
183
+ )
184
+ );
185
+
186
+ return $output .= (
187
+ $args['description']
188
+ ? sprintf( '<p class="description tsf-option-spacer">%s</p>', $args['description'] )
189
+ : ''
190
+ );
191
+ }
192
+
193
+ /**
194
+ * Returns the HTML class wrap for warning/default Checkbox options.
195
+ *
196
+ * @since 4.2.0
197
+ *
198
+ * @param string|string[] $key Required. The option name, or a map of indexes therefor.
199
+ * @return string[] The conditional checked classes.
200
+ */
201
+ public static function get_conditional_checked_classes( $key ) {
202
+ $tsf = \tsf();
203
+ return [
204
+ $tsf->get_default_option( $key ) ? 'tsf-default-selected' : '',
205
+ $tsf->get_warned_option( $key ) ? 'tsf-warning-selected' : '',
206
+ ];
207
+ }
208
+ }
inc/classes/interpreters/sitemap-xsl.class.php ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Classes\Interpreters\Sitemap\XSL
4
+ * @subpackage The_SEO_Framework\Sitemap\XSL
5
+ */
6
+
7
+ namespace The_SEO_Framework\Interpreters;
8
+
9
+ /**
10
+ * The SEO Framework plugin
11
+ * Copyright (C) 2021 Sybre Waaijer, CyberWire B.V. (https://cyberwire.nl/)
12
+ *
13
+ * This program is free software: you can redistribute it and/or modify
14
+ * it under the terms of the GNU General Public License version 3 as published
15
+ * by the Free Software Foundation.
16
+ *
17
+ * This program is distributed in the hope that it will be useful,
18
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
+ * GNU General Public License for more details.
21
+ *
22
+ * You should have received a copy of the GNU General Public License
23
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
24
+ */
25
+
26
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
+
28
+ use function \The_SEO_Framework\umemo;
29
+
30
+ /**
31
+ * Interprets the Sitemap Stylesheet of the optimized Sitemap.
32
+ *
33
+ * @since 4.2.0
34
+ *
35
+ * @access private
36
+ * @final Can't be extended.
37
+ */
38
+ final class Sitemap_XSL {
39
+
40
+ /**
41
+ * Prepares the sitemap stylesheet: loads all actions.
42
+ *
43
+ * @since 4.2.0
44
+ */
45
+ public static function prepare() {
46
+
47
+ $class = static::class;
48
+
49
+ // Adds site icon tags to the sitemap stylesheet.
50
+ \add_action( 'the_seo_framework_xsl_head', 'wp_site_icon', 99 );
51
+
52
+ \add_action( 'the_seo_framework_xsl_head', "$class::_print_xsl_global_variables", 0 );
53
+ \add_action( 'the_seo_framework_xsl_head', "$class::_print_xsl_title" );
54
+ \add_action( 'the_seo_framework_xsl_head', "$class::_print_xsl_styles" );
55
+
56
+ \add_action( 'the_seo_framework_xsl_description', "$class::_print_xsl_description" );
57
+
58
+ \add_action( 'the_seo_framework_xsl_content', "$class::_print_xsl_content" );
59
+
60
+ \add_action( 'the_seo_framework_xsl_footer', "$class::_print_xsl_footer" );
61
+ \add_action( 'site_icon_meta_tags', "$class::_convert_site_icon_meta_tags", PHP_INT_MAX );
62
+ }
63
+
64
+ /**
65
+ * Prints global XSL variables.
66
+ *
67
+ * @since 3.1.0
68
+ * @since 4.2.0 1. $tableMinWidth no longer adds 'px'.
69
+ * 2. Moved to class.
70
+ * @access private
71
+ * @param \The_SEO_Framework\Load $tsf tsf() object.
72
+ */
73
+ public static function _print_xsl_global_variables( $tsf ) {
74
+ $tsf->get_view( 'sitemap/xsl/vars' );
75
+ }
76
+
77
+ /**
78
+ * Prints XSL title.
79
+ *
80
+ * @since 3.1.0
81
+ * @since 4.0.0 Now uses a consistent titling scheme.
82
+ * @since 4.2.0 Moved to class
83
+ * @access private
84
+ * @param \The_SEO_Framework\Load $tsf tsf() object.
85
+ */
86
+ public static function _print_xsl_title( $tsf ) {
87
+ $tsf->get_view( 'sitemap/xsl/title' );
88
+ }
89
+
90
+ /**
91
+ * Prints XSL styles.
92
+ *
93
+ * @since 3.1.0
94
+ * @since 4.2.0 1. Centered sitemap.
95
+ * 2. Moved to class.
96
+ * @access private
97
+ * @param \The_SEO_Framework\Load $tsf tsf() object.
98
+ */
99
+ public static function _print_xsl_styles( $tsf ) {
100
+ $tsf->get_view( 'sitemap/xsl/styles' );
101
+ }
102
+
103
+ /**
104
+ * Prints XSL description.
105
+ *
106
+ * @since 3.1.0
107
+ * @since 4.2.0 Moved to class;
108
+ *
109
+ * @access private
110
+ * @param \The_SEO_Framework\Load $tsf tsf() object.
111
+ */
112
+ public static function _print_xsl_description( $tsf ) {
113
+ $tsf->get_view( 'sitemap/xsl/description' );
114
+ }
115
+ /**
116
+ * Prints XSL content.
117
+ *
118
+ * @since 3.1.0
119
+ * @since 4.2.0 Moved to class.
120
+ *
121
+ * @param \The_SEO_Framework\Load $tsf tsf() object.
122
+ */
123
+ public static function _print_xsl_content( $tsf ) {
124
+ $tsf->get_view( 'sitemap/xsl/table' );
125
+ }
126
+
127
+ /**
128
+ * Prints XSL footer.
129
+ *
130
+ * @since 3.1.0
131
+ * @since 4.2.0 Moved to class
132
+ * @access private
133
+ *
134
+ * @param \The_SEO_Framework\Load $tsf tsf() object.
135
+ */
136
+ public static function _print_xsl_footer( $tsf ) {
137
+ /**
138
+ * @since 2.8.0
139
+ * @param bool $indicator
140
+ */
141
+ \apply_filters( 'the_seo_framework_indicator_sitemap', true )
142
+ and $tsf->get_view( 'sitemap/xsl/footer' );
143
+ }
144
+
145
+ /**
146
+ * Converts meta tags that aren't XHTML to XHTML, loosely.
147
+ * Doesn't fix attribute minimization. TODO?..
148
+ *
149
+ * @since 3.1.4
150
+ * @since 4.2.0 Moved to class.
151
+ *
152
+ * @param array $tags Site Icon meta elements.
153
+ * @return array The converted meta tags.
154
+ */
155
+ public static function _convert_site_icon_meta_tags( $tags ) {
156
+
157
+ foreach ( $tags as &$tag ) {
158
+ $tag = \wp_kses(
159
+ \force_balance_tags( $tag ),
160
+ [
161
+ 'link' => [
162
+ 'charset' => [],
163
+ 'rel' => [],
164
+ 'sizes' => [],
165
+ 'href' => [],
166
+ 'hreflang' => [],
167
+ 'media' => [],
168
+ 'rev' => [],
169
+ 'target' => [],
170
+ 'type' => [],
171
+ ],
172
+ 'meta' => [
173
+ 'content' => [],
174
+ 'property' => [],
175
+ 'http-equiv' => [],
176
+ 'name' => [],
177
+ 'scheme' => [],
178
+ ],
179
+ ],
180
+ []
181
+ );
182
+ }
183
+
184
+ return $tags;
185
+ }
186
+ }
inc/classes/load.class.php CHANGED
@@ -86,27 +86,27 @@ final class Load extends Cache {
86
 
87
  if ( _has_run( __METHOD__ ) ) {
88
  // Don't construct twice, warn developer.
89
- $this->_doing_it_wrong( __METHOD__, 'Do not instance this class. Use function <code>the_seo_framework()</code> instead.', '3.1.0' );
90
  return null;
91
  }
92
 
93
- //= Setup debug vars before initializing anything else.
94
  $this->init_debug_vars();
95
 
96
  if ( $this->the_seo_framework_debug ) {
97
- $debug_instance = Debug::get_instance();
98
 
99
  \add_action( 'the_seo_framework_do_before_output', [ $debug_instance, '_set_debug_query_output_cache' ] );
100
  \add_action( 'admin_footer', [ $debug_instance, '_debug_output' ] );
101
  \add_action( 'wp_footer', [ $debug_instance, '_debug_output' ] );
102
  }
103
 
104
- //= Register the capabilities early.
105
  \add_filter( 'option_page_capability_' . THE_SEO_FRAMEWORK_SITE_OPTIONS, [ $this, 'get_settings_capability' ] );
106
 
107
  $this->pretty_permalinks = '' !== \get_option( 'permalink_structure' );
108
 
109
- //= Load plugin at init 0.
110
  \add_action( 'init', [ $this, 'init_the_seo_framework' ], 0 );
111
 
112
  $this->is_headless = [
@@ -122,7 +122,7 @@ final class Load extends Cache {
122
  'constant THE_SEO_FRAMEWORK_HEADLESS'
123
  ) ) \defined( 'THE_SEO_FRAMEWORK_HEADLESS' ) or \define( 'THE_SEO_FRAMEWORK_HEADLESS', true );
124
 
125
- //= A headless boi is a good boi. Far less annoying, they are.
126
  if ( \defined( 'THE_SEO_FRAMEWORK_HEADLESS' ) ) {
127
  $this->is_headless = [
128
  'meta' => true,
@@ -147,7 +147,7 @@ final class Load extends Cache {
147
 
148
  $this->the_seo_framework_debug = \defined( 'THE_SEO_FRAMEWORK_DEBUG' ) && THE_SEO_FRAMEWORK_DEBUG ?: $this->the_seo_framework_debug;
149
  if ( $this->the_seo_framework_debug )
150
- \The_SEO_Framework\Debug::_set_instance( $this->the_seo_framework_debug );
151
 
152
  $this->the_seo_framework_use_transients = \defined( 'THE_SEO_FRAMEWORK_DISABLE_TRANSIENTS' ) && THE_SEO_FRAMEWORK_DISABLE_TRANSIENTS ? false : $this->the_seo_framework_use_transients;
153
 
@@ -207,6 +207,11 @@ final class Load extends Cache {
207
  // Easy Digital Downloads.
208
  $this->_include_compat( 'edd', 'plugin' );
209
  }
 
 
 
 
 
210
  }
211
 
212
  /**
@@ -222,7 +227,8 @@ final class Load extends Cache {
222
  * @param string $replacement Optional. The function that should have been called. Default null.
223
  */
224
  public function _deprecated_function( $function, $version, $replacement = null ) { // phpcs:ignore -- Wrong asserts, copied method name.
225
- Debug::get_instance()->_deprecated_function( $function, $version, $replacement ); // phpcs:ignore -- Wrong asserts, copied method name.
 
226
  }
227
 
228
  /**
@@ -238,7 +244,8 @@ final class Load extends Cache {
238
  * @param string $version The version of WordPress where the message was added.
239
  */
240
  public function _doing_it_wrong( $function, $message, $version = null ) { // phpcs:ignore -- Wrong asserts, copied method name.
241
- Debug::get_instance()->_doing_it_wrong( $function, $message, $version ); // phpcs:ignore -- Wrong asserts, copied method name.
 
242
  }
243
 
244
  /**
@@ -252,6 +259,6 @@ final class Load extends Cache {
252
  * @param string $message A message explaining what has been done incorrectly.
253
  */
254
  public function _inaccessible_p_or_m( $p_or_m, $message = '' ) {
255
- Debug::get_instance()->_inaccessible_p_or_m( $p_or_m, $message );
256
  }
257
  }
86
 
87
  if ( _has_run( __METHOD__ ) ) {
88
  // Don't construct twice, warn developer.
89
+ $this->_doing_it_wrong( __METHOD__, 'Do not instance this class. Use function <code>tsf()</code> instead.', '3.1.0' );
90
  return null;
91
  }
92
 
93
+ // Setup debug vars before initializing anything else.
94
  $this->init_debug_vars();
95
 
96
  if ( $this->the_seo_framework_debug ) {
97
+ $debug_instance = Internal\Debug::get_instance();
98
 
99
  \add_action( 'the_seo_framework_do_before_output', [ $debug_instance, '_set_debug_query_output_cache' ] );
100
  \add_action( 'admin_footer', [ $debug_instance, '_debug_output' ] );
101
  \add_action( 'wp_footer', [ $debug_instance, '_debug_output' ] );
102
  }
103
 
104
+ // Register the capabilities early.
105
  \add_filter( 'option_page_capability_' . THE_SEO_FRAMEWORK_SITE_OPTIONS, [ $this, 'get_settings_capability' ] );
106
 
107
  $this->pretty_permalinks = '' !== \get_option( 'permalink_structure' );
108
 
109
+ // Load plugin at init 0.
110
  \add_action( 'init', [ $this, 'init_the_seo_framework' ], 0 );
111
 
112
  $this->is_headless = [
122
  'constant THE_SEO_FRAMEWORK_HEADLESS'
123
  ) ) \defined( 'THE_SEO_FRAMEWORK_HEADLESS' ) or \define( 'THE_SEO_FRAMEWORK_HEADLESS', true );
124
 
125
+ // A headless boi is a good boi. Far less annoying, they are.
126
  if ( \defined( 'THE_SEO_FRAMEWORK_HEADLESS' ) ) {
127
  $this->is_headless = [
128
  'meta' => true,
147
 
148
  $this->the_seo_framework_debug = \defined( 'THE_SEO_FRAMEWORK_DEBUG' ) && THE_SEO_FRAMEWORK_DEBUG ?: $this->the_seo_framework_debug;
149
  if ( $this->the_seo_framework_debug )
150
+ Internal\Debug::_set_instance( $this->the_seo_framework_debug );
151
 
152
  $this->the_seo_framework_use_transients = \defined( 'THE_SEO_FRAMEWORK_DISABLE_TRANSIENTS' ) && THE_SEO_FRAMEWORK_DISABLE_TRANSIENTS ? false : $this->the_seo_framework_use_transients;
153
 
207
  // Easy Digital Downloads.
208
  $this->_include_compat( 'edd', 'plugin' );
209
  }
210
+
211
+ if ( $this->detect_plugin( [ 'constants' => [ 'ELEMENTOR_VERSION' ] ] ) ) {
212
+ // Elementor
213
+ $this->_include_compat( 'elementor', 'plugin' );
214
+ }
215
  }
216
 
217
  /**
227
  * @param string $replacement Optional. The function that should have been called. Default null.
228
  */
229
  public function _deprecated_function( $function, $version, $replacement = null ) { // phpcs:ignore -- Wrong asserts, copied method name.
230
+ // phpcs:ignore -- Wrong asserts, copied method name.
231
+ Internal\Debug::get_instance()->_deprecated_function( $function, $version, $replacement );
232
  }
233
 
234
  /**
244
  * @param string $version The version of WordPress where the message was added.
245
  */
246
  public function _doing_it_wrong( $function, $message, $version = null ) { // phpcs:ignore -- Wrong asserts, copied method name.
247
+ // phpcs:ignore -- Wrong asserts, copied method name.
248
+ Internal\Debug::get_instance()->_doing_it_wrong( $function, $message, $version );
249
  }
250
 
251
  /**
259
  * @param string $message A message explaining what has been done incorrectly.
260
  */
261
  public function _inaccessible_p_or_m( $p_or_m, $message = '' ) {
262
+ Internal\Debug::get_instance()->_inaccessible_p_or_m( $p_or_m, $message );
263
  }
264
  }
inc/classes/post-data.class.php CHANGED
@@ -74,12 +74,10 @@ class Post_Data extends Detect {
74
  * @param string $item The item to get.
75
  * @param int $post_id The post ID.
76
  * @param bool $use_cache Whether to use caching.
 
77
  */
78
  public function get_post_meta_item( $item, $post_id = 0, $use_cache = true ) {
79
-
80
- $meta = $this->get_post_meta( $post_id ?: $this->get_the_real_ID(), $use_cache );
81
-
82
- return isset( $meta[ $item ] ) ? $meta[ $item ] : null;
83
  }
84
 
85
  /**
@@ -103,18 +101,17 @@ class Post_Data extends Detect {
103
  */
104
  public function get_post_meta( $post_id, $use_cache = true ) {
105
 
106
- if ( $use_cache ) {
107
- static $cache = [];
108
-
109
- if ( isset( $cache[ $post_id ] ) )
110
- return $cache[ $post_id ];
111
- }
112
 
113
  // get_post_meta() requires a valid post ID. Make sure that post exists.
114
  $post = \get_post( $post_id );
115
 
116
- if ( empty( $post->ID ) || ! $this->is_post_type_supported( $post->post_type ) )
117
- return $cache[ $post_id ] = [];
 
 
 
118
 
119
  /**
120
  * We can't trust the filter to always contain the expected keys.
@@ -129,14 +126,15 @@ class Post_Data extends Detect {
129
  $meta = [];
130
  } else {
131
  // Filter the post meta items based on defaults' keys.
 
132
  $meta = array_intersect_key(
133
  \get_post_meta( $post->ID ), // Gets all post meta. This is a discrepancy with get_term_meta()!
134
  $defaults
135
  );
136
 
137
  // WP converts all entries to arrays, because we got ALL entries. Disarray!
138
- foreach ( $meta as $key => $value )
139
- $meta[ $key ] = $value[0];
140
  }
141
 
142
  /**
@@ -157,8 +155,9 @@ class Post_Data extends Detect {
157
  ]
158
  );
159
 
160
- // Cache using the input ID, otherwise invalid queries can bypass the cache.
161
- return $cache[ $post_id ] = $meta;
 
162
  }
163
 
164
  /**
@@ -176,36 +175,21 @@ class Post_Data extends Detect {
176
  * @return array The default post meta.
177
  */
178
  public function get_post_meta_defaults( $post_id = 0 ) {
179
-
180
  /**
181
  * @since 4.1.4
 
 
182
  * @param array $defaults
183
  * @param integer $post_id Post ID.
184
  * @param \WP_Post $post Post object.
185
  */
186
- $defaults = (array) \apply_filters_ref_array(
187
  'the_seo_framework_post_meta_defaults',
188
  [
189
  $this->get_unfiltered_post_meta_defaults(),
190
- $post_id,
191
- $post = \get_post( $post_id ),
192
  ]
193
  );
194
-
195
- /**
196
- * @since 3.1.0
197
- * @since 4.1.4 Deprecated. Use filter `the_seo_framework_post_meta_defaults` instead.
198
- * @deprecated
199
- * @param array $defaults
200
- * @param integer $post_id Post ID.
201
- * @param \WP_Post $post Post object.
202
- */
203
- return (array) \apply_filters_deprecated(
204
- 'the_seo_framework_inpost_seo_save_defaults',
205
- [ $defaults, $post_id, $post ],
206
- '4.1.4 of The SEO Framework',
207
- 'the_seo_framework_post_meta_defaults'
208
- );
209
  }
210
 
211
  /**
@@ -270,7 +254,7 @@ class Post_Data extends Detect {
270
  * @param \WP_Post|integer $post The post object or post ID.
271
  * @param array $data The post meta fields, will be merged with the defaults.
272
  */
273
- public function save_post_meta( $post, array $data ) {
274
 
275
  $post = \get_post( $post );
276
 
@@ -545,23 +529,18 @@ class Post_Data extends Detect {
545
  // Can this even fail?
546
  if ( ! $post_type ) return;
547
 
548
- $_taxonomies = $this->get_hierarchical_taxonomies_as( 'names', $post_type );
549
- $values = [];
550
-
551
- foreach ( $_taxonomies as $_taxonomy ) {
552
- $_post_key = '_primary_term_' . $_taxonomy;
553
-
554
- $values[ $_taxonomy ] = [
555
- 'action' => $this->inpost_nonce_field . '_pt',
556
- 'name' => $this->inpost_nonce_name . '_pt_' . $_taxonomy,
557
- 'value' => isset( $_POST['autodescription'][ $_post_key ] ) ? \absint( $_POST['autodescription'][ $_post_key ] ) : 0,
558
- ];
559
- }
560
-
561
- foreach ( $values as $_taxonomy => $v ) {
562
- if ( ! isset( $_POST[ $v['name'] ] ) ) continue;
563
- if ( \wp_verify_nonce( $_POST[ $v['name'] ], $v['action'] ) ) { // Redundant. Fortified.
564
- $this->update_primary_term_id( $post->ID, $_taxonomy, $v['value'] );
565
  }
566
  }
567
  }
@@ -571,17 +550,15 @@ class Post_Data extends Detect {
571
  * Memoizes the return value.
572
  *
573
  * @since 2.4.3
574
- * @since 2.9.3 : 1. Removed object caching.
575
- * : 2. It now uses WP_Query, instead of wpdb.
576
  *
577
  * @return int Latest Post ID.
578
  */
579
  public function get_latest_post_id() {
580
 
581
- static $post_id = null;
582
-
583
- if ( null !== $post_id )
584
- return $post_id;
585
 
586
  $query = new \WP_Query( [
587
  'posts_per_page' => 1,
@@ -595,7 +572,7 @@ class Post_Data extends Detect {
595
  'no_found_rows' => true,
596
  ] );
597
 
598
- return $post_id = reset( $query->posts );
599
  }
600
 
601
  /**
@@ -608,71 +585,8 @@ class Post_Data extends Detect {
608
  * @return string The post content.
609
  */
610
  public function get_post_content( $id = 0 ) {
611
- $post = \get_post( $id ?: $this->get_the_real_ID() );
612
- return empty( $post->post_content ) ? '' : $post->post_content;
613
- }
614
-
615
- /**
616
- * Determines whether the post has a page builder attached to it.
617
- * Doesn't use plugin detection features as some builders might be incorporated within themes.
618
- *
619
- * Detects the following builders:
620
- * - Elementor by Elementor LTD
621
- * - Divi Builder by Elegant Themes
622
- * - Visual Composer by WPBakery
623
- * - Page Builder by SiteOrigin
624
- * - Beaver Builder by Fastline Media
625
- *
626
- * @since 2.6.6
627
- * @since 3.1.0 Added Elementor detection
628
- * @since 4.0.0 Now detects page builders before looping over the meta.
629
- * @TODO deprecate? -> We may use this data for they have FSE builders. We may want to interface with those, some day.
630
- * @ignore unused.
631
- *
632
- * @param int $post_id The post ID to check.
633
- * @return bool
634
- */
635
- public function uses_page_builder( $post_id ) {
636
-
637
- $meta = \get_post_meta( $post_id );
638
-
639
- /**
640
- * @since 2.6.6
641
- * @since 3.1.0 : 1. Now defaults to `null`
642
- * 2. Now, when a boolean (either true or false) is defined, it'll short-circuit this function.
643
- * @param boolean|null $detected Whether a builder should be detected.
644
- * @param int $post_id The current Post ID.
645
- * @param array $meta The current post meta.
646
- */
647
- $detected = \apply_filters( 'the_seo_framework_detect_page_builder', null, $post_id, $meta );
648
-
649
- if ( \is_bool( $detected ) )
650
- return $detected;
651
-
652
- if ( ! $this->detect_page_builder() )
653
- return false;
654
-
655
- if ( empty( $meta ) )
656
- return false;
657
-
658
- if ( isset( $meta['_elementor_edit_mode'][0] ) && '' !== $meta['_elementor_edit_mode'][0] && \defined( 'ELEMENTOR_VERSION' ) ) :
659
- // Elementor by Elementor LTD
660
- return true;
661
- elseif ( isset( $meta['_et_pb_use_builder'][0] ) && 'on' === $meta['_et_pb_use_builder'][0] && \defined( 'ET_BUILDER_VERSION' ) ) :
662
- // Divi Builder by Elegant Themes
663
- return true;
664
- elseif ( isset( $meta['_wpb_vc_js_status'][0] ) && 'true' === $meta['_wpb_vc_js_status'][0] && \defined( 'WPB_VC_VERSION' ) ) :
665
- // Visual Composer by WPBakery
666
- return true;
667
- elseif ( isset( $meta['panels_data'][0] ) && '' !== $meta['panels_data'][0] && \defined( 'SITEORIGIN_PANELS_VERSION' ) ) :
668
- // Page Builder by SiteOrigin
669
- return true;
670
- elseif ( isset( $meta['_fl_builder_enabled'][0] ) && '1' === $meta['_fl_builder_enabled'][0] && \defined( 'FL_BUILDER_VERSION' ) ) :
671
- // Beaver Builder by Fastline Media...
672
- return true;
673
- endif;
674
-
675
- return false;
676
  }
677
 
678
  /**
@@ -703,16 +617,14 @@ class Post_Data extends Detect {
703
  if ( \is_bool( $detected ) )
704
  return $detected;
705
 
706
- if ( ! $this->detect_non_html_page_builder() )
707
- return false;
708
-
709
- if ( empty( $meta ) )
710
  return false;
711
 
712
- if ( isset( $meta['_et_pb_use_builder'][0] ) && 'on' === $meta['_et_pb_use_builder'][0] && \defined( 'ET_BUILDER_VERSION' ) ) :
713
  // Divi Builder by Elegant Themes
714
  return true;
715
- elseif ( isset( $meta['_wpb_vc_js_status'][0] ) && 'true' === $meta['_wpb_vc_js_status'][0] && \defined( 'WPB_VC_VERSION' ) ) :
716
  // Visual Composer by WPBakery
717
  return true;
718
  endif;
@@ -728,12 +640,13 @@ class Post_Data extends Detect {
728
  * @since 3.0.0 1. No longer checks for current query.
729
  * 2. Input parameter now default to null.
730
  * This currently doesn't affect how it works.
 
731
  *
732
  * @param int|null|\WP_Post $post The post ID or WP Post object.
733
  * @return bool True if protected or private, false otherwise.
734
  */
735
  public function is_protected( $post = null ) {
736
- $post = \get_post( $post ); // This is here so we don't have to create another instance in the methods called.
737
  return $this->is_password_protected( $post ) || $this->is_private( $post );
738
  }
739
 
@@ -746,8 +659,8 @@ class Post_Data extends Detect {
746
  * @return bool True if protected, false otherwise.
747
  */
748
  public function is_password_protected( $post = null ) {
749
- $post = \get_post( $post );
750
- return isset( $post->post_password ) && '' !== $post->post_password;
751
  }
752
 
753
  /**
@@ -759,8 +672,8 @@ class Post_Data extends Detect {
759
  * @return bool True if private, false otherwise.
760
  */
761
  public function is_private( $post = null ) {
762
- $post = \get_post( $post );
763
- return isset( $post->post_status ) && 'private' === $post->post_status;
764
  }
765
 
766
  /**
@@ -772,8 +685,7 @@ class Post_Data extends Detect {
772
  * @return bool True if draft, false otherwise.
773
  */
774
  public function is_draft( $post = null ) {
775
- $post = \get_post( $post );
776
- return isset( $post->post_status ) && \in_array( $post->post_status, [ 'draft', 'auto-draft', 'pending' ], true );
777
  }
778
 
779
  /**
@@ -809,12 +721,9 @@ class Post_Data extends Detect {
809
  * @return string The Post Type name/label, if found.
810
  */
811
  public function get_post_type_label( $post_type, $singular = true ) {
812
-
813
- $pto = \get_post_type_object( $post_type );
814
-
815
- return $singular
816
- ? ( isset( $pto->labels->singular_name ) ? $pto->labels->singular_name : '' )
817
- : ( isset( $pto->labels->name ) ? $pto->labels->name : '' );
818
  }
819
 
820
  /**
@@ -834,12 +743,12 @@ class Post_Data extends Detect {
834
 
835
  static $primary_terms = [];
836
 
837
- if ( isset( $primary_terms[ $post_id ][ $taxonomy ] ) )
838
- return $primary_terms[ $post_id ][ $taxonomy ];
839
 
840
- $primary_id = (int) \get_post_meta( $post_id, '_primary_term_' . $taxonomy, true ) ?: 0;
841
 
842
- if ( ! $primary_id ) return $primary_terms[ $post_id ][ $taxonomy ] = false;
843
 
844
  // Users can alter the term list via quick/bulk edit, but cannot set a primary term that way.
845
  // Users can also delete a term from the site that was previously assigned as primary.
@@ -859,7 +768,7 @@ class Post_Data extends Detect {
859
  }
860
  }
861
 
862
- return $primary_terms[ $post_id ][ $taxonomy ] = $primary_term;
863
  }
864
 
865
  /**
@@ -874,8 +783,7 @@ class Post_Data extends Detect {
874
  * @return int The primary term ID. 0 if not found.
875
  */
876
  public function get_primary_term_id( $post_id, $taxonomy ) {
877
- $primary_term = $this->get_primary_term( $post_id, $taxonomy );
878
- return isset( $primary_term->term_id ) ? $primary_term->term_id : 0;
879
  }
880
 
881
  /**
@@ -890,9 +798,9 @@ class Post_Data extends Detect {
890
  */
891
  public function update_primary_term_id( $post_id = null, $taxonomy = '', $value = 0 ) {
892
  if ( ! $value ) {
893
- $success = \delete_post_meta( $post_id, '_primary_term_' . $taxonomy );
894
  } else {
895
- $success = \update_post_meta( $post_id, '_primary_term_' . $taxonomy, $value );
896
  }
897
  return $success;
898
  }
74
  * @param string $item The item to get.
75
  * @param int $post_id The post ID.
76
  * @param bool $use_cache Whether to use caching.
77
+ * @return mixed The post meta item's value. Null when item isn't registered.
78
  */
79
  public function get_post_meta_item( $item, $post_id = 0, $use_cache = true ) {
80
+ return $this->get_post_meta( $post_id ?: $this->get_the_real_ID(), $use_cache )[ $item ] ?? null;
 
 
 
81
  }
82
 
83
  /**
101
  */
102
  public function get_post_meta( $post_id, $use_cache = true ) {
103
 
104
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
105
+ if ( $use_cache && ( $memo = umemo( __METHOD__, null, $post_id ) ) ) return $memo;
 
 
 
 
106
 
107
  // get_post_meta() requires a valid post ID. Make sure that post exists.
108
  $post = \get_post( $post_id );
109
 
110
+ // We test post type support for "post_query"-queries might get past this point.
111
+ if ( empty( $post->ID ) || ! $this->is_post_type_supported( $post->post_type ) ) {
112
+ // Do not overwrite cache when not requested. Otherwise, we'd have two "initial" states, causing incongruities.
113
+ return $use_cache ? umemo( __METHOD__, [], $post_id ) : [];
114
+ }
115
 
116
  /**
117
  * We can't trust the filter to always contain the expected keys.
126
  $meta = [];
127
  } else {
128
  // Filter the post meta items based on defaults' keys.
129
+ // Fix: <https://github.com/sybrew/the-seo-framework/issues/185>
130
  $meta = array_intersect_key(
131
  \get_post_meta( $post->ID ), // Gets all post meta. This is a discrepancy with get_term_meta()!
132
  $defaults
133
  );
134
 
135
  // WP converts all entries to arrays, because we got ALL entries. Disarray!
136
+ foreach ( $meta as &$value )
137
+ $value = $value[0];
138
  }
139
 
140
  /**
155
  ]
156
  );
157
 
158
+ // Cache using $post_id, not $post->ID, otherwise invalid queries can bypass the cache.
159
+ // Do not overwrite cache when not requested. Otherwise, we'd have two "initial" states, causing incongruities.
160
+ return $use_cache ? umemo( __METHOD__, $meta, $post_id ) : $meta;
161
  }
162
 
163
  /**
175
  * @return array The default post meta.
176
  */
177
  public function get_post_meta_defaults( $post_id = 0 ) {
 
178
  /**
179
  * @since 4.1.4
180
+ * @since 4.2.0 1. Now corrects the $post_id when none is supplied.
181
+ * 2. No longer returns the third parameter.
182
  * @param array $defaults
183
  * @param integer $post_id Post ID.
184
  * @param \WP_Post $post Post object.
185
  */
186
+ return (array) \apply_filters_ref_array(
187
  'the_seo_framework_post_meta_defaults',
188
  [
189
  $this->get_unfiltered_post_meta_defaults(),
190
+ $post_id ?: $this->get_the_real_ID(),
 
191
  ]
192
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  }
194
 
195
  /**
254
  * @param \WP_Post|integer $post The post object or post ID.
255
  * @param array $data The post meta fields, will be merged with the defaults.
256
  */
257
+ public function save_post_meta( $post, $data ) {
258
 
259
  $post = \get_post( $post );
260
 
529
  // Can this even fail?
530
  if ( ! $post_type ) return;
531
 
532
+ foreach ( $this->get_hierarchical_taxonomies_as( 'names', $post_type ) as $_taxonomy ) {
533
+ $_post_key = "_primary_term_{$_taxonomy}";
534
+
535
+ if ( \wp_verify_nonce(
536
+ $_POST[ "{$this->inpost_nonce_name}_pt_{$_taxonomy}" ] ?? '', // If empty, wp_verify_nonce will return false.
537
+ $this->inpost_nonce_field . '_pt'
538
+ ) ) { // Redundant. Fortified.
539
+ $this->update_primary_term_id(
540
+ $post->ID,
541
+ $_taxonomy,
542
+ \absint( $_POST['autodescription'][ $_post_key ] ?? 0 )
543
+ );
 
 
 
 
 
544
  }
545
  }
546
  }
550
  * Memoizes the return value.
551
  *
552
  * @since 2.4.3
553
+ * @since 2.9.3 1. Removed object caching.
554
+ * 2. It now uses WP_Query, instead of wpdb.
555
  *
556
  * @return int Latest Post ID.
557
  */
558
  public function get_latest_post_id() {
559
 
560
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
561
+ if ( null !== $memo = memo() ) return $memo;
 
 
562
 
563
  $query = new \WP_Query( [
564
  'posts_per_page' => 1,
572
  'no_found_rows' => true,
573
  ] );
574
 
575
+ return memo( reset( $query->posts ) );
576
  }
577
 
578
  /**
585
  * @return string The post content.
586
  */
587
  public function get_post_content( $id = 0 ) {
588
+ // '0' is not deemed content. Return empty string for it's a slippery slope.
589
+ return ( \get_post( $id ?: $this->get_the_real_ID() )->post_content ?? '' ) ?: '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590
  }
591
 
592
  /**
617
  if ( \is_bool( $detected ) )
618
  return $detected;
619
 
620
+ // If there's no meta, or no builder active, it doesn't use a builder.
621
+ if ( empty( $meta ) || ! $this->detect_non_html_page_builder() )
 
 
622
  return false;
623
 
624
+ if ( 'on' === ( $meta['_et_pb_use_builder'][0] ?? '' ) && \defined( 'ET_BUILDER_VERSION' ) ) :
625
  // Divi Builder by Elegant Themes
626
  return true;
627
+ elseif ( 'true' === ( $meta['_wpb_vc_js_status'][0] ?? '' ) && \defined( 'WPB_VC_VERSION' ) ) :
628
  // Visual Composer by WPBakery
629
  return true;
630
  endif;
640
  * @since 3.0.0 1. No longer checks for current query.
641
  * 2. Input parameter now default to null.
642
  * This currently doesn't affect how it works.
643
+ * @since 4.2.0 Added caching. Can be reversed if https://core.trac.wordpress.org/ticket/50567 is fixed.
644
  *
645
  * @param int|null|\WP_Post $post The post ID or WP Post object.
646
  * @return bool True if protected or private, false otherwise.
647
  */
648
  public function is_protected( $post = null ) {
649
+ $post = \get_post( $post ); // This is here so we don't have to create another instance hereinafter.
650
  return $this->is_password_protected( $post ) || $this->is_private( $post );
651
  }
652
 
659
  * @return bool True if protected, false otherwise.
660
  */
661
  public function is_password_protected( $post = null ) {
662
+ // return '' !== ( \get_post( $post )->post_password ?? '' ); // https://core.trac.wordpress.org/ticket/50567
663
+ return '' !== ( $post->post_password ?? \get_post( $post )->post_password ?? '' );
664
  }
665
 
666
  /**
672
  * @return bool True if private, false otherwise.
673
  */
674
  public function is_private( $post = null ) {
675
+ // return 'private' === ( \get_post( $post )->post_status ?? '' ); // https://core.trac.wordpress.org/ticket/50567
676
+ return 'private' === ( $post->post_status ?? \get_post( $post )->post_status ?? '' );
677
  }
678
 
679
  /**
685
  * @return bool True if draft, false otherwise.
686
  */
687
  public function is_draft( $post = null ) {
688
+ return \in_array( \get_post( $post )->post_status ?? '', [ 'draft', 'auto-draft', 'pending' ], true );
 
689
  }
690
 
691
  /**
721
  * @return string The Post Type name/label, if found.
722
  */
723
  public function get_post_type_label( $post_type, $singular = true ) {
724
+ return \get_post_type_object( $post_type )->labels->{
725
+ $singular ? 'singular_name' : 'name'
726
+ } ?? '';
 
 
 
727
  }
728
 
729
  /**
743
 
744
  static $primary_terms = [];
745
 
746
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
747
+ if ( null !== $memo = memo( null, $post_id, $taxonomy ) ) return $memo;
748
 
749
+ $primary_id = (int) \get_post_meta( $post_id, "_primary_term_{$taxonomy}", true ) ?: 0;
750
 
751
+ if ( ! $primary_id ) return memo( false, $post_id, $taxonomy );
752
 
753
  // Users can alter the term list via quick/bulk edit, but cannot set a primary term that way.
754
  // Users can also delete a term from the site that was previously assigned as primary.
768
  }
769
  }
770
 
771
+ return memo( $primary_term, $post_id, $taxonomy );
772
  }
773
 
774
  /**
783
  * @return int The primary term ID. 0 if not found.
784
  */
785
  public function get_primary_term_id( $post_id, $taxonomy ) {
786
+ return $this->get_primary_term( $post_id, $taxonomy )->term_id ?? 0;
 
787
  }
788
 
789
  /**
798
  */
799
  public function update_primary_term_id( $post_id = null, $taxonomy = '', $value = 0 ) {
800
  if ( ! $value ) {
801
+ $success = \delete_post_meta( $post_id, "_primary_term_{$taxonomy}" );
802
  } else {
803
+ $success = \update_post_meta( $post_id, "_primary_term_{$taxonomy}", $value );
804
  }
805
  return $success;
806
  }
inc/classes/query.class.php CHANGED
@@ -61,16 +61,15 @@ class Query extends Core {
61
  */
62
  protected function can_cache_query( $method ) {
63
 
64
- static $cache;
 
65
 
66
- if ( isset( $cache ) )
67
- return $cache;
68
 
69
  if ( \defined( 'WP_CLI' ) && WP_CLI )
70
- return $cache = false;
71
-
72
  if ( isset( $GLOBALS['wp_query']->query ) || isset( $GLOBALS['current_screen'] ) )
73
- return $cache = true;
74
 
75
  $this->the_seo_framework_debug
76
  and $this->do_query_error_notice( $method );
@@ -92,8 +91,8 @@ class Query extends Core {
92
 
93
  $trace = debug_backtrace( DEBUG_BACKTRACE_PROVIDE_OBJECT, 4 );
94
  if ( ! empty( $trace[3] ) ) {
95
- $message .= ' - In file: ' . $trace[3]['file'];
96
- $message .= ' - On line: ' . $trace[3]['line'];
97
  }
98
 
99
  $this->_doing_it_wrong( \esc_html( $method ), \esc_html( $message ), '2.9.0' );
@@ -112,15 +111,32 @@ class Query extends Core {
112
  * Returns the post type name from query input or real ID.
113
  *
114
  * @since 4.0.5
 
115
  *
116
  * @param int|WP_Post|null $post (Optional) Post ID or post object.
117
  * @return string|false Post type on success, false on failure.
118
  */
119
  public function get_post_type_real_ID( $post = null ) {
120
 
121
- $post = \is_null( $post ) ? $this->get_the_real_ID() : $post;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
 
123
- return \get_post_type( $post );
124
  }
125
 
126
  /**
@@ -132,8 +148,7 @@ class Query extends Core {
132
  * @return string
133
  */
134
  public function get_admin_post_type() {
135
- global $current_screen;
136
- return isset( $current_screen->post_type ) ? $current_screen->post_type : '';
137
  }
138
 
139
  /**
@@ -149,7 +164,7 @@ class Query extends Core {
149
  $taxonomy = $taxonomy ?: $this->get_current_taxonomy();
150
  $tax = $taxonomy ? \get_taxonomy( $taxonomy ) : null;
151
 
152
- return ! empty( $tax->object_type ) ? $tax->object_type : [];
153
  }
154
 
155
  /**
@@ -167,29 +182,39 @@ class Query extends Core {
167
  if ( \is_admin() )
168
  return $this->get_the_real_admin_ID();
169
 
 
 
 
170
  $use_cache = $use_cache && $this->can_cache_query( __METHOD__ );
171
 
 
172
  if ( $use_cache ) {
173
- static $id = null;
174
-
175
- if ( isset( $id ) )
176
- return $id;
177
- }
178
-
179
- // Try to get ID from plugins when caching is available.
180
- $id = $use_cache ? $this->check_the_real_ID() : 0;
181
-
182
- if ( ! $id ) {
183
- // This catches most IDs. Even Post IDs.
184
- $id = \get_queried_object_id();
185
  }
186
 
187
  /**
188
  * @since 2.6.2
189
- * @param int $id Can be either the Post ID, or the Term ID.
190
- * @param bool Whether this value is stored in runtime caching.
191
  */
192
- return $id = (int) \apply_filters( 'the_seo_framework_current_object_id', $id, $use_cache );
 
 
 
 
 
 
 
 
 
 
193
  }
194
 
195
  /**
@@ -212,31 +237,6 @@ class Query extends Core {
212
  return (int) \apply_filters( 'the_seo_framework_current_admin_id', $id );
213
  }
214
 
215
- /**
216
- * Get the real ID from plugins.
217
- *
218
- * Only works on front-end as there's no need to check for inconsistent
219
- * functions for the current ID in the admin.
220
- *
221
- * @since 2.5.0
222
- * @since 3.1.0 1. Now checks for the feed.
223
- * 2. No longer caches.
224
- * @since 4.0.5 1. The shop ID is now handled via the filter.
225
- * 2. The question ID (AnsPress) is no longer called. This should work out-of-the-box since AnsPress 4.1.
226
- *
227
- * @return int The admin ID.
228
- */
229
- public function check_the_real_ID() { // phpcs:ignore -- ID is capitalized because WordPress does that too: get_the_ID().
230
-
231
- $id = $this->is_feed() ? \get_the_ID() : 0;
232
-
233
- /**
234
- * @since 2.5.0
235
- * @param int $id
236
- */
237
- return (int) \apply_filters( 'the_seo_framework_real_id', $id );
238
- }
239
-
240
  /**
241
  * Returns the front page ID, if home is a page.
242
  *
@@ -245,10 +245,11 @@ class Query extends Core {
245
  * @return int the ID.
246
  */
247
  public function get_the_front_page_ID() { // phpcs:ignore -- ID is capitalized because WordPress does that too: get_the_ID().
248
- static $front_id;
249
- return isset( $front_id )
250
- ? $front_id
251
- : $front_id = ( $this->has_page_on_front() ? (int) \get_option( 'page_on_front' ) : 0 );
 
252
  }
253
 
254
  /**
@@ -286,14 +287,11 @@ class Query extends Core {
286
  * @return string The queried taxonomy type.
287
  */
288
  public function get_current_taxonomy() {
289
-
290
- static $cache;
291
-
292
- if ( isset( $cache ) ) return $cache;
293
-
294
- $_object = \is_admin() ? $GLOBALS['current_screen'] : \get_queried_object();
295
-
296
- return $cache = ! empty( $_object->taxonomy ) ? $_object->taxonomy : '';
297
  }
298
 
299
  /**
@@ -346,17 +344,8 @@ class Query extends Core {
346
  if ( ! $attachment )
347
  return \is_attachment();
348
 
349
- // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
350
- if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $attachment ) )
351
- return $cache;
352
-
353
- $this->set_query_cache(
354
- __METHOD__,
355
- $is_attachment = \is_attachment( $attachment ),
356
- $attachment
357
- );
358
-
359
- return $is_attachment;
360
  }
361
 
362
  /**
@@ -388,37 +377,30 @@ class Query extends Core {
388
  public function is_singular_archive( $post = null ) {
389
 
390
  if ( isset( $post ) ) {
391
- $post = \get_post( $post );
392
- $id = $post ? $post->ID : 0;
 
393
  } else {
394
  $id = null;
395
  }
396
 
397
- // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
398
- if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $id ) )
399
- return $cache;
400
-
401
- /**
402
- * @since 4.0.5
403
- * @since 4.0.7 The $id can now be null, when no post is given.
404
- * @param bool $is_singular_archive Whether the post ID is a singular archive.
405
- * @param int|null $id The supplied post ID. Null when in the loop.
406
- */
407
- $is_singular_archive = \apply_filters_ref_array(
408
- 'the_seo_framework_is_singular_archive',
409
- [
410
- isset( $id ) ? $this->is_blog_page_by_id( $id ) : $this->is_blog_page(),
411
- $id,
412
- ]
413
- );
414
-
415
- $this->set_query_cache(
416
- __METHOD__,
417
- $is_singular_archive,
418
- $id
419
- );
420
-
421
- return $is_singular_archive;
422
  }
423
 
424
  /**
@@ -434,17 +416,23 @@ class Query extends Core {
434
  if ( \is_admin() )
435
  return $this->is_archive_admin();
436
 
 
 
 
 
 
437
  if ( \is_archive() && false === $this->is_singular() )
438
- return true;
439
 
440
- if ( $this->can_cache_query( __METHOD__ ) && false === $this->is_singular() ) {
 
441
  global $wp_query;
442
 
443
- if ( $wp_query->is_post_type_archive || $wp_query->is_date || $wp_query->is_author || $wp_query->is_category || $wp_query->is_tag || $wp_query->is_tax )
444
- return true;
445
  }
446
 
447
- return false;
448
  }
449
 
450
  /**
@@ -456,8 +444,7 @@ class Query extends Core {
456
  * @return bool Post Type is archive
457
  */
458
  public function is_archive_admin() {
459
- global $current_screen;
460
- return isset( $current_screen->base ) && \in_array( $current_screen->base, [ 'edit-tags', 'term' ], true );
461
  }
462
 
463
  /**
@@ -469,19 +456,7 @@ class Query extends Core {
469
  * @return bool True if on Term Edit screen. False otherwise.
470
  */
471
  public function is_term_edit() {
472
-
473
- // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
474
- if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
475
- return $cache;
476
-
477
- global $current_screen;
478
-
479
- $this->set_query_cache(
480
- __METHOD__,
481
- $is_term_edit = isset( $current_screen->base ) && ( 'term' === $current_screen->base )
482
- );
483
-
484
- return $is_term_edit;
485
  }
486
 
487
  /**
@@ -493,8 +468,7 @@ class Query extends Core {
493
  * @return bool We're on Post Edit screen.
494
  */
495
  public function is_post_edit() {
496
- global $current_screen;
497
- return isset( $current_screen->base ) && 'post' === $current_screen->base;
498
  }
499
 
500
  /**
@@ -506,8 +480,7 @@ class Query extends Core {
506
  * @return bool We're on the edit screen.
507
  */
508
  public function is_wp_lists_edit() {
509
- global $current_screen;
510
- return isset( $current_screen->base ) && \in_array( $current_screen->base, [ 'edit-tags', 'edit' ], true );
511
  }
512
 
513
  /**
@@ -519,8 +492,7 @@ class Query extends Core {
519
  * @return bool True if on Profile Edit screen. False otherwise.
520
  */
521
  public function is_profile_edit() {
522
- global $current_screen;
523
- return isset( $current_screen->base ) && \in_array( $current_screen->base, [ 'profile', 'user-edit' ], true );
524
  }
525
 
526
  /**
@@ -534,89 +506,50 @@ class Query extends Core {
534
  */
535
  public function is_author( $author = '' ) {
536
 
537
- if ( empty( $author ) )
538
  return \is_author();
539
 
540
- // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
541
- if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $author ) )
542
- return $cache;
543
-
544
- $this->set_query_cache(
545
- __METHOD__,
546
- $is_author = \is_author( $author ),
547
- $author
548
- );
549
-
550
- return $is_author;
551
  }
552
 
553
  /**
554
- * Detect the non-home blog page by query (ID).
555
  *
556
- * @since 2.3.4
557
- * @todo deprecate
558
- * @see is_wc_shop() -- that's the correct implementation. However, we're dealing with erratic queries here (ET & legacy WP)
559
  *
560
- * @param int $id the Page ID.
561
- * @return bool true if is blog page. Always false if blog page is homepage.
 
562
  */
563
- public function is_blog_page( $id = 0 ) {
564
 
565
- // When the blog page is the front page, treat it as front instead of blog.
566
- if ( ! $this->has_page_on_front() )
567
- return false;
568
-
569
- $id = $id ?: $this->get_the_real_ID();
570
-
571
- // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
572
- if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $id ) )
573
- return $cache;
574
-
575
- $is_blog_page = false;
576
-
577
- static $pfp = null;
578
-
579
- if ( \is_null( $pfp ) )
580
- $pfp = (int) \get_option( 'page_for_posts' );
581
 
582
- if ( $id && $id === $pfp && false === \is_archive() ) {
583
- $is_blog_page = true;
584
- } elseif ( \is_home() ) {
585
- $is_blog_page = true;
586
  }
587
 
588
- $this->set_query_cache(
589
- __METHOD__,
590
- $is_blog_page,
591
- $id
592
- );
593
-
594
- return $is_blog_page;
595
  }
596
 
597
  /**
598
- * Checks blog page by sole ID.
599
  *
600
- * @since 4.0.0
601
- * @since 4.1.4 1. Improved performance by switching the conditional.
602
- * 2. Improved performance by adding memoization.
603
- * @todo deprecate
604
- * @see is_wc_shop() -- that's the correct implementation.
605
  *
606
- * @param int $id The ID to check
 
607
  * @return bool
608
  */
609
- public function is_blog_page_by_id( $id ) {
610
-
611
- // ID 0 cannot be a blog page.
612
- if ( ! $id ) return false;
613
-
614
- static $pfp = null;
615
-
616
- if ( \is_null( $pfp ) )
617
- $pfp = (int) \get_option( 'page_for_posts' );
618
-
619
- return $pfp === $id;
620
  }
621
 
622
  /**
@@ -633,17 +566,8 @@ class Query extends Core {
633
  if ( \is_admin() )
634
  return $this->is_category_admin();
635
 
636
- // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
637
- if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $category ) )
638
- return $cache;
639
-
640
- $this->set_query_cache(
641
- __METHOD__,
642
- $is_category = \is_category( $category ),
643
- $category
644
- );
645
-
646
- return $is_category;
647
  }
648
 
649
  /**
@@ -718,28 +642,18 @@ class Query extends Core {
718
  public function is_real_front_page() {
719
 
720
  // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
721
- if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
722
  return $cache;
723
 
724
- $is_front_page = false;
725
-
726
- if ( \is_front_page() )
727
- $is_front_page = true;
728
 
729
- // Elegant Themes Support. Yay.
730
- if ( false === $is_front_page && 0 === $this->get_the_real_ID() && $this->is_home() ) {
731
- $sof = \get_option( 'show_on_front' );
732
-
733
- if ( 'page' !== $sof && 'posts' !== $sof )
734
- $is_front_page = true;
735
  }
736
 
737
- $this->set_query_cache(
738
- __METHOD__,
739
- $is_front_page
740
- );
741
-
742
- return $is_front_page;
743
  }
744
 
745
  /**
@@ -761,74 +675,6 @@ class Query extends Core {
761
  return $id === $this->get_the_front_page_ID();
762
  }
763
 
764
- /**
765
- * Checks for front page by input ID.
766
- *
767
- * NOTE: Doesn't always return true when the ID is 0, although the homepage might be.
768
- * This is because it checks for the query, to prevent conflicts.
769
- *
770
- * @see $this->is_real_front_page_by_id(); Alternative to NOTE above.
771
- *
772
- * @since 2.9.0
773
- * @since 2.9.3 Now tests for archive and 404 before testing homepage as blog.
774
- * @since 3.2.2 Removed SEO settings page check. This now returns false on that page.
775
- *
776
- * @param int $id The page ID, required. Can be 0.
777
- * @return bool True if ID if for the homepage.
778
- */
779
- public function is_front_page_by_id( $id ) {
780
-
781
- $id = (int) $id;
782
-
783
- // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
784
- if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $id ) )
785
- return $cache;
786
-
787
- $is_front_page = false;
788
-
789
- $sof = \get_option( 'show_on_front' );
790
-
791
- // Compare against $id
792
- if ( 'page' === $sof ) {
793
- if ( (int) \get_option( 'page_on_front' ) === $id ) {
794
- $is_front_page = true;
795
- }
796
- } elseif ( 'posts' === $sof ) {
797
- if ( 0 === $id ) {
798
- // 0 as ID causes many issues. Just test for is_home().
799
- if ( $this->is_home() ) {
800
- $is_front_page = true;
801
- }
802
- } elseif ( (int) \get_option( 'page_for_posts' ) === $id ) {
803
- $is_front_page = true;
804
- }
805
- } else {
806
- // Elegant Themes' Extra support
807
- if ( 0 === $id && $this->is_home() ) {
808
- $is_front_page = true;
809
- }
810
- }
811
-
812
- $this->set_query_cache(
813
- __METHOD__,
814
- $is_front_page,
815
- $id
816
- );
817
-
818
- return $is_front_page;
819
- }
820
-
821
- /**
822
- * Determines whether the query is for the blog page.
823
- *
824
- * @since 2.6.0
825
- *
826
- * @return bool
827
- */
828
- public function is_home() {
829
- return \is_home();
830
- }
831
-
832
  /**
833
  * Detects month archives.
834
  *
@@ -860,23 +706,13 @@ class Query extends Core {
860
  if ( empty( $page ) )
861
  return \is_page();
862
 
863
- // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
864
- if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $page ) )
865
- return $cache;
866
-
867
- if ( \is_int( $page ) || $page instanceof \WP_Post ) {
868
- $is_page = \in_array( \get_post_type( $page ), $this->get_hierarchical_post_types(), true );
869
- } else {
870
- $is_page = \is_page( $page );
871
- }
872
-
873
- $this->set_query_cache(
874
- __METHOD__,
875
- $is_page,
876
- $page
877
- );
878
-
879
- return $is_page;
880
  }
881
 
882
  /**
@@ -909,11 +745,11 @@ class Query extends Core {
909
  $is_preview = false;
910
 
911
  if ( \is_preview()
912
- && \is_user_logged_in()
913
- && \is_singular()
914
- && \current_user_can( 'edit_post', \get_the_ID() )
915
- && isset( $_GET['preview_id'], $_GET['preview_nonce'] )
916
- && \wp_verify_nonce( $_GET['preview_nonce'], 'post_preview_' . (int) $_GET['preview_id'] ) // WP doesn't check for unslash either; who would've guessed.
917
  ) {
918
  $is_preview = true;
919
  }
@@ -949,23 +785,13 @@ class Query extends Core {
949
  if ( \is_admin() )
950
  return $this->is_single_admin();
951
 
952
- // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
953
- if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $post ) )
954
- return $cache;
955
-
956
- if ( \is_int( $post ) || $post instanceof \WP_Post ) {
957
- $is_single = \in_array( \get_post_type( $post ), $this->get_nonhierarchical_post_types(), true );
958
- } else {
959
- $is_single = \is_single( $post );
960
- }
961
-
962
- $this->set_query_cache(
963
- __METHOD__,
964
- $is_single,
965
- $post
966
- );
967
-
968
- return $is_single;
969
  }
970
 
971
  /**
@@ -1008,18 +834,8 @@ class Query extends Core {
1008
  if ( $post_types )
1009
  return \is_singular( $post_types );
1010
 
1011
- // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1012
- if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
1013
- return $cache;
1014
-
1015
- $is_singular = \is_singular() || $this->is_singular_archive();
1016
-
1017
- $this->set_query_cache(
1018
- __METHOD__,
1019
- $is_singular
1020
- );
1021
-
1022
- return $is_singular;
1023
  }
1024
 
1025
  /**
@@ -1033,8 +849,7 @@ class Query extends Core {
1033
  * @return bool Post Type is singular
1034
  */
1035
  public function is_singular_admin() {
1036
- global $current_screen;
1037
- return isset( $current_screen->base ) && \in_array( $current_screen->base, [ 'edit', 'post' ], true );
1038
  }
1039
 
1040
  /**
@@ -1048,10 +863,11 @@ class Query extends Core {
1048
  */
1049
  public function is_static_frontpage( $id = 0 ) {
1050
 
1051
- static $front_id;
1052
-
1053
- if ( ! isset( $front_id ) )
1054
- $front_id = 'page' === \get_option( 'show_on_front' ) ? (int) \get_option( 'page_on_front' ) : false;
 
1055
 
1056
  return false !== $front_id && ( $id ?: $this->get_the_real_ID() ) === $front_id;
1057
  }
@@ -1071,17 +887,8 @@ class Query extends Core {
1071
  if ( \is_admin() )
1072
  return $this->is_tag_admin();
1073
 
1074
- // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1075
- if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $tag ) )
1076
- return $cache;
1077
-
1078
- $this->set_query_cache(
1079
- __METHOD__,
1080
- $is_tag = \is_tag( $tag ),
1081
- $tag
1082
- );
1083
-
1084
- return $is_tag;
1085
  }
1086
 
1087
  /**
@@ -1108,19 +915,8 @@ class Query extends Core {
1108
  * @return bool
1109
  */
1110
  public function is_tax( $taxonomy = '', $term = '' ) {
1111
-
1112
- // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1113
- if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $taxonomy, $term ) )
1114
- return $cache;
1115
-
1116
- $this->set_query_cache(
1117
- __METHOD__,
1118
- $is_tax = \is_tax( $taxonomy, $term ),
1119
- $taxonomy,
1120
- $term
1121
- );
1122
-
1123
- return $is_tax;
1124
  }
1125
 
1126
  /**
@@ -1133,22 +929,17 @@ class Query extends Core {
1133
  * @return bool
1134
  */
1135
  public function is_shop( $post = null ) {
1136
-
1137
- // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1138
- if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $post ) )
1139
- return $cache;
1140
-
1141
- /**
1142
- * @since 4.0.5
1143
- * @since 4.1.4 Now has its return value memoized.
1144
- * @param bool $is_shop Whether the post ID is a shop.
1145
- * @param int $id The current or supplied post ID.
1146
- */
1147
- $is_shop = \apply_filters_ref_array( 'the_seo_framework_is_shop', [ false, $post ] );
1148
-
1149
- $this->set_query_cache( __METHOD__, $is_shop, $post );
1150
-
1151
- return $is_shop;
1152
  }
1153
 
1154
  /**
@@ -1165,21 +956,17 @@ class Query extends Core {
1165
  if ( \is_admin() )
1166
  return $this->is_product_admin();
1167
 
1168
- // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1169
- if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $post ) )
1170
- return $cache;
1171
-
1172
- /**
1173
- * @since 4.0.5
1174
- * @since 4.1.4 Now has its return value memoized.
1175
- * @param bool $is_product
1176
- * @param int|WP_Post|null $post (Optional) Post ID or post object.
1177
- */
1178
- $is_product = (bool) \apply_filters_ref_array( 'the_seo_framework_is_product', [ false, $post ] );
1179
-
1180
- $this->set_query_cache( __METHOD__, $is_product, $post );
1181
-
1182
- return $is_product;
1183
  }
1184
 
1185
  /**
@@ -1191,21 +978,15 @@ class Query extends Core {
1191
  * @return bool
1192
  */
1193
  public function is_product_admin() {
1194
-
1195
- // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1196
- if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
1197
- return $cache;
1198
-
1199
- /**
1200
- * @since 4.0.5
1201
- * @since 4.1.4 Now has its return value memoized.
1202
- * @param bool $is_product_admin
1203
- */
1204
- $is_product_admin = (bool) \apply_filters( 'the_seo_framework_is_product_admin', false );
1205
-
1206
- $this->set_query_cache( __METHOD__, $is_product_admin );
1207
-
1208
- return $is_product_admin;
1209
  }
1210
 
1211
  /**
@@ -1228,10 +1009,8 @@ class Query extends Core {
1228
  * @return bool True if SSL, false otherwise.
1229
  */
1230
  public function is_ssl() {
1231
-
1232
- static $cache = null;
1233
-
1234
- return isset( $cache ) ? $cache : $cache = \is_ssl();
1235
  }
1236
 
1237
  /**
@@ -1254,16 +1033,8 @@ class Query extends Core {
1254
  if ( ! $secure )
1255
  return $this->is_menu_page( $this->seo_settings_page_hook, $this->seo_settings_page_slug );
1256
 
1257
- // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1258
- if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
1259
- return $cache;
1260
-
1261
- $this->set_query_cache(
1262
- __METHOD__,
1263
- $page = $this->is_menu_page( $this->seo_settings_page_hook )
1264
- );
1265
-
1266
- return $page;
1267
  }
1268
 
1269
  /**
@@ -1295,29 +1066,30 @@ class Query extends Core {
1295
  return $page_hook === $pagehook;
1296
  } elseif ( \is_admin() && $pageslug ) {
1297
  // N.B. $_GET['page'] === $plugin_page after admin_init...
1298
-
1299
  // phpcs:ignore, WordPress.Security.NonceVerification -- This is a public variable, no data is processed.
1300
- return ! empty( $_GET['page'] ) && $pageslug === $_GET['page'];
1301
  }
1302
 
1303
  return false;
1304
  }
1305
 
1306
  /**
1307
- * Fetches the amount of pages on the screen.
1308
- * Fetches global $page through Query Var to prevent conflicts.
1309
  *
1310
  * @since 2.6.0
1311
  * @since 3.2.4 1. Added overflow protection.
1312
  * 2. Now always returns 1 on the admin screens.
 
 
1313
  *
1314
  * @return int (R>0) $page Always a positive number.
1315
  */
1316
  public function page() {
1317
 
1318
  // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1319
- if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
1320
- return $cache;
1321
 
1322
  if ( $this->is_multipage() ) {
1323
  $page = (int) \get_query_var( 'page' );
@@ -1330,17 +1102,12 @@ class Query extends Core {
1330
  $page = 1;
1331
  }
1332
 
1333
- $this->set_query_cache(
1334
- __METHOD__,
1335
- $page = $page ?: 1
1336
- );
1337
-
1338
- return $page;
1339
  }
1340
 
1341
  /**
1342
- * Fetches the number of the current page.
1343
- * Fetches global $paged through Query var to prevent conflicts.
1344
  *
1345
  * @since 2.6.0
1346
  * @since 3.2.4 1. Added overflow protection.
@@ -1351,8 +1118,8 @@ class Query extends Core {
1351
  public function paged() {
1352
 
1353
  // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1354
- if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
1355
- return $cache;
1356
 
1357
  if ( $this->is_multipage() ) {
1358
  $paged = (int) \get_query_var( 'paged' );
@@ -1366,12 +1133,7 @@ class Query extends Core {
1366
  $paged = 1;
1367
  }
1368
 
1369
- $this->set_query_cache(
1370
- __METHOD__,
1371
- $paged = $paged ?: 1
1372
- );
1373
-
1374
- return $paged;
1375
  }
1376
 
1377
  /**
@@ -1389,19 +1151,16 @@ class Query extends Core {
1389
  public function numpages() {
1390
 
1391
  // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1392
- if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
1393
- return $cache;
1394
 
1395
  if ( \is_admin() ) {
1396
- $numpages = 1;
1397
- $this->set_query_cache( __METHOD__, $numpages );
1398
- return $numpages;
1399
  }
1400
 
1401
  global $wp_query;
1402
 
1403
- $post = null;
1404
-
1405
  if ( $this->is_singular() && ! $this->is_singular_archive() )
1406
  $post = \get_post( $this->get_the_real_ID() );
1407
 
@@ -1441,9 +1200,7 @@ class Query extends Core {
1441
  $numpages = 0;
1442
  }
1443
 
1444
- $this->set_query_cache( __METHOD__, $numpages );
1445
-
1446
- return $numpages;
1447
  }
1448
 
1449
  /**
@@ -1465,18 +1222,13 @@ class Query extends Core {
1465
  * Memoizes the return value once set.
1466
  *
1467
  * @since 2.9.2
1468
- * @since 4.0.0 Now uses static variables instead of class properties.
1469
  *
1470
  * @param bool $set Whether to set "doing sitemap".
1471
  * @return bool
1472
  */
1473
  public function is_sitemap( $set = false ) {
1474
-
1475
- static $doing_sitemap = false;
1476
-
1477
- if ( $set ) $doing_sitemap = true;
1478
-
1479
- return $doing_sitemap;
1480
  }
1481
 
1482
  /**
@@ -1491,64 +1243,35 @@ class Query extends Core {
1491
  }
1492
 
1493
  /**
1494
- * Handles object cache for the query class.
 
1495
  *
1496
- * @since 2.7.0
1497
- * @see $this->set_query_cache(); to set query cache.
1498
- *
1499
- * @param string $method The method that wants to cache, used as the key to set or get.
1500
- * @param mixed $value_to_set The value to set.
1501
- * @param mixed ...$hash Extra arguments, that are used to differentiaty queries.
1502
- * @return mixed : {
1503
- * mixed The cached value if set and $value_to_set is null.
1504
- * null If the query can't be cached yet, or when no value has been set.
1505
- * If $value_to_set is set : {
1506
- * true If the value is being set for the first time.
1507
- * false If the value has been set and $value_to_set is being overwritten.
1508
- * }
1509
- * }
1510
  */
1511
- public function get_query_cache( $method, $value_to_set = null, ...$hash ) {
1512
 
1513
  static $can_cache_query = null;
1514
 
 
 
 
1515
  if ( null === $can_cache_query ) {
1516
- if ( $this->can_cache_query( $method ) ) {
1517
  $can_cache_query = true;
1518
  } else {
1519
- return null;
1520
  }
1521
  }
1522
 
1523
- static $cache = [];
1524
-
1525
- // phpcs:ignore, WordPress.PHP.DiscouragedPHPFunctions -- No objects are inserted, nor is this ever unserialized.
1526
- $hash = $hash ? serialize( $hash ) : false;
1527
-
1528
- if ( isset( $value_to_set ) ) {
1529
- $fresh_set = ! isset( $cache[ $method ][ $hash ] );
1530
- $cache[ $method ][ $hash ] = $value_to_set;
1531
- return $fresh_set;
1532
- }
1533
-
1534
- return isset( $cache[ $method ][ $hash ] ) ? $cache[ $method ][ $hash ] : null;
1535
- }
1536
-
1537
- /**
1538
- * Object cache handler for the query class.
1539
- *
1540
- * @since 2.7.0
1541
- * @see $this->get_query_cache()
1542
- *
1543
- * @param string $method The method that wants to set. Used as a caching key.
1544
- * @param mixed $value_to_set If null, no cache will be set.
1545
- * @param mixed ...$hash Extra arguments, that will be used to generate an alternative cache key.
1546
- * @return bool : {
1547
- * true If the value is being set for the first time.
1548
- * false If the value has been set and $value_to_set is being overwritten.
1549
- * }
1550
- */
1551
- public function set_query_cache( $method, $value_to_set, ...$hash ) {
1552
- return $this->get_query_cache( $method, $value_to_set, ...$hash );
1553
  }
1554
  }
61
  */
62
  protected function can_cache_query( $method ) {
63
 
64
+ // Don't use method memo() here for this method is called over 85 times per page.
65
+ static $memo;
66
 
67
+ if ( isset( $memo ) ) return $memo;
 
68
 
69
  if ( \defined( 'WP_CLI' ) && WP_CLI )
70
+ return $memo = false;
 
71
  if ( isset( $GLOBALS['wp_query']->query ) || isset( $GLOBALS['current_screen'] ) )
72
+ return $memo = true;
73
 
74
  $this->the_seo_framework_debug
75
  and $this->do_query_error_notice( $method );
91
 
92
  $trace = debug_backtrace( DEBUG_BACKTRACE_PROVIDE_OBJECT, 4 );
93
  if ( ! empty( $trace[3] ) ) {
94
+ $message .= " - In file: {$trace[3]['file']}";
95
+ $message .= " - On line: {$trace[3]['line']}";
96
  }
97
 
98
  $this->_doing_it_wrong( \esc_html( $method ), \esc_html( $message ), '2.9.0' );
111
  * Returns the post type name from query input or real ID.
112
  *
113
  * @since 4.0.5
114
+ * @since 4.2.0 Now supports common archives without relying on the first post.
115
  *
116
  * @param int|WP_Post|null $post (Optional) Post ID or post object.
117
  * @return string|false Post type on success, false on failure.
118
  */
119
  public function get_post_type_real_ID( $post = null ) {
120
 
121
+ if ( isset( $post ) )
122
+ return \get_post_type( $post );
123
+
124
+ if ( $this->is_archive() ) {
125
+ if ( $this->is_category() || $this->is_tag() || $this->is_tax() ) {
126
+ $post_type = $this->get_post_types_from_taxonomy();
127
+ $post_type = \is_array( $post_type ) ? reset( $post_type ) : $post_type;
128
+ } elseif ( \is_post_type_archive() ) {
129
+ $post_type = \get_query_var( 'post_type' );
130
+ $post_type = \is_array( $post_type ) ? reset( $post_type ) : $post_type;
131
+ } else {
132
+ // Let WP guess for us. This works reliable (enough) on non-404 queries.
133
+ $post_type = \get_post_type();
134
+ }
135
+ } else {
136
+ $post_type = \get_post_type( $this->get_the_real_ID() );
137
+ }
138
 
139
+ return $post_type;
140
  }
141
 
142
  /**
148
  * @return string
149
  */
150
  public function get_admin_post_type() {
151
+ return $GLOBALS['current_screen']->post_type ?? '';
 
152
  }
153
 
154
  /**
164
  $taxonomy = $taxonomy ?: $this->get_current_taxonomy();
165
  $tax = $taxonomy ? \get_taxonomy( $taxonomy ) : null;
166
 
167
+ return $tax->object_type ?? [];
168
  }
169
 
170
  /**
182
  if ( \is_admin() )
183
  return $this->get_the_real_admin_ID();
184
 
185
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
186
+ if ( $use_cache && null !== $memo = umemo( __METHOD__ ) ) return $memo;
187
+
188
  $use_cache = $use_cache && $this->can_cache_query( __METHOD__ );
189
 
190
+ // Try to get ID from plugins or feed when caching is available.
191
  if ( $use_cache ) {
192
+ /**
193
+ * @since 2.5.0
194
+ * @param int $id
195
+ */
196
+ $id = \apply_filters(
197
+ 'the_seo_framework_real_id',
198
+ $this->is_feed() ? \get_the_ID() : 0
199
+ );
 
 
 
 
200
  }
201
 
202
  /**
203
  * @since 2.6.2
204
+ * @param int $id Can be either the Post ID, or the Term ID.
205
+ * @param bool $use_cache Whether this value is stored in runtime caching.
206
  */
207
+ $id = (int) \apply_filters_ref_array(
208
+ 'the_seo_framework_current_object_id',
209
+ [
210
+ // This catches most IDs. Even Post IDs.
211
+ ( $id ?? 0 ) ?: \get_queried_object_id(),
212
+ $use_cache,
213
+ ]
214
+ );
215
+
216
+ // Do not overwrite cache when not requested. Otherwise, we'd have two "initial" states, causing incongruities.
217
+ return $use_cache ? umemo( __METHOD__, $id ) : $id;
218
  }
219
 
220
  /**
237
  return (int) \apply_filters( 'the_seo_framework_current_admin_id', $id );
238
  }
239
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
  /**
241
  * Returns the front page ID, if home is a page.
242
  *
245
  * @return int the ID.
246
  */
247
  public function get_the_front_page_ID() { // phpcs:ignore -- ID is capitalized because WordPress does that too: get_the_ID().
248
+ return umemo( __METHOD__ )
249
+ ?? umemo(
250
+ __METHOD__,
251
+ $this->has_page_on_front() ? (int) \get_option( 'page_on_front' ) : 0
252
+ );
253
  }
254
 
255
  /**
287
  * @return string The queried taxonomy type.
288
  */
289
  public function get_current_taxonomy() {
290
+ return $this->memo_query()
291
+ ?? $this->memo_query(
292
+ ( \is_admin() ? $GLOBALS['current_screen'] : \get_queried_object() )
293
+ ->taxonomy ?? ''
294
+ );
 
 
 
295
  }
296
 
297
  /**
344
  if ( ! $attachment )
345
  return \is_attachment();
346
 
347
+ return $this->memo_query( null, $attachment )
348
+ ?? $this->memo_query( \is_attachment( $attachment ), $attachment );
 
 
 
 
 
 
 
 
 
349
  }
350
 
351
  /**
377
  public function is_singular_archive( $post = null ) {
378
 
379
  if ( isset( $post ) ) {
380
+ $id = \is_int( $post )
381
+ ? $post
382
+ : ( \get_post( $post )->ID ?? 0 );
383
  } else {
384
  $id = null;
385
  }
386
 
387
+ return $this->memo_query( null, $id )
388
+ ?? $this->memo_query(
389
+ /**
390
+ * @since 4.0.5
391
+ * @since 4.0.7 The $id can now be null, when no post is given.
392
+ * @param bool $is_singular_archive Whether the post ID is a singular archive.
393
+ * @param int|null $id The supplied post ID. Null when in the loop.
394
+ */
395
+ \apply_filters_ref_array(
396
+ 'the_seo_framework_is_singular_archive',
397
+ [
398
+ $this->is_home_as_page( $id ),
399
+ $id,
400
+ ]
401
+ ),
402
+ $id
403
+ );
 
 
 
 
 
 
 
 
404
  }
405
 
406
  /**
416
  if ( \is_admin() )
417
  return $this->is_archive_admin();
418
 
419
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
420
+ if ( null !== $memo = $this->memo_query() ) return $memo;
421
+
422
+ $can_cache = $this->can_cache_query( __METHOD__ );
423
+
424
  if ( \is_archive() && false === $this->is_singular() )
425
+ return $can_cache ? $this->memo_query( true ) : true;
426
 
427
+ // The $can_cache check is used here because it asserted $wp_query is valid on the front-end.
428
+ if ( $can_cache && false === $this->is_singular() ) {
429
  global $wp_query;
430
 
431
+ if ( $wp_query->is_category || $wp_query->is_tag || $wp_query->is_tax || $wp_query->is_post_type_archive || $wp_query->is_date || $wp_query->is_author )
432
+ return $this->memo_query( true );
433
  }
434
 
435
+ return $can_cache ? $this->memo_query( false ) : false;
436
  }
437
 
438
  /**
444
  * @return bool Post Type is archive
445
  */
446
  public function is_archive_admin() {
447
+ return \in_array( $GLOBALS['current_screen']->base ?? '', [ 'edit-tags', 'term' ], true );
 
448
  }
449
 
450
  /**
456
  * @return bool True if on Term Edit screen. False otherwise.
457
  */
458
  public function is_term_edit() {
459
+ return 'term' === ( $GLOBALS['current_screen']->base ?? '' );
 
 
 
 
 
 
 
 
 
 
 
 
460
  }
461
 
462
  /**
468
  * @return bool We're on Post Edit screen.
469
  */
470
  public function is_post_edit() {
471
+ return 'post' === ( $GLOBALS['current_screen']->base ?? '' );
 
472
  }
473
 
474
  /**
480
  * @return bool We're on the edit screen.
481
  */
482
  public function is_wp_lists_edit() {
483
+ return \in_array( $GLOBALS['current_screen']->base ?? '', [ 'edit-tags', 'edit' ], true );
 
484
  }
485
 
486
  /**
492
  * @return bool True if on Profile Edit screen. False otherwise.
493
  */
494
  public function is_profile_edit() {
495
+ return \in_array( $GLOBALS['current_screen']->base ?? '', [ 'profile', 'user-edit' ], true );
 
496
  }
497
 
498
  /**
506
  */
507
  public function is_author( $author = '' ) {
508
 
509
+ if ( ! $author )
510
  return \is_author();
511
 
512
+ return $this->memo_query( null, $author )
513
+ ?? $this->memo_query( \is_author( $author ), $author );
 
 
 
 
 
 
 
 
 
514
  }
515
 
516
  /**
517
+ * Detects the blog page.
518
  *
519
+ * @since 2.6.0
520
+ * @since 4.2.0 Added the first parameter to allow custom query testing.
 
521
  *
522
+ * @param int|WP_Post|null $post Optional. Post ID or post object.
523
+ * Do not supply from WP_Query's main loop-query.
524
+ * @return bool
525
  */
526
+ public function is_home( $post = null ) {
527
 
528
+ if ( isset( $post ) ) {
529
+ $id = \is_int( $post )
530
+ ? $post
531
+ : ( \get_post( $post )->ID ?? 0 );
 
 
 
 
 
 
 
 
 
 
 
 
532
 
533
+ $is_pfp = (int) \get_option( 'page_for_posts' ) === $id;
534
+ } else {
535
+ $is_pfp = \is_home();
 
536
  }
537
 
538
+ return $is_pfp;
 
 
 
 
 
 
539
  }
540
 
541
  /**
542
+ * Detects the non-front blog page.
543
  *
544
+ * @since 4.2.0
 
 
 
 
545
  *
546
+ * @param int|WP_Post|null $post Optional. Post ID or post object.
547
+ * Do not supply from WP_Query's main loop-query.
548
  * @return bool
549
  */
550
+ public function is_home_as_page( $post = null ) {
551
+ // If front is a blog, the blog is never a page.
552
+ return $this->has_page_on_front() ? $this->is_home( $post ) : false;
 
 
 
 
 
 
 
 
553
  }
554
 
555
  /**
566
  if ( \is_admin() )
567
  return $this->is_category_admin();
568
 
569
+ return $this->memo_query( null, $category )
570
+ ?? $this->memo_query( \is_category( $category ), $category );
 
 
 
 
 
 
 
 
 
571
  }
572
 
573
  /**
642
  public function is_real_front_page() {
643
 
644
  // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
645
+ if ( null !== $cache = $this->memo_query() )
646
  return $cache;
647
 
648
+ $is_front_page = \is_front_page();
 
 
 
649
 
650
+ if ( ! $is_front_page ) {
651
+ // Elegant Themes's Extra Support: Assert home, but only when it's not registered as such.
652
+ $is_front_page = $this->is_home() && 0 === $this->get_the_real_ID()
653
+ && ! \in_array( \get_option( 'show_on_front' ), [ 'page', 'post' ], true );
 
 
654
  }
655
 
656
+ return $this->memo_query( $is_front_page );
 
 
 
 
 
657
  }
658
 
659
  /**
675
  return $id === $this->get_the_front_page_ID();
676
  }
677
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
678
  /**
679
  * Detects month archives.
680
  *
706
  if ( empty( $page ) )
707
  return \is_page();
708
 
709
+ return $this->memo_query( null, $page )
710
+ ?? $this->memo_query(
711
+ \is_int( $page ) || $page instanceof \WP_Post
712
+ ? \in_array( \get_post_type( $page ), $this->get_hierarchical_post_types(), true )
713
+ : \is_page( $page ),
714
+ $page
715
+ );
 
 
 
 
 
 
 
 
 
 
716
  }
717
 
718
  /**
745
  $is_preview = false;
746
 
747
  if ( \is_preview()
748
+ && \is_user_logged_in()
749
+ && \is_singular()
750
+ && \current_user_can( 'edit_post', \get_the_ID() )
751
+ && isset( $_GET['preview_id'], $_GET['preview_nonce'] )
752
+ && \wp_verify_nonce( $_GET['preview_nonce'], 'post_preview_' . (int) $_GET['preview_id'] )
753
  ) {
754
  $is_preview = true;
755
  }
785
  if ( \is_admin() )
786
  return $this->is_single_admin();
787
 
788
+ return $this->memo_query( null, $post )
789
+ ?? $this->memo_query(
790
+ \is_int( $post ) || $post instanceof \WP_Post
791
+ ? \in_array( \get_post_type( $post ), $this->get_nonhierarchical_post_types(), true )
792
+ : \is_single( $post ),
793
+ $post
794
+ );
 
 
 
 
 
 
 
 
 
 
795
  }
796
 
797
  /**
834
  if ( $post_types )
835
  return \is_singular( $post_types );
836
 
837
+ return $this->memo_query()
838
+ ?? $this->memo_query( \is_singular() || $this->is_singular_archive() );
 
 
 
 
 
 
 
 
 
 
839
  }
840
 
841
  /**
849
  * @return bool Post Type is singular
850
  */
851
  public function is_singular_admin() {
852
+ return \in_array( $GLOBALS['current_screen']->base ?? '', [ 'edit', 'post' ], true );
 
853
  }
854
 
855
  /**
863
  */
864
  public function is_static_frontpage( $id = 0 ) {
865
 
866
+ $front_id = umemo( __METHOD__ )
867
+ ?? umemo(
868
+ __METHOD__,
869
+ 'page' === \get_option( 'show_on_front' ) ? (int) \get_option( 'page_on_front' ) : false
870
+ );
871
 
872
  return false !== $front_id && ( $id ?: $this->get_the_real_ID() ) === $front_id;
873
  }
887
  if ( \is_admin() )
888
  return $this->is_tag_admin();
889
 
890
+ return $this->memo_query( null, $tag )
891
+ ?? $this->memo_query( \is_tag( $tag ), $tag );
 
 
 
 
 
 
 
 
 
892
  }
893
 
894
  /**
915
  * @return bool
916
  */
917
  public function is_tax( $taxonomy = '', $term = '' ) {
918
+ return $this->memo_query( null, $taxonomy, $term )
919
+ ?? $this->memo_query( \is_tax( $taxonomy, $term ), $taxonomy, $term );
 
 
 
 
 
 
 
 
 
 
 
920
  }
921
 
922
  /**
929
  * @return bool
930
  */
931
  public function is_shop( $post = null ) {
932
+ return $this->memo_query( null, $post )
933
+ ?? $this->memo_query(
934
+ /**
935
+ * @since 4.0.5
936
+ * @since 4.1.4 Now has its return value memoized.
937
+ * @param bool $is_shop Whether the post ID is a shop.
938
+ * @param int $id The current or supplied post ID.
939
+ */
940
+ \apply_filters_ref_array( 'the_seo_framework_is_shop', [ false, $post ] ),
941
+ $post
942
+ );
 
 
 
 
 
943
  }
944
 
945
  /**
956
  if ( \is_admin() )
957
  return $this->is_product_admin();
958
 
959
+ return $this->memo_query( null, $post )
960
+ ?? $this->memo_query(
961
+ /**
962
+ * @since 4.0.5
963
+ * @since 4.1.4 Now has its return value memoized.
964
+ * @param bool $is_product
965
+ * @param int|WP_Post|null $post (Optional) Post ID or post object.
966
+ */
967
+ (bool) \apply_filters_ref_array( 'the_seo_framework_is_product', [ false, $post ] ),
968
+ $post
969
+ );
 
 
 
 
970
  }
971
 
972
  /**
978
  * @return bool
979
  */
980
  public function is_product_admin() {
981
+ return $this->memo_query()
982
+ ?? $this->memo_query(
983
+ /**
984
+ * @since 4.0.5
985
+ * @since 4.1.4 Now has its return value memoized.
986
+ * @param bool $is_product_admin
987
+ */
988
+ (bool) \apply_filters( 'the_seo_framework_is_product_admin', false )
989
+ );
 
 
 
 
 
 
990
  }
991
 
992
  /**
1009
  * @return bool True if SSL, false otherwise.
1010
  */
1011
  public function is_ssl() {
1012
+ return umemo( __METHOD__ )
1013
+ ?? umemo( __METHOD__, \is_ssl() );
 
 
1014
  }
1015
 
1016
  /**
1033
  if ( ! $secure )
1034
  return $this->is_menu_page( $this->seo_settings_page_hook, $this->seo_settings_page_slug );
1035
 
1036
+ return $this->memo_query()
1037
+ ?? $this->memo_query( $this->is_menu_page( $this->seo_settings_page_hook ) );
 
 
 
 
 
 
 
 
1038
  }
1039
 
1040
  /**
1066
  return $page_hook === $pagehook;
1067
  } elseif ( \is_admin() && $pageslug ) {
1068
  // N.B. $_GET['page'] === $plugin_page after admin_init...
 
1069
  // phpcs:ignore, WordPress.Security.NonceVerification -- This is a public variable, no data is processed.
1070
+ return ( $_GET['page'] ?? '' ) === $pageslug;
1071
  }
1072
 
1073
  return false;
1074
  }
1075
 
1076
  /**
1077
+ * Returns the current page number.
1078
+ * Fetches global `$page` from `WP_Query` to prevent conflicts.
1079
  *
1080
  * @since 2.6.0
1081
  * @since 3.2.4 1. Added overflow protection.
1082
  * 2. Now always returns 1 on the admin screens.
1083
+ * @TODO Add better protection? This can get filled by users when is_paged() is true.
1084
+ * WordPress has no protection/test for this, either.
1085
  *
1086
  * @return int (R>0) $page Always a positive number.
1087
  */
1088
  public function page() {
1089
 
1090
  // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1091
+ if ( null !== $memo = $this->memo_query() )
1092
+ return $memo;
1093
 
1094
  if ( $this->is_multipage() ) {
1095
  $page = (int) \get_query_var( 'page' );
1102
  $page = 1;
1103
  }
1104
 
1105
+ return $this->memo_query( $page ?: 1 );
 
 
 
 
 
1106
  }
1107
 
1108
  /**
1109
+ * Returns the current page number.
1110
+ * Fetches global `$paged` from `WP_Query` to prevent conflicts.
1111
  *
1112
  * @since 2.6.0
1113
  * @since 3.2.4 1. Added overflow protection.
1118
  public function paged() {
1119
 
1120
  // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1121
+ if ( null !== $memo = $this->memo_query() )
1122
+ return $memo;
1123
 
1124
  if ( $this->is_multipage() ) {
1125
  $paged = (int) \get_query_var( 'paged' );
1133
  $paged = 1;
1134
  }
1135
 
1136
+ return $this->memo_query( $paged ?: 1 );
 
 
 
 
 
1137
  }
1138
 
1139
  /**
1151
  public function numpages() {
1152
 
1153
  // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1154
+ if ( null !== $memo = $this->memo_query() )
1155
+ return $memo;
1156
 
1157
  if ( \is_admin() ) {
1158
+ // Disable pagination detection in admin: Always on page 1.
1159
+ return $this->memo_query( 1 );
 
1160
  }
1161
 
1162
  global $wp_query;
1163
 
 
 
1164
  if ( $this->is_singular() && ! $this->is_singular_archive() )
1165
  $post = \get_post( $this->get_the_real_ID() );
1166
 
1200
  $numpages = 0;
1201
  }
1202
 
1203
+ return $this->memo_query( $numpages );
 
 
1204
  }
1205
 
1206
  /**
1222
  * Memoizes the return value once set.
1223
  *
1224
  * @since 2.9.2
1225
+ * @since 4.0.0 Now memoizes instead of populating class properties.
1226
  *
1227
  * @param bool $set Whether to set "doing sitemap".
1228
  * @return bool
1229
  */
1230
  public function is_sitemap( $set = false ) {
1231
+ return memo( $set ?: null ) ?? false;
 
 
 
 
 
1232
  }
1233
 
1234
  /**
1243
  }
1244
 
1245
  /**
1246
+ * Memoizes queries.
1247
+ * Should not be used on methods that aren't final.
1248
  *
1249
+ * The first parameter might not get retrieved in a later call, for this method
1250
+ * also tests whether the query is setup correctly at the time of the call.
1251
+ *
1252
+ * @since 4.2.0
1253
+ *
1254
+ * @param mixed $value_to_set The value to set.
1255
+ * @param mixed ...$args Extra arguments, that are used to differentiaty queries.
1256
+ * @return mixed $value_to_set when provided.
1257
+ * Otherwise, the previously sent $value_to_set.
1258
+ * When that's not set either, null.
 
 
 
 
1259
  */
1260
+ protected function memo_query( $value_to_set = null, ...$args ) {
1261
 
1262
  static $can_cache_query = null;
1263
 
1264
+ // phpcs:ignore, WordPress.PHP.DevelopmentFunctions -- This is the only efficient way.
1265
+ $caller = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 2 )[1]['function'] ?? '';
1266
+
1267
  if ( null === $can_cache_query ) {
1268
+ if ( $this->can_cache_query( $caller ) ) {
1269
  $can_cache_query = true;
1270
  } else {
1271
+ return $value_to_set;
1272
  }
1273
  }
1274
 
1275
+ return umemo( __METHOD__, $value_to_set, $caller, ...$args );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1276
  }
1277
  }
inc/classes/render.class.php CHANGED
@@ -38,7 +38,7 @@ class Render extends Admin_Init {
38
  * Returns the document title.
39
  *
40
  * This method serves as a callback for filter `pre_get_document_title`.
41
- * Use the_seo_framework()->get_title() instead.
42
  *
43
  * @since 3.1.0
44
  * @see $this->get_title()
@@ -69,7 +69,7 @@ class Render extends Admin_Init {
69
  * Returns the document title.
70
  *
71
  * This method serves as a callback for filter `wp_title`.
72
- * Use the_seo_framework()->get_title() instead.
73
  *
74
  * @since 3.1.0
75
  * @since 4.0.0 Removed extraneous, unused parameters.
@@ -112,14 +112,12 @@ class Render extends Admin_Init {
112
  */
113
  public function get_image_from_cache() {
114
 
115
- $url = '';
116
-
117
  foreach ( $this->get_image_details_from_cache( ! $this->get_option( 'multi_og_image' ) ) as $image ) {
118
  $url = $image['url'];
119
  if ( $url ) break;
120
  }
121
 
122
- return $url;
123
  }
124
 
125
  /**
@@ -132,8 +130,7 @@ class Render extends Admin_Init {
132
  * @return string The cached Twitter card.
133
  */
134
  public function get_current_twitter_card_type() {
135
- static $cache;
136
- return isset( $cache ) ? $cache : $cache = $this->generate_twitter_card_type();
137
  }
138
 
139
  /**
@@ -158,7 +155,7 @@ class Render extends Admin_Init {
158
  * @param bool|string $text The element's contents, if any.
159
  * @param bool $new_line Whether to add a new line to the end of the element.
160
  */
161
- public function render_element( array $attributes = [], $tag = 'meta', $text = false, $new_line = true ) {
162
 
163
  $attr = '';
164
 
@@ -817,9 +814,9 @@ class Render extends Admin_Init {
817
  *
818
  * @since 2.2.2
819
  * @since 2.8.0 Returns empty on product pages.
820
- * @since 3.0.0 : 1. Now checks for 0000 timestamps.
821
- * 2. Now uses timestamp formats.
822
- * 3. Now uses GMT time.
823
  *
824
  * @return string The Article Publishing Time meta tag.
825
  */
@@ -861,8 +858,8 @@ class Render extends Admin_Init {
861
  * @since 2.2.2
862
  * @since 2.7.0 Listens to $this->get_the_real_ID() instead of WordPress Core ID determination.
863
  * @since 2.8.0 Returns empty on product pages.
864
- * @since 3.0.0 : 1. Now checks for 0000 timestamps.
865
- * 2. Now uses timestamp formats.
866
  * @since 4.1.4 No longer renders the Open Graph Updated Time meta tag.
867
  * @see og_updated_time()
868
  *
@@ -1143,20 +1140,19 @@ class Render extends Admin_Init {
1143
  * @return array
1144
  */
1145
  public function get_robots_meta() {
1146
-
1147
- static $cache;
1148
-
1149
- /**
1150
- * @since 2.6.0
1151
- * @param array $meta The robots meta.
1152
- * @param int $id The current post or term ID.
1153
- */
1154
- return isset( $cache ) ? $cache : $cache = (array) \apply_filters_ref_array(
1155
- 'the_seo_framework_robots_meta',
1156
- [
1157
- $this->generate_robots_meta(),
1158
- $this->get_the_real_ID(),
1159
- ]
1160
  );
1161
  }
1162
 
@@ -1203,9 +1199,8 @@ class Render extends Admin_Init {
1203
  */
1204
  public function paged_urls() {
1205
 
1206
- $id = $this->get_the_real_ID();
1207
-
1208
  $paged_urls = $this->get_paged_urls();
 
1209
 
1210
  /**
1211
  * @since 2.6.0
@@ -1256,60 +1251,65 @@ class Render extends Admin_Init {
1256
  *
1257
  * @since 2.9.2
1258
  * @since 4.0.0 Added boot timers.
1259
- *
1260
- * @param string $where Determines the position of the indicator.
1261
- * Accepts 'before' for before, anything else for after.
1262
- * @param int $timing Determines when the output started.
 
 
 
 
1263
  * @return string The SEO Framework's HTML plugin indicator.
1264
  */
1265
- public function get_plugin_indicator( $where = 'before', $timing = 0 ) {
1266
-
1267
- static $cache;
1268
-
1269
- if ( ! $cache ) {
1270
- $cache = [
1271
- /**
1272
- * @since 2.0.0
1273
- * @param bool $run Whether to run and show the plugin indicator.
1274
- */
1275
- 'run' => (bool) \apply_filters( 'the_seo_framework_indicator', true ),
1276
- /**
1277
- * @since 2.4.0
1278
- * @param bool $sybre Whether to show the author name in the indicator.
1279
- */
1280
- // phpcs:ignore, WordPress.NamingConventions.ValidHookName -- Easter egg.
1281
- 'author' => (bool) \apply_filters( 'sybre_waaijer_<3', true ) ? \esc_html__( 'by Sybre Waaijer', 'autodescription' ) : '',
1282
- /**
1283
- * @since 2.4.0
1284
- * @param bool $show_timer Whether to show the generation time in the indicator.
1285
- */
1286
- 'show_timer' => (bool) \apply_filters( 'the_seo_framework_indicator_timing', true ),
1287
- ];
1288
- }
1289
-
1290
- if ( false === $cache['run'] )
1291
- return '';
1292
-
1293
- if ( 'before' === $where ) {
1294
- /* translators: 1 = The SEO Framework, 2 = 'by Sybre Waaijer */
1295
- $output = sprintf( '%1$s %2$s', 'The SEO Framework', $cache['author'] );
1296
-
1297
- return sprintf( '<!-- %s -->', trim( $output ) ) . PHP_EOL;
1298
- } else {
1299
- if ( $cache['show_timer'] && $timing ) {
1300
- $timers = sprintf(
1301
- ' | %s meta | %s boot',
1302
- number_format( ( microtime( true ) - $timing ) * 1e3, 2, null, '' ) . 'ms',
1303
- number_format( _bootstrap_timer() * 1e3, 2, null, '' ) . 'ms'
1304
- );
1305
- } else {
1306
- $timers = '';
1307
- }
1308
- /* translators: 1 = The SEO Framework, 2 = 'by Sybre Waaijer */
1309
- $output = sprintf( '%1$s %2$s', 'The SEO Framework', $cache['author'] ) . $timers;
1310
 
1311
- return sprintf( '<!-- / %s -->', trim( $output ) ) . PHP_EOL;
1312
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1313
  }
1314
 
1315
  /**
@@ -1350,21 +1350,23 @@ class Render extends Admin_Init {
1350
  *
1351
  * @since 2.6.0
1352
  * @since 3.1.0 Removed cache.
1353
- * @since 3.1.4 : 1. Added filter.
1354
- * 2. Reintroduced cache because of filter.
1355
- * @TODO add facebook validation.
 
1356
  *
1357
  * @return bool
1358
  */
1359
  public function use_og_tags() {
1360
- static $cache;
1361
- /**
1362
- * @since 3.1.4
1363
- * @param bool $use
1364
- */
1365
- return isset( $cache ) ? $cache : $cache = (bool) \apply_filters(
1366
- 'the_seo_framework_use_og_tags',
1367
- (bool) $this->get_option( 'og_tags' )
 
1368
  );
1369
  }
1370
 
@@ -1374,20 +1376,17 @@ class Render extends Admin_Init {
1374
  *
1375
  * @since 2.6.0
1376
  * @since 3.1.0 Removed cache.
1377
- * @since 3.1.4 : 1. Added filter.
1378
- * 2. Reintroduced cache because of filter.
1379
  *
1380
  * @return bool
1381
  */
1382
  public function use_facebook_tags() {
1383
- static $cache;
1384
- /**
1385
- * @since 3.1.4
1386
- * @param bool $use
1387
- */
1388
- return isset( $cache ) ? $cache : $cache = (bool) \apply_filters(
1389
- 'the_seo_framework_use_facebook_tags',
1390
- (bool) $this->get_option( 'facebook_tags' )
1391
  );
1392
  }
1393
 
@@ -1402,14 +1401,11 @@ class Render extends Admin_Init {
1402
  * @return bool
1403
  */
1404
  public function use_twitter_tags() {
1405
- static $cache;
1406
- /**
1407
- * @since 3.1.4
1408
- * @param bool $use
1409
- */
1410
- return isset( $cache ) ? $cache : $cache = (bool) \apply_filters(
1411
- 'the_seo_framework_use_twitter_tags',
1412
- $this->get_option( 'twitter_tags' ) && $this->get_current_twitter_card_type()
1413
  );
1414
  }
1415
  }
38
  * Returns the document title.
39
  *
40
  * This method serves as a callback for filter `pre_get_document_title`.
41
+ * Use tsf()->get_title() instead.
42
  *
43
  * @since 3.1.0
44
  * @see $this->get_title()
69
  * Returns the document title.
70
  *
71
  * This method serves as a callback for filter `wp_title`.
72
+ * Use tsf()->get_title() instead.
73
  *
74
  * @since 3.1.0
75
  * @since 4.0.0 Removed extraneous, unused parameters.
112
  */
113
  public function get_image_from_cache() {
114
 
 
 
115
  foreach ( $this->get_image_details_from_cache( ! $this->get_option( 'multi_og_image' ) ) as $image ) {
116
  $url = $image['url'];
117
  if ( $url ) break;
118
  }
119
 
120
+ return $url ?? '';
121
  }
122
 
123
  /**
130
  * @return string The cached Twitter card.
131
  */
132
  public function get_current_twitter_card_type() {
133
+ return memo() ?? memo( $this->generate_twitter_card_type() );
 
134
  }
135
 
136
  /**
155
  * @param bool|string $text The element's contents, if any.
156
  * @param bool $new_line Whether to add a new line to the end of the element.
157
  */
158
+ public function render_element( $attributes = [], $tag = 'meta', $text = false, $new_line = true ) {
159
 
160
  $attr = '';
161
 
814
  *
815
  * @since 2.2.2
816
  * @since 2.8.0 Returns empty on product pages.
817
+ * @since 3.0.0 1. Now checks for 0000 timestamps.
818
+ * 2. Now uses timestamp formats.
819
+ * 3. Now uses GMT time.
820
  *
821
  * @return string The Article Publishing Time meta tag.
822
  */
858
  * @since 2.2.2
859
  * @since 2.7.0 Listens to $this->get_the_real_ID() instead of WordPress Core ID determination.
860
  * @since 2.8.0 Returns empty on product pages.
861
+ * @since 3.0.0 1. Now checks for 0000 timestamps.
862
+ * 2. Now uses timestamp formats.
863
  * @since 4.1.4 No longer renders the Open Graph Updated Time meta tag.
864
  * @see og_updated_time()
865
  *
1140
  * @return array
1141
  */
1142
  public function get_robots_meta() {
1143
+ return memo() ?? memo(
1144
+ /**
1145
+ * @since 2.6.0
1146
+ * @param array $meta The robots meta.
1147
+ * @param int $id The current post or term ID.
1148
+ */
1149
+ (array) \apply_filters_ref_array(
1150
+ 'the_seo_framework_robots_meta',
1151
+ [
1152
+ $this->generate_robots_meta(),
1153
+ $this->get_the_real_ID(),
1154
+ ]
1155
+ )
 
1156
  );
1157
  }
1158
 
1199
  */
1200
  public function paged_urls() {
1201
 
 
 
1202
  $paged_urls = $this->get_paged_urls();
1203
+ $id = $this->get_the_real_ID();
1204
 
1205
  /**
1206
  * @since 2.6.0
1251
  *
1252
  * @since 2.9.2
1253
  * @since 4.0.0 Added boot timers.
1254
+ * @since 4.2.0 1. The annotation is translatable again (regressed in 4.0.0).
1255
+ * 2. Is now a protected function.
1256
+ * @access private
1257
+ *
1258
+ * @param string $where Determines the position of the indicator.
1259
+ * Accepts 'before' for before, anything else for after.
1260
+ * @param int $meta_timer Total meta time.
1261
+ * @param int $bootstrap_timer Total bootstrap time.
1262
  * @return string The SEO Framework's HTML plugin indicator.
1263
  */
1264
+ protected function get_plugin_indicator( $where = 'before', $meta_timer = 0, $bootstrap_timer = 0 ) {
1265
+
1266
+ $cache = memo() ?? memo( [
1267
+ /**
1268
+ * @since 2.0.0
1269
+ * @param bool $run Whether to run and show the plugin indicator.
1270
+ */
1271
+ 'run' => (bool) \apply_filters( 'the_seo_framework_indicator', true ),
1272
+ /**
1273
+ * @since 2.4.0
1274
+ * @param bool $show_timer Whether to show the generation time in the indicator.
1275
+ */
1276
+ 'show_timer' => (bool) \apply_filters( 'the_seo_framework_indicator_timing', true ),
1277
+ 'annotation' => trim( vsprintf(
1278
+ /* translators: 1 = The SEO Framework, 2 = 'by Sybre Waaijer */
1279
+ \esc_html__( '%1$s %2$s', 'autodescription' ),
1280
+ [
1281
+ 'The SEO Framework',
1282
+ /**
1283
+ * @since 2.4.0
1284
+ * @param bool $sybre Whether to show the author name in the indicator.
1285
+ */
1286
+ \apply_filters( 'sybre_waaijer_<3', true ) // phpcs:ignore, WordPress.NamingConventions.ValidHookName -- Easter egg.
1287
+ ? \esc_html__( 'by Sybre Waaijer', 'autodescription' )
1288
+ : '',
1289
+ ]
1290
+ ) ),
1291
+ ] );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1292
 
1293
+ if ( ! $cache['run'] ) return '';
1294
+
1295
+ switch ( $where ) :
1296
+ case 'before':
1297
+ return "<!-- {$cache['annotation']} -->\n";
1298
+
1299
+ case 'after':
1300
+ default:
1301
+ if ( $cache['show_timer'] && $meta_timer && $bootstrap_timer ) {
1302
+ $timers = sprintf(
1303
+ ' | %s meta | %s boot',
1304
+ number_format( $meta_timer * 1e3, 2, null, '' ) . 'ms',
1305
+ number_format( $bootstrap_timer * 1e3, 2, null, '' ) . 'ms'
1306
+ );
1307
+ } else {
1308
+ $timers = '';
1309
+ }
1310
+
1311
+ return "<!-- / {$cache['annotation']}{$timers} -->\n";
1312
+ endswitch;
1313
  }
1314
 
1315
  /**
1350
  *
1351
  * @since 2.6.0
1352
  * @since 3.1.0 Removed cache.
1353
+ * @since 3.1.4 1. Added filter.
1354
+ * 2. Reintroduced cache because of filter.
1355
+ * @TODO add facebook validation? -> Not all services that use OG tags are called Facebook.
1356
+ * And not all of those services require the same standards as Facebook.
1357
  *
1358
  * @return bool
1359
  */
1360
  public function use_og_tags() {
1361
+ return memo() ?? memo(
1362
+ /**
1363
+ * @since 3.1.4
1364
+ * @param bool $use
1365
+ */
1366
+ (bool) \apply_filters(
1367
+ 'the_seo_framework_use_og_tags',
1368
+ (bool) $this->get_option( 'og_tags' )
1369
+ )
1370
  );
1371
  }
1372
 
1376
  *
1377
  * @since 2.6.0
1378
  * @since 3.1.0 Removed cache.
1379
+ * @since 3.1.4 1. Added filter.
1380
+ * 2. Reintroduced cache because of filter.
1381
  *
1382
  * @return bool
1383
  */
1384
  public function use_facebook_tags() {
1385
+ return memo() ?? memo(
1386
+ (bool) \apply_filters(
1387
+ 'the_seo_framework_use_facebook_tags',
1388
+ (bool) $this->get_option( 'facebook_tags' )
1389
+ )
 
 
 
1390
  );
1391
  }
1392
 
1401
  * @return bool
1402
  */
1403
  public function use_twitter_tags() {
1404
+ return memo() ?? memo(
1405
+ (bool) \apply_filters(
1406
+ 'the_seo_framework_use_twitter_tags',
1407
+ $this->get_option( 'twitter_tags' ) && $this->get_current_twitter_card_type()
1408
+ )
 
 
 
1409
  );
1410
  }
1411
  }
inc/classes/sanitize.class.php CHANGED
@@ -51,10 +51,8 @@ class Sanitize extends Admin_Pages {
51
  */
52
  protected function verify_seo_settings_nonce() {
53
 
54
- static $validated = null;
55
-
56
- if ( isset( $validated ) )
57
- return $validated;
58
 
59
  /**
60
  * If this page doesn't parse the site options,
@@ -65,17 +63,17 @@ class Sanitize extends Admin_Pages {
65
  */
66
  if ( empty( $_POST[ THE_SEO_FRAMEWORK_SITE_OPTIONS ] )
67
  || ! \is_array( $_POST[ THE_SEO_FRAMEWORK_SITE_OPTIONS ] ) )
68
- return $validated = false;
69
 
70
  // This is also handled in /wp-admin/options.php. Nevertheless, one might register outside of scope.
71
  if ( ! \current_user_can( $this->get_settings_capability() ) )
72
- return $validated = false;
73
 
74
  // This is also handled in /wp-admin/options.php. Nevertheless, one might register outside of scope.
75
  // This also checks the nonce: `_wpnonce`.
76
  \check_admin_referer( THE_SEO_FRAMEWORK_SITE_OPTIONS . '-options' );
77
 
78
- return $validated = true;
79
  }
80
 
81
  /**
@@ -141,8 +139,8 @@ class Sanitize extends Admin_Pages {
141
  *
142
  * @since 3.1.0
143
  * @since 4.0.0 Emptied and is no longer enqueued.
144
- * @since 4.1.0 : 1. Added taxonomical robots options backward compat.
145
- * 2. Added the first two parameters.
146
  * @access private
147
  *
148
  * @param mixed $new_value The new, unserialized, and filtered option value.
@@ -151,13 +149,13 @@ class Sanitize extends Admin_Pages {
151
  public function _set_backward_compatibility( $new_value ) {
152
 
153
  db_4103:
154
- //= Category and Tag robots backward compat.
155
  foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) :
156
  $robots_option_id = $this->get_robots_taxonomy_option_id( $r );
157
- $new_robots_options = isset( $new_value[ $robots_option_id ] ) ? $new_value[ $robots_option_id ] : [];
158
 
159
- $new_category_option = isset( $new_robots_options['category'] ) ? $new_robots_options['category'] : 0;
160
- $new_tag_option = isset( $new_robots_options['post_tag'] ) ? $new_robots_options['post_tag'] : 0;
161
 
162
  // Don't compare to old option--it's never reliably set; it might skip otherwise, although it's always correct.
163
  // Do not resanitize. Others might've overwritten that, let's keep their value.
@@ -198,18 +196,12 @@ class Sanitize extends Admin_Pages {
198
  ]
199
  );
200
 
201
- $this->add_option_filter(
202
- 's_title',
203
- THE_SEO_FRAMEWORK_SITE_OPTIONS,
204
- [
205
- 'knowledge_name',
206
- ]
207
- );
208
-
209
  $this->add_option_filter(
210
  's_title_raw',
211
  THE_SEO_FRAMEWORK_SITE_OPTIONS,
212
  [
 
 
213
  'homepage_title',
214
  'homepage_title_tagline',
215
  'homepage_og_title',
@@ -341,7 +333,6 @@ class Sanitize extends Admin_Pages {
341
  'sitemaps_output',
342
  'sitemaps_robots',
343
  'sitemaps_modified',
344
- 'sitemaps_priority',
345
  'sitemap_styles',
346
  'sitemap_logo',
347
  ]
@@ -402,6 +393,14 @@ class Sanitize extends Admin_Pages {
402
  ]
403
  );
404
 
 
 
 
 
 
 
 
 
405
  /**
406
  * @todo create content="code" stripper in PHP (redundant from JS's)
407
  */
@@ -541,7 +540,7 @@ class Sanitize extends Admin_Pages {
541
 
542
  // Memoize whether a filter has been set for the option already. Should only run once internally.
543
  if ( ! isset( $registered[ $option ] ) ) {
544
- \add_filter( 'sanitize_option_' . $option, [ $this, 'sanitize' ], 10, 2 );
545
  $registered[ $option ] = true;
546
  }
547
 
@@ -620,8 +619,8 @@ class Sanitize extends Admin_Pages {
620
  // Array of suboption values to loop through
621
  $old_value = \get_option( $option, [] );
622
  foreach ( $filters[ $option ] as $suboption => $filter ) {
623
- $old_value[ $suboption ] = isset( $old_value[ $suboption ] ) ? $old_value[ $suboption ] : '';
624
- $new_value[ $suboption ] = isset( $new_value[ $suboption ] ) ? $new_value[ $suboption ] : '';
625
  $new_value[ $suboption ] = $this->do_filter( $filter, $new_value[ $suboption ], $old_value[ $suboption ], $option, $suboption );
626
  }
627
  return $new_value;
@@ -684,38 +683,74 @@ class Sanitize extends Admin_Pages {
684
  return (array) \apply_filters(
685
  'the_seo_framework_available_sanitizer_filters',
686
  [
687
- 's_left_right' => [ $this, 's_left_right' ],
688
- 's_left_right_home' => [ $this, 's_left_right_home' ],
689
- 's_title_separator' => [ $this, 's_title_separator' ],
690
- 's_description' => [ $this, 's_description' ],
691
- 's_description_raw' => [ $this, 's_description_raw' ],
692
- 's_title' => [ $this, 's_title' ],
693
- 's_title_raw' => [ $this, 's_title_raw' ],
694
- 's_knowledge_type' => [ $this, 's_knowledge_type' ],
695
- 's_alter_query_type' => [ $this, 's_alter_query_type' ],
696
- 's_one_zero' => [ $this, 's_one_zero' ],
697
- 's_disabled_post_types' => [ $this, 's_disabled_post_types' ],
698
- 's_disabled_taxonomies' => [ $this, 's_disabled_taxonomies' ],
699
- 's_post_types' => [ $this, 's_post_types' ],
700
- 's_taxonomies' => [ $this, 's_taxonomies' ],
701
- 's_numeric_string' => [ $this, 's_numeric_string' ],
702
- 's_no_html' => [ $this, 's_no_html' ],
703
- 's_no_html_space' => [ $this, 's_no_html_space' ],
704
- 's_absint' => [ $this, 's_absint' ],
705
- 's_safe_html' => [ $this, 's_safe_html' ],
706
- 's_url' => [ $this, 's_url' ],
707
- 's_url_query' => [ $this, 's_url_query' ],
708
- 's_facebook_profile' => [ $this, 's_facebook_profile' ],
709
- 's_twitter_name' => [ $this, 's_twitter_name' ],
710
- 's_twitter_card' => [ $this, 's_twitter_card' ],
711
- 's_canonical_scheme' => [ $this, 's_canonical_scheme' ],
712
- 's_min_max_sitemap' => [ $this, 's_min_max_sitemap' ],
713
- 's_image_preview' => [ $this, 's_image_preview' ],
714
- 's_snippet_length' => [ $this, 's_snippet_length' ],
 
715
  ]
716
  );
717
  }
718
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
719
  /**
720
  * Sanitizes term meta.
721
  *
@@ -724,7 +759,7 @@ class Sanitize extends Admin_Pages {
724
  * @param array $data The term meta to sanitize.
725
  * @return array The sanitized term meta.
726
  */
727
- public function s_term_meta( array $data ) {
728
 
729
  foreach ( $data as $key => &$value ) :
730
  switch ( $key ) :
@@ -781,7 +816,7 @@ class Sanitize extends Admin_Pages {
781
  * @param array $data The post meta to sanitize.
782
  * @return array The sanitized post meta.
783
  */
784
- public function s_post_meta( array $data ) {
785
 
786
  foreach ( $data as $key => &$value ) :
787
  switch ( $key ) :
@@ -837,11 +872,12 @@ class Sanitize extends Admin_Pages {
837
  * Sanitizes user meta.
838
  *
839
  * @since 4.1.4
 
840
  *
841
  * @param array $data The user meta to sanitize.
842
  * @return array The sanitized user meta.
843
  */
844
- public function s_user_meta( array $data ) {
845
 
846
  foreach ( $data as $key => &$value ) :
847
  switch ( $key ) :
@@ -853,6 +889,13 @@ class Sanitize extends Admin_Pages {
853
  $value = $this->s_twitter_name( $value );
854
  continue 2;
855
 
 
 
 
 
 
 
 
856
  default:
857
  unset( $data[ $key ] );
858
  break;
@@ -1006,8 +1049,8 @@ class Sanitize extends Admin_Pages {
1006
  * Sanitizes input excerpt.
1007
  *
1008
  * @since 2.8.0
1009
- * @since 2.8.2 : 1. Added $allow_shortcodes parameter.
1010
- * 2. Added $escape parameter.
1011
  * @since 3.2.4 Now selectively clears tags.
1012
  * @since 4.1.0 Moved `figcaption`, `figure`, `footer`, and `tfoot`, from `space` to `clear`.
1013
  * @see `$this->strip_tags_cs()`
@@ -1253,9 +1296,8 @@ class Sanitize extends Admin_Pages {
1253
 
1254
  if ( ! \is_array( $new_values ) ) return [];
1255
 
1256
- foreach ( $this->get_forced_supported_post_types() as $forced ) {
1257
  unset( $new_values[ $forced ] );
1258
- }
1259
 
1260
  return $this->s_post_types( $new_values );
1261
  }
@@ -1274,9 +1316,8 @@ class Sanitize extends Admin_Pages {
1274
 
1275
  if ( ! \is_array( $new_values ) ) return [];
1276
 
1277
- foreach ( $new_values as $index => &$value ) {
1278
  $value = $this->s_one_zero( $value );
1279
- }
1280
 
1281
  return $new_values;
1282
  }
@@ -1295,9 +1336,8 @@ class Sanitize extends Admin_Pages {
1295
 
1296
  if ( ! \is_array( $new_values ) ) return [];
1297
 
1298
- foreach ( $this->get_forced_supported_taxonomies() as $forced ) {
1299
  unset( $new_values[ $forced ] );
1300
- }
1301
 
1302
  return $this->s_taxonomies( $new_values );
1303
  }
@@ -1476,10 +1516,10 @@ class Sanitize extends Admin_Pages {
1476
  *
1477
  * @since 2.2.2
1478
  * @since 2.8.0 Method is now public.
1479
- * @since 3.0.0 : 1. Now removes '@' from the URL path.
1480
- * 2. Now removes spaces and tabs.
1481
- * @since 4.0.0 : 1. Now returns empty on lone `@` entries.
1482
- * 2. Now returns empty when using only spaces and tabs.
1483
  *
1484
  * @param string $new_value String with potentially wrong Twitter username.
1485
  * @return string String with 'correct' Twitter username
@@ -1500,7 +1540,7 @@ class Sanitize extends Admin_Pages {
1500
  if ( '@' === $profile ) return '';
1501
 
1502
  if ( '@' !== substr( $profile, 0, 1 ) )
1503
- $profile = '@' . $profile;
1504
 
1505
  return str_replace( [ ' ', "\t" ], '', $profile );
1506
  }
@@ -1511,8 +1551,8 @@ class Sanitize extends Admin_Pages {
1511
  * @since 2.2.2
1512
  * @since 2.8.0 Method is now public.
1513
  * @since 3.0.6 Now allows a sole query argument when profile.php is used.
1514
- * @since 4.0.0 : 1. No longer returns a plain Facebook URL when the entry path is sanitized to become empty.
1515
- * 2. Now returns empty when using only spaces and tabs.
1516
  *
1517
  * @param string $new_value String with potentially wrong Facebook profile URL.
1518
  * @return string String with 'correct' Facebook profile URL.
@@ -1532,10 +1572,10 @@ class Sanitize extends Admin_Pages {
1532
 
1533
  if ( ! $path ) return '';
1534
 
1535
- $link = 'https://www.facebook.com/' . $path;
1536
 
1537
  if ( strpos( $link, 'profile.php' ) ) {
1538
- //= Gets query parameters.
1539
  parse_str( parse_url( $link, PHP_URL_QUERY ), $r );
1540
  if ( isset( $r['id'] ) ) {
1541
  $link = 'https://www.facebook.com/profile.php?id=' . \absint( $r['id'] );
@@ -1598,11 +1638,11 @@ class Sanitize extends Admin_Pages {
1598
  * @since 2.2.4
1599
  * @since 2.8.0 Method is now public.
1600
  * @since 3.0.6 Noqueries is now disabled by default.
1601
- * @since 4.0.0 : 1. Removed rudimentary relative URL testing.
1602
- * 2. Removed input transformation filters, and with that, removed redundant multisite spam protection.
1603
- * 3. Now allows all protocols. Enjoy!
1604
- * 4. Now no longer lets through double-absolute URLs (e.g. `https://google.com/https://google.com/path/to/file/`)
1605
- * when filter `the_seo_framework_allow_external_redirect` is set to false.
1606
  *
1607
  * @param string $new_value String with potentially unwanted redirect URL.
1608
  * @return string The Sanitized Redirect URL
@@ -1837,7 +1877,7 @@ class Sanitize extends Admin_Pages {
1837
  /**
1838
  * Strips all URLs that are placed on new lines. These are prone to be embeds.
1839
  *
1840
- * This might leave stray line feeds. Use `the_seo_framework()->s_singleline()` to fix that.
1841
  *
1842
  * @since 3.1.0
1843
  * @see \WP_Embed::autoembed()
@@ -1852,7 +1892,7 @@ class Sanitize extends Admin_Pages {
1852
  /**
1853
  * Strips all URLs that are placed in paragraphs on their own. These are prone to be embeds.
1854
  *
1855
- * This might leave stray line feeds. Use `the_seo_framework()->s_singleline()` to fix that.
1856
  *
1857
  * @since 3.1.0
1858
  * @see \WP_Embed::autoembed()
@@ -1916,15 +1956,10 @@ class Sanitize extends Admin_Pages {
1916
  $args = $default_args;
1917
  } else {
1918
  foreach ( [ 'space', 'clear' ] as $type ) {
1919
- if ( isset( $args[ $type ] ) ) {
1920
- if ( ! $args[ $type ] ) {
1921
- $args[ $type ] = [];
1922
- } else {
1923
- $args[ $type ] = (array) $args[ $type ];
1924
- }
1925
- }
1926
  }
1927
- $args['strip'] = isset( $args['strip'] ) ? $args['strip'] : $default_args['strip'];
1928
  }
1929
 
1930
  // Clear first, so there's less to process; then add spaces.
@@ -1933,11 +1968,9 @@ class Sanitize extends Admin_Pages {
1933
 
1934
  // void = element without content.
1935
  $void_query = array_intersect( $args[ $type ], $void );
1936
- // fill = Normal, template, raw text, escapable text, foreign.
1937
  $fill_query = array_diff( $args[ $type ], $void );
1938
 
1939
- $_regex = sprintf( '<(%s)\b[^>]*?>', implode( '|', $args[ $type ] ) );
1940
-
1941
  if ( $void_query ) {
1942
  $_regex = sprintf( '<(%s)\b[^>]*?>', implode( '|', $void_query ) );
1943
  $_replace = 'space' === $type ? ' ' : '';
@@ -1973,7 +2006,7 @@ class Sanitize extends Admin_Pages {
1973
  * string alt: The image alt tag,
1974
  * }
1975
  */
1976
- public function s_image_details( array $details ) {
1977
 
1978
  if ( array_values( $details ) === $details )
1979
  return $this->s_image_details_deep( $details );
@@ -1986,7 +2019,7 @@ class Sanitize extends Admin_Pages {
1986
  'alt' => '',
1987
  ];
1988
 
1989
- list( $url, $id, $width, $height, $alt ) = array_values( array_merge( $defaults, $details ) );
1990
 
1991
  if ( ! $url ) return $defaults;
1992
 
@@ -2053,7 +2086,7 @@ class Sanitize extends Admin_Pages {
2053
  * string alt: The image alt tag,
2054
  * }
2055
  */
2056
- public function s_image_details_deep( array $details_array ) {
2057
 
2058
  $cleaned_details = [];
2059
 
51
  */
52
  protected function verify_seo_settings_nonce() {
53
 
54
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
55
+ if ( null !== $memo = memo() ) return $memo;
 
 
56
 
57
  /**
58
  * If this page doesn't parse the site options,
63
  */
64
  if ( empty( $_POST[ THE_SEO_FRAMEWORK_SITE_OPTIONS ] )
65
  || ! \is_array( $_POST[ THE_SEO_FRAMEWORK_SITE_OPTIONS ] ) )
66
+ return memo( false );
67
 
68
  // This is also handled in /wp-admin/options.php. Nevertheless, one might register outside of scope.
69
  if ( ! \current_user_can( $this->get_settings_capability() ) )
70
+ return memo( false );
71
 
72
  // This is also handled in /wp-admin/options.php. Nevertheless, one might register outside of scope.
73
  // This also checks the nonce: `_wpnonce`.
74
  \check_admin_referer( THE_SEO_FRAMEWORK_SITE_OPTIONS . '-options' );
75
 
76
+ return memo( true );
77
  }
78
 
79
  /**
139
  *
140
  * @since 3.1.0
141
  * @since 4.0.0 Emptied and is no longer enqueued.
142
+ * @since 4.1.0 1. Added taxonomical robots options backward compat.
143
+ * 2. Added the first two parameters.
144
  * @access private
145
  *
146
  * @param mixed $new_value The new, unserialized, and filtered option value.
149
  public function _set_backward_compatibility( $new_value ) {
150
 
151
  db_4103:
152
+ // Category and Tag robots backward compat.
153
  foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) :
154
  $robots_option_id = $this->get_robots_taxonomy_option_id( $r );
155
+ $new_robots_options = $new_value[ $robots_option_id ] ?? [];
156
 
157
+ $new_category_option = $new_robots_options['category'] ?? 0;
158
+ $new_tag_option = $new_robots_options['post_tag'] ?? 0;
159
 
160
  // Don't compare to old option--it's never reliably set; it might skip otherwise, although it's always correct.
161
  // Do not resanitize. Others might've overwritten that, let's keep their value.
196
  ]
197
  );
198
 
 
 
 
 
 
 
 
 
199
  $this->add_option_filter(
200
  's_title_raw',
201
  THE_SEO_FRAMEWORK_SITE_OPTIONS,
202
  [
203
+ 'site_title',
204
+ 'knowledge_name',
205
  'homepage_title',
206
  'homepage_title_tagline',
207
  'homepage_og_title',
333
  'sitemaps_output',
334
  'sitemaps_robots',
335
  'sitemaps_modified',
 
336
  'sitemap_styles',
337
  'sitemap_logo',
338
  ]
393
  ]
394
  );
395
 
396
+ $this->add_option_filter(
397
+ 's_all_post_type_archive_meta',
398
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
399
+ [
400
+ 'pta',
401
+ ]
402
+ );
403
+
404
  /**
405
  * @todo create content="code" stripper in PHP (redundant from JS's)
406
  */
540
 
541
  // Memoize whether a filter has been set for the option already. Should only run once internally.
542
  if ( ! isset( $registered[ $option ] ) ) {
543
+ \add_filter( "sanitize_option_{$option}", [ $this, 'sanitize' ], 10, 2 );
544
  $registered[ $option ] = true;
545
  }
546
 
619
  // Array of suboption values to loop through
620
  $old_value = \get_option( $option, [] );
621
  foreach ( $filters[ $option ] as $suboption => $filter ) {
622
+ $old_value[ $suboption ] = $old_value[ $suboption ] ?? '';
623
+ $new_value[ $suboption ] = $new_value[ $suboption ] ?? '';
624
  $new_value[ $suboption ] = $this->do_filter( $filter, $new_value[ $suboption ], $old_value[ $suboption ], $option, $suboption );
625
  }
626
  return $new_value;
683
  return (array) \apply_filters(
684
  'the_seo_framework_available_sanitizer_filters',
685
  [
686
+ 's_left_right' => [ $this, 's_left_right' ],
687
+ 's_left_right_home' => [ $this, 's_left_right_home' ],
688
+ 's_title_separator' => [ $this, 's_title_separator' ],
689
+ 's_description' => [ $this, 's_description' ],
690
+ 's_description_raw' => [ $this, 's_description_raw' ],
691
+ 's_title' => [ $this, 's_title' ],
692
+ 's_title_raw' => [ $this, 's_title_raw' ],
693
+ 's_knowledge_type' => [ $this, 's_knowledge_type' ],
694
+ 's_alter_query_type' => [ $this, 's_alter_query_type' ],
695
+ 's_one_zero' => [ $this, 's_one_zero' ],
696
+ 's_disabled_post_types' => [ $this, 's_disabled_post_types' ],
697
+ 's_disabled_taxonomies' => [ $this, 's_disabled_taxonomies' ],
698
+ 's_post_types' => [ $this, 's_post_types' ],
699
+ 's_taxonomies' => [ $this, 's_taxonomies' ],
700
+ 's_all_post_type_archive_meta' => [ $this, 's_all_post_type_archive_meta' ],
701
+ 's_numeric_string' => [ $this, 's_numeric_string' ],
702
+ 's_no_html' => [ $this, 's_no_html' ],
703
+ 's_no_html_space' => [ $this, 's_no_html_space' ],
704
+ 's_absint' => [ $this, 's_absint' ],
705
+ 's_safe_html' => [ $this, 's_safe_html' ],
706
+ 's_url' => [ $this, 's_url' ],
707
+ 's_url_query' => [ $this, 's_url_query' ],
708
+ 's_facebook_profile' => [ $this, 's_facebook_profile' ],
709
+ 's_twitter_name' => [ $this, 's_twitter_name' ],
710
+ 's_twitter_card' => [ $this, 's_twitter_card' ],
711
+ 's_canonical_scheme' => [ $this, 's_canonical_scheme' ],
712
+ 's_min_max_sitemap' => [ $this, 's_min_max_sitemap' ],
713
+ 's_image_preview' => [ $this, 's_image_preview' ],
714
+ 's_snippet_length' => [ $this, 's_snippet_length' ],
715
  ]
716
  );
717
  }
718
 
719
+ /**
720
+ * Sanitizes post type archive meta.
721
+ *
722
+ * @since 4.2.0
723
+ *
724
+ * @param array $data The post type archive meta to sanitize : {
725
+ * string $post_type => array $data
726
+ * }
727
+ * @return array The sanitized post type archive meta.
728
+ */
729
+ public function s_all_post_type_archive_meta( $data ) {
730
+
731
+ if ( ! $data )
732
+ return [];
733
+
734
+ // Do NOT test for post type's existence -- it might be registered incorrectly.
735
+ // If the metadata yields empty -- do not unset key! It'll override "defaults" that way.
736
+ foreach ( $data as $_post_type => &$meta )
737
+ $meta = $this->s_post_type_archive_meta( $meta );
738
+
739
+ return $data;
740
+ }
741
+
742
+ /**
743
+ * Sanitizes post type archive meta.
744
+ *
745
+ * @since 4.2.0
746
+ *
747
+ * @param array $data The post type archive meta to sanitize.
748
+ * @return array The sanitized post type archive meta.
749
+ */
750
+ public function s_post_type_archive_meta( $data ) {
751
+ return $this->s_term_meta( $data ); // Coincidence? I think not.
752
+ }
753
+
754
  /**
755
  * Sanitizes term meta.
756
  *
759
  * @param array $data The term meta to sanitize.
760
  * @return array The sanitized term meta.
761
  */
762
+ public function s_term_meta( $data ) {
763
 
764
  foreach ( $data as $key => &$value ) :
765
  switch ( $key ) :
816
  * @param array $data The post meta to sanitize.
817
  * @return array The sanitized post meta.
818
  */
819
+ public function s_post_meta( $data ) {
820
 
821
  foreach ( $data as $key => &$value ) :
822
  switch ( $key ) :
872
  * Sanitizes user meta.
873
  *
874
  * @since 4.1.4
875
+ * @since 4.2.0 Now accepts and sanitizes the 'counter_type' index.
876
  *
877
  * @param array $data The user meta to sanitize.
878
  * @return array The sanitized user meta.
879
  */
880
+ public function s_user_meta( $data ) {
881
 
882
  foreach ( $data as $key => &$value ) :
883
  switch ( $key ) :
889
  $value = $this->s_twitter_name( $value );
890
  continue 2;
891
 
892
+ case 'counter_type':
893
+ $value = \absint( $value );
894
+
895
+ if ( $value > 3 )
896
+ $value = 0;
897
+ continue 2;
898
+
899
  default:
900
  unset( $data[ $key ] );
901
  break;
1049
  * Sanitizes input excerpt.
1050
  *
1051
  * @since 2.8.0
1052
+ * @since 2.8.2 1. Added $allow_shortcodes parameter.
1053
+ * 2. Added $escape parameter.
1054
  * @since 3.2.4 Now selectively clears tags.
1055
  * @since 4.1.0 Moved `figcaption`, `figure`, `footer`, and `tfoot`, from `space` to `clear`.
1056
  * @see `$this->strip_tags_cs()`
1296
 
1297
  if ( ! \is_array( $new_values ) ) return [];
1298
 
1299
+ foreach ( $this->get_forced_supported_post_types() as $forced )
1300
  unset( $new_values[ $forced ] );
 
1301
 
1302
  return $this->s_post_types( $new_values );
1303
  }
1316
 
1317
  if ( ! \is_array( $new_values ) ) return [];
1318
 
1319
+ foreach ( $new_values as $index => &$value )
1320
  $value = $this->s_one_zero( $value );
 
1321
 
1322
  return $new_values;
1323
  }
1336
 
1337
  if ( ! \is_array( $new_values ) ) return [];
1338
 
1339
+ foreach ( $this->get_forced_supported_taxonomies() as $forced )
1340
  unset( $new_values[ $forced ] );
 
1341
 
1342
  return $this->s_taxonomies( $new_values );
1343
  }
1516
  *
1517
  * @since 2.2.2
1518
  * @since 2.8.0 Method is now public.
1519
+ * @since 3.0.0 1. Now removes '@' from the URL path.
1520
+ * 2. Now removes spaces and tabs.
1521
+ * @since 4.0.0 1. Now returns empty on lone `@` entries.
1522
+ * 2. Now returns empty when using only spaces and tabs.
1523
  *
1524
  * @param string $new_value String with potentially wrong Twitter username.
1525
  * @return string String with 'correct' Twitter username
1540
  if ( '@' === $profile ) return '';
1541
 
1542
  if ( '@' !== substr( $profile, 0, 1 ) )
1543
+ $profile = "@$profile";
1544
 
1545
  return str_replace( [ ' ', "\t" ], '', $profile );
1546
  }
1551
  * @since 2.2.2
1552
  * @since 2.8.0 Method is now public.
1553
  * @since 3.0.6 Now allows a sole query argument when profile.php is used.
1554
+ * @since 4.0.0 1. No longer returns a plain Facebook URL when the entry path is sanitized to become empty.
1555
+ * 2. Now returns empty when using only spaces and tabs.
1556
  *
1557
  * @param string $new_value String with potentially wrong Facebook profile URL.
1558
  * @return string String with 'correct' Facebook profile URL.
1572
 
1573
  if ( ! $path ) return '';
1574
 
1575
+ $link = "https://www.facebook.com/{$path}";
1576
 
1577
  if ( strpos( $link, 'profile.php' ) ) {
1578
+ // Gets query parameters.
1579
  parse_str( parse_url( $link, PHP_URL_QUERY ), $r );
1580
  if ( isset( $r['id'] ) ) {
1581
  $link = 'https://www.facebook.com/profile.php?id=' . \absint( $r['id'] );
1638
  * @since 2.2.4
1639
  * @since 2.8.0 Method is now public.
1640
  * @since 3.0.6 Noqueries is now disabled by default.
1641
+ * @since 4.0.0 1. Removed rudimentary relative URL testing.
1642
+ * 2. Removed input transformation filters, and with that, removed redundant multisite spam protection.
1643
+ * 3. Now allows all protocols. Enjoy!
1644
+ * 4. Now no longer lets through double-absolute URLs (e.g. `https://google.com/https://google.com/path/to/file/`)
1645
+ * when filter `the_seo_framework_allow_external_redirect` is set to false.
1646
  *
1647
  * @param string $new_value String with potentially unwanted redirect URL.
1648
  * @return string The Sanitized Redirect URL
1877
  /**
1878
  * Strips all URLs that are placed on new lines. These are prone to be embeds.
1879
  *
1880
+ * This might leave stray line feeds. Use `tsf()->s_singleline()` to fix that.
1881
  *
1882
  * @since 3.1.0
1883
  * @see \WP_Embed::autoembed()
1892
  /**
1893
  * Strips all URLs that are placed in paragraphs on their own. These are prone to be embeds.
1894
  *
1895
+ * This might leave stray line feeds. Use `tsf()->s_singleline()` to fix that.
1896
  *
1897
  * @since 3.1.0
1898
  * @see \WP_Embed::autoembed()
1956
  $args = $default_args;
1957
  } else {
1958
  foreach ( [ 'space', 'clear' ] as $type ) {
1959
+ if ( isset( $args[ $type ] ) )
1960
+ $args[ $type ] = $args[ $type ] ? (array) $args[ $type ] : [];
 
 
 
 
 
1961
  }
1962
+ $args['strip'] = $args['strip'] ?? $default_args['strip'];
1963
  }
1964
 
1965
  // Clear first, so there's less to process; then add spaces.
1968
 
1969
  // void = element without content.
1970
  $void_query = array_intersect( $args[ $type ], $void );
1971
+ // fill = <normal | template | raw text | escapable text | foreign> element.
1972
  $fill_query = array_diff( $args[ $type ], $void );
1973
 
 
 
1974
  if ( $void_query ) {
1975
  $_regex = sprintf( '<(%s)\b[^>]*?>', implode( '|', $void_query ) );
1976
  $_replace = 'space' === $type ? ' ' : '';
2006
  * string alt: The image alt tag,
2007
  * }
2008
  */
2009
+ public function s_image_details( $details ) {
2010
 
2011
  if ( array_values( $details ) === $details )
2012
  return $this->s_image_details_deep( $details );
2019
  'alt' => '',
2020
  ];
2021
 
2022
+ [ $url, $id, $width, $height, $alt ] = array_values( array_merge( $defaults, $details ) );
2023
 
2024
  if ( ! $url ) return $defaults;
2025
 
2086
  * string alt: The image alt tag,
2087
  * }
2088
  */
2089
+ public function s_image_details_deep( $details_array ) {
2090
 
2091
  $cleaned_details = [];
2092
 
inc/classes/site-options.class.php CHANGED
@@ -99,7 +99,8 @@ class Site_Options extends Sanitize {
99
  'disabled_taxonomies' => [], // Taxonomy support.
100
 
101
  // Title.
102
- 'title_separator' => 'hyphen', // Title separator, dropdown
 
103
  'title_location' => $titleloc, // Title separation location
104
  'title_rem_additions' => 0, // Remove title additions
105
  'title_rem_prefixes' => 0, // Remove title prefixes from archives.
@@ -170,6 +171,9 @@ class Site_Options extends Sanitize {
170
  'homepage_social_image_url' => '',
171
  'homepage_social_image_id' => 0,
172
 
 
 
 
173
  // Relationships.
174
  'shortlink_tag' => 0, // Adds shortlink tag
175
  'prev_next_posts' => 1, // Adds next/prev tags
@@ -249,7 +253,6 @@ class Site_Options extends Sanitize {
249
  'sitemap_query_limit' => 1000, // Sitemap post limit.
250
 
251
  'sitemaps_modified' => 1, // Add sitemap modified time.
252
- 'sitemaps_priority' => 0, // Add sitemap priorities.
253
 
254
  'sitemaps_robots' => 1, // Add sitemap location to robots.txt
255
 
@@ -286,40 +289,43 @@ class Site_Options extends Sanitize {
286
  * @since 3.1.0 Now applies the "the_seo_framework_warned_site_options" filter.
287
  * @since 4.1.0 Added robots' post type setting warnings.
288
  * @since 4.1.2 Added `ping_use_cron_prerender`.
 
289
  *
290
  * @return array $options.
291
  */
292
  public function get_warned_site_options() {
293
- /**
294
- * Warned site settings. Only accepts checkbox options.
295
- * When listed as 1, it's a feature which can destroy your website's SEO value when checked.
296
- *
297
- * Unchecking a box is simply "I'm not active." - Removing features generally do not negatively impact SEO value.
298
- * Since it's all about the content.
299
- *
300
- * Only used within the SEO Settings page.
301
- *
302
- * @since 2.3.4
303
- * @param array $options The warned site options.
304
- */
305
- return (array) \apply_filters(
306
- 'the_seo_framework_warned_site_options',
307
- [
308
- 'title_rem_additions' => 1, // Title remove additions.
309
- 'site_noindex' => 1, // Site Page robots noindex.
310
- 'site_nofollow' => 1, // Site Page robots nofollow.
311
- 'homepage_noindex' => 1, // Homepage robots noindex.
312
- 'homepage_nofollow' => 1, // Homepage robots noarchive.
313
- $this->get_robots_post_type_option_id( 'noindex' ) => [
314
- 'post' => 1,
315
- 'page' => 1,
316
- ],
317
- $this->get_robots_post_type_option_id( 'nofollow' ) => [
318
- 'post' => 1,
319
- 'page' => 1,
320
- ],
321
- 'ping_use_cron_prerender' => 1, // Sitemap cron-ping prerender.
322
- ]
 
 
323
  );
324
  }
325
 
@@ -329,32 +335,53 @@ class Site_Options extends Sanitize {
329
  * @since 2.2.2
330
  * @since 2.8.2 No longer decodes entities on request.
331
  * @since 3.1.0 Now uses the filterable call when caching is disabled.
 
332
  * @uses THE_SEO_FRAMEWORK_SITE_OPTIONS
333
  *
334
- * @param string $key Option name.
335
- * @param boolean $use_cache Optional. Whether to use the cache value or not. Defaults to true.
 
336
  * @return mixed The value of this $key in the database. Empty string when not set.
337
  */
338
  public function get_option( $key, $use_cache = true ) {
339
 
340
  if ( ! $use_cache ) {
341
- $options = $this->get_all_options( THE_SEO_FRAMEWORK_SITE_OPTIONS, true );
342
- return isset( $options[ $key ] ) ? \stripslashes_deep( $options[ $key ] ) : '';
 
 
 
 
 
 
343
  }
344
 
345
- static $cache = [];
 
 
 
 
 
 
346
 
347
- if ( ! isset( $cache[ THE_SEO_FRAMEWORK_SITE_OPTIONS ] ) )
348
- $cache[ THE_SEO_FRAMEWORK_SITE_OPTIONS ] = \stripslashes_deep( $this->get_all_options( THE_SEO_FRAMEWORK_SITE_OPTIONS ) );
 
349
 
350
- // TODO fall back to default if not registered? This means we no longer have to rely on upgrading. Or, array merge (recursive) at get_all_options?
351
- return isset( $cache[ THE_SEO_FRAMEWORK_SITE_OPTIONS ][ $key ] ) ? $cache[ THE_SEO_FRAMEWORK_SITE_OPTIONS ][ $key ] : '';
352
  }
353
 
354
  /**
355
  * Return current option array.
356
  * Memoizes the return value, can be bypassed and reset with second parameter.
357
  *
 
 
 
 
 
 
 
358
  * @since 2.6.0
359
  * @since 2.9.2 Added $use_current parameter.
360
  *
@@ -367,12 +394,12 @@ class Site_Options extends Sanitize {
367
 
368
  static $cache = [];
369
 
370
- if ( ! $reset && isset( $cache[ $setting ] ) )
371
- return $cache[ $setting ];
372
-
373
  if ( ! $setting )
374
  $setting = THE_SEO_FRAMEWORK_SITE_OPTIONS;
375
 
 
 
 
376
  /**
377
  * @since 2.0.0
378
  * @since 4.1.4 1. Now considers headlessness.
@@ -398,15 +425,47 @@ class Site_Options extends Sanitize {
398
  * Return Default SEO options from the SEO options array.
399
  *
400
  * @since 2.2.5
 
 
 
401
  * @uses $this->get_default_settings() Return option from the options table and cache result.
402
  * @uses THE_SEO_FRAMEWORK_SITE_OPTIONS
403
  *
404
- * @param string $key Required. The option name.
405
- * @param boolean $use_cache Optional. Whether to use the cache value or not.
406
- * @return mixed The value of this $key in the database.
407
  */
408
- public function get_default_option( $key, $use_cache = true ) {
409
- return $this->get_default_settings( $key, '', $use_cache );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
  }
411
 
412
  /**
@@ -444,8 +503,7 @@ class Site_Options extends Sanitize {
444
  * @return mixed Cache value on success, $default if non-existent.
445
  */
446
  public function get_static_cache( $key, $default = false ) {
447
- $cache = \get_option( THE_SEO_FRAMEWORK_SITE_CACHE, [] );
448
- return isset( $cache[ $key ] ) ? $cache[ $key ] : $default;
449
  }
450
 
451
  /**
@@ -544,415 +602,323 @@ class Site_Options extends Sanitize {
544
  }
545
 
546
  /**
547
- * Get the default of any of the The SEO Framework settings.
548
  *
549
- * @since 2.2.4
550
- * @since 2.8.2 No longer decodes entities on request.
551
- * @since 3.1.0 : 1. Now returns null if the option doesn't exist, instead of -1.
552
- * 2. Is now influenced by filters.
553
- * 3. Now also strips slashes when using cache.
554
- * 4. The second parameter is deprecated.
555
- * @uses $this->get_default_site_options()
556
- *
557
- * @param string $key Required. The option name.
558
- * @param string $depr Deprecated. Leave empty.
559
- * @param bool $use_cache Optional. Whether to use the options cache or bypass it.
560
- * @return mixed default option
561
- * null If option doesn't exist.
562
  */
563
- public function get_default_settings( $key, $depr = '', $use_cache = true ) {
564
-
565
- if ( ! $key ) return false;
566
-
567
- if ( $depr )
568
- $this->_doing_it_wrong( __METHOD__, 'The second parameter is deprecated.', '3.1.0' );
569
-
570
- // If we need to bypass the cache
571
- if ( ! $use_cache ) {
572
- $defaults = $this->get_default_site_options();
573
- return isset( $defaults[ $key ] ) ? \stripslashes_deep( $defaults[ $key ] ) : null;
574
- }
575
-
576
- static $cache;
577
 
578
- if ( ! isset( $cache ) )
579
- $cache = \stripslashes_deep( $this->get_default_site_options() );
 
 
 
 
 
 
 
 
 
 
580
 
581
- return isset( $cache[ $key ] ) ? $cache[ $key ] : null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
  }
583
 
584
  /**
585
- * Get the warned setting of any of the The SEO Framework settings.
586
  *
587
- * @since 2.3.4
588
- * @since 3.1.0 Now returns 0 if the option doesn't exist, instead of -1.
589
- * @uses THE_SEO_FRAMEWORK_SITE_OPTIONS
590
- * @uses $this->get_warned_site_options()
 
591
  *
592
- * @param string $key Required. The option name.
593
- * @param string $depr Deprecated. Leave empty.
594
- * @param bool $use_cache Optional. Whether to use the options cache or bypass it.
595
- * @return int 0|1 Whether the option is flagged as dangerous for SEO.
596
  */
597
- public function get_warned_settings( $key, $depr = '', $use_cache = true ) {
598
 
599
- if ( empty( $key ) )
600
- return false;
601
 
602
- if ( $depr )
603
- $this->_doing_it_wrong( __METHOD__, 'The second parameter is deprecated.', '3.1.0' );
 
 
 
 
 
 
604
 
605
- // If we need to bypass the cache
606
- if ( ! $use_cache ) {
607
- $warned = $this->get_warned_site_options();
608
- return $this->s_one_zero( ! empty( $warned[ $key ] ) );
 
 
 
609
  }
610
 
611
- static $cache;
612
-
613
- if ( ! isset( $cache ) )
614
- $cache = $this->get_warned_site_options();
 
 
 
 
 
 
 
 
 
 
 
615
 
616
- return $this->s_one_zero( ! empty( $cache[ $key ] ) );
 
617
  }
618
 
619
  /**
620
- * Returns the option value for Post Type robots settings.
621
  *
622
- * @since 3.1.0
623
  *
624
- * @param string $type Accepts 'noindex', 'nofollow', 'noarchive'.
625
- * @return string
 
 
626
  */
627
- public function get_robots_post_type_option_id( $type ) {
628
- return $this->s_field_id( "{$type}_post_types" );
 
 
 
629
  }
630
 
 
631
  /**
632
- * Returns the option value for Taxonomy robots settings.
633
  *
634
- * @since 4.1.0
635
  *
636
- * @param string $type Accepts 'noindex', 'nofollow', 'noarchive'.
637
- * @return string
638
  */
639
- public function get_robots_taxonomy_option_id( $type ) {
640
- return $this->s_field_id( "{$type}_taxonomies" );
 
 
 
 
641
  }
 
642
 
643
  /**
644
- * Returns Facebook locales array values.
645
  *
646
- * @since 2.5.2
647
- * TODO collapse this with language_keys(), ll_CC => ll?, return array_keys here, array_values there?
648
  *
649
- * @see https://www.facebook.com/translations/FacebookLocales.xml (deprecated)
650
- * @see https://wordpress.org/support/topic/oglocale-problem/#post-11456346
651
- * mirror: http://web.archive.org/web/20190601043836/https://wordpress.org/support/topic/oglocale-problem/
652
- * @see $this->language_keys() for the associative array keys.
653
- *
654
- * @return array Valid Facebook locales
655
  */
656
- public function fb_locales() {
657
- return [
658
- 'af_ZA', // Afrikaans
659
- 'ak_GH', // Akan
660
- 'am_ET', // Amharic
661
- 'ar_AR', // Arabic
662
- 'as_IN', // Assamese
663
- 'ay_BO', // Aymara
664
- 'az_AZ', // Azerbaijani
665
- 'be_BY', // Belarusian
666
- 'bg_BG', // Bulgarian
667
- 'bn_IN', // Bengali
668
- 'br_FR', // Breton
669
- 'bs_BA', // Bosnian
670
- 'ca_ES', // Catalan
671
- 'cb_IQ', // Sorani Kurdish
672
- 'ck_US', // Cherokee
673
- 'co_FR', // Corsican
674
- 'cs_CZ', // Czech
675
- 'cx_PH', // Cebuano
676
- 'cy_GB', // Welsh
677
- 'da_DK', // Danish
678
- 'de_DE', // German
679
- 'el_GR', // Greek
680
- 'en_GB', // English (UK)
681
- 'en_IN', // English (India)
682
- 'en_PI', // English (Pirate)
683
- 'en_UD', // English (Upside Down)
684
- 'en_US', // English (US)
685
- 'eo_EO', // Esperanto
686
- 'es_CL', // Spanish (Chile)
687
- 'es_CO', // Spanish (Colombia)
688
- 'es_ES', // Spanish (Spain)
689
- 'es_LA', // Spanish
690
- 'es_MX', // Spanish (Mexico)
691
- 'es_VE', // Spanish (Venezuela)
692
- 'et_EE', // Estonian
693
- 'eu_ES', // Basque
694
- 'fa_IR', // Persian
695
- 'fb_LT', // Leet Speak
696
- 'ff_NG', // Fulah
697
- 'fi_FI', // Finnish
698
- 'fo_FO', // Faroese
699
- 'fr_CA', // French (Canada)
700
- 'fr_FR', // French (France)
701
- 'fy_NL', // Frisian
702
- 'ga_IE', // Irish
703
- 'gl_ES', // Galician
704
- 'gn_PY', // Guarani
705
- 'gu_IN', // Gujarati
706
- 'gx_GR', // Classical Greek
707
- 'ha_NG', // Hausa
708
- 'he_IL', // Hebrew
709
- 'hi_IN', // Hindi
710
- 'hr_HR', // Croatian
711
- 'hu_HU', // Hungarian
712
- 'hy_AM', // Armenian
713
- 'id_ID', // Indonesian
714
- 'ig_NG', // Igbo
715
- 'is_IS', // Icelandic
716
- 'it_IT', // Italian
717
- 'ja_JP', // Japanese
718
- 'ja_KS', // Japanese (Kansai)
719
- 'jv_ID', // Javanese
720
- 'ka_GE', // Georgian
721
- 'kk_KZ', // Kazakh
722
- 'km_KH', // Khmer
723
- 'kn_IN', // Kannada
724
- 'ko_KR', // Korean
725
- 'ku_TR', // Kurdish (Kurmanji)
726
- 'ky_KG', // Kyrgyz
727
- 'la_VA', // Latin
728
- 'lg_UG', // Ganda
729
- 'li_NL', // Limburgish
730
- 'ln_CD', // Lingala
731
- 'lo_LA', // Lao
732
- 'lt_LT', // Lithuanian
733
- 'lv_LV', // Latvian
734
- 'mg_MG', // Malagasy
735
- 'mi_NZ', // Māori
736
- 'mk_MK', // Macedonian
737
- 'ml_IN', // Malayalam
738
- 'mn_MN', // Mongolian
739
- 'mr_IN', // Marathi
740
- 'ms_MY', // Malay
741
- 'mt_MT', // Maltese
742
- 'my_MM', // Burmese
743
- 'nb_NO', // Norwegian (bokmal)
744
- 'nd_ZW', // Ndebele
745
- 'ne_NP', // Nepali
746
- 'nl_BE', // Dutch (België)
747
- 'nl_NL', // Dutch
748
- 'nn_NO', // Norwegian (nynorsk)
749
- 'ny_MW', // Chewa
750
- 'or_IN', // Oriya
751
- 'pa_IN', // Punjabi
752
- 'pl_PL', // Polish
753
- 'ps_AF', // Pashto
754
- 'pt_BR', // Portuguese (Brazil)
755
- 'pt_PT', // Portuguese (Portugal)
756
- 'qu_PE', // Quechua
757
- 'rm_CH', // Romansh
758
- 'ro_RO', // Romanian
759
- 'ru_RU', // Russian
760
- 'rw_RW', // Kinyarwanda
761
- 'sa_IN', // Sanskrit
762
- 'sc_IT', // Sardinian
763
- 'se_NO', // Northern Sámi
764
- 'si_LK', // Sinhala
765
- 'sk_SK', // Slovak
766
- 'sl_SI', // Slovenian
767
- 'sn_ZW', // Shona
768
- 'so_SO', // Somali
769
- 'sq_AL', // Albanian
770
- 'sr_RS', // Serbian
771
- 'sv_SE', // Swedish
772
- 'sy_SY', // Swahili
773
- 'sw_KE', // Syriac
774
- 'sz_PL', // Silesian
775
- 'ta_IN', // Tamil
776
- 'te_IN', // Telugu
777
- 'tg_TJ', // Tajik
778
- 'th_TH', // Thai
779
- 'tk_TM', // Turkmen
780
- 'tl_PH', // Filipino
781
- 'tl_ST', // Klingon
782
- 'tr_TR', // Turkish
783
- 'tt_RU', // Tatar
784
- 'tz_MA', // Tamazight
785
- 'uk_UA', // Ukrainian
786
- 'ur_PK', // Urdu
787
- 'uz_UZ', // Uzbek
788
- 'vi_VN', // Vietnamese
789
- 'wo_SN', // Wolof
790
- 'xh_ZA', // Xhosa
791
- 'yi_DE', // Yiddish
792
- 'yo_NG', // Yoruba
793
- 'zh_CN', // Simplified Chinese (China)
794
- 'zh_HK', // Traditional Chinese (Hong Kong)
795
- 'zh_TW', // Traditional Chinese (Taiwan)
796
- 'zu_ZA', // Zulu
797
- 'zz_TR', // Zazaki
798
- ];
799
  }
800
 
801
  /**
802
- * Returns Facebook locales' associative array keys.
803
- *
804
- * This is apart from the fb_locales array since there are "duplicated" keys.
805
- * Use this to compare the numeric key position.
806
  *
807
- * @since 2.5.2
808
- * @see https://www.facebook.com/translations/FacebookLocales.xml (deprecated)
809
- * @see https://wordpress.org/support/topic/oglocale-problem/#post-11456346
810
- * mirror: http://web.archive.org/web/20190601043836/https://wordpress.org/support/topic/oglocale-problem/
811
  *
812
- * @return array Valid Facebook locale keys
813
  */
814
- public function language_keys() {
815
  return [
816
- 'af', // Afrikaans
817
- 'ak', // Akan
818
- 'am', // Amharic
819
- 'ar', // Arabic
820
- 'as', // Assamese
821
- 'ay', // Aymara
822
- 'az', // Azerbaijani
823
- 'be', // Belarusian
824
- 'bg', // Bulgarian
825
- 'bn', // Bengali
826
- 'br', // Breton
827
- 'bs', // Bosnian
828
- 'ca', // Catalan
829
- 'cb', // Sorani Kurdish
830
- 'ck', // Cherokee
831
- 'co', // Corsican
832
- 'cs', // Czech
833
- 'cx', // Cebuano
834
- 'cy', // Welsh
835
- 'da', // Danish
836
- 'de', // German
837
- 'el', // Greek
838
- 'en', // English (UK)
839
- 'en', // English (India)
840
- 'en', // English (Pirate)
841
- 'en', // English (Upside Down)
842
- 'en', // English (US)
843
- 'eo', // Esperanto
844
- 'es', // Spanish (Chile)
845
- 'es', // Spanish (Colombia)
846
- 'es', // Spanish (Spain)
847
- 'es', // Spanish
848
- 'es', // Spanish (Mexico)
849
- 'es', // Spanish (Venezuela)
850
- 'et', // Estonian
851
- 'eu', // Basque
852
- 'fa', // Persian
853
- 'fb', // Leet Speak
854
- 'ff', // Fulah
855
- 'fi', // Finnish
856
- 'fo', // Faroese
857
- 'fr', // French (Canada)
858
- 'fr', // French (France)
859
- 'fy', // Frisian
860
- 'ga', // Irish
861
- 'gl', // Galician
862
- 'gn', // Guarani
863
- 'gu', // Gujarati
864
- 'gx', // Classical Greek
865
- 'ha', // Hausa
866
- 'he', // Hebrew
867
- 'hi', // Hindi
868
- 'hr', // Croatian
869
- 'hu', // Hungarian
870
- 'hy', // Armenian
871
- 'id', // Indonesian
872
- 'ig', // Igbo
873
- 'is', // Icelandic
874
- 'it', // Italian
875
- 'ja', // Japanese
876
- 'ja', // Japanese (Kansai)
877
- 'jv', // Javanese
878
- 'ka', // Georgian
879
- 'kk', // Kazakh
880
- 'km', // Khmer
881
- 'kn', // Kannada
882
- 'ko', // Korean
883
- 'ku', // Kurdish (Kurmanji)
884
- 'ky', // Kyrgyz
885
- 'la', // Latin
886
- 'lg', // Ganda
887
- 'li', // Limburgish
888
- 'ln', // Lingala
889
- 'lo', // Lao
890
- 'lt', // Lithuanian
891
- 'lv', // Latvian
892
- 'mg', // Malagasy
893
- 'mi', // Māori
894
- 'mk', // Macedonian
895
- 'ml', // Malayalam
896
- 'mn', // Mongolian
897
- 'mr', // Marathi
898
- 'ms', // Malay
899
- 'mt', // Maltese
900
- 'my', // Burmese
901
- 'nb', // Norwegian (bokmal)
902
- 'nd', // Ndebele
903
- 'ne', // Nepali
904
- 'nl', // Dutch (België)
905
- 'nl', // Dutch
906
- 'nn', // Norwegian (nynorsk)
907
- 'ny', // Chewa
908
- 'or', // Oriya
909
- 'pa', // Punjabi
910
- 'pl', // Polish
911
- 'ps', // Pashto
912
- 'pt', // Portuguese (Brazil)
913
- 'pt', // Portuguese (Portugal)
914
- 'qu', // Quechua
915
- 'rm', // Romansh
916
- 'ro', // Romanian
917
- 'ru', // Russian
918
- 'rw', // Kinyarwanda
919
- 'sa', // Sanskrit
920
- 'sc', // Sardinian
921
- 'se', // Northern Sámi
922
- 'si', // Sinhala
923
- 'sk', // Slovak
924
- 'sl', // Slovenian
925
- 'sn', // Shona
926
- 'so', // Somali
927
- 'sq', // Albanian
928
- 'sr', // Serbian
929
- 'sv', // Swedish
930
- 'sy', // Swahili
931
- 'sw', // Syriac
932
- 'sz', // Silesian
933
- 'ta', // Tamil
934
- 'te', // Telugu
935
- 'tg', // Tajik
936
- 'th', // Thai
937
- 'tk', // Turkmen
938
- 'tl', // Filipino
939
- 'tl', // Klingon
940
- 'tr', // Turkish
941
- 'tt', // Tatar
942
- 'tz', // Tamazight
943
- 'uk', // Ukrainian
944
- 'ur', // Urdu
945
- 'uz', // Uzbek
946
- 'vi', // Vietnamese
947
- 'wo', // Wolof
948
- 'xh', // Xhosa
949
- 'yi', // Yiddish
950
- 'yo', // Yoruba
951
- 'zh', // Simplified Chinese (China)
952
- 'zh', // Traditional Chinese (Hong Kong)
953
- 'zh', // Traditional Chinese (Taiwan)
954
- 'zu', // Zulu
955
- 'zz', // Zazaki
956
  ];
957
  }
958
  }
99
  'disabled_taxonomies' => [], // Taxonomy support.
100
 
101
  // Title.
102
+ 'site_title' => '', // Blog name.
103
+ 'title_separator' => 'hyphen', // Title separator, radio selection.
104
  'title_location' => $titleloc, // Title separation location
105
  'title_rem_additions' => 0, // Remove title additions
106
  'title_rem_prefixes' => 0, // Remove title prefixes from archives.
171
  'homepage_social_image_url' => '',
172
  'homepage_social_image_id' => 0,
173
 
174
+ // Post Type Archives.
175
+ 'pta' => $this->get_all_post_type_archive_meta_defaults(), // All of it. See $this->get_post_type_archive_meta(), which calls this index.
176
+
177
  // Relationships.
178
  'shortlink_tag' => 0, // Adds shortlink tag
179
  'prev_next_posts' => 1, // Adds next/prev tags
253
  'sitemap_query_limit' => 1000, // Sitemap post limit.
254
 
255
  'sitemaps_modified' => 1, // Add sitemap modified time.
 
256
 
257
  'sitemaps_robots' => 1, // Add sitemap location to robots.txt
258
 
289
  * @since 3.1.0 Now applies the "the_seo_framework_warned_site_options" filter.
290
  * @since 4.1.0 Added robots' post type setting warnings.
291
  * @since 4.1.2 Added `ping_use_cron_prerender`.
292
+ * @since 4.2.0 Now memoizes its return value.
293
  *
294
  * @return array $options.
295
  */
296
  public function get_warned_site_options() {
297
+ return memo() ?? memo(
298
+ /**
299
+ * Warned site settings. Only accepts checkbox options.
300
+ * When listed as 1, it's a feature which can destroy your website's SEO value when checked.
301
+ *
302
+ * Unchecking a box is simply "I'm not active." - Removing features generally do not negatively impact SEO value.
303
+ * Since it's all about the content.
304
+ *
305
+ * Only used within the SEO Settings page.
306
+ *
307
+ * @since 2.3.4
308
+ * @param array $options The warned site options.
309
+ */
310
+ (array) \apply_filters(
311
+ 'the_seo_framework_warned_site_options',
312
+ [
313
+ 'title_rem_additions' => 1, // Title remove additions.
314
+ 'site_noindex' => 1, // Site Page robots noindex.
315
+ 'site_nofollow' => 1, // Site Page robots nofollow.
316
+ 'homepage_noindex' => 1, // Homepage robots noindex.
317
+ 'homepage_nofollow' => 1, // Homepage robots noarchive.
318
+ $this->get_robots_post_type_option_id( 'noindex' ) => [
319
+ 'post' => 1,
320
+ 'page' => 1,
321
+ ],
322
+ $this->get_robots_post_type_option_id( 'nofollow' ) => [
323
+ 'post' => 1,
324
+ 'page' => 1,
325
+ ],
326
+ 'ping_use_cron_prerender' => 1, // Sitemap cron-ping prerender.
327
+ ]
328
+ )
329
  );
330
  }
331
 
335
  * @since 2.2.2
336
  * @since 2.8.2 No longer decodes entities on request.
337
  * @since 3.1.0 Now uses the filterable call when caching is disabled.
338
+ * @since 4.2.0 Now supports an option index as a $key.
339
  * @uses THE_SEO_FRAMEWORK_SITE_OPTIONS
340
  *
341
+ * @param string|string[] $key Option name, or a map of indexes therefor.
342
+ * If you send an empty array, you'll get all options. Don't.
343
+ * @param boolean $use_cache Optional. Whether to use the cache value or not. Defaults to true.
344
  * @return mixed The value of this $key in the database. Empty string when not set.
345
  */
346
  public function get_option( $key, $use_cache = true ) {
347
 
348
  if ( ! $use_cache ) {
349
+ // Preassign all options to $val so we can loop through it.
350
+ $val = $this->get_all_options( THE_SEO_FRAMEWORK_SITE_OPTIONS, true );
351
+
352
+ // This loop digs through itself: $val[ $index ][ $index ]... etc.
353
+ foreach ( (array) $key as $k )
354
+ $val = $val[ $k ] ?? '';
355
+
356
+ return ! empty( $val ) ? \stripslashes_deep( $val ) : '';
357
  }
358
 
359
+ static $cache;
360
+
361
+ // PHP 7.4: null coalesce equal operator: ??=
362
+ if ( ! isset( $cache ) )
363
+ $cache = \stripslashes_deep( $this->get_all_options( THE_SEO_FRAMEWORK_SITE_OPTIONS ) );
364
+
365
+ $val = $cache;
366
 
367
+ // This loop digs through itself: $val[ $index ][ $index ]... etc.
368
+ foreach ( (array) $key as $k )
369
+ $val = $val[ $k ] ?? '';
370
 
371
+ return $val;
 
372
  }
373
 
374
  /**
375
  * Return current option array.
376
  * Memoizes the return value, can be bypassed and reset with second parameter.
377
  *
378
+ * This method does NOT merge the default post options.
379
+ *
380
+ * @TODO Should we fall back to default options when one isn't registered (correctly)?
381
+ * See get_term_meta(), which falls back to default term meta.
382
+ * We'd need our fancy in-house array_merge_recursive_distinct() though.
383
+ * BLOCKER: This method always runs BEFORE 'pta' can be filled by other plugins.
384
+ *
385
  * @since 2.6.0
386
  * @since 2.9.2 Added $use_current parameter.
387
  *
394
 
395
  static $cache = [];
396
 
 
 
 
397
  if ( ! $setting )
398
  $setting = THE_SEO_FRAMEWORK_SITE_OPTIONS;
399
 
400
+ if ( ! $reset && isset( $cache[ $setting ] ) )
401
+ return $cache[ $setting ];
402
+
403
  /**
404
  * @since 2.0.0
405
  * @since 4.1.4 1. Now considers headlessness.
425
  * Return Default SEO options from the SEO options array.
426
  *
427
  * @since 2.2.5
428
+ * @since 4.2.0 1. Now supports an option index as `$key`.
429
+ * 2. Removed second parameter (`$use_cache`).
430
+ * 3. Now always memoizes.
431
  * @uses $this->get_default_settings() Return option from the options table and cache result.
432
  * @uses THE_SEO_FRAMEWORK_SITE_OPTIONS
433
  *
434
+ * @param string|string[] $key Required. The option name, or a map of indexes.
435
+ * @return mixed The default option. Null if it's not registered.
 
436
  */
437
+ public function get_default_option( $key ) {
438
+
439
+ $val = umemo( __METHOD__ )
440
+ ?? umemo( __METHOD__, \stripslashes_deep( $this->get_default_site_options() ) );
441
+
442
+ // This loop digs through itself: $val[ $index ][ $index ]... etc.
443
+ foreach ( (array) $key as $k )
444
+ $val = $val[ $k ] ?? null;
445
+
446
+ return $val ?? null;
447
+ }
448
+
449
+ /**
450
+ * Return Warned SEO options from the SEO options array.
451
+ *
452
+ * @since 4.2.0
453
+ * @uses $this->get_default_settings() Return option from the options table and cache result.
454
+ * @uses THE_SEO_FRAMEWORK_SITE_OPTIONS
455
+ *
456
+ * @param string|string[] $key Required. The option name, or a map of indexes.
457
+ * @return bool True if warning is registered. False otherwise.
458
+ */
459
+ public function get_warned_option( $key ) {
460
+
461
+ $val = umemo( __METHOD__ )
462
+ ?? umemo( __METHOD__, \stripslashes_deep( $this->get_warned_site_options() ) );
463
+
464
+ // This loop digs through itself: $val[ $index ][ $index ]... etc.
465
+ foreach ( (array) $key as $k )
466
+ $val = $val[ $k ] ?? null;
467
+
468
+ return (bool) ( $val ?? false );
469
  }
470
 
471
  /**
503
  * @return mixed Cache value on success, $default if non-existent.
504
  */
505
  public function get_static_cache( $key, $default = false ) {
506
+ return \get_option( THE_SEO_FRAMEWORK_SITE_CACHE, [] )[ $key ] ?? $default;
 
507
  }
508
 
509
  /**
602
  }
603
 
604
  /**
605
+ * Returns the option value for Post Type robots settings.
606
  *
607
+ * @since 3.1.0
608
+ * @since 4.2.0 No longer sanitizes the input parameter.
609
+ *
610
+ * @param string $type Accepts 'noindex', 'nofollow', 'noarchive'.
611
+ * @return string
 
 
 
 
 
 
 
 
612
  */
613
+ public function get_robots_post_type_option_id( $type ) {
614
+ return "{$type}_post_types";
615
+ }
 
 
 
 
 
 
 
 
 
 
 
616
 
617
+ /**
618
+ * Returns the option value for Taxonomy robots settings.
619
+ *
620
+ * @since 4.1.0
621
+ * @since 4.2.0 No longer sanitizes the input parameter.
622
+ *
623
+ * @param string $type Accepts 'noindex', 'nofollow', 'noarchive'.
624
+ * @return string
625
+ */
626
+ public function get_robots_taxonomy_option_id( $type ) {
627
+ return "{$type}_taxonomies";
628
+ }
629
 
630
+ /**
631
+ * Returns supported social site locales.
632
+ *
633
+ * @since 4.2.0
634
+ * @see https://www.facebook.com/translations/FacebookLocales.xml (deprecated)
635
+ * @see https://wordpress.org/support/topic/oglocale-problem/#post-11456346
636
+ * mirror: http://web.archive.org/web/20190601043836/https://wordpress.org/support/topic/oglocale-problem/
637
+ *
638
+ * @return array Valid social locales
639
+ */
640
+ public function supported_social_locales() {
641
+ return [
642
+ 'af_ZA' => 'af', // Afrikaans
643
+ 'ak_GH' => 'ak', // Akan
644
+ 'am_ET' => 'am', // Amharic
645
+ 'ar_AR' => 'ar', // Arabic
646
+ 'as_IN' => 'as', // Assamese
647
+ 'ay_BO' => 'ay', // Aymara
648
+ 'az_AZ' => 'az', // Azerbaijani
649
+ 'be_BY' => 'be', // Belarusian
650
+ 'bg_BG' => 'bg', // Bulgarian
651
+ 'bn_IN' => 'bn', // Bengali
652
+ 'br_FR' => 'br', // Breton
653
+ 'bs_BA' => 'bs', // Bosnian
654
+ 'ca_ES' => 'ca', // Catalan
655
+ 'cb_IQ' => 'cb', // Sorani Kurdish
656
+ 'ck_US' => 'ck', // Cherokee
657
+ 'co_FR' => 'co', // Corsican
658
+ 'cs_CZ' => 'cs', // Czech
659
+ 'cx_PH' => 'cx', // Cebuano
660
+ 'cy_GB' => 'cy', // Welsh
661
+ 'da_DK' => 'da', // Danish
662
+ 'de_DE' => 'de', // German
663
+ 'el_GR' => 'el', // Greek
664
+ 'en_GB' => 'en', // English (UK)
665
+ 'en_IN' => 'en', // English (India)
666
+ 'en_PI' => 'en', // English (Pirate)
667
+ 'en_UD' => 'en', // English (Upside Down)
668
+ 'en_US' => 'en', // English (US)
669
+ 'eo_EO' => 'eo', // Esperanto
670
+ 'es_CL' => 'es', // Spanish (Chile)
671
+ 'es_CO' => 'es', // Spanish (Colombia)
672
+ 'es_ES' => 'es', // Spanish (Spain)
673
+ 'es_LA' => 'es', // Spanish
674
+ 'es_MX' => 'es', // Spanish (Mexico)
675
+ 'es_VE' => 'es', // Spanish (Venezuela)
676
+ 'et_EE' => 'et', // Estonian
677
+ 'eu_ES' => 'eu', // Basque
678
+ 'fa_IR' => 'fa', // Persian
679
+ 'fb_LT' => 'fb', // Leet Speak
680
+ 'ff_NG' => 'ff', // Fulah
681
+ 'fi_FI' => 'fi', // Finnish
682
+ 'fo_FO' => 'fo', // Faroese
683
+ 'fr_CA' => 'fr', // French (Canada)
684
+ 'fr_FR' => 'fr', // French (France)
685
+ 'fy_NL' => 'fy', // Frisian
686
+ 'ga_IE' => 'ga', // Irish
687
+ 'gl_ES' => 'gl', // Galician
688
+ 'gn_PY' => 'gn', // Guarani
689
+ 'gu_IN' => 'gu', // Gujarati
690
+ 'gx_GR' => 'gx', // Classical Greek
691
+ 'ha_NG' => 'ha', // Hausa
692
+ 'he_IL' => 'he', // Hebrew
693
+ 'hi_IN' => 'hi', // Hindi
694
+ 'hr_HR' => 'hr', // Croatian
695
+ 'hu_HU' => 'hu', // Hungarian
696
+ 'hy_AM' => 'hy', // Armenian
697
+ 'id_ID' => 'id', // Indonesian
698
+ 'ig_NG' => 'ig', // Igbo
699
+ 'is_IS' => 'is', // Icelandic
700
+ 'it_IT' => 'it', // Italian
701
+ 'ja_JP' => 'ja', // Japanese
702
+ 'ja_KS' => 'ja', // Japanese (Kansai)
703
+ 'jv_ID' => 'jv', // Javanese
704
+ 'ka_GE' => 'ka', // Georgian
705
+ 'kk_KZ' => 'kk', // Kazakh
706
+ 'km_KH' => 'km', // Khmer
707
+ 'kn_IN' => 'kn', // Kannada
708
+ 'ko_KR' => 'ko', // Korean
709
+ 'ku_TR' => 'ku', // Kurdish (Kurmanji)
710
+ 'ky_KG' => 'ky', // Kyrgyz
711
+ 'la_VA' => 'la', // Latin
712
+ 'lg_UG' => 'lg', // Ganda
713
+ 'li_NL' => 'li', // Limburgish
714
+ 'ln_CD' => 'ln', // Lingala
715
+ 'lo_LA' => 'lo', // Lao
716
+ 'lt_LT' => 'lt', // Lithuanian
717
+ 'lv_LV' => 'lv', // Latvian
718
+ 'mg_MG' => 'mg', // Malagasy
719
+ 'mi_NZ' => 'mi', // Māori
720
+ 'mk_MK' => 'mk', // Macedonian
721
+ 'ml_IN' => 'ml', // Malayalam
722
+ 'mn_MN' => 'mn', // Mongolian
723
+ 'mr_IN' => 'mr', // Marathi
724
+ 'ms_MY' => 'ms', // Malay
725
+ 'mt_MT' => 'mt', // Maltese
726
+ 'my_MM' => 'my', // Burmese
727
+ 'nb_NO' => 'nb', // Norwegian (bokmal)
728
+ 'nd_ZW' => 'nd', // Ndebele
729
+ 'ne_NP' => 'ne', // Nepali
730
+ 'nl_BE' => 'nl', // Dutch (België)
731
+ 'nl_NL' => 'nl', // Dutch
732
+ 'nn_NO' => 'nn', // Norwegian (nynorsk)
733
+ 'ny_MW' => 'ny', // Chewa
734
+ 'or_IN' => 'or', // Oriya
735
+ 'pa_IN' => 'pa', // Punjabi
736
+ 'pl_PL' => 'pl', // Polish
737
+ 'ps_AF' => 'ps', // Pashto
738
+ 'pt_BR' => 'pt', // Portuguese (Brazil)
739
+ 'pt_PT' => 'pt', // Portuguese (Portugal)
740
+ 'qu_PE' => 'qu', // Quechua
741
+ 'rm_CH' => 'rm', // Romansh
742
+ 'ro_RO' => 'ro', // Romanian
743
+ 'ru_RU' => 'ru', // Russian
744
+ 'rw_RW' => 'rw', // Kinyarwanda
745
+ 'sa_IN' => 'sa', // Sanskrit
746
+ 'sc_IT' => 'sc', // Sardinian
747
+ 'se_NO' => 'se', // Northern Sámi
748
+ 'si_LK' => 'si', // Sinhala
749
+ 'sk_SK' => 'sk', // Slovak
750
+ 'sl_SI' => 'sl', // Slovenian
751
+ 'sn_ZW' => 'sn', // Shona
752
+ 'so_SO' => 'so', // Somali
753
+ 'sq_AL' => 'sq', // Albanian
754
+ 'sr_RS' => 'sr', // Serbian
755
+ 'sv_SE' => 'sv', // Swedish
756
+ 'sy_SY' => 'sy', // Swahili
757
+ 'sw_KE' => 'sw', // Syriac
758
+ 'sz_PL' => 'sz', // Silesian
759
+ 'ta_IN' => 'ta', // Tamil
760
+ 'te_IN' => 'te', // Telugu
761
+ 'tg_TJ' => 'tg', // Tajik
762
+ 'th_TH' => 'th', // Thai
763
+ 'tk_TM' => 'tk', // Turkmen
764
+ 'tl_PH' => 'tl', // Filipino
765
+ 'tl_ST' => 'tl', // Klingon
766
+ 'tr_TR' => 'tr', // Turkish
767
+ 'tt_RU' => 'tt', // Tatar
768
+ 'tz_MA' => 'tz', // Tamazight
769
+ 'uk_UA' => 'uk', // Ukrainian
770
+ 'ur_PK' => 'ur', // Urdu
771
+ 'uz_UZ' => 'uz', // Uzbek
772
+ 'vi_VN' => 'vi', // Vietnamese
773
+ 'wo_SN' => 'wo', // Wolof
774
+ 'xh_ZA' => 'xh', // Xhosa
775
+ 'yi_DE' => 'yi', // Yiddish
776
+ 'yo_NG' => 'yo', // Yoruba
777
+ 'zh_CN' => 'zh', // Simplified Chinese (China)
778
+ 'zh_HK' => 'zh', // Traditional Chinese (Hong Kong)
779
+ 'zh_TW' => 'zh', // Traditional Chinese (Taiwan)
780
+ 'zu_ZA' => 'zu', // Zulu
781
+ 'zz_TR' => 'zz', // Zazaki
782
+ ];
783
  }
784
 
785
  /**
786
+ * Returns all post type archive meta.
787
  *
788
+ * We do not test whether a post type is supported, for it'll conflict with data-fills on the
789
+ * SEO settings page. This meta should never get called on the front-end if the post type is
790
+ * disabled, anyway, for we never query post types externally, aside from the SEO settings page.
791
+ *
792
+ * @since 4.2.0
793
  *
794
+ * @param string $post_type The post type.
795
+ * @param bool $use_cache Whether to use caching.
796
+ * @return array The post type archive's meta item's values.
 
797
  */
798
+ public function get_post_type_archive_meta( $post_type, $use_cache = true ) {
799
 
800
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
801
+ if ( $use_cache && ( $memo = memo( null, $post_type ) ) ) return $memo;
802
 
803
+ /**
804
+ * We can't trust the filter to always contain the expected keys.
805
+ * However, it may contain more keys than we anticipated. Merge them.
806
+ */
807
+ $defaults = array_merge(
808
+ $this->get_unfiltered_post_type_archive_meta_defaults(),
809
+ $this->get_post_type_archive_meta_defaults( $post_type )
810
+ );
811
 
812
+ // Yes, we abide by "settings". WordPress never gave us Post Type Archive settings-pages.
813
+ if ( $this->is_headless['settings'] ) {
814
+ $meta = [];
815
+ } else {
816
+ // Unlike get_post_meta(), we need not filter here.
817
+ // See: <https://github.com/sybrew/the-seo-framework/issues/185>
818
+ $meta = $this->get_option( [ 'pta', $post_type ], $use_cache ) ?: [];
819
  }
820
 
821
+ /**
822
+ * @since 4.2.0
823
+ * @note Do not delete/unset/add indexes! It'll cause errors.
824
+ * @param array $meta The current post type archive meta.
825
+ * @param int $post_type The post type.
826
+ * @param bool $headless Whether the meta are headless.
827
+ */
828
+ $meta = \apply_filters_ref_array(
829
+ 'the_seo_framework_post_type_archive_meta',
830
+ [
831
+ array_merge( $defaults, $meta ),
832
+ $post_type,
833
+ $this->is_headless['settings'],
834
+ ]
835
+ );
836
 
837
+ // Do not overwrite cache when not requested. Otherwise, we'd have two "initial" states, causing incongruities.
838
+ return $use_cache ? memo( $meta, $post_type ) : $meta;
839
  }
840
 
841
  /**
842
+ * Returns a single post type archive item's value.
843
  *
844
+ * @since 4.2.0
845
  *
846
+ * @param string $item The item to get.
847
+ * @param string $post_type The post type.
848
+ * @param bool $use_cache Whether to use caching.
849
+ * @return array|null The post type archive's meta item's value. Null when item isn't registered.
850
  */
851
+ public function get_post_type_archive_meta_item( $item, $post_type = '', $use_cache = true ) {
852
+ return $this->get_post_type_archive_meta(
853
+ $post_type ?: $this->get_current_post_type(),
854
+ $use_cache
855
+ )[ $item ] ?? null;
856
  }
857
 
858
+ // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- You don't love PHP 7.
859
  /**
860
+ * Returns an array of all public post type archive option defaults.
861
  *
862
+ * @since 4.2.0
863
  *
864
+ * @return array[] The Post Type Archive Metadata default options
865
+ * of all public Post Type archives.
866
  */
867
+ public function get_all_post_type_archive_meta_defaults() {
868
+
869
+ foreach ( $this->get_public_post_type_archives() as $pta )
870
+ $defaults[ $pta ] = $this->get_post_type_archive_meta_defaults( $pta );
871
+
872
+ return $defaults ?? [];
873
  }
874
+ // phpcs:enable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
875
 
876
  /**
877
+ * Returns an array of default post type archive meta.
878
  *
879
+ * @since 4.2.0
 
880
  *
881
+ * @param int $post_type The post type.
882
+ * @return array The Post Type Archive Metadata default options.
 
 
 
 
883
  */
884
+ public function get_post_type_archive_meta_defaults( $post_type = '' ) {
885
+ /**
886
+ * @since 4.2.0
887
+ * @param array $defaults
888
+ * @param int $term_id The current term ID.
889
+ */
890
+ return (array) \apply_filters_ref_array(
891
+ 'the_seo_framework_get_post_type_archive_meta_defaults',
892
+ [
893
+ $this->get_unfiltered_post_type_archive_meta_defaults(),
894
+ $post_type ?: $this->get_current_post_type(),
895
+ ]
896
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
897
  }
898
 
899
  /**
900
+ * Returns the unfiltered post type archive meta defaults.
 
 
 
901
  *
902
+ * @since 4.2.0
 
 
 
903
  *
904
+ * @return array The default, unfiltered, post type archive meta.
905
  */
906
+ protected function get_unfiltered_post_type_archive_meta_defaults() {
907
  return [
908
+ 'doctitle' => '',
909
+ 'title_no_blog_name' => 0,
910
+ 'description' => '',
911
+ 'og_title' => '',
912
+ 'og_description' => '',
913
+ 'tw_title' => '',
914
+ 'tw_description' => '',
915
+ 'social_image_url' => '',
916
+ 'social_image_id' => 0,
917
+ 'canonical' => '',
918
+ 'noindex' => 0,
919
+ 'nofollow' => 0,
920
+ 'noarchive' => 0,
921
+ 'redirect' => '',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
922
  ];
923
  }
924
  }
inc/classes/term-data.class.php CHANGED
@@ -60,6 +60,7 @@ class Term_Data extends Post_Data {
60
  * Returns the term meta item by key.
61
  *
62
  * @since 4.0.0
 
63
  *
64
  * @param string $item The item to get.
65
  * @param int $term_id The Term ID.
@@ -67,39 +68,7 @@ class Term_Data extends Post_Data {
67
  * @return mixed The term meta item. Null when not found.
68
  */
69
  public function get_term_meta_item( $item, $term_id = 0, $use_cache = true ) {
70
-
71
- if ( ! $term_id ) {
72
- $meta = $this->get_current_term_meta();
73
- } else {
74
- $meta = $this->get_term_meta( $term_id, $use_cache );
75
- }
76
-
77
- return isset( $meta[ $item ] ) ? $meta[ $item ] : null;
78
- }
79
-
80
- /**
81
- * Returns and caches term meta for the current query.
82
- * Memoizes the return value for the current request.
83
- *
84
- * @since 3.0.0
85
- * @since 4.0.1 Now uses the filterable `get_the_real_ID()`
86
- *
87
- * @return array The current term meta.
88
- */
89
- public function get_current_term_meta() {
90
-
91
- static $cache;
92
-
93
- if ( isset( $cache ) )
94
- return $cache;
95
-
96
- if ( $this->is_term_meta_capable() ) {
97
- $cache = $this->get_term_meta( $this->get_the_real_ID() ) ?: [];
98
- } else {
99
- $cache = [];
100
- }
101
-
102
- return $cache;
103
  }
104
 
105
  /**
@@ -116,6 +85,7 @@ class Term_Data extends Post_Data {
116
  * 2. Now fills in defaults.
117
  * @since 4.1.4 1. Removed deprecated filter.
118
  * 2. Now considers headlessness.
 
119
  *
120
  * @param int $term_id The Term ID.
121
  * @param bool $use_cache Whether to use caching.
@@ -123,11 +93,15 @@ class Term_Data extends Post_Data {
123
  */
124
  public function get_term_meta( $term_id, $use_cache = true ) {
125
 
126
- if ( $use_cache ) {
127
- static $cache = [];
128
 
129
- if ( isset( $cache[ $term_id ] ) )
130
- return $cache[ $term_id ];
 
 
 
 
131
  }
132
 
133
  /**
@@ -136,13 +110,15 @@ class Term_Data extends Post_Data {
136
  */
137
  $defaults = array_merge(
138
  $this->get_unfiltered_term_meta_defaults(),
139
- $this->get_term_meta_defaults( $term_id )
140
  );
141
 
142
  if ( $this->is_headless['meta'] ) {
143
  $meta = [];
144
  } else {
145
- $meta = \get_term_meta( $term_id, THE_SEO_FRAMEWORK_TERM_OPTIONS, true ) ?: [];
 
 
146
  }
147
 
148
  /**
@@ -158,12 +134,14 @@ class Term_Data extends Post_Data {
158
  'the_seo_framework_term_meta',
159
  [
160
  array_merge( $defaults, $meta ),
161
- $term_id,
162
  $this->is_headless['meta'],
163
  ]
164
  );
165
 
166
- return $cache[ $term_id ] = $meta;
 
 
167
  }
168
 
169
  /**
@@ -171,10 +149,10 @@ class Term_Data extends Post_Data {
171
  *
172
  * @since 2.7.0
173
  * @since 3.1.0 This is now always used.
174
- * @since 4.0.0 : 1. Added $term_id parameter.
175
- * 2. Added 'redirect' value.
176
- * 3. Added 'title_no_blog_name' value.
177
- * 4. Removed 'saved_flag' value.
178
  *
179
  * @param int $term_id The term ID.
180
  * @return array The Term Metadata default options.
@@ -199,7 +177,7 @@ class Term_Data extends Post_Data {
199
  *
200
  * @since 4.0.0
201
  *
202
- * @return array The default, unfiltered, post meta.
203
  */
204
  protected function get_unfiltered_term_meta_defaults() {
205
  return [
@@ -224,15 +202,15 @@ class Term_Data extends Post_Data {
224
  * Sanitizes and saves term meta data when a term is altered.
225
  *
226
  * @since 2.7.0
227
- * @since 4.0.0 : 1. Renamed from `update_term_meta`
228
- * 2. noindex, nofollow, noarchive are now converted to qubits.
229
- * 3. Added new keys to sanitize.
230
- * 4. Now marked as private.
231
- * 5. Added more sanity protection.
232
- * 6. No longer runs when no `autodescription-meta` POST data is sent.
233
- * 7. Now uses the current term meta to set new values.
234
- * 8. No longer deletes meta from abstracting plugins on save when they're deactivated.
235
- * 9. Now allows updating during `WP_AJAX`.
236
  * @securitycheck 3.0.0 OK.
237
  * @access private
238
  * Use $this->save_term_meta() instead.
@@ -257,8 +235,8 @@ class Term_Data extends Post_Data {
257
  * Overwrites all of the term meta on term-edit.
258
  *
259
  * @since 4.0.0
260
- * @since 4.0.2 : 1. Now tests for valid term ID in the term object.
261
- * 2. Now continues using the filtered term object.
262
  *
263
  * @param int $term_id Term ID.
264
  * @param int $tt_id Term taxonomy ID.
@@ -276,7 +254,7 @@ class Term_Data extends Post_Data {
276
  // Note, however: function wp_update_term() already performs all these checks for us before firing this callback's action.
277
  if ( ! \current_user_can( 'edit_term', $term->term_id ) ) return;
278
  if ( ! isset( $_POST['_wpnonce'] ) ) return;
279
- if ( ! \wp_verify_nonce( $_POST['_wpnonce'], 'update-tag_' . $term->term_id ) ) return;
280
 
281
  $data = (array) $_POST['autodescription-meta'];
282
 
@@ -288,8 +266,8 @@ class Term_Data extends Post_Data {
288
  * Overwrites a part of the term meta on quick-edit.
289
  *
290
  * @since 4.0.0
291
- * @since 4.0.2 : 1. Now tests for valid term ID in the term object.
292
- * 2. Now continues using the filtered term object.
293
  *
294
  * @param int $term_id Term ID.
295
  * @param int $tt_id Term taxonomy ID.
@@ -326,8 +304,8 @@ class Term_Data extends Post_Data {
326
  * as it reprocesses all term meta.
327
  *
328
  * @since 4.0.0
329
- * @since 4.0.2 : 1. Now tests for valid term ID in the term object.
330
- * 2. Now continues using the filtered term object.
331
  * @uses $this->save_term_meta() to process all data.
332
  *
333
  * @param string $item The item to update.
@@ -353,15 +331,15 @@ class Term_Data extends Post_Data {
353
  * Updates term meta from input.
354
  *
355
  * @since 4.0.0
356
- * @since 4.0.2 : 1. Now tests for valid term ID in the term object.
357
- * 2. Now continues using the filtered term object.
358
  *
359
  * @param int $term_id Term ID.
360
  * @param int $tt_id Term Taxonomy ID.
361
  * @param string $taxonomy Taxonomy slug.
362
  * @param array $data The data to save.
363
  */
364
- public function save_term_meta( $term_id, $tt_id, $taxonomy, array $data ) {
365
 
366
  $term = \get_term( $term_id, $taxonomy );
367
 
@@ -407,9 +385,8 @@ class Term_Data extends Post_Data {
407
  $data = \get_term_meta( $term_id, THE_SEO_FRAMEWORK_TERM_OPTIONS, true );
408
 
409
  if ( \is_array( $data ) ) {
410
- foreach ( $this->get_term_meta_defaults( $term_id ) as $key => $value ) {
411
  unset( $data[ $key ] );
412
- }
413
  }
414
 
415
  // Only delete when no values are left, because someone else might've filtered it.
@@ -430,10 +407,8 @@ class Term_Data extends Post_Data {
430
  */
431
  public function get_latest_category_id() {
432
 
433
- static $cat_id = null;
434
-
435
- if ( null !== $cat_id )
436
- return $cat_id;
437
 
438
  $cats = \get_terms( [
439
  'taxonomy' => 'category',
@@ -444,7 +419,7 @@ class Term_Data extends Post_Data {
444
  'number' => 1,
445
  ] );
446
 
447
- return $cat_id = reset( $cats );
448
  }
449
 
450
  /**
@@ -458,12 +433,9 @@ class Term_Data extends Post_Data {
458
  * @return string The Taxonomy Type name/label, if found.
459
  */
460
  public function get_tax_type_label( $tax_type, $singular = true ) {
461
-
462
- $tto = \get_taxonomy( $tax_type );
463
-
464
- return $singular
465
- ? ( isset( $tto->labels->singular_name ) ? $tto->labels->singular_name : '' )
466
- : ( isset( $tto->labels->name ) ? $tto->labels->name : '' );
467
  }
468
 
469
  /**
@@ -479,7 +451,7 @@ class Term_Data extends Post_Data {
479
  */
480
  public function get_hierarchical_taxonomies_as( $get = 'objects', $post_type = '' ) {
481
 
482
- $post_type = $post_type ?: $this->get_post_type_real_ID();
483
 
484
  if ( ! $post_type )
485
  return [];
60
  * Returns the term meta item by key.
61
  *
62
  * @since 4.0.0
63
+ * @since 4.2.0 No longer accidentally returns an empty array on failure.
64
  *
65
  * @param string $item The item to get.
66
  * @param int $term_id The Term ID.
68
  * @return mixed The term meta item. Null when not found.
69
  */
70
  public function get_term_meta_item( $item, $term_id = 0, $use_cache = true ) {
71
+ return $this->get_term_meta( $term_id ?: $this->get_the_real_ID(), $use_cache )[ $item ] ?? null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  }
73
 
74
  /**
85
  * 2. Now fills in defaults.
86
  * @since 4.1.4 1. Removed deprecated filter.
87
  * 2. Now considers headlessness.
88
+ * @since 4.2.0 Now returns an empty array when the term's taxonomy isn't supported.
89
  *
90
  * @param int $term_id The Term ID.
91
  * @param bool $use_cache Whether to use caching.
93
  */
94
  public function get_term_meta( $term_id, $use_cache = true ) {
95
 
96
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
97
+ if ( $use_cache && ( $memo = memo( null, $term_id ) ) ) return $memo;
98
 
99
+ $term = \get_term( $term_id );
100
+
101
+ // We test taxonomy support to be consistent with `get_post_meta()`.
102
+ if ( empty( $term->term_id ) || ! $this->is_taxonomy_supported( $term->taxonomy ) ) {
103
+ // Do not overwrite cache when not requested. Otherwise, we'd have two "initial" states, causing incongruities.
104
+ return $use_cache ? memo( [], $term_id ) : [];
105
  }
106
 
107
  /**
110
  */
111
  $defaults = array_merge(
112
  $this->get_unfiltered_term_meta_defaults(),
113
+ $this->get_term_meta_defaults( $term->term_id )
114
  );
115
 
116
  if ( $this->is_headless['meta'] ) {
117
  $meta = [];
118
  } else {
119
+ // Unlike get_post_meta(), we need not filter here.
120
+ // See: <https://github.com/sybrew/the-seo-framework/issues/185>
121
+ $meta = \get_term_meta( $term->term_id, THE_SEO_FRAMEWORK_TERM_OPTIONS, true ) ?: [];
122
  }
123
 
124
  /**
134
  'the_seo_framework_term_meta',
135
  [
136
  array_merge( $defaults, $meta ),
137
+ $term->term_id,
138
  $this->is_headless['meta'],
139
  ]
140
  );
141
 
142
+ // Cache using $term_id, not $term->term_id, otherwise invalid queries can bypass the cache.
143
+ // Do not overwrite cache when not requested. Otherwise, we'd have two "initial" states, causing incongruities.
144
+ return $use_cache ? memo( $meta, $term_id ) : $meta;
145
  }
146
 
147
  /**
149
  *
150
  * @since 2.7.0
151
  * @since 3.1.0 This is now always used.
152
+ * @since 4.0.0 1. Added $term_id parameter.
153
+ * 2. Added 'redirect' value.
154
+ * 3. Added 'title_no_blog_name' value.
155
+ * 4. Removed 'saved_flag' value.
156
  *
157
  * @param int $term_id The term ID.
158
  * @return array The Term Metadata default options.
177
  *
178
  * @since 4.0.0
179
  *
180
+ * @return array The default, unfiltered, term meta.
181
  */
182
  protected function get_unfiltered_term_meta_defaults() {
183
  return [
202
  * Sanitizes and saves term meta data when a term is altered.
203
  *
204
  * @since 2.7.0
205
+ * @since 4.0.0 1. Renamed from `update_term_meta`
206
+ * 2. noindex, nofollow, noarchive are now converted to qubits.
207
+ * 3. Added new keys to sanitize.
208
+ * 4. Now marked as private.
209
+ * 5. Added more sanity protection.
210
+ * 6. No longer runs when no `autodescription-meta` POST data is sent.
211
+ * 7. Now uses the current term meta to set new values.
212
+ * 8. No longer deletes meta from abstracting plugins on save when they're deactivated.
213
+ * 9. Now allows updating during `WP_AJAX`.
214
  * @securitycheck 3.0.0 OK.
215
  * @access private
216
  * Use $this->save_term_meta() instead.
235
  * Overwrites all of the term meta on term-edit.
236
  *
237
  * @since 4.0.0
238
+ * @since 4.0.2 1. Now tests for valid term ID in the term object.
239
+ * 2. Now continues using the filtered term object.
240
  *
241
  * @param int $term_id Term ID.
242
  * @param int $tt_id Term taxonomy ID.
254
  // Note, however: function wp_update_term() already performs all these checks for us before firing this callback's action.
255
  if ( ! \current_user_can( 'edit_term', $term->term_id ) ) return;
256
  if ( ! isset( $_POST['_wpnonce'] ) ) return;
257
+ if ( ! \wp_verify_nonce( $_POST['_wpnonce'], "update-tag_{$term->term_id}" ) ) return;
258
 
259
  $data = (array) $_POST['autodescription-meta'];
260
 
266
  * Overwrites a part of the term meta on quick-edit.
267
  *
268
  * @since 4.0.0
269
+ * @since 4.0.2 1. Now tests for valid term ID in the term object.
270
+ * 2. Now continues using the filtered term object.
271
  *
272
  * @param int $term_id Term ID.
273
  * @param int $tt_id Term taxonomy ID.
304
  * as it reprocesses all term meta.
305
  *
306
  * @since 4.0.0
307
+ * @since 4.0.2 1. Now tests for valid term ID in the term object.
308
+ * 2. Now continues using the filtered term object.
309
  * @uses $this->save_term_meta() to process all data.
310
  *
311
  * @param string $item The item to update.
331
  * Updates term meta from input.
332
  *
333
  * @since 4.0.0
334
+ * @since 4.0.2 1. Now tests for valid term ID in the term object.
335
+ * 2. Now continues using the filtered term object.
336
  *
337
  * @param int $term_id Term ID.
338
  * @param int $tt_id Term Taxonomy ID.
339
  * @param string $taxonomy Taxonomy slug.
340
  * @param array $data The data to save.
341
  */
342
+ public function save_term_meta( $term_id, $tt_id, $taxonomy, $data ) {
343
 
344
  $term = \get_term( $term_id, $taxonomy );
345
 
385
  $data = \get_term_meta( $term_id, THE_SEO_FRAMEWORK_TERM_OPTIONS, true );
386
 
387
  if ( \is_array( $data ) ) {
388
+ foreach ( $this->get_term_meta_defaults( $term_id ) as $key => $value )
389
  unset( $data[ $key ] );
 
390
  }
391
 
392
  // Only delete when no values are left, because someone else might've filtered it.
407
  */
408
  public function get_latest_category_id() {
409
 
410
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
411
+ if ( null !== $memo = memo() ) return $memo;
 
 
412
 
413
  $cats = \get_terms( [
414
  'taxonomy' => 'category',
419
  'number' => 1,
420
  ] );
421
 
422
+ return memo( reset( $cats ) );
423
  }
424
 
425
  /**
433
  * @return string The Taxonomy Type name/label, if found.
434
  */
435
  public function get_tax_type_label( $tax_type, $singular = true ) {
436
+ return \get_taxonomy( $tax_type )->labels->{
437
+ $singular ? 'singular_name' : 'name'
438
+ } ?? '';
 
 
 
439
  }
440
 
441
  /**
451
  */
452
  public function get_hierarchical_taxonomies_as( $get = 'objects', $post_type = '' ) {
453
 
454
+ $post_type = $post_type ?: $this->get_current_post_type();
455
 
456
  if ( ! $post_type )
457
  return [];
inc/classes/user-data.class.php CHANGED
@@ -55,14 +55,7 @@ class User_Data extends Term_Data {
55
  * @return mixed The user meta item. Null when not found.
56
  */
57
  public function get_user_meta_item( $item, $user_id = 0, $use_cache = true ) {
58
-
59
- if ( ! $user_id ) {
60
- $meta = $this->get_user_meta( $this->get_user_id(), $use_cache );
61
- } else {
62
- $meta = $this->get_user_meta( $user_id, $use_cache );
63
- }
64
-
65
- return isset( $meta[ $item ] ) ? $meta[ $item ] : null;
66
  }
67
 
68
  /**
@@ -83,12 +76,12 @@ class User_Data extends Term_Data {
83
  * Memoizes the return value for the current request.
84
  *
85
  * @since 4.1.4
 
86
  *
87
  * @return array The current author meta.
88
  */
89
  public function get_current_post_author_meta() {
90
- static $cache;
91
- return isset( $cache ) ? $cache : $cache = $this->get_user_meta( $this->get_current_post_author_id() );
92
  }
93
 
94
  /**
@@ -99,7 +92,8 @@ class User_Data extends Term_Data {
99
  * @since 2.8.0 Always returns array, even if no value is assigned.
100
  * @since 4.1.4 1. Now returns default values when custom values are missing.
101
  * 2. Now listens to headlessness.
102
- * 3. Deprecated the second argument, and moved it to the second.
 
103
  *
104
  * @param int $user_id The user ID.
105
  * @param bool $use_cache Whether to store and use options from cache, or bypass it.
@@ -112,12 +106,9 @@ class User_Data extends Term_Data {
112
 
113
  $user_id = $user_id ?: $this->get_user_id();
114
 
115
- if ( $use_cache ) {
116
- static $cache = [];
117
-
118
- if ( isset( $cache[ $user_id ] ) )
119
- return $cache[ $user_id ];
120
- }
121
 
122
  /**
123
  * We can't trust the filter to always contain the expected keys.
@@ -165,7 +156,8 @@ class User_Data extends Term_Data {
165
  ]
166
  );
167
 
168
- return $cache[ $user_id ] = $meta;
 
169
  }
170
 
171
  /**
@@ -210,6 +202,7 @@ class User_Data extends Term_Data {
210
  * Saves user profile fields.
211
  *
212
  * @since 4.1.4
 
213
  * @access private
214
  *
215
  * @param int $user_id The user ID.
@@ -218,20 +211,23 @@ class User_Data extends Term_Data {
218
 
219
  if ( empty( $_POST ) ) return;
220
 
221
- \check_admin_referer( 'update-user_' . $user_id );
222
  if ( ! \current_user_can( 'edit_user', $user_id ) ) return;
223
 
224
  $user = \get_userdata( $user_id );
225
 
226
  if ( ! $user->has_cap( THE_SEO_FRAMEWORK_AUTHOR_INFO_CAP ) ) return;
227
 
228
- $data = (array) $_POST['tsf-user-meta'];
 
 
 
229
 
230
  $this->save_user_meta( $user_id, $data );
231
  }
232
 
233
  /**
234
- * Updates user SEO option.
235
  *
236
  * @since 4.1.4
237
  *
@@ -256,20 +252,18 @@ class User_Data extends Term_Data {
256
  * Updates users meta from input.
257
  *
258
  * @since 4.1.4
 
259
  *
260
  * @param int $user_id The user ID.
261
  * @param array $data The data to save.
262
  */
263
- public function save_user_meta( $user_id, array $data ) {
264
 
265
  $user = \get_userdata( $user_id );
266
 
267
  // We could test for !$user, but this is more to the point.
268
  if ( empty( $user->ID ) ) return;
269
 
270
- $data = (array) \wp_parse_args( $data, $this->get_user_meta_defaults( $user->ID ) );
271
- $data = $this->s_user_meta( $data );
272
-
273
  /**
274
  * @since 4.1.4
275
  * @param array $data The data that's going to be saved.
@@ -278,12 +272,15 @@ class User_Data extends Term_Data {
278
  $data = (array) \apply_filters_ref_array(
279
  'the_seo_framework_save_user_data',
280
  [
281
- $data,
 
 
 
282
  $user->ID,
283
  ]
284
  );
285
 
286
- return \update_user_meta( $user->ID, THE_SEO_FRAMEWORK_USER_OPTIONS, $data );
287
  }
288
 
289
  /**
@@ -291,23 +288,17 @@ class User_Data extends Term_Data {
291
  * Memoizes the return value for the current request.
292
  *
293
  * @since 3.0.0
294
- * @since 3.2.2 : 1. Now no longer returns the latest post author ID on home-as-blog pages.
295
- * 2. Now always returns an integer.
296
  *
297
  * @return int Post author ID on success, 0 on failure.
298
  */
299
  public function get_current_post_author_id() {
300
-
301
- static $cache;
302
-
303
- if ( isset( $cache ) ) return $cache;
304
-
305
- if ( $this->is_singular() ) {
306
- $post = \get_post( $this->get_the_real_ID() );
307
- $cache = isset( $post->post_author ) ? (int) $post->post_author : 0;
308
- }
309
-
310
- return $cache ?: ( $cache = 0 );
311
  }
312
 
313
  /**
@@ -320,14 +311,12 @@ class User_Data extends Term_Data {
320
  */
321
  public function get_user_id() {
322
 
323
- static $user_id = null;
324
-
325
- if ( isset( $user_id ) )
326
- return $user_id;
327
 
328
  $user = \wp_get_current_user();
329
 
330
- return $user_id = $user->exists() ? (int) $user->ID : 0;
331
  }
332
 
333
  /**
55
  * @return mixed The user meta item. Null when not found.
56
  */
57
  public function get_user_meta_item( $item, $user_id = 0, $use_cache = true ) {
58
+ return $this->get_user_meta( $user_id ?: $this->get_user_id(), $use_cache )[ $item ] ?? null;
 
 
 
 
 
 
 
59
  }
60
 
61
  /**
76
  * Memoizes the return value for the current request.
77
  *
78
  * @since 4.1.4
79
+ * @TODO Throw this away? We do not use it... never have.
80
  *
81
  * @return array The current author meta.
82
  */
83
  public function get_current_post_author_meta() {
84
+ return memo() ?? memo( $this->get_user_meta( $this->get_current_post_author_id() ) );
 
85
  }
86
 
87
  /**
92
  * @since 2.8.0 Always returns array, even if no value is assigned.
93
  * @since 4.1.4 1. Now returns default values when custom values are missing.
94
  * 2. Now listens to headlessness.
95
+ * 3. Deprecated the third argument, and moved it to the second.
96
+ * @todo Send deprecation warning for 3rd parameter
97
  *
98
  * @param int $user_id The user ID.
99
  * @param bool $use_cache Whether to store and use options from cache, or bypass it.
106
 
107
  $user_id = $user_id ?: $this->get_user_id();
108
 
109
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
110
+ if ( $use_cache && null !== $memo = memo( null, $user_id ) )
111
+ return $memo;
 
 
 
112
 
113
  /**
114
  * We can't trust the filter to always contain the expected keys.
156
  ]
157
  );
158
 
159
+ // Do not overwrite cache when not requested. Otherwise, we'd have two "initial" states, causing incongruities.
160
+ return $use_cache ? memo( $meta, $user_id ) : $meta;
161
  }
162
 
163
  /**
202
  * Saves user profile fields.
203
  *
204
  * @since 4.1.4
205
+ * @since 4.2.0 Now repopulates not-posted user metadata.
206
  * @access private
207
  *
208
  * @param int $user_id The user ID.
211
 
212
  if ( empty( $_POST ) ) return;
213
 
214
+ \check_admin_referer( "update-user_{$user_id}" );
215
  if ( ! \current_user_can( 'edit_user', $user_id ) ) return;
216
 
217
  $user = \get_userdata( $user_id );
218
 
219
  if ( ! $user->has_cap( THE_SEO_FRAMEWORK_AUTHOR_INFO_CAP ) ) return;
220
 
221
+ $data = \wp_parse_args(
222
+ (array) $_POST['tsf-user-meta'],
223
+ $this->get_user_meta( $user_id )
224
+ );
225
 
226
  $this->save_user_meta( $user_id, $data );
227
  }
228
 
229
  /**
230
+ * Updates user TSF-meta option.
231
  *
232
  * @since 4.1.4
233
  *
252
  * Updates users meta from input.
253
  *
254
  * @since 4.1.4
255
+ * @since 4.2.0 No longer returns the update success state.
256
  *
257
  * @param int $user_id The user ID.
258
  * @param array $data The data to save.
259
  */
260
+ public function save_user_meta( $user_id, $data ) {
261
 
262
  $user = \get_userdata( $user_id );
263
 
264
  // We could test for !$user, but this is more to the point.
265
  if ( empty( $user->ID ) ) return;
266
 
 
 
 
267
  /**
268
  * @since 4.1.4
269
  * @param array $data The data that's going to be saved.
272
  $data = (array) \apply_filters_ref_array(
273
  'the_seo_framework_save_user_data',
274
  [
275
+ $this->s_user_meta( (array) \wp_parse_args(
276
+ $data,
277
+ $this->get_user_meta_defaults( $user->ID )
278
+ ) ),
279
  $user->ID,
280
  ]
281
  );
282
 
283
+ \update_user_meta( $user->ID, THE_SEO_FRAMEWORK_USER_OPTIONS, $data );
284
  }
285
 
286
  /**
288
  * Memoizes the return value for the current request.
289
  *
290
  * @since 3.0.0
291
+ * @since 3.2.2 1. Now no longer returns the latest post author ID on home-as-blog pages.
292
+ * 2. Now always returns an integer.
293
  *
294
  * @return int Post author ID on success, 0 on failure.
295
  */
296
  public function get_current_post_author_id() {
297
+ return memo() ?? memo(
298
+ $this->is_singular()
299
+ ? (int) ( \get_post( $this->get_the_real_ID() )->post_author ?? 0 )
300
+ : 0
301
+ );
 
 
 
 
 
 
302
  }
303
 
304
  /**
311
  */
312
  public function get_user_id() {
313
 
314
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
315
+ if ( null !== $memo = memo() ) return $memo;
 
 
316
 
317
  $user = \wp_get_current_user();
318
 
319
+ return memo( $user->exists() ? (int) $user->ID : 0 );
320
  }
321
 
322
  /**
inc/compat/plugin-bbpress.php CHANGED
@@ -6,7 +6,7 @@
6
 
7
  namespace The_SEO_Framework;
8
 
9
- \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and \the_seo_framework()->_verify_include_secret( $_secret ) or die;
10
 
11
  /**
12
  * Override wp_title's bbPress title with the one generated by The SEO Framework.
@@ -254,7 +254,7 @@ function _bbpress_filter_pre_title( $title = '', $args = null ) {
254
  if ( null === $args && \is_bbpress() ) {
255
  if ( \bbp_is_topic_tag() ) {
256
  $term = \get_queried_object();
257
- $title = isset( $term->name ) ? $term->name : \the_seo_framework()->get_static_untitled_title();
258
  }
259
  }
260
 
@@ -278,7 +278,7 @@ function _bbpress_filter_pre_title( $title = '', $args = null ) {
278
  *
279
  * @param string $excerpt The excerpt to use.
280
  * @param int $page_id Deprecated.
281
- * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
282
  * Is null when query is autodetermined.
283
  * @return string The excerpt.
284
  */
@@ -290,7 +290,7 @@ function _bbpress_filter_excerpt_generation( $excerpt = '', $page_id = 0, $args
290
  $description = $term->description ?: '';
291
 
292
  // Always overwrite, even when none is found.
293
- $excerpt = \the_seo_framework()->s_description_raw( $description );
294
  }
295
  }
296
 
@@ -310,7 +310,7 @@ function _bbpress_filter_excerpt_generation( $excerpt = '', $page_id = 0, $args
310
  * @access private
311
  *
312
  * @param string $desc The custom-field description.
313
- * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
314
  * Is null when query is autodetermined.
315
  * @return string The custom description.
316
  */
@@ -318,7 +318,7 @@ function _bbpress_filter_custom_field_description( $desc = '', $args = null ) {
318
 
319
  if ( null === $args && \is_bbpress() ) {
320
  if ( \bbp_is_topic_tag() ) {
321
- $data = \the_seo_framework()->get_term_meta( \get_queried_object_id() );
322
  if ( ! empty( $data['description'] ) ) {
323
  $desc = $data['description'];
324
  } else {
6
 
7
  namespace The_SEO_Framework;
8
 
9
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and \tsf()->_verify_include_secret( $_secret ) or die;
10
 
11
  /**
12
  * Override wp_title's bbPress title with the one generated by The SEO Framework.
254
  if ( null === $args && \is_bbpress() ) {
255
  if ( \bbp_is_topic_tag() ) {
256
  $term = \get_queried_object();
257
+ $title = $term->name ?? \tsf()->get_static_untitled_title();
258
  }
259
  }
260
 
278
  *
279
  * @param string $excerpt The excerpt to use.
280
  * @param int $page_id Deprecated.
281
+ * @param array|null $args The query arguments. Contains 'id', 'taxonomy', and 'pta'.
282
  * Is null when query is autodetermined.
283
  * @return string The excerpt.
284
  */
290
  $description = $term->description ?: '';
291
 
292
  // Always overwrite, even when none is found.
293
+ $excerpt = \tsf()->s_description_raw( $description );
294
  }
295
  }
296
 
310
  * @access private
311
  *
312
  * @param string $desc The custom-field description.
313
+ * @param array|null $args The query arguments. Contains 'id', 'taxonomy', and 'pta'.
314
  * Is null when query is autodetermined.
315
  * @return string The custom description.
316
  */
318
 
319
  if ( null === $args && \is_bbpress() ) {
320
  if ( \bbp_is_topic_tag() ) {
321
+ $data = \tsf()->get_term_meta( \get_queried_object_id() );
322
  if ( ! empty( $data['description'] ) ) {
323
  $desc = $data['description'];
324
  } else {
inc/compat/plugin-buddypress.php CHANGED
@@ -6,7 +6,7 @@
6
 
7
  namespace The_SEO_Framework;
8
 
9
- \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and \the_seo_framework()->_verify_include_secret( $_secret ) or die;
10
 
11
  \add_filter( 'wp_head', __NAMESPACE__ . '\\_buddypress_init_compat', 0 );
12
  /**
@@ -18,7 +18,7 @@ namespace The_SEO_Framework;
18
  */
19
  function _buddypress_init_compat() {
20
  if ( \is_buddypress() ) {
21
- //= Remove TSF canonical URL, and let BuddyPress handle it.
22
  \add_filter( 'the_seo_framework_rel_canonical_output', '__return_empty_string' );
23
  }
24
  }
6
 
7
  namespace The_SEO_Framework;
8
 
9
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and \tsf()->_verify_include_secret( $_secret ) or die;
10
 
11
  \add_filter( 'wp_head', __NAMESPACE__ . '\\_buddypress_init_compat', 0 );
12
  /**
18
  */
19
  function _buddypress_init_compat() {
20
  if ( \is_buddypress() ) {
21
+ // Remove TSF canonical URL, and let BuddyPress handle it.
22
  \add_filter( 'the_seo_framework_rel_canonical_output', '__return_empty_string' );
23
  }
24
  }
inc/compat/plugin-edd.php CHANGED
@@ -6,7 +6,7 @@
6
 
7
  namespace The_SEO_Framework;
8
 
9
- \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and \the_seo_framework()->_verify_include_secret( $_secret ) or die;
10
 
11
  \add_filter( 'the_seo_framework_is_product', __NAMESPACE__ . '\\_set_edd_is_product', 10, 2 );
12
  /**
@@ -24,7 +24,7 @@ function _set_edd_is_product( $is_product, $post ) {
24
  if ( ! $is_product ) {
25
  if ( \function_exists( 'edd_get_download' ) ) {
26
  $download = \edd_get_download(
27
- $post ? \get_post( $post ) : \the_seo_framework()->get_the_real_ID()
28
  );
29
 
30
  $is_product = ! empty( $download->ID );
@@ -48,7 +48,7 @@ function _set_edd_is_product( $is_product, $post ) {
48
  function _set_edd_is_product_admin( $is_product_admin ) {
49
 
50
  if ( ! $is_product_admin ) {
51
- $tsf = \the_seo_framework();
52
  // Checks for "is_singular_admin()" because the post type is non-hierarchical.
53
  $is_product_admin = $tsf->is_singular_admin() && 'download' === $tsf->get_admin_post_type();
54
  }
6
 
7
  namespace The_SEO_Framework;
8
 
9
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and \tsf()->_verify_include_secret( $_secret ) or die;
10
 
11
  \add_filter( 'the_seo_framework_is_product', __NAMESPACE__ . '\\_set_edd_is_product', 10, 2 );
12
  /**
24
  if ( ! $is_product ) {
25
  if ( \function_exists( 'edd_get_download' ) ) {
26
  $download = \edd_get_download(
27
+ $post ? \get_post( $post ) : \tsf()->get_the_real_ID()
28
  );
29
 
30
  $is_product = ! empty( $download->ID );
48
  function _set_edd_is_product_admin( $is_product_admin ) {
49
 
50
  if ( ! $is_product_admin ) {
51
+ $tsf = \tsf();
52
  // Checks for "is_singular_admin()" because the post type is non-hierarchical.
53
  $is_product_admin = $tsf->is_singular_admin() && 'download' === $tsf->get_admin_post_type();
54
  }
inc/compat/plugin-elementor.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Compat\Plugin\Elementor
4
+ * @subpackage The_SEO_Framework\Compatibility
5
+ */
6
+
7
+ namespace The_SEO_Framework;
8
+
9
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and \tsf()->_verify_include_secret( $_secret ) or die;
10
+
11
+ \add_filter( 'the_seo_framework_public_post_types', __NAMESPACE__ . '\\_elementor_fix_dumb_post_types' );
12
+ /**
13
+ * Does the job Elementor was sought to do by everyone back in 2016, by chiseling
14
+ * off their non-public post types purported as public.
15
+ *
16
+ * This solely affects The SEO Framework.
17
+ *
18
+ * @since 4.2.0
19
+ *
20
+ * @param string[] $post_types The list of should-be public post types.
21
+ * @return string[] The list of actual public post types.
22
+ */
23
+ function _elementor_fix_dumb_post_types( $post_types ) {
24
+ return array_diff( $post_types, [ 'e-landing-page', 'elementor_library' ] );
25
+ }
inc/compat/plugin-polylang.php CHANGED
@@ -6,7 +6,7 @@
6
 
7
  namespace The_SEO_Framework;
8
 
9
- \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and \the_seo_framework()->_verify_include_secret( $_secret ) or die;
10
 
11
  \add_filter( 'the_seo_framework_sitemap_base_path', __NAMESPACE__ . '\\_polylang_fix_sitemap_base_bath' );
12
  /**
@@ -62,7 +62,7 @@ function _polylang_set_sitemap_language() {
62
  if ( ! ( \PLL() instanceof \PLL_Frontend ) ) return;
63
 
64
  // phpcs:ignore, WordPress.Security.NonceVerification.Recommended -- Arbitrary input expected.
65
- $lang = isset( $_GET['lang'] ) ? $_GET['lang'] : '';
66
 
67
  // Language codes are user-definable: copy Polylang's filtering.
68
  // The preg_match's source: \PLL_Admin_Model::validate_lang();
@@ -88,13 +88,22 @@ function _polylang_set_sitemap_language() {
88
  \PLL()->curlang = $new_lang;
89
  }
90
 
91
- \add_action( 'the_seo_framework_sitemap_hpt_query_args', __NAMESPACE__ . '\\_polylang_sitemap_append_non_translatables' );
92
- \add_action( 'the_seo_framework_sitemap_nhpt_query_args', __NAMESPACE__ . '\\_polylang_sitemap_append_non_translatables' );
93
  /**
94
  * Appends nontranslatable post types to the sitemap query arguments.
95
  * Only appends when the default sitemap language is displayed.
96
  *
 
 
 
 
97
  * @since 4.1.2
 
 
 
 
 
98
  * @access private
99
  *
100
  * @param array $args The query arguments.
@@ -102,7 +111,7 @@ function _polylang_set_sitemap_language() {
102
  */
103
  function _polylang_sitemap_append_non_translatables( $args ) {
104
 
105
- if ( ! \the_seo_framework()->can_i_use( [
106
  'functions' => [
107
  'PLL',
108
  'pll_languages_list',
@@ -112,11 +121,12 @@ function _polylang_sitemap_append_non_translatables( $args ) {
112
 
113
  if ( ! ( \PLL() instanceof \PLL_Frontend ) ) return $args;
114
 
 
115
  $default_lang = \pll_default_language( \OBJECT );
116
 
117
- if ( ! isset( $default_lang->slug, $default_lang->term_taxonomy_id ) ) return $args;
118
 
119
- if ( \PLL()->curlang->slug === $default_lang->slug ) {
120
  $args['lang'] = ''; // Select all lang, so that Polylang doesn't affect the query below with an AND (we need OR).
121
  $args['tax_query'] = [
122
  'relation' => 'OR',
@@ -127,7 +137,7 @@ function _polylang_sitemap_append_non_translatables( $args ) {
127
  ],
128
  [
129
  'taxonomy' => 'language',
130
- 'terms' => $default_lang->term_taxonomy_id,
131
  'operator' => 'IN',
132
  ],
133
  ];
@@ -143,6 +153,7 @@ function _polylang_sitemap_append_non_translatables( $args ) {
143
  */
144
  \add_filter( 'the_seo_framework_warn_homepage_global_title', '__return_true' );
145
  \add_filter( 'the_seo_framework_warn_homepage_global_description', '__return_true' );
 
146
 
147
  \add_filter( 'the_seo_framework_title_from_custom_field', __NAMESPACE__ . '\\pll__' );
148
  \add_filter( 'the_seo_framework_title_from_generation', __NAMESPACE__ . '\\pll__' );
@@ -158,11 +169,10 @@ function _polylang_sitemap_append_non_translatables( $args ) {
158
  * @return string
159
  */
160
  function pll__( $string ) {
161
- if ( \function_exists( 'PLL' ) && \function_exists( '\\pll__' ) ) {
162
- if ( \PLL() instanceof \PLL_Frontend ) {
163
  return \pll__( $string );
164
- }
165
- }
166
  return $string;
167
  }
168
 
@@ -202,22 +212,22 @@ function _polylang_blocklist_tsf_urls( $blocklist ) {
202
  return $blocklist;
203
  }
204
 
205
- \add_filter( 'the_seo_framework_rel_canonical_output', __NAMESPACE__ . '\\_polylang_fix_home_url', 10, 2 );
206
- \add_filter( 'the_seo_framework_ogurl_output', __NAMESPACE__ . '\\_polylang_fix_home_url', 10, 2 );
207
  /**
208
  * Adds a trailing slash to whatever's deemed as the homepage URL.
209
  * This fixes user_trailingslashit() issues.
210
  *
211
  * @since 3.2.4
212
  * @since 4.1.2 Prefixed function name with _polylang.
 
213
  * @access private
214
  *
215
  * @param string $url The url to fix.
216
- * @param int $id The page or term ID.
217
  * @return string The fixed home URL.
218
  */
219
- function _polylang_fix_home_url( $url, $id ) {
220
- return \the_seo_framework()->is_front_page_by_ID( $id ) && \get_option( 'permalink_structure' ) ? \trailingslashit( $url ) : $url;
221
  }
222
 
223
  \add_action( 'the_seo_framework_delete_cache_sitemap', __NAMESPACE__ . '\\_polylang_flush_sitemap', 10, 4 );
@@ -230,7 +240,7 @@ function _polylang_fix_home_url( $url, $id ) {
230
  * @access private
231
  *
232
  * @param string $type The flush type. Comes in handy when you use a catch-all function.
233
- * @param int $id The post, page or TT ID. Defaults to the_seo_framework()->get_the_real_ID().
234
  * @param array $args Additional arguments. They can overwrite $type and $id.
235
  * @param bool $success Whether the action cleared.
236
  */
6
 
7
  namespace The_SEO_Framework;
8
 
9
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and \tsf()->_verify_include_secret( $_secret ) or die;
10
 
11
  \add_filter( 'the_seo_framework_sitemap_base_path', __NAMESPACE__ . '\\_polylang_fix_sitemap_base_bath' );
12
  /**
62
  if ( ! ( \PLL() instanceof \PLL_Frontend ) ) return;
63
 
64
  // phpcs:ignore, WordPress.Security.NonceVerification.Recommended -- Arbitrary input expected.
65
+ $lang = $_GET['lang'] ?? '';
66
 
67
  // Language codes are user-definable: copy Polylang's filtering.
68
  // The preg_match's source: \PLL_Admin_Model::validate_lang();
88
  \PLL()->curlang = $new_lang;
89
  }
90
 
91
+ \add_filter( 'the_seo_framework_sitemap_hpt_query_args', __NAMESPACE__ . '\\_polylang_sitemap_append_non_translatables' );
92
+ \add_filter( 'the_seo_framework_sitemap_nhpt_query_args', __NAMESPACE__ . '\\_polylang_sitemap_append_non_translatables' );
93
  /**
94
  * Appends nontranslatable post types to the sitemap query arguments.
95
  * Only appends when the default sitemap language is displayed.
96
  *
97
+ * TODO Should we fix this? If user unassigns a post type as translatable, previously "translated" posts are still
98
+ * found "translated" by this query. This query, however, is forwarded to WP_Query, which Polylang can filter.
99
+ * It wouldn't surprise me if they added another black/white list for that. So, my investigation stops here.
100
+ *
101
  * @since 4.1.2
102
+ * @since 4.2.0 Now relies on the term_id, instead of mixing term_taxonomy_id and term_id.
103
+ * This is unlike Polylang, which relies on term_taxonomy_id somewhat consistently; however,
104
+ * in this case we can use term_id since we're specifying the taxonomy directly.
105
+ * WordPress 4.4.0 and later also rectifies term_id/term_taxonomy_id stratification, which is
106
+ * why we couldn't find an issue whilst introducing this filter.
107
  * @access private
108
  *
109
  * @param array $args The query arguments.
111
  */
112
  function _polylang_sitemap_append_non_translatables( $args ) {
113
 
114
+ if ( ! \tsf()->can_i_use( [
115
  'functions' => [
116
  'PLL',
117
  'pll_languages_list',
121
 
122
  if ( ! ( \PLL() instanceof \PLL_Frontend ) ) return $args;
123
 
124
+ // Redundantly prefixed \, OBJECT is actually a constant; however, the linter derps out, thinking it's a cast.
125
  $default_lang = \pll_default_language( \OBJECT );
126
 
127
+ if ( ! isset( $default_lang->slug, $default_lang->term_id ) ) return $args;
128
 
129
+ if ( ( \PLL()->curlang->slug ?? null ) === $default_lang->slug ) {
130
  $args['lang'] = ''; // Select all lang, so that Polylang doesn't affect the query below with an AND (we need OR).
131
  $args['tax_query'] = [
132
  'relation' => 'OR',
137
  ],
138
  [
139
  'taxonomy' => 'language',
140
+ 'terms' => $default_lang->term_id,
141
  'operator' => 'IN',
142
  ],
143
  ];
153
  */
154
  \add_filter( 'the_seo_framework_warn_homepage_global_title', '__return_true' );
155
  \add_filter( 'the_seo_framework_warn_homepage_global_description', '__return_true' );
156
+ \add_filter( 'the_seo_framework_tell_multilingual_sitemap', '__return_true' );
157
 
158
  \add_filter( 'the_seo_framework_title_from_custom_field', __NAMESPACE__ . '\\pll__' );
159
  \add_filter( 'the_seo_framework_title_from_generation', __NAMESPACE__ . '\\pll__' );
169
  * @return string
170
  */
171
  function pll__( $string ) {
172
+ if ( \function_exists( 'PLL' ) && \function_exists( '\\pll__' ) )
173
+ if ( \PLL() instanceof \PLL_Frontend )
174
  return \pll__( $string );
175
+
 
176
  return $string;
177
  }
178
 
212
  return $blocklist;
213
  }
214
 
215
+ \add_filter( 'the_seo_framework_rel_canonical_output', __NAMESPACE__ . '\\_polylang_fix_home_url', 10, 1 );
216
+ \add_filter( 'the_seo_framework_ogurl_output', __NAMESPACE__ . '\\_polylang_fix_home_url', 10, 1 );
217
  /**
218
  * Adds a trailing slash to whatever's deemed as the homepage URL.
219
  * This fixes user_trailingslashit() issues.
220
  *
221
  * @since 3.2.4
222
  * @since 4.1.2 Prefixed function name with _polylang.
223
+ * @since 4.2.0 No longer uses the second parameter, and relies on theq query to find the homepage, instead.
224
  * @access private
225
  *
226
  * @param string $url The url to fix.
 
227
  * @return string The fixed home URL.
228
  */
229
+ function _polylang_fix_home_url( $url ) {
230
+ return \tsf()->is_real_front_page() && \get_option( 'permalink_structure' ) ? \trailingslashit( $url ) : $url;
231
  }
232
 
233
  \add_action( 'the_seo_framework_delete_cache_sitemap', __NAMESPACE__ . '\\_polylang_flush_sitemap', 10, 4 );
240
  * @access private
241
  *
242
  * @param string $type The flush type. Comes in handy when you use a catch-all function.
243
+ * @param int $id The post, page or TT ID. Defaults to tsf()->get_the_real_ID().
244
  * @param array $args Additional arguments. They can overwrite $type and $id.
245
  * @param bool $success Whether the action cleared.
246
  */
inc/compat/plugin-ultimatemember.php CHANGED
@@ -6,122 +6,59 @@
6
 
7
  namespace The_SEO_Framework;
8
 
9
- \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and \the_seo_framework()->_verify_include_secret( $_secret ) or die;
10
 
 
 
11
  /**
12
- * Removes extraneous (therefore erroneous) Open Graph and meta functionality of Ultimate Member.
13
- * We're replacing it, using their API.
14
- */
15
- \remove_action( 'wp_head', 'um_profile_dynamic_meta_desc', 9999999 );
16
-
17
- \add_filter( 'the_seo_framework_title_from_generation', __NAMESPACE__ . '\\_um_filter_generated_title', 10, 2 );
18
- /**
19
- * Filters the custom title for UM.
20
  *
21
- * @since 3.1.0
22
- * @since 4.0.0 No longer overrules external queries.
23
  * @access private
24
- *
25
- * @param string $title The filter title.
26
- * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
27
- * Is null when query is autodetermined.
28
- * @return string The filtered title.
29
  */
30
- function _um_filter_generated_title( $title = '', $args = null ) {
31
 
32
- if ( null === $args && \the_seo_framework()->can_i_use( [
33
  'functions' => [
34
  'um_is_core_page',
35
  'um_get_requested_user',
36
- 'um_fetch_user',
37
- 'um_reset_user',
38
- 'um_user',
39
- 'um_get_display_name',
40
  ],
41
- ] ) ) {
42
- if ( \um_is_core_page( 'user' ) && \um_get_requested_user() ) {
43
- \um_fetch_user( \um_get_requested_user() );
44
- $user_id = \um_user( 'ID' );
45
- \um_reset_user();
46
 
47
- $title = \um_get_display_name( $user_id );
48
- }
 
 
49
  }
50
-
51
- return $title;
52
  }
53
 
54
- \add_filter( 'the_seo_framework_ogurl_output', __NAMESPACE__ . '\\_um_filter_generated_url', 10, 1 );
55
- \add_filter( 'the_seo_framework_rel_canonical_output', __NAMESPACE__ . '\\_um_filter_generated_url', 10, 1 );
56
  /**
57
- * Filters the canonical URL for UM.
58
  *
59
- * @since 3.1.0
60
  * @access private
61
  *
62
- * @param string $url The current URL.
63
- * @return string The filtered URL.
64
  */
65
- function _um_filter_generated_url( $url = '' ) {
66
 
67
- if ( \the_seo_framework()->can_i_use( [
68
- 'functions' => [
69
- 'um_is_core_page',
70
- 'um_get_requested_user',
71
- 'um_fetch_user',
72
- 'um_reset_user',
73
- 'um_user_profile_url',
74
- ],
75
- ] ) ) {
76
- if ( \um_is_core_page( 'user' ) && \um_get_requested_user() ) {
77
- \um_fetch_user( \um_get_requested_user() );
78
- $url = \um_user_profile_url();
79
- \um_reset_user();
80
- }
81
- }
82
 
83
- return $url;
84
- }
85
-
86
- \add_filter( 'the_seo_framework_generated_description', __NAMESPACE__ . '\\_um_filter_generated_description', 10, 2 );
87
- /**
88
- * Filters the generated description for UM.
89
- *
90
- * @since 3.1.0
91
- * @since 4.0.0 No longer overrules external queries.
92
- * @access private
93
- *
94
- * @param string $desc The generated description.
95
- * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
96
- * Is null when query is autodetermined.
97
- * @return string The filtered description.
98
- */
99
- function _um_filter_generated_description( $desc = '', $args = null ) {
100
-
101
- if ( null === $args && \the_seo_framework()->can_i_use( [
102
  'functions' => [
 
103
  'um_is_core_page',
104
- 'um_get_requested_user',
105
- 'um_fetch_user',
106
- 'um_reset_user',
107
- 'um_convert_tags',
108
- 'UM',
109
  ],
110
- ] ) ) {
111
- if ( \um_is_core_page( 'user' ) && \um_get_requested_user() ) {
112
- \um_fetch_user( \um_get_requested_user() );
113
-
114
- //!! PHP 7 won't fail on the exception. On other versions, an is_callable() loop is too expensive.
115
- //? However, their deprecated "um_get_option()" short API function tells us to use this.
116
- try {
117
- $_description = \um_convert_tags( \UM()->options()->get( 'profile_desc' ) );
118
- } catch ( \Exception $e ) {
119
- $_description = '';
120
- }
121
- $desc = $_description ?: $desc;
122
- \um_reset_user();
123
- }
124
- }
125
 
126
- return $desc;
 
 
 
 
 
 
127
  }
6
 
7
  namespace The_SEO_Framework;
8
 
9
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and \tsf()->_verify_include_secret( $_secret ) or die;
10
 
11
+ // At 9999 the user query should be registered (um\core\Rewrite::locate_user_profile). So, we use 9999+1 = 100000.
12
+ \add_action( 'template_redirect', __NAMESPACE__ . '\\_um_reinstate_title_support', 100000 );
13
  /**
14
+ * Reinstates title support if a UM-controlled profile page is detected.
 
 
 
 
 
 
 
15
  *
16
+ * @since 4.2.0
 
17
  * @access private
 
 
 
 
 
18
  */
19
+ function _um_reinstate_title_support() {
20
 
21
+ if ( ! \tsf()->can_i_use( [
22
  'functions' => [
23
  'um_is_core_page',
24
  'um_get_requested_user',
 
 
 
 
25
  ],
26
+ ] ) ) return;
 
 
 
 
27
 
28
+ if ( \um_is_core_page( 'user' ) && \um_get_requested_user() ) {
29
+ // This number has nothing to do with the reasoning hereinbefore -- merely to reflect their API.
30
+ \add_filter( 'wp_title', 'um_dynamic_user_profile_pagetitle', 100000, 2 );
31
+ \add_filter( 'pre_get_document_title', 'um_dynamic_user_profile_pagetitle', 100000, 2 );
32
  }
 
 
33
  }
34
 
35
+ \add_filter( 'the_seo_framework_query_supports_seo', __NAMESPACE__ . '\\_um_determine_support' );
 
36
  /**
37
+ * Filters query support on UM pages.
38
  *
39
+ * @since 4.2.0
40
  * @access private
41
  *
42
+ * @param bool $supported Whether the query supports SEO.
43
+ * @return string The filtered title.
44
  */
45
+ function _um_determine_support( $supported = true ) {
46
 
47
+ // No need to modify support if it's already not supported.
48
+ if ( ! $supported ) return $supported;
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
+ if ( ! \tsf()->can_i_use( [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  'functions' => [
52
+ 'um_queried_user',
53
  'um_is_core_page',
 
 
 
 
 
54
  ],
55
+ ] ) ) return $supported;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
+ /**
58
+ * We do not test for 'um_get_requested_user()' -- but this is safe.
59
+ * If `um_queried_user() && um_is_core_page( 'user' ) is true, UM forces um_get_requested_user()
60
+ * to return something, or otherwise redirects the visitor. This means we
61
+ * can safely hand over SEO-support to Ultimate Member.
62
+ */
63
+ return ! ( \um_queried_user() && \um_is_core_page( 'user' ) );
64
  }
inc/compat/plugin-woocommerce.php CHANGED
@@ -6,7 +6,7 @@
6
 
7
  namespace The_SEO_Framework;
8
 
9
- \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and \the_seo_framework()->_verify_include_secret( $_secret ) or die;
10
 
11
  \add_action( 'woocommerce_init', __NAMESPACE__ . '\\_init_wc_compat' );
12
  /**
@@ -24,7 +24,7 @@ namespace The_SEO_Framework;
24
  function _init_wc_compat() {
25
  \add_action(
26
  'the_seo_framework_do_before_output',
27
- function() {
28
  /**
29
  * Removes TSF breadcrumbs. WooCommerce outputs theirs.
30
  */
@@ -34,7 +34,7 @@ function _init_wc_compat() {
34
  }
35
  );
36
 
37
- $tsf = \the_seo_framework();
38
 
39
  // Adjust the product link acknowledging the primary category.
40
  \add_filter( 'wc_product_post_type_link_product_cat', [ $tsf, '_adjust_post_link_category' ], 10, 3 );
@@ -51,6 +51,30 @@ function _init_wc_compat() {
51
  \remove_filter( 'wp_robots', 'wc_page_no_robots' );
52
  }
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  \add_filter( 'the_seo_framework_real_id', __NAMESPACE__ . '\\_set_real_id_wc_shop' );
55
  /**
56
  * Sets the correct shop ID on the shop page.
@@ -63,7 +87,8 @@ function _init_wc_compat() {
63
  */
64
  function _set_real_id_wc_shop( $id ) {
65
 
66
- if ( \the_seo_framework()->is_wc_shop() )
 
67
  $id = (int) \get_option( 'woocommerce_shop_page_id' );
68
 
69
  return $id;
@@ -81,7 +106,8 @@ function _set_real_id_wc_shop( $id ) {
81
  * @return bool
82
  */
83
  function _set_shop_singular_archive( $is_singular_archive, $id ) {
84
- return $is_singular_archive || \the_seo_framework()->is_wc_shop( $id );
 
85
  }
86
 
87
  \add_filter( 'the_seo_framework_is_shop', __NAMESPACE__ . '\\_set_wc_is_shop', 10, 2 );
@@ -98,19 +124,8 @@ function _set_shop_singular_archive( $is_singular_archive, $id ) {
98
  * @return bool
99
  */
100
  function _set_wc_is_shop( $is_shop, $post ) {
101
-
102
- if ( $is_shop ) return $is_shop;
103
-
104
- if ( isset( $post ) ) {
105
- $post = \get_post( $post );
106
- $id = $post ? $post->ID : 0;
107
-
108
- $is_shop = (int) \get_option( 'woocommerce_shop_page_id' ) === $id;
109
- } else {
110
- $is_shop = ! \is_admin() && \function_exists( 'is_shop' ) && \is_shop();
111
- }
112
-
113
- return $is_shop;
114
  }
115
 
116
  \add_filter( 'the_seo_framework_is_product', __NAMESPACE__ . '\\_set_wc_is_product', 10, 2 );
@@ -153,7 +168,7 @@ function _set_wc_is_product_admin( $is_product_admin ) {
153
 
154
  if ( $is_product_admin ) return $is_product_admin;
155
 
156
- $tsf = \the_seo_framework();
157
 
158
  return $tsf->is_singular_admin() && 'product' === $tsf->get_admin_post_type();
159
  }
@@ -173,9 +188,9 @@ function _set_wc_is_product_admin( $is_product_admin ) {
173
  * string 'max_image_preview', ideally be empty or 'max-image-preview:<none|standard|large>'
174
  * string 'max_video_preview', ideally be empty or 'max-video-preview:<R>=-1>'
175
  * }
176
- * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
177
- * Is null when query is autodetermined.
178
- * @param int <bit> $ignore The ignore level. {
179
  * 0 = 0b00: Ignore nothing.
180
  * 1 = 0b01: Ignore protection. (\The_SEO_Framework\ROBOTS_IGNORE_PROTECTION)
181
  * 2 = 0b10: Ignore post/term setting. (\The_SEO_Framework\ROBOTS_IGNORE_SETTINGS)
@@ -183,12 +198,12 @@ function _set_wc_is_product_admin( $is_product_admin ) {
183
  * }
184
  * @return array
185
  */
186
- function _set_wc_noindex_defaults( $meta, $args, $ignore ) {
187
 
188
  // Nothing to do here...
189
  if ( 'noindex' === $meta['noindex'] ) return $meta;
190
 
191
- $tsf = \the_seo_framework();
192
 
193
  if ( null === $args ) {
194
  if ( \is_singular() )
@@ -213,7 +228,7 @@ function _set_wc_noindex_defaults( $meta, $args, $ignore ) {
213
  if ( ! \in_array( $page_id, $page_ids, true ) ) return $meta;
214
 
215
  // Set the default.
216
- if ( $ignore & \The_SEO_Framework\ROBOTS_IGNORE_SETTINGS ) {
217
  $meta['noindex'] = 'noindex';
218
  } elseif ( 0 === $tsf->s_qubit( $tsf->get_post_meta_item( '_genesis_noindex', $page_id ) ) ) {
219
  $meta['noindex'] = 'noindex';
@@ -249,11 +264,11 @@ function _assert_wc_noindex_defaults_seo_bar( $interpreter ) {
249
 
250
  $index_item = &$interpreter::edit_seo_bar_item( 'indexing' );
251
  $index_item['status'] =
252
- 0 !== \the_seo_framework()->s_qubit(
253
- \The_SEO_Framework\Builders\SeoBar_Page::get_instance()->get_query_cache()['meta']['_genesis_noindex']
254
  )
255
- ? $interpreter::STATE_OKAY
256
- : $interpreter::STATE_UNKNOWN;
257
  $index_item['assess']['recommends'] = \__( 'WooCommerce recommends not indexing this dynamic page.', 'autodescription' );
258
  }
259
 
@@ -261,7 +276,8 @@ function _assert_wc_noindex_defaults_seo_bar( $interpreter ) {
261
  /**
262
  * Adjusts image generation parameters.
263
  *
264
- * @since 4.0.5 (introduced @ 4.0.0, renamed to prevent conflict)
 
265
  * @access private
266
  *
267
  * @param array $params : [
@@ -270,7 +286,7 @@ function _assert_wc_noindex_defaults_seo_bar( $interpreter ) {
270
  * array cbs: The callbacks to parse. Ideally be generators, so we can halt remotely.
271
  * array fallback: The callbacks to parse. Ideally be generators, so we can halt remotely.
272
  * ];
273
- * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
274
  * Is null when query is autodetermined.
275
  * @return array $params
276
  */
@@ -280,7 +296,7 @@ function _adjust_wc_image_generation_params( $params, $args ) {
280
  $is_product_category = false;
281
 
282
  if ( null === $args ) {
283
- $is_product = \the_seo_framework()->is_wc_product();
284
  $is_product_category = \function_exists( '\\is_product_category' ) && \is_product_category();
285
  } else {
286
  if ( $args['taxonomy'] ) {
@@ -288,8 +304,10 @@ function _adjust_wc_image_generation_params( $params, $args ) {
288
  $term = \get_term( $args['id'], $args['taxonomy'] );
289
  $is_product_category = $term && \is_product_category( $term );
290
  }
 
 
291
  } else {
292
- $is_product = \the_seo_framework()->is_wc_product( $args['id'] );
293
  }
294
  }
295
 
@@ -308,10 +326,11 @@ function _adjust_wc_image_generation_params( $params, $args ) {
308
  * Generates image URLs and IDs from the WooCommerce product gallary entries.
309
  *
310
  * @since 4.0.0
 
311
  * @access private
312
  * @generator
313
  *
314
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
315
  * Leave null to autodetermine query.
316
  * @param string $size The size of the image to get.
317
  * @yield array : {
@@ -321,14 +340,19 @@ function _adjust_wc_image_generation_params( $params, $args ) {
321
  */
322
  function _get_product_gallery_image_details( $args = null, $size = 'full' ) {
323
 
324
- $post_id = isset( $args['id'] ) ? $args['id'] : \the_seo_framework()->get_the_real_ID();
325
-
326
  $attachment_ids = [];
327
 
328
  if ( $post_id && \metadata_exists( 'post', $post_id, '_product_image_gallery' ) ) {
329
- $product_image_gallery = \get_post_meta( $post_id, '_product_image_gallery', true );
330
-
331
- $attachment_ids = array_map( '\\absint', array_filter( explode( ',', $product_image_gallery ) ) );
 
 
 
 
 
 
332
  }
333
 
334
  if ( $attachment_ids ) {
@@ -353,7 +377,7 @@ function _get_product_gallery_image_details( $args = null, $size = 'full' ) {
353
  * @access private
354
  * @generator
355
  *
356
- * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
357
  * Leave null to autodetermine query.
358
  * @param string $size The size of the image to get.
359
  * @yield array : {
@@ -363,8 +387,7 @@ function _get_product_gallery_image_details( $args = null, $size = 'full' ) {
363
  */
364
  function _get_product_category_thumbnail_image_details( $args = null, $size = 'full' ) {
365
 
366
- $term_id = isset( $args['id'] ) ? $args['id'] : \the_seo_framework()->get_the_real_ID();
367
-
368
  $thumbnail_id = \get_term_meta( $term_id, 'thumbnail_id', true ) ?: 0;
369
 
370
  if ( $thumbnail_id ) {
6
 
7
  namespace The_SEO_Framework;
8
 
9
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and \tsf()->_verify_include_secret( $_secret ) or die;
10
 
11
  \add_action( 'woocommerce_init', __NAMESPACE__ . '\\_init_wc_compat' );
12
  /**
24
  function _init_wc_compat() {
25
  \add_action(
26
  'the_seo_framework_do_before_output',
27
+ static function() {
28
  /**
29
  * Removes TSF breadcrumbs. WooCommerce outputs theirs.
30
  */
34
  }
35
  );
36
 
37
+ $tsf = \tsf();
38
 
39
  // Adjust the product link acknowledging the primary category.
40
  \add_filter( 'wc_product_post_type_link_product_cat', [ $tsf, '_adjust_post_link_category' ], 10, 3 );
51
  \remove_filter( 'wp_robots', 'wc_page_no_robots' );
52
  }
53
 
54
+ /**
55
+ * Sets the correct shop ID on the shop page.
56
+ *
57
+ * @since 4.2.0
58
+ * @access private
59
+ *
60
+ * @param int|WP_Post|null $post Post ID or post object.
61
+ * @return bool
62
+ */
63
+ function _is_shop( $post = null ) {
64
+
65
+ if ( isset( $post ) ) {
66
+ $id = \is_int( $post )
67
+ ? $post
68
+ : ( \get_post( $post )->ID ?? 0 );
69
+
70
+ $is_shop = (int) \get_option( 'woocommerce_shop_page_id' ) === $id;
71
+ } else {
72
+ $is_shop = ! \is_admin() && \function_exists( 'is_shop' ) && \is_shop();
73
+ }
74
+
75
+ return $is_shop;
76
+ }
77
+
78
  \add_filter( 'the_seo_framework_real_id', __NAMESPACE__ . '\\_set_real_id_wc_shop' );
79
  /**
80
  * Sets the correct shop ID on the shop page.
87
  */
88
  function _set_real_id_wc_shop( $id ) {
89
 
90
+ // phpcs:ignore, TSF.Performance.Opcodes.ShouldHaveNamespaceEscape -- local func
91
+ if ( _is_shop() )
92
  $id = (int) \get_option( 'woocommerce_shop_page_id' );
93
 
94
  return $id;
106
  * @return bool
107
  */
108
  function _set_shop_singular_archive( $is_singular_archive, $id ) {
109
+ // phpcs:ignore, TSF.Performance.Opcodes.ShouldHaveNamespaceEscape -- local func
110
+ return $is_singular_archive || _is_shop( $id );
111
  }
112
 
113
  \add_filter( 'the_seo_framework_is_shop', __NAMESPACE__ . '\\_set_wc_is_shop', 10, 2 );
124
  * @return bool
125
  */
126
  function _set_wc_is_shop( $is_shop, $post ) {
127
+ // phpcs:ignore, TSF.Performance.Opcodes.ShouldHaveNamespaceEscape -- local func
128
+ return $is_shop || _is_shop( $post );
 
 
 
 
 
 
 
 
 
 
 
129
  }
130
 
131
  \add_filter( 'the_seo_framework_is_product', __NAMESPACE__ . '\\_set_wc_is_product', 10, 2 );
168
 
169
  if ( $is_product_admin ) return $is_product_admin;
170
 
171
+ $tsf = \tsf();
172
 
173
  return $tsf->is_singular_admin() && 'product' === $tsf->get_admin_post_type();
174
  }
188
  * string 'max_image_preview', ideally be empty or 'max-image-preview:<none|standard|large>'
189
  * string 'max_video_preview', ideally be empty or 'max-video-preview:<R>=-1>'
190
  * }
191
+ * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
192
+ * Is null when query is autodetermined.
193
+ * @param int <bit> $options The generator settings. {
194
  * 0 = 0b00: Ignore nothing.
195
  * 1 = 0b01: Ignore protection. (\The_SEO_Framework\ROBOTS_IGNORE_PROTECTION)
196
  * 2 = 0b10: Ignore post/term setting. (\The_SEO_Framework\ROBOTS_IGNORE_SETTINGS)
198
  * }
199
  * @return array
200
  */
201
+ function _set_wc_noindex_defaults( $meta, $args, $options ) {
202
 
203
  // Nothing to do here...
204
  if ( 'noindex' === $meta['noindex'] ) return $meta;
205
 
206
+ $tsf = \tsf();
207
 
208
  if ( null === $args ) {
209
  if ( \is_singular() )
228
  if ( ! \in_array( $page_id, $page_ids, true ) ) return $meta;
229
 
230
  // Set the default.
231
+ if ( $options & \The_SEO_Framework\ROBOTS_IGNORE_SETTINGS ) {
232
  $meta['noindex'] = 'noindex';
233
  } elseif ( 0 === $tsf->s_qubit( $tsf->get_post_meta_item( '_genesis_noindex', $page_id ) ) ) {
234
  $meta['noindex'] = 'noindex';
264
 
265
  $index_item = &$interpreter::edit_seo_bar_item( 'indexing' );
266
  $index_item['status'] =
267
+ 0 !== \tsf()->s_qubit(
268
+ \The_SEO_Framework\Builders\SEOBar\Page::get_instance()->get_query_cache()['meta']['_genesis_noindex']
269
  )
270
+ ? $interpreter::STATE_OKAY
271
+ : $interpreter::STATE_UNKNOWN;
272
  $index_item['assess']['recommends'] = \__( 'WooCommerce recommends not indexing this dynamic page.', 'autodescription' );
273
  }
274
 
276
  /**
277
  * Adjusts image generation parameters.
278
  *
279
+ * @since 4.0.5 (introduced @ 4.0.0, renamed to prevent conflict).
280
+ * @since 4.2.0 Now supports the `$args['pta']` index.
281
  * @access private
282
  *
283
  * @param array $params : [
286
  * array cbs: The callbacks to parse. Ideally be generators, so we can halt remotely.
287
  * array fallback: The callbacks to parse. Ideally be generators, so we can halt remotely.
288
  * ];
289
+ * @param array|null $args The query arguments. Contains 'id', 'taxonomy', and 'pta'.
290
  * Is null when query is autodetermined.
291
  * @return array $params
292
  */
296
  $is_product_category = false;
297
 
298
  if ( null === $args ) {
299
+ $is_product = \tsf()->is_product();
300
  $is_product_category = \function_exists( '\\is_product_category' ) && \is_product_category();
301
  } else {
302
  if ( $args['taxonomy'] ) {
304
  $term = \get_term( $args['id'], $args['taxonomy'] );
305
  $is_product_category = $term && \is_product_category( $term );
306
  }
307
+ } elseif ( $args['pta'] ) { // phpcs:ignore, Generic.CodeAnalysis.EmptyStatement.DetectedElseif
308
+ // TODO ? Which public non-page-PTA does WC have, actually?
309
  } else {
310
+ $is_product = \tsf()->is_product( $args['id'] );
311
  }
312
  }
313
 
326
  * Generates image URLs and IDs from the WooCommerce product gallary entries.
327
  *
328
  * @since 4.0.0
329
+ * @since 4.2.0 Now supports the `$args['pta']` index.
330
  * @access private
331
  * @generator
332
  *
333
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
334
  * Leave null to autodetermine query.
335
  * @param string $size The size of the image to get.
336
  * @yield array : {
340
  */
341
  function _get_product_gallery_image_details( $args = null, $size = 'full' ) {
342
 
343
+ $post_id = $args['id'] ?? \tsf()->get_the_real_ID();
 
344
  $attachment_ids = [];
345
 
346
  if ( $post_id && \metadata_exists( 'post', $post_id, '_product_image_gallery' ) ) {
347
+ $attachment_ids = array_map(
348
+ 'absint',
349
+ array_filter(
350
+ explode(
351
+ ',',
352
+ \get_post_meta( $post_id, '_product_image_gallery', true )
353
+ )
354
+ )
355
+ );
356
  }
357
 
358
  if ( $attachment_ids ) {
377
  * @access private
378
  * @generator
379
  *
380
+ * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'.
381
  * Leave null to autodetermine query.
382
  * @param string $size The size of the image to get.
383
  * @yield array : {
387
  */
388
  function _get_product_category_thumbnail_image_details( $args = null, $size = 'full' ) {
389
 
390
+ $term_id = $args['id'] ?? \tsf()->get_the_real_ID();
 
391
  $thumbnail_id = \get_term_meta( $term_id, 'thumbnail_id', true ) ?: 0;
392
 
393
  if ( $thumbnail_id ) {
inc/compat/plugin-wpforo.php CHANGED
@@ -6,15 +6,15 @@
6
 
7
  namespace The_SEO_Framework;
8
 
9
- \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and \the_seo_framework()->_verify_include_secret( $_secret ) or die;
10
 
11
  \add_action( 'the_seo_framework_init', __NAMESPACE__ . '\\_wpforo_fix_page' );
12
  /**
13
  * Initializes wpForo page fixes.
14
  *
15
  * @since 2.9.2
16
- * @since 3.1.2 : 1. Now disables HTML output when wpForo SEO is enabled.
17
- * 2. Now disables title override when wpForo Title SEO is enabled.
18
  */
19
  function _wpforo_fix_page() {
20
 
@@ -56,7 +56,7 @@ function _wpforo_fix_page() {
56
  * @access private
57
  */
58
  function _wpforo_disable_html_output() {
59
- \remove_action( 'wp_head', [ \the_seo_framework(), 'html_output' ], 1 );
60
  }
61
 
62
  /**
@@ -91,7 +91,7 @@ function _wpforo_filter_canonical_url( $canonical_url, $post ) { // phpcs:ignore
91
  function _wpforo_filter_pre_title( $title = '', $args = null ) {
92
 
93
  if ( null === $args ) {
94
- $wpforo_title = \wpforo_meta_title( '' ); //= Either &$title or [ $title, ... ];
95
  $title = \is_array( $wpforo_title ) && ! empty( $wpforo_title[0] ) ? $wpforo_title[0] : $title;
96
  }
97
 
6
 
7
  namespace The_SEO_Framework;
8
 
9
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and \tsf()->_verify_include_secret( $_secret ) or die;
10
 
11
  \add_action( 'the_seo_framework_init', __NAMESPACE__ . '\\_wpforo_fix_page' );
12
  /**
13
  * Initializes wpForo page fixes.
14
  *
15
  * @since 2.9.2
16
+ * @since 3.1.2 1. Now disables HTML output when wpForo SEO is enabled.
17
+ * 2. Now disables title override when wpForo Title SEO is enabled.
18
  */
19
  function _wpforo_fix_page() {
20
 
56
  * @access private
57
  */
58
  function _wpforo_disable_html_output() {
59
+ \remove_action( 'wp_head', [ \tsf(), 'html_output' ], 1 );
60
  }
61
 
62
  /**
91
  function _wpforo_filter_pre_title( $title = '', $args = null ) {
92
 
93
  if ( null === $args ) {
94
+ $wpforo_title = \wpforo_meta_title( '' ); // This is either &$title or [ $title, ... ];
95
  $title = \is_array( $wpforo_title ) && ! empty( $wpforo_title[0] ) ? $wpforo_title[0] : $title;
96
  }
97
 
inc/compat/plugin-wpml.php CHANGED
@@ -6,7 +6,7 @@
6
 
7
  namespace The_SEO_Framework;
8
 
9
- \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and \the_seo_framework()->_verify_include_secret( $_secret ) or die;
10
 
11
  /**
12
  * Warns homepage global title and description about receiving input.
@@ -15,6 +15,7 @@ namespace The_SEO_Framework;
15
  */
16
  \add_filter( 'the_seo_framework_warn_homepage_global_title', '__return_true' );
17
  \add_filter( 'the_seo_framework_warn_homepage_global_description', '__return_true' );
 
18
 
19
  \add_action( 'current_screen', __NAMESPACE__ . '\\_wpml_do_current_screen_action' );
20
  /**
@@ -25,7 +26,7 @@ namespace The_SEO_Framework;
25
  */
26
  function _wpml_do_current_screen_action() {
27
 
28
- if ( \the_seo_framework()->is_seo_settings_page() ) {
29
  \add_filter( 'wpml_admin_language_switcher_items', __NAMESPACE__ . '\\_wpml_remove_all_languages' );
30
  }
31
  }
@@ -58,7 +59,7 @@ function _wpml_remove_all_languages( $languages_links = [] ) {
58
  * @access private
59
  *
60
  * @param string $type The flush type. Comes in handy when you use a catch-all function.
61
- * @param int $id The post, page or TT ID. Defaults to the_seo_framework()->get_the_real_ID().
62
  * @param array $args Additional arguments. They can overwrite $type and $id.
63
  * @param bool $success Whether the action cleared.
64
  */
@@ -122,7 +123,8 @@ function _wpml_sitemap_filter_non_translatables( $args ) {
122
  if ( empty( $sitepress )
123
  || ! method_exists( $sitepress, 'get_default_language' )
124
  || ! method_exists( $sitepress, 'get_current_language' )
125
- || ! method_exists( $sitepress, 'is_translated_post_type' ) ) return $args;
 
126
 
127
  if ( $sitepress->get_default_language() === $sitepress->get_current_language() ) return $args;
128
 
6
 
7
  namespace The_SEO_Framework;
8
 
9
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and \tsf()->_verify_include_secret( $_secret ) or die;
10
 
11
  /**
12
  * Warns homepage global title and description about receiving input.
15
  */
16
  \add_filter( 'the_seo_framework_warn_homepage_global_title', '__return_true' );
17
  \add_filter( 'the_seo_framework_warn_homepage_global_description', '__return_true' );
18
+ \add_filter( 'the_seo_framework_tell_multilingual_sitemap', '__return_true' );
19
 
20
  \add_action( 'current_screen', __NAMESPACE__ . '\\_wpml_do_current_screen_action' );
21
  /**
26
  */
27
  function _wpml_do_current_screen_action() {
28
 
29
+ if ( \tsf()->is_seo_settings_page() ) {
30
  \add_filter( 'wpml_admin_language_switcher_items', __NAMESPACE__ . '\\_wpml_remove_all_languages' );
31
  }
32
  }
59
  * @access private
60
  *
61
  * @param string $type The flush type. Comes in handy when you use a catch-all function.
62
+ * @param int $id The post, page or TT ID. Defaults to tsf()->get_the_real_ID().
63
  * @param array $args Additional arguments. They can overwrite $type and $id.
64
  * @param bool $success Whether the action cleared.
65
  */
123
  if ( empty( $sitepress )
124
  || ! method_exists( $sitepress, 'get_default_language' )
125
  || ! method_exists( $sitepress, 'get_current_language' )
126
+ || ! method_exists( $sitepress, 'is_translated_post_type' ) )
127
+ return $args;
128
 
129
  if ( $sitepress->get_default_language() === $sitepress->get_current_language() ) return $args;
130
 
inc/compat/theme-genesis.php CHANGED
@@ -6,7 +6,7 @@
6
 
7
  namespace The_SEO_Framework;
8
 
9
- \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and \the_seo_framework()->_verify_include_secret( $_secret ) or die;
10
 
11
  // Disable Genesis SEO.
12
  \add_filter( 'genesis_detect_seo_plugins', __NAMESPACE__ . '\\_disable_genesis_seo', 10, 1 );
6
 
7
  namespace The_SEO_Framework;
8
 
9
+ \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and \tsf()->_verify_include_secret( $_secret ) or die;
10
 
11
  // Disable Genesis SEO.
12
  \add_filter( 'genesis_detect_seo_plugins', __NAMESPACE__ . '\\_disable_genesis_seo', 10, 1 );
inc/functions/api.php CHANGED
@@ -29,10 +29,27 @@ namespace {
29
  * Returns the class from cache.
30
  *
31
  * This is the recommended way of calling the class, if needed.
32
- * Call this after action 'init' priority 0 otherwise it will kill the plugin,
33
- * or even other plugins.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  *
35
  * @since 2.2.5
 
36
  *
37
  * @return null|object The plugin class object.
38
  */
@@ -57,21 +74,17 @@ namespace {
57
  *
58
  * @since 2.7.0
59
  * @since 2.8.0 Added `did_action()` check.
 
60
  *
61
  * @return string|bool The SEO Framework class name. False if The SEO Framework isn't loaded (yet).
62
  */
63
  function the_seo_framework_class() {
64
 
65
- static $class = null;
66
-
67
- if ( isset( $class ) )
68
- return $class;
69
-
70
- // did_action() checks for current action too.
71
  if ( ! did_action( 'plugins_loaded' ) )
72
  return false;
73
 
74
- return $class = get_class( the_seo_framework() );
75
  }
76
  }
77
 
@@ -87,17 +100,12 @@ namespace The_SEO_Framework {
87
  * @return bool Whether to allow loading of plugin.
88
  */
89
  function _can_load() {
90
-
91
  static $load = null;
92
-
93
- if ( isset( $load ) )
94
- return $load;
95
-
96
  /**
97
  * @since 2.3.7
98
  * @param bool $load
99
  */
100
- return $load = (bool) \apply_filters( 'the_seo_framework_load', true );
101
  }
102
 
103
  /**
@@ -111,15 +119,9 @@ namespace The_SEO_Framework {
111
  * @return bool True if loaded, false otherwise.
112
  */
113
  function _load_trait( $file ) {
114
-
115
- static $loaded = [];
116
-
117
- if ( isset( $loaded[ $file ] ) )
118
- return $loaded[ $file ];
119
-
120
- $_file = str_replace( '/', DIRECTORY_SEPARATOR, $file );
121
-
122
- return $loaded[ $file ] = (bool) require THE_SEO_FRAMEWORK_DIR_PATH_TRAIT . $_file . '.trait.php';
123
  }
124
 
125
  /**
@@ -132,10 +134,8 @@ namespace The_SEO_Framework {
132
  * @return bool True if already called, false otherwise.
133
  */
134
  function _has_run( $caller ) {
135
-
136
- static $cache = [];
137
-
138
- return isset( $cache[ $caller ] ) ?: ( ( $cache[ $caller ] = true ) && false );
139
  }
140
 
141
  /**
@@ -151,4 +151,185 @@ namespace The_SEO_Framework {
151
  static $time = 0;
152
  return $time += $add;
153
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  }
29
  * Returns the class from cache.
30
  *
31
  * This is the recommended way of calling the class, if needed.
32
+ * Call this after action 'plugins_loaded' priority 5 otherwise you'll cause
33
+ * unforeseen issues.
34
+ *
35
+ * @since 4.2.0
36
+ * @see `the_seo_framework()` alias.
37
+ *
38
+ * @return null|object The plugin class object.
39
+ */
40
+ function tsf() {
41
+ return The_SEO_Framework\_init_tsf();
42
+ }
43
+
44
+ /**
45
+ * Returns the class from cache.
46
+ *
47
+ * This is the recommended way of calling the class, if needed.
48
+ * Call this after action 'plugins_loaded' priority 5 otherwise you'll cause
49
+ * unforeseen issues.
50
  *
51
  * @since 2.2.5
52
+ * @see `tsf()` alias.
53
  *
54
  * @return null|object The plugin class object.
55
  */
74
  *
75
  * @since 2.7.0
76
  * @since 2.8.0 Added `did_action()` check.
77
+ * @since 4.2.0 Removed memoization.
78
  *
79
  * @return string|bool The SEO Framework class name. False if The SEO Framework isn't loaded (yet).
80
  */
81
  function the_seo_framework_class() {
82
 
83
+ // did_action() returns true for current action match, too.
 
 
 
 
 
84
  if ( ! did_action( 'plugins_loaded' ) )
85
  return false;
86
 
87
+ return get_class( tsf() );
88
  }
89
  }
90
 
100
  * @return bool Whether to allow loading of plugin.
101
  */
102
  function _can_load() {
 
103
  static $load = null;
 
 
 
 
104
  /**
105
  * @since 2.3.7
106
  * @param bool $load
107
  */
108
+ return $load = $load ?? (bool) \apply_filters( 'the_seo_framework_load', true );
109
  }
110
 
111
  /**
119
  * @return bool True if loaded, false otherwise.
120
  */
121
  function _load_trait( $file ) {
122
+ static $loaded = [];
123
+ return $loaded[ $file ] = $loaded[ $file ]
124
+ ?? (bool) require THE_SEO_FRAMEWORK_DIR_PATH_TRAIT . str_replace( '/', DIRECTORY_SEPARATOR, $file ) . '.trait.php';
 
 
 
 
 
 
125
  }
126
 
127
  /**
134
  * @return bool True if already called, false otherwise.
135
  */
136
  function _has_run( $caller ) {
137
+ static $runners = [];
138
+ return $runners[ $caller ] ?? ! ( $runners[ $caller ] = true );
 
 
139
  }
140
 
141
  /**
151
  static $time = 0;
152
  return $time += $add;
153
  }
154
+
155
+ /**
156
+ * Stores and returns memoized values for the caller.
157
+ *
158
+ * This method is not forward-compatible with PHP: It expects values it doesn't want populated,
159
+ * instead of filtering what's actually useful for memoization. For example, it expects `file`
160
+ * and `line` from debug_backtrace() -- those are expected to be dynamic from the caller, and
161
+ * we set them to `0` to prevent a few opcode calls, rather than telling which array indexes
162
+ * we want exactly. The chance this failing in a future update is slim, for all useful data of
163
+ * the callee is given already via debug_backtrace().
164
+ * We also populate the `args` value "manually" for it's faster than using debug_backtrace()'s
165
+ * `DEBUG_BACKTRACE_PROVIDE_OBJECT` option.
166
+ *
167
+ * We should keep a tap on debug_backtrace changes. Hopefully, they allow us to ignore
168
+ * more than just args.
169
+ *
170
+ * This method does not memoize the object via debug_backtrace. This means that the
171
+ * objects will have values memoized cross-instantiations.
172
+ *
173
+ * Example usage:
174
+ * ```
175
+ * function expensive_call( $arg ) {
176
+ * print( "expensive $arg!" );
177
+ * return $arg * 2;
178
+ * }
179
+ * function my_function( $arg ) {
180
+ * return memo( null, $arg );
181
+ * ?? memo( expensive_call( $arg ), $arg );
182
+ * }
183
+ * my_function( 1 ); // prints "expensive 1!", returns 2.
184
+ * my_function( 1 ); // returns 2.
185
+ * my_function( 2 ); // prints "expensive 2!", returns 4.
186
+ *
187
+ * function test() {
188
+ * return memo() ?? memo( expensive_call( 42 ) );
189
+ * }
190
+ * test(); // prints "expensive 42", returns 84.
191
+ * test(); // returns 84.
192
+ * ```
193
+ *
194
+ * @since 4.2.0
195
+ * @see umemo() -- sacrifices cleanliness for performance.
196
+ * @see fmemo() -- sacrifices everything for readability.
197
+ *
198
+ * @param mixed $value_to_set The value to set.
199
+ * @param mixed ...$args Extra arguments, that are used to differentiaty callbacks.
200
+ * Arguments may not contain \Closure()s.
201
+ * @return mixed : {
202
+ * mixed The cached value if set and $value_to_set is null.
203
+ * null When no value has been set.
204
+ * If $value_to_set is set, the new value.
205
+ * }
206
+ */
207
+ function memo( $value_to_set = null, ...$args ) {
208
+
209
+ static $memo = [];
210
+
211
+ // phpcs:ignore, WordPress.PHP.DiscouragedPHPFunctions -- No objects inserted, nor ever unserialized.
212
+ $hash = serialize(
213
+ [
214
+ 'args' => $args,
215
+ 'file' => 0,
216
+ 'line' => 0,
217
+ ]
218
+ // phpcs:ignore, WordPress.PHP.DevelopmentFunctions -- This is the only efficient way.
219
+ + debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 2 )[1]
220
+ );
221
+
222
+ if ( isset( $value_to_set ) )
223
+ return $memo[ $hash ] = $value_to_set;
224
+
225
+ return $memo[ $hash ] ?? null;
226
+ }
227
+
228
+ /**
229
+ * Stores and returns memoized values for the caller.
230
+ * This is 10 times faster than memo(), but requires from you a $key.
231
+ *
232
+ * We're talking milliseconds over thousands of iterations, though.
233
+ *
234
+ * Example usage:
235
+ * ```
236
+ * function expensive_call( $arg ) {
237
+ * print( "expensive $arg!" );
238
+ * return $arg * 2;
239
+ * }
240
+ * function my_function( $arg ) {
241
+ * return umemo( 'something', null, $arg );
242
+ * ?? umemo( __METHOD__, expensive_call( $arg ), $arg );
243
+ * }
244
+ * my_function( 1 ); // prints "expensive 1!", returns 2.
245
+ * my_function( 1 ); // returns 2.
246
+ * my_function( 2 ); // prints "expensive 2!", returns 4.
247
+ * ```
248
+ *
249
+ * @since 4.2.0
250
+ * @see memo() -- sacrifices performance for cleanliness.
251
+ * @see fmemo() -- sacrifices everything for readability.
252
+ *
253
+ * @param string $key The key you want to use to memoize. It's best to use the method name.
254
+ * @param mixed $value_to_set The value to set.
255
+ * @param mixed ...$args Extra arguments, that are used to differentiate callbacks.
256
+ * Arguments may not contain \Closure()s.
257
+ * @return mixed : {
258
+ * mixed The cached value if set and $value_to_set is null.
259
+ * null When no value has been set.
260
+ * If $value_to_set is set, the new value.
261
+ * }
262
+ */
263
+ function umemo( $key, $value_to_set = null, ...$args ) {
264
+
265
+ static $memo = [];
266
+
267
+ // phpcs:ignore, WordPress.PHP.DiscouragedPHPFunctions -- No objects are inserted, nor is this ever unserialized.
268
+ $hash = serialize( [ $key, $args ] );
269
+
270
+ if ( isset( $value_to_set ) )
271
+ return $memo[ $hash ] = $value_to_set;
272
+
273
+ return $memo[ $hash ] ?? null;
274
+ }
275
+
276
+ /**
277
+ * Stores and returns memoized values for the Closure caller. This helps wrap
278
+ * a whole function inside a single memoization call.
279
+ *
280
+ * This method does not memoize the object via debug_backtrace. This means that the
281
+ * objects will have values memoized cross-instantiations.
282
+ *
283
+ * Example usage, PHP7.4+:
284
+ * ```
285
+ * function my_function( $arg ) { return fmemo( fn() => print( $arg ) + 5 ); }
286
+ * my_function( 1 ); // prints '1', returns 6.
287
+ * my_function( 1 ); // does not print, returns 6.
288
+ * ```
289
+ * Arrow functions are neat with this for they automatically register only necessary arguments to fmemo().
290
+ * This way, callers of my_function() won't bust the cache by sending unregistered superfluous arguments.
291
+ *
292
+ * ```
293
+ * function printer() { print( 69 ); }
294
+ * function print_once() { fmemo( 'printer' ); }
295
+ * print_once(); // 69
296
+ * print_once(); // *cricket noises*
297
+ * ```
298
+ *
299
+ * @since 4.2.0
300
+ * @see memo() -- sacrifices performance for cleanliness.
301
+ * @see umemo() -- sacrifices cleanliness for performance.
302
+ * @ignore We couldn't find a use for this... yet. Probably once we support only PHP7.4+
303
+ *
304
+ * @param \Closure $fn The Closure or function to memoize.
305
+ * @return mixed : {
306
+ * mixed The cached value if set and $value_to_set is null.
307
+ * null When no value has been set.
308
+ * If $value_to_set is set, the new value.
309
+ * }
310
+ */
311
+ function fmemo( $fn ) {
312
+
313
+ static $memo = [];
314
+
315
+ // phpcs:ignore, WordPress.PHP.DiscouragedPHPFunctions -- This is ever unserialized.
316
+ $hash = serialize(
317
+ [
318
+ 'file' => '',
319
+ 'line' => 0,
320
+ ]
321
+ // phpcs:ignore, WordPress.PHP.DevelopmentFunctions -- This is the only efficient way.
322
+ + debug_backtrace( 0, 2 )[1]
323
+ );
324
+
325
+ // If the hash is stored, return null back to the caller.
326
+ if ( isset( $memo[ $hash ] ) )
327
+ return $memo[ $hash ] === $hash ? null : $memo[ $hash ];
328
+
329
+ // Store the result of the function. If that's null/void, store hash.
330
+ $memo[ $hash ] = \call_user_func( $fn ) ?? $hash;
331
+
332
+ // If the hash is stored, return null back to the caller.
333
+ return $memo[ $hash ] === $hash ? null : $memo[ $hash ];
334
+ }
335
  }
inc/functions/upgrade-suggestion.php CHANGED
@@ -45,7 +45,6 @@ _prepare( $previous_version, $current_version );
45
  * 1. The constant 'TSF_DISABLE_SUGGESTIONS' is not defined or false.
46
  * 2. The current dashboard is the main site's.
47
  * 3. TSFEM isn't already installed.
48
- * 4. PHP and WP requirements of TSFEM are met.
49
  *
50
  * The notice is automatically dismissed after X views, and it can be ignored without reappearing.
51
  *
@@ -57,6 +56,7 @@ _prepare( $previous_version, $current_version );
57
  * 5. Now tests if the upgrade actually happened, before invoking the suggestion.
58
  * @since 4.1.2 Can now communicate with Extension Manager for the edge-case sale.
59
  * @since 4.1.3 Commented out sale notification conditions, as those can't be met anyway.
 
60
  * @access private
61
  *
62
  * @param string $previous_version The previous version the site upgraded from, if any.
@@ -72,17 +72,14 @@ function _prepare( $previous_version, $current_version ) {
72
  //? 2
73
  if ( ! \is_main_site() ) return;
74
 
75
- // phpcs:disable, Squiz.PHP.CommentedOutCode, Squiz.Commenting.InlineComment
76
- // $show_sale = true;
77
-
78
- // if ( \function_exists( '\\tsf_extension_manager' ) && method_exists( \tsf_extension_manager(), 'is_connected_user' ) ) {
79
- // $show_sale = ! \tsf_extension_manager()->is_connected_user();
80
- // }
81
- // if ( $show_sale ) {
82
- // // phpcs:ignore, TSF.Performance.Opcodes.ShouldHaveNamespaceEscape
83
- // _suggest_temp_sale( $previous_version, $current_version );
84
- // }
85
- // phpcs:enable, Squiz.PHP.CommentedOutCode, Squiz.Commenting.InlineComment
86
 
87
  //? 3a
88
  if ( \defined( 'TSF_EXTENSION_MANAGER_VERSION' ) ) return;
@@ -93,23 +90,6 @@ function _prepare( $previous_version, $current_version ) {
93
  //? 3b
94
  if ( ! empty( \get_plugins()['the-seo-framework-extension-manager/the-seo-framework-extension-manager.php'] ) ) return;
95
 
96
- /** @source https://github.com/sybrew/The-SEO-Framework-Extension-Manager/blob/08db1ab7410874c47d8f05b15479ce923857c35e/bootstrap/envtest.php#L68-L77 */
97
- // We can forgo this test, since TSF has a higher requirement. We'll probably keep the plugins in line henceforth...
98
- $requirements = [
99
- 'php' => 50605,
100
- 'wp' => '4.9-dev',
101
- ];
102
-
103
- // phpcs:disable, Generic.Formatting.MultipleStatementAlignment, WordPress.WhiteSpace.PrecisionAlignment
104
- //? PHP_VERSION_ID is definitely defined, but let's keep it homonymous with the envtest of TSFEM.
105
- ! \defined( 'PHP_VERSION_ID' ) || PHP_VERSION_ID < $requirements['php'] and $test = 1
106
- or version_compare( $GLOBALS['wp_version'], $requirements['wp'], '<' ) and $test = 2
107
- or $test = true;
108
- // phpcs:enable, Generic.Formatting.MultipleStatementAlignment, WordPress.WhiteSpace.PrecisionAlignment
109
-
110
- //? 4
111
- if ( true !== $test ) return;
112
-
113
  // phpcs:ignore, TSF.Performance.Opcodes.ShouldHaveNamespaceEscape
114
  _suggest_extension_manager( $previous_version, $current_version );
115
  }
@@ -126,7 +106,7 @@ function _prepare( $previous_version, $current_version ) {
126
  */
127
  function _suggest_extension_manager( $previous_version, $current_version ) {
128
 
129
- $tsf = \the_seo_framework();
130
 
131
  $suggest_key = 'suggest-extension-manager';
132
  $suggest_args = [
@@ -180,7 +160,7 @@ function _suggest_extension_manager( $previous_version, $current_version ) {
180
  */
181
  function _suggest_temp_sale( $previous_version, $current_version ) {
182
 
183
- $tsf = \the_seo_framework();
184
 
185
  $suggest_key = 'suggest-sale';
186
  $suggest_args = [
@@ -193,15 +173,15 @@ function _suggest_temp_sale( $previous_version, $current_version ) {
193
  'excl_screens' => [ 'update-core', 'post', 'term', 'upload', 'media', 'plugin-editor', 'plugin-install', 'themes', 'widgets', 'user', 'nav-menus', 'theme-editor', 'profile', 'export', 'site-health', 'export-personal-data', 'erase-personal-data' ],
194
  'capability' => 'install_plugins',
195
  'user' => 0,
196
- 'count' => 2,
197
- 'timeout' => strtotime( 'December 6th, 2020, 22:50GMT+1' ) - time(),
198
  ];
199
 
200
- if ( $previous_version < '4120' && $current_version < '4200' )
201
  $tsf->register_dismissible_persistent_notice(
202
  $tsf->convert_markdown(
203
  sprintf(
204
- '<p>The SEO Framework: Cyber Monday [30~50%% off](%s). This notification will self-destruct when the sale ends, or when you dismiss it.</p>',
205
  'https://theseoframework.com/?p=3527'
206
  ),
207
  [ 'a', 'em', 'strong' ],
45
  * 1. The constant 'TSF_DISABLE_SUGGESTIONS' is not defined or false.
46
  * 2. The current dashboard is the main site's.
47
  * 3. TSFEM isn't already installed.
 
48
  *
49
  * The notice is automatically dismissed after X views, and it can be ignored without reappearing.
50
  *
56
  * 5. Now tests if the upgrade actually happened, before invoking the suggestion.
57
  * @since 4.1.2 Can now communicate with Extension Manager for the edge-case sale.
58
  * @since 4.1.3 Commented out sale notification conditions, as those can't be met anyway.
59
+ * @since 4.2.1 No longer tests WP and PHP requirements for Extension Manager.
60
  * @access private
61
  *
62
  * @param string $previous_version The previous version the site upgraded from, if any.
72
  //? 2
73
  if ( ! \is_main_site() ) return;
74
 
75
+ $show_sale = true;
76
+ if ( \function_exists( '\\tsf_extension_manager' ) && method_exists( \tsf_extension_manager(), 'is_connected_user' ) ) {
77
+ $show_sale = ! \tsf_extension_manager()->is_connected_user();
78
+ }
79
+ if ( $show_sale ) {
80
+ // phpcs:ignore, TSF.Performance.Opcodes.ShouldHaveNamespaceEscape
81
+ _suggest_temp_sale( $previous_version, $current_version );
82
+ }
 
 
 
83
 
84
  //? 3a
85
  if ( \defined( 'TSF_EXTENSION_MANAGER_VERSION' ) ) return;
90
  //? 3b
91
  if ( ! empty( \get_plugins()['the-seo-framework-extension-manager/the-seo-framework-extension-manager.php'] ) ) return;
92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  // phpcs:ignore, TSF.Performance.Opcodes.ShouldHaveNamespaceEscape
94
  _suggest_extension_manager( $previous_version, $current_version );
95
  }
106
  */
107
  function _suggest_extension_manager( $previous_version, $current_version ) {
108
 
109
+ $tsf = \tsf();
110
 
111
  $suggest_key = 'suggest-extension-manager';
112
  $suggest_args = [
160
  */
161
  function _suggest_temp_sale( $previous_version, $current_version ) {
162
 
163
+ $tsf = \tsf();
164
 
165
  $suggest_key = 'suggest-sale';
166
  $suggest_args = [
173
  'excl_screens' => [ 'update-core', 'post', 'term', 'upload', 'media', 'plugin-editor', 'plugin-install', 'themes', 'widgets', 'user', 'nav-menus', 'theme-editor', 'profile', 'export', 'site-health', 'export-personal-data', 'erase-personal-data' ],
174
  'capability' => 'install_plugins',
175
  'user' => 0,
176
+ 'count' => 3,
177
+ 'timeout' => strtotime( 'December 6th, 2021, 22:50GMT+1' ) - time(),
178
  ];
179
 
180
+ if ( $previous_version < '4200' && $current_version < '4201' )
181
  $tsf->register_dismissible_persistent_notice(
182
  $tsf->convert_markdown(
183
  sprintf(
184
+ '<p>The SEO Framework: [Cyber Sale &ndash; 60%% off](%s). This notification will self-destruct when the sale ends, or when you dismiss it.</p>',
185
  'https://theseoframework.com/?p=3527'
186
  ),
187
  [ 'a', 'em', 'strong' ],
inc/views/admin/wrap-content.php DELETED
@@ -1,60 +0,0 @@
1
- <?php
2
- /**
3
- * @package The_SEO_Framework\Views\Admin
4
- * @subpackage The_SEO_Framework\Admin\Settings
5
- */
6
-
7
- // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- includes.
8
- // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
9
-
10
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
11
-
12
- // Whether tabs are active.
13
- $use_tabs = $use_tabs && count( $tabs ) > 1;
14
- $count = 1;
15
-
16
- /**
17
- * Start Content.
18
- *
19
- * The content is relative to the navigation and outputs navigational tabs too, but uses CSS to become invisible on JS.
20
- */
21
- foreach ( $tabs as $tab => $value ) :
22
-
23
- $the_id = 'tsf-' . $id . '-tab-' . $tab . '-content';
24
- $the_name = 'tsf-' . $id . '-tabs-content';
25
-
26
- // Current tab for JS.
27
- $current = 1 === $count ? ' tsf-active-tab-content' : '';
28
-
29
- ?>
30
- <div class="tsf-tabs-content <?php echo esc_attr( $the_name . $current ); ?>" id="<?php echo esc_attr( $the_id ); ?>" >
31
- <?php
32
- // No-JS tabs.
33
- if ( $use_tabs ) :
34
- $dashicon = isset( $value['dashicon'] ) ? $value['dashicon'] : '';
35
- $name = isset( $value['name'] ) ? $value['name'] : '';
36
-
37
- ?>
38
- <div class="hide-if-tsf-js tsf-content-no-js">
39
- <div class="tsf-tab tsf-tab-no-js">
40
- <span class="tsf-nav-tab tsf-active-tab">
41
- <?php echo $dashicon ? '<span class="dashicons dashicons-' . esc_attr( $dashicon ) . ' tsf-dashicons-tabs"></span>' : ''; ?>
42
- <?php echo $name ? '<span>' . esc_attr( $name ) . '</span>' : ''; ?>
43
- </span>
44
- </div>
45
- </div>
46
- <?php
47
- endif;
48
-
49
- $callback = isset( $value['callback'] ) ? $value['callback'] : '';
50
-
51
- if ( $callback ) {
52
- $params = isset( $value['args'] ) ? [ $value['args'] ] : [];
53
- call_user_func_array( $callback, $params );
54
- }
55
- ?>
56
- </div>
57
- <?php
58
-
59
- $count++;
60
- endforeach;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/views/debug/output.php CHANGED
@@ -7,27 +7,32 @@
7
  // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- includes.
8
  // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
9
 
10
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
11
 
12
- $id = $this->get_the_real_ID();
13
- $mdash = ' &mdash; ';
14
- $taxonomy = $this->get_current_taxonomy();
 
 
 
15
 
16
  // This will return 'Page' on all non-archive types (except the homepage)
17
- if ( ! $this->is_archive() && $this->is_real_front_page() || $this->is_front_page_by_id( $id ) ) {
18
  $type = 'Front Page';
19
  } elseif ( $taxonomy ) {
20
  $type = $this->get_tax_type_label( $taxonomy );
 
 
21
  } else {
22
- $type = $this->get_post_type_label( $this->get_post_type_real_ID() );
23
  }
24
 
25
  $cache_key = $this->generate_cache_key( $id, $taxonomy );
26
 
27
  if ( is_admin() ) {
28
  $bstyle = is_rtl()
29
- ? 'direction:ltr;color:#444;font-family:Georgio,sans-serif;font-size:14px;clear:both;float:left;position:relative;width:calc( 100% - 200px );min-height:700px;padding:0;margin:20px 180px 40px 20px;overflow:hidden;border:1px solid #ccc;border-radius:3px;line-height:18pxfont-feature-settings:normal;font-variant:normal'
30
- : 'direction:ltr;color:#444;font-family:Georgio,sans-serif;font-size:14px;clear:both;float:left;position:relative;width:calc( 100% - 200px );min-height:700px;padding:0;margin:20px 20px 40px 180px;overflow:hidden;border:1px solid #ccc;border-radius:3px;line-height:18pxfont-feature-settings:normal;font-variant:normal';
31
  ?>
32
  <div style="<?php echo $bstyle; // phpcs:ignore, WordPress.Security.EscapeOutput.OutputNotEscaped ?>">
33
  <h3 style="font-size:14px;padding:0 12px;margin:0;line-height:39px;border-bottom:2px solid #aaa;position:absolute;z-index:9002;width:100%;right:0;left:0;top:0;background:#fff;border-radius:3px 3px 0 0;height:39px;">
@@ -35,7 +40,7 @@ if ( is_admin() ) {
35
  <?php
36
  if ( $this->is_post_edit() || $this->is_term_edit() ) :
37
  echo ' :: ';
38
- echo esc_html( 'Type: ' . $type );
39
  echo esc_html( $mdash . 'ID: ' . $id );
40
  echo esc_html( $mdash . 'Cache key: ' . ( $cache_key ?: 'N/A' ) );
41
  echo esc_html( $mdash . 'Plugin version: ' . THE_SEO_FRAMEWORK_VERSION );
@@ -45,8 +50,8 @@ if ( is_admin() ) {
45
  </h3>
46
  <div style="position:absolute;bottom:0;right:0;left:0;top:39px;margin:0;padding:0;background:#fff;border-radius:3px;overflow-x:hidden;z-index:9001">
47
  <?php
48
- The_SEO_Framework\Debug::_output_debug_header();
49
- The_SEO_Framework\Debug::_output_debug_query();
50
  ?>
51
  </div>
52
  </div>
@@ -68,15 +73,15 @@ if ( is_admin() ) {
68
  </h3>
69
  <div style="position:absolute;bottom:0;right:0;left:0;top:39px;margin:0;padding:0;background:#fff;border-radius:3px;overflow-x:hidden;z-index:9001">
70
  <?php
71
- The_SEO_Framework\Debug::_output_debug_header();
72
  ?>
73
  <div style="width:50%;float:left;">
74
  <?php
75
- The_SEO_Framework\Debug::_output_debug_query_from_cache();
76
  ?>
77
  </div><div style="width:50%;float:right;">
78
  <?php
79
- The_SEO_Framework\Debug::_output_debug_query();
80
  ?>
81
  </div>
82
  </div>
7
  // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- includes.
8
  // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
9
 
10
+ use The_SEO_Framework\Internal\Debug;
11
 
12
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
13
+
14
+ $id = $this->get_the_real_ID();
15
+ $mdash = ' &mdash; ';
16
+ $taxonomy = $this->get_current_taxonomy();
17
+ $post_type = $this->get_current_post_type();
18
 
19
  // This will return 'Page' on all non-archive types (except the homepage)
20
+ if ( $this->is_real_front_page() ) {
21
  $type = 'Front Page';
22
  } elseif ( $taxonomy ) {
23
  $type = $this->get_tax_type_label( $taxonomy );
24
+ } elseif ( $post_type ) {
25
+ $type = $this->get_post_type_label( $post_type );
26
  } else {
27
+ $type = 'Unknown';
28
  }
29
 
30
  $cache_key = $this->generate_cache_key( $id, $taxonomy );
31
 
32
  if ( is_admin() ) {
33
  $bstyle = is_rtl()
34
+ ? 'direction:ltr;color:#444;font-family:Georgio,sans-serif;font-size:14px;clear:both;float:left;position:relative;width:calc( 100% - 200px );min-height:700px;padding:0;margin:20px 180px 40px 20px;overflow:hidden;border:1px solid #ccc;border-radius:3px;line-height:18pxfont-feature-settings:normal;font-variant:normal'
35
+ : 'direction:ltr;color:#444;font-family:Georgio,sans-serif;font-size:14px;clear:both;float:left;position:relative;width:calc( 100% - 200px );min-height:700px;padding:0;margin:20px 20px 40px 180px;overflow:hidden;border:1px solid #ccc;border-radius:3px;line-height:18pxfont-feature-settings:normal;font-variant:normal';
36
  ?>
37
  <div style="<?php echo $bstyle; // phpcs:ignore, WordPress.Security.EscapeOutput.OutputNotEscaped ?>">
38
  <h3 style="font-size:14px;padding:0 12px;margin:0;line-height:39px;border-bottom:2px solid #aaa;position:absolute;z-index:9002;width:100%;right:0;left:0;top:0;background:#fff;border-radius:3px 3px 0 0;height:39px;">
40
  <?php
41
  if ( $this->is_post_edit() || $this->is_term_edit() ) :
42
  echo ' :: ';
43
+ echo esc_html( "Type: $type" );
44
  echo esc_html( $mdash . 'ID: ' . $id );
45
  echo esc_html( $mdash . 'Cache key: ' . ( $cache_key ?: 'N/A' ) );
46
  echo esc_html( $mdash . 'Plugin version: ' . THE_SEO_FRAMEWORK_VERSION );
50
  </h3>
51
  <div style="position:absolute;bottom:0;right:0;left:0;top:39px;margin:0;padding:0;background:#fff;border-radius:3px;overflow-x:hidden;z-index:9001">
52
  <?php
53
+ Debug::_output_debug_header();
54
+ Debug::_output_debug_query();
55
  ?>
56
  </div>
57
  </div>
73
  </h3>
74
  <div style="position:absolute;bottom:0;right:0;left:0;top:39px;margin:0;padding:0;background:#fff;border-radius:3px;overflow-x:hidden;z-index:9001">
75
  <?php
76
+ Debug::_output_debug_header();
77
  ?>
78
  <div style="width:50%;float:left;">
79
  <?php
80
+ Debug::_output_debug_query_from_cache();
81
  ?>
82
  </div><div style="width:50%;float:right;">
83
  <?php
84
+ Debug::_output_debug_query();
85
  ?>
86
  </div>
87
  </div>
inc/views/edit/seo-settings-singular-gutenberg-data.php CHANGED
@@ -7,7 +7,7 @@
7
  // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- includes.
8
  // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
9
 
10
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
11
 
12
  printf(
13
  '<div id=%s data-post-id=%d class=hidden></div>',
7
  // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- includes.
8
  // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
9
 
10
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
11
 
12
  printf(
13
  '<div id=%s data-post-id=%d class=hidden></div>',
inc/views/edit/seo-settings-singular.php CHANGED
@@ -11,35 +11,33 @@ use The_SEO_Framework\Bridges\PostSettings,
11
  The_SEO_Framework\Interpreters\HTML,
12
  The_SEO_Framework\Interpreters\Form;
13
 
14
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
15
-
16
- // Fetch the required instance within this file.
17
- $instance = $this->get_view_instance( 'inpost', $instance );
18
 
19
  // Setup default vars.
20
  $post_id = $this->get_the_real_ID(); // We also have access to object $post at the main call...
21
 
22
- $_generator_args = [
23
- 'id' => $post_id,
24
- 'taxonomy' => '',
25
- ];
26
 
27
- switch ( $instance ) :
28
  case 'inpost_main':
 
 
29
  $default_tabs = [
30
  'general' => [
31
  'name' => __( 'General', 'autodescription' ),
32
- 'callback' => PostSettings::class . '::_general_tab',
33
  'dashicon' => 'admin-generic',
34
  ],
35
  'social' => [
36
  'name' => __( 'Social', 'autodescription' ),
37
- 'callback' => PostSettings::class . '::_social_tab',
38
  'dashicon' => 'share',
39
  ],
40
  'visibility' => [
41
  'name' => __( 'Visibility', 'autodescription' ),
42
- 'callback' => PostSettings::class . '::_visibility_tab',
43
  'dashicon' => 'visibility',
44
  ],
45
  ];
@@ -60,7 +58,7 @@ switch ( $instance ) :
60
  echo '</div>';
61
  break;
62
 
63
- case 'inpost_general':
64
  if ( $this->get_option( 'display_seo_bar_metabox' ) ) :
65
  ?>
66
  <div class="tsf-flex-setting tsf-flex" id="tsf-doing-it-right-wrap">
@@ -84,25 +82,23 @@ switch ( $instance ) :
84
  <?php
85
  endif;
86
 
87
- if ( $this->is_static_frontpage( $post_id ) ) {
88
  $_has_home_title = (bool) $this->escape_title( $this->get_option( 'homepage_title' ) );
89
  $_has_home_desc = (bool) $this->escape_title( $this->get_option( 'homepage_description' ) );
90
 
91
- // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
92
  // When the homepage title is set, we can safely get the custom field.
93
  $default_title = $_has_home_title
94
- ? $this->get_custom_field_title( $_generator_args )
95
- : $this->get_filtered_raw_generated_title( $_generator_args );
96
  $title_ref_locked = $_has_home_title;
97
  $title_additions = $this->get_home_title_additions();
98
  $title_seplocation = $this->get_home_title_seplocation();
99
 
100
  // When the homepage description is set, we can safely get the custom field.
101
  $default_description = $_has_home_desc
102
- ? $this->get_description_from_custom_field( $_generator_args )
103
- : $this->get_generated_description( $_generator_args );
104
  $description_ref_locked = $_has_home_desc;
105
- // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
106
  } else {
107
  $default_title = $this->get_filtered_raw_generated_title( $_generator_args );
108
  $title_ref_locked = false;
@@ -138,7 +134,7 @@ switch ( $instance ) :
138
  </div>
139
  <div class="tsf-flex-setting-input tsf-flex">
140
  <div class=tsf-title-wrap>
141
- <input class="large-text" type="text" name="autodescription[_genesis_title]" id="autodescription_title" value="<?php echo $this->esc_attr_preserve_amp( $this->get_post_meta_item( '_genesis_title', $post_id ) ); ?>" autocomplete=off />
142
  <?php
143
  $this->output_js_title_elements(); // legacy
144
  $this->output_js_title_data(
@@ -146,10 +142,10 @@ switch ( $instance ) :
146
  [
147
  'state' => [
148
  'refTitleLocked' => $title_ref_locked,
149
- 'defaultTitle' => $default_title,
150
  'addAdditions' => $this->use_title_branding( $_generator_args ),
151
  'useSocialTagline' => $this->use_title_branding( $_generator_args, true ),
152
- 'additionValue' => $this->s_title_raw( $title_additions ),
153
  'additionPlacement' => 'left' === $title_seplocation ? 'before' : 'after',
154
  'hasLegacy' => true,
155
  ],
@@ -161,7 +157,7 @@ switch ( $instance ) :
161
  <div class="tsf-checkbox-wrapper">
162
  <label for="autodescription_title_no_blogname">
163
  <?php
164
- if ( $this->is_static_frontpage( $post_id ) ) :
165
  // Disable the input, and hide the previously stored value.
166
  ?>
167
  <input type="checkbox" id="autodescription_title_no_blogname" value="1" <?php checked( $this->get_post_meta_item( '_tsf_title_no_blogname' ) ); ?> disabled />
@@ -207,14 +203,14 @@ switch ( $instance ) :
207
  </div>
208
  </div>
209
  <div class="tsf-flex-setting-input tsf-flex">
210
- <textarea class="large-text" name="autodescription[_genesis_description]" id="autodescription_description" rows="4" cols="4" autocomplete=off><?php echo $this->esc_attr_preserve_amp( $this->get_post_meta_item( '_genesis_description', $post_id ) ); ?></textarea>
211
  <?php
212
  $this->output_js_description_elements(); // legacy
213
  $this->output_js_description_data(
214
  'autodescription_description',
215
  [
216
  'state' => [
217
- 'defaultDescription' => $default_description,
218
  'refDescriptionLocked' => $description_ref_locked,
219
  'hasLegacy' => true,
220
  ],
@@ -226,14 +222,14 @@ switch ( $instance ) :
226
  <?php
227
  break;
228
 
229
- case 'inpost_visibility':
230
  $canonical = $this->get_post_meta_item( '_genesis_canonical_uri' );
231
  $canonical_placeholder = $this->create_canonical_url( $_generator_args );
232
 
233
  // Get robots defaults.
234
  $r_defaults = $this->generate_robots_meta(
235
  $_generator_args,
236
- null,
237
  The_SEO_Framework\ROBOTS_IGNORE_SETTINGS | The_SEO_Framework\ROBOTS_IGNORE_PROTECTION
238
  );
239
  $r_settings = [
@@ -300,7 +296,7 @@ switch ( $instance ) :
300
  </div>
301
  </div>
302
  <?php
303
- if ( $this->is_static_frontpage( $post_id ) ) {
304
  printf(
305
  '<div class=tsf-flex-setting-label-sub-item><span class="description attention">%s</span></div>',
306
  esc_html__( 'Warning: No public site should ever apply "noindex" or "nofollow" to the homepage.', 'autodescription' )
@@ -418,23 +414,71 @@ switch ( $instance ) :
418
  <?php
419
  break;
420
 
421
- case 'inpost_social':
422
- $social_placeholders = $this->_get_social_placeholders( $_generator_args );
423
-
424
  // Yes, this is hacky, but we don't want to lose the user's input.
425
  $show_og = (bool) $this->get_option( 'og_tags' );
426
  $show_tw = (bool) $this->get_option( 'twitter_tags' );
427
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
428
  ?>
429
  <div class="tsf-flex-setting tsf-flex" <?php echo $show_og ? '' : 'style=display:none'; ?>>
430
  <div class="tsf-flex-setting-label tsf-flex">
431
  <div class="tsf-flex-setting-label-inner-wrap tsf-flex">
432
  <label for="autodescription_og_title" class="tsf-flex-setting-label-item tsf-flex">
433
- <div><strong>
434
- <?php
435
- esc_html_e( 'Open Graph Title', 'autodescription' );
436
- ?>
437
- </strong></div>
438
  </label>
439
  <?php
440
  $this->get_option( 'display_character_counter' )
@@ -444,7 +488,7 @@ switch ( $instance ) :
444
  </div>
445
  <div class="tsf-flex-setting-input tsf-flex">
446
  <div id="tsf-og-title-wrap">
447
- <input class="large-text" type="text" name="autodescription[_open_graph_title]" id="autodescription_og_title" placeholder="<?php echo esc_attr( $social_placeholders['title']['og'] ); ?>" value="<?php echo $this->esc_attr_preserve_amp( $this->get_post_meta_item( '_open_graph_title' ) ); ?>" autocomplete=off />
448
  </div>
449
  </div>
450
  </div>
@@ -453,11 +497,7 @@ switch ( $instance ) :
453
  <div class="tsf-flex-setting-label tsf-flex">
454
  <div class="tsf-flex-setting-label-inner-wrap tsf-flex">
455
  <label for="autodescription_og_description" class="tsf-flex-setting-label-item tsf-flex">
456
- <div><strong>
457
- <?php
458
- esc_html_e( 'Open Graph Description', 'autodescription' );
459
- ?>
460
- </strong></div>
461
  </label>
462
  <?php
463
  $this->get_option( 'display_character_counter' )
@@ -466,7 +506,7 @@ switch ( $instance ) :
466
  </div>
467
  </div>
468
  <div class="tsf-flex-setting-input tsf-flex">
469
- <textarea class="large-text" name="autodescription[_open_graph_description]" id="autodescription_og_description" placeholder="<?php echo esc_attr( $social_placeholders['description']['og'] ); ?>" rows="3" cols="4" autocomplete=off><?php echo $this->esc_attr_preserve_amp( $this->get_post_meta_item( '_open_graph_description' ) ); ?></textarea>
470
  </div>
471
  </div>
472
 
@@ -474,11 +514,7 @@ switch ( $instance ) :
474
  <div class="tsf-flex-setting-label tsf-flex">
475
  <div class="tsf-flex-setting-label-inner-wrap tsf-flex">
476
  <label for="autodescription_twitter_title" class="tsf-flex-setting-label-item tsf-flex">
477
- <div><strong>
478
- <?php
479
- esc_html_e( 'Twitter Title', 'autodescription' );
480
- ?>
481
- </strong></div>
482
  </label>
483
  <?php
484
  $this->get_option( 'display_character_counter' )
@@ -488,7 +524,7 @@ switch ( $instance ) :
488
  </div>
489
  <div class="tsf-flex-setting-input tsf-flex">
490
  <div id="tsf-twitter-title-wrap">
491
- <input class="large-text" type="text" name="autodescription[_twitter_title]" id="autodescription_twitter_title" placeholder="<?php echo esc_attr( $social_placeholders['title']['twitter'] ); ?>" value="<?php echo $this->esc_attr_preserve_amp( $this->get_post_meta_item( '_twitter_title' ) ); ?>" autocomplete=off />
492
  </div>
493
  </div>
494
  </div>
@@ -497,11 +533,7 @@ switch ( $instance ) :
497
  <div class="tsf-flex-setting-label tsf-flex">
498
  <div class="tsf-flex-setting-label-inner-wrap tsf-flex">
499
  <label for="autodescription_twitter_description" class="tsf-flex-setting-label-item tsf-flex">
500
- <div><strong>
501
- <?php
502
- esc_html_e( 'Twitter Description', 'autodescription' );
503
- ?>
504
- </strong></div>
505
  </label>
506
  <?php
507
  $this->get_option( 'display_character_counter' )
@@ -510,21 +542,20 @@ switch ( $instance ) :
510
  </div>
511
  </div>
512
  <div class="tsf-flex-setting-input tsf-flex">
513
- <textarea class="large-text" name="autodescription[_twitter_description]" id="autodescription_twitter_description" placeholder="<?php echo esc_attr( $social_placeholders['description']['twitter'] ); ?>" rows="3" cols="4" autocomplete=off><?php
514
  // Textareas don't require sanitization in HTML5... other than removing the closing </textarea> tag...?
515
  echo $this->esc_attr_preserve_amp( $this->get_post_meta_item( '_twitter_description' ) );
516
- ?></textarea>
 
517
  </div>
518
  </div>
519
  <?php
520
 
521
  // Fetch image placeholder.
522
- if ( $this->is_static_frontpage( $post_id ) && $this->get_option( 'homepage_social_image_url' ) ) {
523
- $image_details = current( $this->get_image_details( $_generator_args, true, 'social', true ) );
524
- $image_placeholder = isset( $image_details['url'] ) ? $image_details['url'] : '';
525
  } else {
526
- $image_details = current( $this->get_generated_image_details( $_generator_args, true, 'social', true ) );
527
- $image_placeholder = isset( $image_details['url'] ) ? $image_details['url'] : '';
528
  }
529
 
530
  ?>
11
  The_SEO_Framework\Interpreters\HTML,
12
  The_SEO_Framework\Interpreters\Form;
13
 
14
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
 
 
 
15
 
16
  // Setup default vars.
17
  $post_id = $this->get_the_real_ID(); // We also have access to object $post at the main call...
18
 
19
+ $_generator_args = [ 'id' => $post_id ];
20
+
21
+ $_is_static_frontpage = $this->is_static_frontpage( $post_id );
 
22
 
23
+ switch ( $this->get_view_instance( 'inpost', $instance ) ) :
24
  case 'inpost_main':
25
+ $post_settings_class = PostSettings::class;
26
+
27
  $default_tabs = [
28
  'general' => [
29
  'name' => __( 'General', 'autodescription' ),
30
+ 'callback' => "$post_settings_class::_general_tab",
31
  'dashicon' => 'admin-generic',
32
  ],
33
  'social' => [
34
  'name' => __( 'Social', 'autodescription' ),
35
+ 'callback' => "$post_settings_class::_social_tab",
36
  'dashicon' => 'share',
37
  ],
38
  'visibility' => [
39
  'name' => __( 'Visibility', 'autodescription' ),
40
+ 'callback' => "$post_settings_class::_visibility_tab",
41
  'dashicon' => 'visibility',
42
  ],
43
  ];
58
  echo '</div>';
59
  break;
60
 
61
+ case 'inpost_general_tab':
62
  if ( $this->get_option( 'display_seo_bar_metabox' ) ) :
63
  ?>
64
  <div class="tsf-flex-setting tsf-flex" id="tsf-doing-it-right-wrap">
82
  <?php
83
  endif;
84
 
85
+ if ( $_is_static_frontpage ) {
86
  $_has_home_title = (bool) $this->escape_title( $this->get_option( 'homepage_title' ) );
87
  $_has_home_desc = (bool) $this->escape_title( $this->get_option( 'homepage_description' ) );
88
 
 
89
  // When the homepage title is set, we can safely get the custom field.
90
  $default_title = $_has_home_title
91
+ ? $this->get_custom_field_title( $_generator_args )
92
+ : $this->get_filtered_raw_generated_title( $_generator_args );
93
  $title_ref_locked = $_has_home_title;
94
  $title_additions = $this->get_home_title_additions();
95
  $title_seplocation = $this->get_home_title_seplocation();
96
 
97
  // When the homepage description is set, we can safely get the custom field.
98
  $default_description = $_has_home_desc
99
+ ? $this->get_description_from_custom_field( $_generator_args )
100
+ : $this->get_generated_description( $_generator_args );
101
  $description_ref_locked = $_has_home_desc;
 
102
  } else {
103
  $default_title = $this->get_filtered_raw_generated_title( $_generator_args );
104
  $title_ref_locked = false;
134
  </div>
135
  <div class="tsf-flex-setting-input tsf-flex">
136
  <div class=tsf-title-wrap>
137
+ <input class="large-text" type="text" name="autodescription[_genesis_title]" id="autodescription_title" value="<?php echo $this->esc_attr_preserve_amp( $this->get_post_meta_item( '_genesis_title' ) ); ?>" autocomplete=off />
138
  <?php
139
  $this->output_js_title_elements(); // legacy
140
  $this->output_js_title_data(
142
  [
143
  'state' => [
144
  'refTitleLocked' => $title_ref_locked,
145
+ 'defaultTitle' => $this->s_title( $default_title ),
146
  'addAdditions' => $this->use_title_branding( $_generator_args ),
147
  'useSocialTagline' => $this->use_title_branding( $_generator_args, true ),
148
+ 'additionValue' => $this->s_title( $title_additions ),
149
  'additionPlacement' => 'left' === $title_seplocation ? 'before' : 'after',
150
  'hasLegacy' => true,
151
  ],
157
  <div class="tsf-checkbox-wrapper">
158
  <label for="autodescription_title_no_blogname">
159
  <?php
160
+ if ( $_is_static_frontpage ) :
161
  // Disable the input, and hide the previously stored value.
162
  ?>
163
  <input type="checkbox" id="autodescription_title_no_blogname" value="1" <?php checked( $this->get_post_meta_item( '_tsf_title_no_blogname' ) ); ?> disabled />
203
  </div>
204
  </div>
205
  <div class="tsf-flex-setting-input tsf-flex">
206
+ <textarea class="large-text" name="autodescription[_genesis_description]" id="autodescription_description" rows="4" cols="4" autocomplete=off><?php echo $this->esc_attr_preserve_amp( $this->get_post_meta_item( '_genesis_description' ) ); ?></textarea>
207
  <?php
208
  $this->output_js_description_elements(); // legacy
209
  $this->output_js_description_data(
210
  'autodescription_description',
211
  [
212
  'state' => [
213
+ 'defaultDescription' => $this->s_description( $default_description ),
214
  'refDescriptionLocked' => $description_ref_locked,
215
  'hasLegacy' => true,
216
  ],
222
  <?php
223
  break;
224
 
225
+ case 'inpost_visibility_tab':
226
  $canonical = $this->get_post_meta_item( '_genesis_canonical_uri' );
227
  $canonical_placeholder = $this->create_canonical_url( $_generator_args );
228
 
229
  // Get robots defaults.
230
  $r_defaults = $this->generate_robots_meta(
231
  $_generator_args,
232
+ [ 'noindex', 'nofollow', 'noarchive' ],
233
  The_SEO_Framework\ROBOTS_IGNORE_SETTINGS | The_SEO_Framework\ROBOTS_IGNORE_PROTECTION
234
  );
235
  $r_settings = [
296
  </div>
297
  </div>
298
  <?php
299
+ if ( $_is_static_frontpage ) {
300
  printf(
301
  '<div class=tsf-flex-setting-label-sub-item><span class="description attention">%s</span></div>',
302
  esc_html__( 'Warning: No public site should ever apply "noindex" or "nofollow" to the homepage.', 'autodescription' )
414
  <?php
415
  break;
416
 
417
+ case 'inpost_social_tab':
 
 
418
  // Yes, this is hacky, but we don't want to lose the user's input.
419
  $show_og = (bool) $this->get_option( 'og_tags' );
420
  $show_tw = (bool) $this->get_option( 'twitter_tags' );
421
 
422
+ if ( $_is_static_frontpage ) {
423
+ $_social_title = [
424
+ 'og' => $this->get_option( 'homepage_og_title' )
425
+ ?: $this->get_option( 'homepage_title' )
426
+ ?: $this->get_generated_open_graph_title( $_generator_args, false ),
427
+ 'tw' => $this->get_option( 'homepage_twitter_title' )
428
+ ?: $this->get_option( 'homepage_og_title' )
429
+ ?: $this->get_option( 'homepage_title' )
430
+ ?: $this->get_generated_twitter_title( $_generator_args, false ),
431
+ ];
432
+ $_social_description = [
433
+ 'og' => $this->get_option( 'homepage_og_description' )
434
+ ?: $this->get_option( 'homepage_description' )
435
+ ?: $this->get_generated_open_graph_description( $_generator_args, false ),
436
+ 'tw' => $this->get_option( 'homepage_twitter_description' )
437
+ ?: $this->get_option( 'homepage_og_description' )
438
+ ?: $this->get_option( 'homepage_description' )
439
+ ?: $this->get_generated_twitter_description( $_generator_args, false ),
440
+ ];
441
+ } else {
442
+ $_social_title = [
443
+ 'og' => $this->get_generated_open_graph_title( $_generator_args, false ),
444
+ 'tw' => $this->get_generated_twitter_title( $_generator_args, false ),
445
+ ];
446
+ $_social_description = [
447
+ 'og' => $this->get_generated_open_graph_description( $_generator_args, false ),
448
+ 'tw' => $this->get_generated_twitter_description( $_generator_args, false ),
449
+ ];
450
+ }
451
+
452
+ $this->output_js_social_data(
453
+ 'autodescription_social_singular',
454
+ [
455
+ 'og' => [
456
+ 'state' => [
457
+ 'defaultTitle' => $this->s_title( $_social_title['og'] ),
458
+ 'addAdditions' => $this->use_title_branding( $_generator_args, 'og' ),
459
+ 'defaultDesc' => $this->s_description( $_social_description['og'] ),
460
+ 'titleLock' => $_is_static_frontpage && $this->get_option( 'homepage_og_title' ),
461
+ 'descLock' => $_is_static_frontpage && $this->get_option( 'homepage_og_description' ),
462
+ ],
463
+ ],
464
+ 'tw' => [
465
+ 'state' => [
466
+ 'defaultTitle' => $this->s_title( $_social_title['tw'] ),
467
+ 'addAdditions' => $this->use_title_branding( $_generator_args, 'twitter' ),
468
+ 'defaultDesc' => $this->s_description( $_social_description['tw'] ),
469
+ 'titleLock' => $_is_static_frontpage && (bool) $this->get_option( 'homepage_twitter_title' ),
470
+ 'descLock' => $_is_static_frontpage && (bool) $this->get_option( 'homepage_twitter_description' ),
471
+ ],
472
+ ],
473
+ ]
474
+ );
475
+
476
  ?>
477
  <div class="tsf-flex-setting tsf-flex" <?php echo $show_og ? '' : 'style=display:none'; ?>>
478
  <div class="tsf-flex-setting-label tsf-flex">
479
  <div class="tsf-flex-setting-label-inner-wrap tsf-flex">
480
  <label for="autodescription_og_title" class="tsf-flex-setting-label-item tsf-flex">
481
+ <div><strong><?php esc_html_e( 'Open Graph Title', 'autodescription' ); ?></strong></div>
 
 
 
 
482
  </label>
483
  <?php
484
  $this->get_option( 'display_character_counter' )
488
  </div>
489
  <div class="tsf-flex-setting-input tsf-flex">
490
  <div id="tsf-og-title-wrap">
491
+ <input class="large-text" type="text" name="autodescription[_open_graph_title]" id="autodescription_og_title" value="<?php echo $this->esc_attr_preserve_amp( $this->get_post_meta_item( '_open_graph_title' ) ); ?>" autocomplete=off data-tsf-social-group=autodescription_social_singular data-tsf-social-type=ogTitle />
492
  </div>
493
  </div>
494
  </div>
497
  <div class="tsf-flex-setting-label tsf-flex">
498
  <div class="tsf-flex-setting-label-inner-wrap tsf-flex">
499
  <label for="autodescription_og_description" class="tsf-flex-setting-label-item tsf-flex">
500
+ <div><strong><?php esc_html_e( 'Open Graph Description', 'autodescription' ); ?></strong></div>
 
 
 
 
501
  </label>
502
  <?php
503
  $this->get_option( 'display_character_counter' )
506
  </div>
507
  </div>
508
  <div class="tsf-flex-setting-input tsf-flex">
509
+ <textarea class="large-text" name="autodescription[_open_graph_description]" id="autodescription_og_description" rows="3" cols="4" autocomplete=off data-tsf-social-group=autodescription_social_singular data-tsf-social-type=ogDesc><?php echo $this->esc_attr_preserve_amp( $this->get_post_meta_item( '_open_graph_description' ) ); ?></textarea>
510
  </div>
511
  </div>
512
 
514
  <div class="tsf-flex-setting-label tsf-flex">
515
  <div class="tsf-flex-setting-label-inner-wrap tsf-flex">
516
  <label for="autodescription_twitter_title" class="tsf-flex-setting-label-item tsf-flex">
517
+ <div><strong><?php esc_html_e( 'Twitter Title', 'autodescription' ); ?></strong></div>
 
 
 
 
518
  </label>
519
  <?php
520
  $this->get_option( 'display_character_counter' )
524
  </div>
525
  <div class="tsf-flex-setting-input tsf-flex">
526
  <div id="tsf-twitter-title-wrap">
527
+ <input class="large-text" type="text" name="autodescription[_twitter_title]" id="autodescription_twitter_title" value="<?php echo $this->esc_attr_preserve_amp( $this->get_post_meta_item( '_twitter_title' ) ); ?>" autocomplete=off data-tsf-social-group=autodescription_social_singular data-tsf-social-type=twTitle />
528
  </div>
529
  </div>
530
  </div>
533
  <div class="tsf-flex-setting-label tsf-flex">
534
  <div class="tsf-flex-setting-label-inner-wrap tsf-flex">
535
  <label for="autodescription_twitter_description" class="tsf-flex-setting-label-item tsf-flex">
536
+ <div><strong><?php esc_html_e( 'Twitter Description', 'autodescription' ); ?></strong></div>
 
 
 
 
537
  </label>
538
  <?php
539
  $this->get_option( 'display_character_counter' )
542
  </div>
543
  </div>
544
  <div class="tsf-flex-setting-input tsf-flex">
545
+ <textarea class="large-text" name="autodescription[_twitter_description]" id="autodescription_twitter_description" rows="3" cols="4" autocomplete=off data-tsf-social-group=autodescription_social_singular data-tsf-social-type=twDesc><?php // phpcs:ignore, Squiz.PHP.EmbeddedPhp -- textarea element's content is input. Do not add spaces/tabs/lines: the php tag should stick to >.
546
  // Textareas don't require sanitization in HTML5... other than removing the closing </textarea> tag...?
547
  echo $this->esc_attr_preserve_amp( $this->get_post_meta_item( '_twitter_description' ) );
548
+ // phpcs:ignore, Squiz.PHP.EmbeddedPhp
549
+ ?></textarea>
550
  </div>
551
  </div>
552
  <?php
553
 
554
  // Fetch image placeholder.
555
+ if ( $_is_static_frontpage && $this->get_option( 'homepage_social_image_url' ) ) {
556
+ $image_placeholder = current( $this->get_image_details( $_generator_args, true, 'social', true ) )['url'] ?? '';
 
557
  } else {
558
+ $image_placeholder = current( $this->get_generated_image_details( $_generator_args, true, 'social', true ) )['url'] ?? '';
 
559
  }
560
 
561
  ?>
inc/views/edit/seo-settings-tt.php CHANGED
@@ -11,7 +11,7 @@ use The_SEO_Framework\Bridges\TermSettings,
11
  The_SEO_Framework\Interpreters\HTML,
12
  The_SEO_Framework\Interpreters\Form;
13
 
14
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
15
 
16
  // Fetch Term ID and taxonomy.
17
  $term_id = $term->term_id;
@@ -41,14 +41,16 @@ $_generator_args = [
41
  $show_og = (bool) $this->get_option( 'og_tags' );
42
  $show_tw = (bool) $this->get_option( 'twitter_tags' );
43
 
44
- $social_placeholders = $this->_get_social_placeholders( $_generator_args );
45
-
46
  //! Social image placeholder.
47
  $image_details = current( $this->get_generated_image_details( $_generator_args, true, 'social', true ) );
48
- $image_placeholder = isset( $image_details['url'] ) ? $image_details['url'] : '';
49
 
50
  $canonical_placeholder = $this->create_canonical_url( $_generator_args ); // implies get_custom_field = false
51
- $robots_defaults = $this->generate_robots_meta( $_generator_args, null, The_SEO_Framework\ROBOTS_IGNORE_SETTINGS );
 
 
 
 
52
 
53
  // TODO reintroduce the info blocks, and place the labels at the left, instead??
54
  $robots_settings = [
@@ -131,7 +133,7 @@ $robots_settings = [
131
  </th>
132
  <td>
133
  <div class=tsf-title-wrap>
134
- <input name="autodescription-meta[doctitle]" id="autodescription-meta[doctitle]" type="text" value="<?php echo $this->esc_attr_preserve_amp( $title ); ?>" size="40" autocomplete=off />
135
  <?php
136
  $this->output_js_title_elements(); // legacy
137
  $this->output_js_title_data(
@@ -139,10 +141,10 @@ $robots_settings = [
139
  [
140
  'state' => [
141
  'refTitleLocked' => false,
142
- 'defaultTitle' => $this->get_filtered_raw_generated_title( $_generator_args ),
143
  'addAdditions' => $this->use_title_branding( $_generator_args ),
144
  'useSocialTagline' => $this->use_title_branding( $_generator_args, true ),
145
- 'additionValue' => $this->s_title_raw( $this->get_blogname() ),
146
  'additionPlacement' => 'left' === $this->get_title_seplocation() ? 'before' : 'after',
147
  'hasLegacy' => true,
148
  ],
@@ -151,7 +153,7 @@ $robots_settings = [
151
  ?>
152
  </div>
153
  <label for="autodescription-meta[title_no_blog_name]" class="tsf-term-checkbox-wrap">
154
- <input type="checkbox" name="autodescription-meta[title_no_blog_name]" id="autodescription-meta[title_no_blog_name]" value="1" <?php checked( $this->get_term_meta_item( 'title_no_blog_name', $term_id ) ); ?> />
155
  <?php
156
  esc_html_e( 'Remove the site title?', 'autodescription' );
157
  echo ' ';
@@ -200,6 +202,28 @@ $robots_settings = [
200
  </table>
201
 
202
  <h2><?php esc_html_e( 'Social SEO Settings', 'autodescription' ); ?></h2>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
  <table class="form-table tsf-term-meta">
205
  <tbody>
@@ -215,7 +239,7 @@ $robots_settings = [
215
  </th>
216
  <td>
217
  <div id="tsf-og-title-wrap">
218
- <input name="autodescription-meta[og_title]" id="autodescription-meta[og_title]" type="text" placeholder="<?php echo esc_attr( $social_placeholders['title']['og'] ); ?>" value="<?php echo $this->esc_attr_preserve_amp( $og_title ); ?>" size="40" autocomplete=off />
219
  </div>
220
  </td>
221
  </tr>
@@ -231,7 +255,7 @@ $robots_settings = [
231
  ?>
232
  </th>
233
  <td>
234
- <textarea name="autodescription-meta[og_description]" id="autodescription-meta[og_description]" placeholder="<?php echo esc_attr( $social_placeholders['description']['og'] ); ?>" rows="4" cols="50" class="large-text" autocomplete=off><?php echo $this->esc_attr_preserve_amp( $og_description ); ?></textarea>
235
  </td>
236
  </tr>
237
 
@@ -247,7 +271,7 @@ $robots_settings = [
247
  </th>
248
  <td>
249
  <div id="tsf-tw-title-wrap">
250
- <input name="autodescription-meta[tw_title]" id="autodescription-meta[tw_title]" type="text" placeholder="<?php echo esc_attr( $social_placeholders['title']['twitter'] ); ?>" value="<?php echo $this->esc_attr_preserve_amp( $tw_title ); ?>" size="40" autocomplete=off />
251
  </div>
252
  </td>
253
  </tr>
@@ -263,7 +287,7 @@ $robots_settings = [
263
  ?>
264
  </th>
265
  <td>
266
- <textarea name="autodescription-meta[tw_description]" id="autodescription-meta[tw_description]" placeholder="<?php echo esc_attr( $social_placeholders['description']['twitter'] ); ?>" rows="4" cols="50" class="large-text" autocomplete=off><?php echo $this->esc_attr_preserve_amp( $tw_description ); ?></textarea>
267
  </td>
268
  </tr>
269
 
@@ -281,7 +305,7 @@ $robots_settings = [
281
  </label>
282
  </th>
283
  <td>
284
- <input name="autodescription-meta[social_image_url]" id="autodescription_meta_socialimage-url" type="url" placeholder="<?php echo esc_attr( $image_placeholder ); ?>" value="<?php echo esc_attr( $social_image_url ); ?>" size="40" autocomplete=off />
285
  <input type="hidden" name="autodescription-meta[social_image_id]" id="autodescription_meta_socialimage-id" value="<?php echo absint( $social_image_id ); ?>" disabled class="tsf-enable-media-if-js" />
286
  <div class="hide-if-no-tsf-js tsf-term-button-wrap">
287
  <?php
@@ -312,7 +336,7 @@ $robots_settings = [
312
  </label>
313
  </th>
314
  <td>
315
- <input name="autodescription-meta[canonical]" id="autodescription-meta[canonical]" type=url placeholder="<?php echo esc_attr( $canonical_placeholder ); ?>" value="<?php echo esc_attr( $canonical ); ?>" size="40" autocomplete=off />
316
  </td>
317
  </tr>
318
 
@@ -370,7 +394,7 @@ $robots_settings = [
370
  </label>
371
  </th>
372
  <td>
373
- <input name="autodescription-meta[redirect]" id="autodescription-meta[redirect]" type=url value="<?php echo esc_attr( $redirect ); ?>" size="40" autocomplete=off />
374
  </td>
375
  </tr>
376
  </tbody>
11
  The_SEO_Framework\Interpreters\HTML,
12
  The_SEO_Framework\Interpreters\Form;
13
 
14
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
15
 
16
  // Fetch Term ID and taxonomy.
17
  $term_id = $term->term_id;
41
  $show_og = (bool) $this->get_option( 'og_tags' );
42
  $show_tw = (bool) $this->get_option( 'twitter_tags' );
43
 
 
 
44
  //! Social image placeholder.
45
  $image_details = current( $this->get_generated_image_details( $_generator_args, true, 'social', true ) );
46
+ $image_placeholder = $image_details['url'] ?? '';
47
 
48
  $canonical_placeholder = $this->create_canonical_url( $_generator_args ); // implies get_custom_field = false
49
+ $robots_defaults = $this->generate_robots_meta(
50
+ $_generator_args,
51
+ [ 'noindex', 'nofollow', 'noarchive' ],
52
+ The_SEO_Framework\ROBOTS_IGNORE_SETTINGS
53
+ );
54
 
55
  // TODO reintroduce the info blocks, and place the labels at the left, instead??
56
  $robots_settings = [
133
  </th>
134
  <td>
135
  <div class=tsf-title-wrap>
136
+ <input type="text" name="autodescription-meta[doctitle]" id="autodescription-meta[doctitle]" value="<?php echo $this->esc_attr_preserve_amp( $title ); ?>" size="40" autocomplete=off />
137
  <?php
138
  $this->output_js_title_elements(); // legacy
139
  $this->output_js_title_data(
141
  [
142
  'state' => [
143
  'refTitleLocked' => false,
144
+ 'defaultTitle' => $this->s_title( $this->get_filtered_raw_generated_title( $_generator_args ) ),
145
  'addAdditions' => $this->use_title_branding( $_generator_args ),
146
  'useSocialTagline' => $this->use_title_branding( $_generator_args, true ),
147
+ 'additionValue' => $this->s_title( $this->get_blogname() ),
148
  'additionPlacement' => 'left' === $this->get_title_seplocation() ? 'before' : 'after',
149
  'hasLegacy' => true,
150
  ],
153
  ?>
154
  </div>
155
  <label for="autodescription-meta[title_no_blog_name]" class="tsf-term-checkbox-wrap">
156
+ <input type="checkbox" name="autodescription-meta[title_no_blog_name]" id="autodescription-meta[title_no_blog_name]" value="1" <?php checked( $this->get_term_meta_item( 'title_no_blog_name' ) ); ?> />
157
  <?php
158
  esc_html_e( 'Remove the site title?', 'autodescription' );
159
  echo ' ';
202
  </table>
203
 
204
  <h2><?php esc_html_e( 'Social SEO Settings', 'autodescription' ); ?></h2>
205
+ <?php
206
+
207
+ $this->output_js_social_data(
208
+ 'autodescription_social_tt',
209
+ [
210
+ 'og' => [
211
+ 'state' => [
212
+ 'defaultTitle' => $this->s_title( $this->get_generated_open_graph_title( $_generator_args, false ) ),
213
+ 'addAdditions' => $this->use_title_branding( $_generator_args, 'og' ),
214
+ 'defaultDesc' => $this->s_description( $this->get_generated_open_graph_description( $_generator_args, false ) ),
215
+ ],
216
+ ],
217
+ 'tw' => [
218
+ 'state' => [
219
+ 'defaultTitle' => $this->s_title( $this->get_generated_twitter_title( $_generator_args, false ) ),
220
+ 'addAdditions' => $this->use_title_branding( $_generator_args, 'twitter' ),
221
+ 'defaultDesc' => $this->s_description( $this->get_generated_twitter_description( $_generator_args, false ) ),
222
+ ],
223
+ ],
224
+ ]
225
+ );
226
+ ?>
227
 
228
  <table class="form-table tsf-term-meta">
229
  <tbody>
239
  </th>
240
  <td>
241
  <div id="tsf-og-title-wrap">
242
+ <input name="autodescription-meta[og_title]" id="autodescription-meta[og_title]" type="text" value="<?php echo $this->esc_attr_preserve_amp( $og_title ); ?>" size="40" autocomplete=off data-tsf-social-group=autodescription_social_tt data-tsf-social-type=ogTitle />
243
  </div>
244
  </td>
245
  </tr>
255
  ?>
256
  </th>
257
  <td>
258
+ <textarea name="autodescription-meta[og_description]" id="autodescription-meta[og_description]" rows="4" cols="50" class="large-text" autocomplete=off data-tsf-social-group=autodescription_social_tt data-tsf-social-type=ogDesc><?php echo $this->esc_attr_preserve_amp( $og_description ); ?></textarea>
259
  </td>
260
  </tr>
261
 
271
  </th>
272
  <td>
273
  <div id="tsf-tw-title-wrap">
274
+ <input name="autodescription-meta[tw_title]" id="autodescription-meta[tw_title]" type="text" value="<?php echo $this->esc_attr_preserve_amp( $tw_title ); ?>" size="40" autocomplete=off data-tsf-social-group=autodescription_social_tt data-tsf-social-type=twTitle />
275
  </div>
276
  </td>
277
  </tr>
287
  ?>
288
  </th>
289
  <td>
290
+ <textarea name="autodescription-meta[tw_description]" id="autodescription-meta[tw_description]" rows="4" cols="50" class="large-text" autocomplete=off data-tsf-social-group=autodescription_social_tt data-tsf-social-type=twDesc><?php echo $this->esc_attr_preserve_amp( $tw_description ); ?></textarea>
291
  </td>
292
  </tr>
293
 
305
  </label>
306
  </th>
307
  <td>
308
+ <input type="url" name="autodescription-meta[social_image_url]" id="autodescription_meta_socialimage-url" placeholder="<?php echo esc_attr( $image_placeholder ); ?>" value="<?php echo esc_attr( $social_image_url ); ?>" size="40" autocomplete=off />
309
  <input type="hidden" name="autodescription-meta[social_image_id]" id="autodescription_meta_socialimage-id" value="<?php echo absint( $social_image_id ); ?>" disabled class="tsf-enable-media-if-js" />
310
  <div class="hide-if-no-tsf-js tsf-term-button-wrap">
311
  <?php
336
  </label>
337
  </th>
338
  <td>
339
+ <input type=url name="autodescription-meta[canonical]" id="autodescription-meta[canonical]" placeholder="<?php echo esc_attr( $canonical_placeholder ); ?>" value="<?php echo esc_attr( $canonical ); ?>" size="40" autocomplete=off />
340
  </td>
341
  </tr>
342
 
394
  </label>
395
  </th>
396
  <td>
397
+ <input type=url name="autodescription-meta[redirect]" id="autodescription-meta[redirect]" value="<?php echo esc_attr( $redirect ); ?>" size="40" autocomplete=off />
398
  </td>
399
  </tr>
400
  </tbody>
inc/views/edit/wrap-content.php CHANGED
@@ -7,7 +7,7 @@
7
  // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- includes.
8
  // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
9
 
10
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
11
 
12
  // Whether tabs are active.
13
  $use_tabs = $use_tabs && count( $tabs ) > 1;
@@ -18,10 +18,10 @@ $count = 1;
18
  *
19
  * The content is relative to the navigation, and uses CSS to become visible.
20
  */
21
- foreach ( $tabs as $tab => $value ) :
22
 
23
- $radio_id = esc_attr( 'tsf-flex-' . $id . '-tab-' . $tab . '-content' );
24
- $radio_class = esc_attr( 'tsf-flex-' . $id . '-tabs-content' );
25
 
26
  // Current tab for JS.
27
  $current_class = 1 === $count ? ' tsf-flex-tab-content-active' : '';
@@ -31,28 +31,41 @@ foreach ( $tabs as $tab => $value ) :
31
  <?php
32
  // No-JS tabs.
33
  if ( $use_tabs ) :
34
- $dashicon = isset( $value['dashicon'] ) ? $value['dashicon'] : '';
35
- $label_name = isset( $value['name'] ) ? $value['name'] : '';
36
 
37
  ?>
38
  <div class="tsf-flex tsf-flex-hide-if-js tsf-flex-tabs-content-no-js">
39
  <div class="tsf-flex tsf-flex-nav-tab tsf-flex-tab-no-js">
40
  <span class="tsf-flex tsf-flex-nav-tab">
41
  <?php echo $dashicon ? '<span class="tsf-flex dashicons dashicons-' . esc_attr( $dashicon ) . ' tsf-flex-nav-dashicon"></span>' : ''; ?>
42
- <?php echo $label_name ? '<span class="tsf-flex tsf-flex-nav-name">' . esc_attr( $label_name ) . '</span>' : ''; ?>
43
  </span>
44
  </div>
45
  </div>
46
  <?php
47
  endif;
48
 
49
- $callback = isset( $value['callback'] ) ? $value['callback'] : '';
 
50
 
51
- if ( $callback ) {
52
- $params = isset( $value['args'] ) ? $value['args'] : '';
53
- call_user_func_array( $callback, (array) $params );
54
- }
55
- ?>
 
 
 
 
 
 
 
 
 
 
 
 
56
  </div>
57
  <?php
58
 
7
  // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- includes.
8
  // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
9
 
10
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
11
 
12
  // Whether tabs are active.
13
  $use_tabs = $use_tabs && count( $tabs ) > 1;
18
  *
19
  * The content is relative to the navigation, and uses CSS to become visible.
20
  */
21
+ foreach ( $tabs as $tab => $params ) :
22
 
23
+ $radio_id = "tsf-flex-{$id}-tab-{$tab}-content";
24
+ $radio_class = "tsf-flex-{$id}-tabs-content";
25
 
26
  // Current tab for JS.
27
  $current_class = 1 === $count ? ' tsf-flex-tab-content-active' : '';
31
  <?php
32
  // No-JS tabs.
33
  if ( $use_tabs ) :
34
+ $dashicon = $params['dashicon'] ?? '';
35
+ $label_name = $params['name'] ?? '';
36
 
37
  ?>
38
  <div class="tsf-flex tsf-flex-hide-if-js tsf-flex-tabs-content-no-js">
39
  <div class="tsf-flex tsf-flex-nav-tab tsf-flex-tab-no-js">
40
  <span class="tsf-flex tsf-flex-nav-tab">
41
  <?php echo $dashicon ? '<span class="tsf-flex dashicons dashicons-' . esc_attr( $dashicon ) . ' tsf-flex-nav-dashicon"></span>' : ''; ?>
42
+ <?php echo $label_name ? '<span class="tsf-flex tsf-flex-nav-name">' . esc_html( $label_name ) . '</span>' : ''; ?>
43
  </span>
44
  </div>
45
  </div>
46
  <?php
47
  endif;
48
 
49
+ if ( ! empty( $params['callback'] ) )
50
+ call_user_func_array( $params['callback'], ( $params['args'] ?? [] ) );
51
 
52
+ /**
53
+ * @since 4.2.0
54
+ * @param array $args The tab arguments: {
55
+ * @param string id
56
+ * @param string tab
57
+ * @param array params
58
+ * }
59
+ */
60
+ do_action(
61
+ 'the_seo_framework_flex_tab_content',
62
+ [
63
+ 'id' => $id,
64
+ 'tab' => $tab,
65
+ 'params' => $params,
66
+ ]
67
+ );
68
+ ?>
69
  </div>
70
  <?php
71
 
inc/views/edit/wrap-nav.php CHANGED
@@ -7,7 +7,7 @@
7
  // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- includes.
8
  // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
9
 
10
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
11
 
12
  // Whether tabs are active.
13
  $use_tabs = $use_tabs && count( $tabs ) > 1;
@@ -24,29 +24,33 @@ if ( $use_tabs ) :
24
  <div class="tsf-flex tsf-flex-nav-tab-inner">
25
  <?php
26
  foreach ( $tabs as $tab => $value ) :
27
- $dashicon = isset( $value['dashicon'] ) ? $value['dashicon'] : '';
28
- $label_name = isset( $value['name'] ) ? $value['name'] : '';
29
 
30
  $wrapper_id = esc_attr( "tsf-flex-nav-tab-{$tab}" );
31
- $wrapper_active = 1 === $count ? ' tsf-flex-nav-tab-active' : '';
32
 
33
  $input_checked = 1 === $count ? 'checked' : '';
34
  $input_id = esc_attr( "tsf-flex-{$id}-tab-{$tab}" );
35
  $input_name = esc_attr( "tsf-flex-{$id}-tabs" );
36
 
37
- // phpcs:disable, WordPress.Security.EscapeOutput.OutputNotEscaped -- All output below is escaped.
38
- ?>
39
- <div class="tsf-flex tsf-flex-nav-tab tsf-flex<?php echo $wrapper_active; ?>" id="<?php echo $wrapper_id; ?>">
40
- <input type="radio" class="tsf-flex-nav-tab-radio tsf-input-not-saved" id="<?php echo $input_id; ?>" name="<?php echo $input_name; ?>" <?php echo $input_checked; ?>>
41
- <label for="<?php echo $input_id; ?>" class="tsf-flex tsf-flex-nav-tab-label">
42
- <?php
43
- echo $dashicon ? '<span class="tsf-flex dashicons ' . esc_attr( "dashicons-$dashicon" ) . ' tsf-flex-nav-dashicon"></span>' : '';
44
- echo $label_name ? '<span class="tsf-flex tsf-flex-nav-name">' . esc_attr( $label_name ) . '</span>' : '';
45
- ?>
 
 
 
 
46
  </label>
47
  </div>
48
- <?php
49
- // phpcs:enable, WordPress.Security.EscapeOutput.OutputNotEscaped
50
 
51
  $count++;
52
  endforeach;
7
  // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- includes.
8
  // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
9
 
10
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
11
 
12
  // Whether tabs are active.
13
  $use_tabs = $use_tabs && count( $tabs ) > 1;
24
  <div class="tsf-flex tsf-flex-nav-tab-inner">
25
  <?php
26
  foreach ( $tabs as $tab => $value ) :
27
+ $dashicon = $value['dashicon'] ?? '';
28
+ $label_name = $value['name'] ?? '';
29
 
30
  $wrapper_id = esc_attr( "tsf-flex-nav-tab-{$tab}" );
31
+ $wrapper_active = 1 === $count ? 'tsf-flex-nav-tab-active' : '';
32
 
33
  $input_checked = 1 === $count ? 'checked' : '';
34
  $input_id = esc_attr( "tsf-flex-{$id}-tab-{$tab}" );
35
  $input_name = esc_attr( "tsf-flex-{$id}-tabs" );
36
 
37
+ if ( $dashicon )
38
+ $dashicon = sprintf( '<span class="tsf-flex dashicons %s tsf-flex-nav-dashicon"></span>', esc_attr( "dashicons-$dashicon" ) );
39
+
40
+ if ( $label_name )
41
+ $label_name = sprintf( '<span class="tsf-flex tsf-flex-nav-name">%s</span>', esc_attr( $label_name ) );
42
+
43
+ // phpcs:ignore, WordPress.Security.EscapeOutput.OutputNotEscaped -- All output below is escaped.
44
+ echo <<<HTML
45
+ <div class="tsf-flex tsf-flex-nav-tab tsf-flex $wrapper_active" id="$wrapper_id">
46
+ <input type="radio" class="tsf-flex-nav-tab-radio tsf-input-not-saved" id="$input_id" name="$input_name" $input_checked>
47
+ <label for="$input_id" class="tsf-flex tsf-flex-nav-tab-label">
48
+ $dashicon
49
+ $label_name
50
  </label>
51
  </div>
52
+ HTML;
53
+ // ^ At PHP 7.3+ we can indent this.
54
 
55
  $count++;
56
  endforeach;
inc/views/list/bulk-post.php CHANGED
@@ -11,7 +11,7 @@ use The_SEO_Framework\Interpreters\Form;
11
  // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- includes.
12
  // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
13
 
14
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
15
 
16
  $robots_settings = [
17
  'noindex' => [
11
  // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- includes.
12
  // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
13
 
14
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
15
 
16
  $robots_settings = [
17
  'noindex' => [
inc/views/list/quick-post.php CHANGED
@@ -11,7 +11,7 @@
11
 
12
  use The_SEO_Framework\Interpreters\Form;
13
 
14
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
15
 
16
  $robots_settings = [
17
  'noindex' => [
11
 
12
  use The_SEO_Framework\Interpreters\Form;
13
 
14
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
15
 
16
  $robots_settings = [
17
  'noindex' => [
inc/views/list/quick-term.php CHANGED
@@ -11,7 +11,7 @@
11
 
12
  use The_SEO_Framework\Interpreters\Form;
13
 
14
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
15
 
16
  $robots_settings = [
17
  'noindex' => [
11
 
12
  use The_SEO_Framework\Interpreters\Form;
13
 
14
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
15
 
16
  $robots_settings = [
17
  'noindex' => [
inc/views/notice/persistent.php CHANGED
@@ -9,7 +9,7 @@
9
 
10
  use The_SEO_Framework\Interpreters\HTML;
11
 
12
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
13
 
14
  if ( ! $message ) return;
15
 
9
 
10
  use The_SEO_Framework\Interpreters\HTML;
11
 
12
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
13
 
14
  if ( ! $message ) return;
15
 
inc/views/profile/author.php CHANGED
@@ -7,7 +7,7 @@
7
  // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- includes.
8
  // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
9
 
10
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
11
 
12
  $fields = [
13
  'tsf-user-meta[facebook_page]' => [
7
  // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- includes.
8
  // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
9
 
10
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
11
 
12
  $fields = [
13
  'tsf-user-meta[facebook_page]' => [
inc/views/{admin/seo-settings-columns.php → settings/columns.php} RENAMED
@@ -7,7 +7,7 @@
7
  // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- includes.
8
  // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
9
 
10
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
11
 
12
  ?>
13
  <div class="metabox-holder columns-2">
7
  // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- includes.
8
  // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
9
 
10
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
11
 
12
  ?>
13
  <div class="metabox-holder columns-2">
inc/views/{admin → settings}/index.php RENAMED
File without changes
inc/views/{admin/metaboxes/description-metabox.php → settings/metaboxes/description.php} RENAMED
@@ -8,16 +8,13 @@
8
  // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
9
 
10
  use The_SEO_Framework\Interpreters\HTML,
11
- The_SEO_Framework\Interpreters\Form;
12
 
13
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
14
 
15
- // Fetch the required instance within this file.
16
- $instance = $this->get_view_instance( 'the_seo_framework_description_metabox', $instance );
17
-
18
- switch ( $instance ) :
19
- case 'the_seo_framework_description_metabox_main':
20
- Form::header_title( __( 'Description Settings', 'autodescription' ) );
21
  HTML::description(
22
  __( 'The meta description can be used to determine the text used under the title on search engine results pages.', 'autodescription' )
23
  );
@@ -25,7 +22,7 @@ switch ( $instance ) :
25
  ?>
26
  <hr>
27
  <?php
28
- Form::header_title( __( 'Automated Description Settings', 'autodescription' ) );
29
  HTML::description(
30
  __( 'A description can be automatically generated for every page.', 'autodescription' )
31
  );
@@ -40,9 +37,9 @@ switch ( $instance ) :
40
  );
41
 
42
  HTML::wrap_fields(
43
- Form::make_checkbox( [
44
  'id' => 'auto_description',
45
- 'label' => esc_html__( 'Automatically generate descriptions?', 'autodescription' ) . ' ' . $info,
46
  'escape' => false,
47
  ] ),
48
  true
8
  // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
9
 
10
  use The_SEO_Framework\Interpreters\HTML,
11
+ The_SEO_Framework\Interpreters\Settings_Input as Input;
12
 
13
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
14
 
15
+ switch ( $this->get_view_instance( 'description', $instance ) ) :
16
+ case 'description_main':
17
+ HTML::header_title( __( 'Description Settings', 'autodescription' ) );
 
 
 
18
  HTML::description(
19
  __( 'The meta description can be used to determine the text used under the title on search engine results pages.', 'autodescription' )
20
  );
22
  ?>
23
  <hr>
24
  <?php
25
+ HTML::header_title( __( 'Automated Description Settings', 'autodescription' ) );
26
  HTML::description(
27
  __( 'A description can be automatically generated for every page.', 'autodescription' )
28
  );
37
  );
38
 
39
  HTML::wrap_fields(
40
+ Input::make_checkbox( [
41
  'id' => 'auto_description',
42
+ 'label' => esc_html__( 'Automatically generate descriptions?', 'autodescription' ) . " $info",
43
  'escape' => false,
44
  ] ),
45
  true
inc/views/{admin/metaboxes/feed-metabox.php → settings/metaboxes/feed.php} RENAMED
@@ -8,23 +8,20 @@
8
  // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
9
 
10
  use The_SEO_Framework\Interpreters\HTML,
11
- The_SEO_Framework\Interpreters\Form;
12
 
13
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
14
 
15
- // Fetch the required instance within this file.
16
- $instance = $this->get_view_instance( 'the_seo_framework_feed_metabox', $instance );
17
-
18
- switch ( $instance ) :
19
- case 'the_seo_framework_feed_metabox_main':
20
- Form::header_title( __( 'Content Feed Settings', 'autodescription' ) );
21
  HTML::description( __( "Sometimes, your content can get stolen by robots through the WordPress feeds. This can cause duplicate content issues. To prevent this from happening, it's recommended to convert the feed's content into an excerpt.", 'autodescription' ) );
22
  HTML::description( __( 'Adding a backlink below the feed entries will also let the visitors know where the content came from.', 'autodescription' ) );
23
 
24
  ?>
25
  <hr>
26
  <?php
27
- Form::header_title( __( 'Change Feed Settings', 'autodescription' ) );
28
  $excerpt_the_feed_label = esc_html__( 'Convert feed entries into excerpts?', 'autodescription' );
29
  $excerpt_the_feed_label .= ' ' . HTML::make_info( __( 'By default the excerpt will be at most 400 characters long.', 'autodescription' ), '', false );
30
 
@@ -36,17 +33,17 @@ switch ( $instance ) :
36
 
37
  HTML::wrap_fields(
38
  [
39
- Form::make_checkbox( [
40
  'id' => 'excerpt_the_feed',
41
  'label' => $excerpt_the_feed_label,
42
  'escape' => false,
43
  ] ),
44
- Form::make_checkbox( [
45
  'id' => 'source_the_feed',
46
  'label' => $source_the_feed_label,
47
  'escape' => false,
48
  ] ),
49
- Form::make_checkbox( [
50
  'id' => 'index_the_feed',
51
  'label' => $index_the_feed_label,
52
  'escape' => false,
8
  // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
9
 
10
  use The_SEO_Framework\Interpreters\HTML,
11
+ The_SEO_Framework\Interpreters\Settings_Input as Input;
12
 
13
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
14
 
15
+ switch ( $this->get_view_instance( 'feed', $instance ) ) :
16
+ case 'feed_main':
17
+ HTML::header_title( __( 'Content Feed Settings', 'autodescription' ) );
 
 
 
18
  HTML::description( __( "Sometimes, your content can get stolen by robots through the WordPress feeds. This can cause duplicate content issues. To prevent this from happening, it's recommended to convert the feed's content into an excerpt.", 'autodescription' ) );
19
  HTML::description( __( 'Adding a backlink below the feed entries will also let the visitors know where the content came from.', 'autodescription' ) );
20
 
21
  ?>
22
  <hr>
23
  <?php
24
+ HTML::header_title( __( 'Change Feed Settings', 'autodescription' ) );
25
  $excerpt_the_feed_label = esc_html__( 'Convert feed entries into excerpts?', 'autodescription' );
26
  $excerpt_the_feed_label .= ' ' . HTML::make_info( __( 'By default the excerpt will be at most 400 characters long.', 'autodescription' ), '', false );
27
 
33
 
34
  HTML::wrap_fields(
35
  [
36
+ Input::make_checkbox( [
37
  'id' => 'excerpt_the_feed',
38
  'label' => $excerpt_the_feed_label,
39
  'escape' => false,
40
  ] ),
41
+ Input::make_checkbox( [
42
  'id' => 'source_the_feed',
43
  'label' => $source_the_feed_label,
44
  'escape' => false,
45
  ] ),
46
+ Input::make_checkbox( [
47
  'id' => 'index_the_feed',
48
  'label' => $index_the_feed_label,
49
  'escape' => false,
inc/views/{admin/metaboxes/general-metabox.php → settings/metaboxes/general.php} RENAMED
@@ -9,76 +9,73 @@
9
 
10
  use The_SEO_Framework\Bridges\SeoSettings,
11
  The_SEO_Framework\Interpreters\HTML,
12
- The_SEO_Framework\Interpreters\Form;
13
 
14
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
15
 
16
- // Fetch the required instance within this file.
17
- $instance = $this->get_view_instance( 'the_seo_framework_general_metabox', $instance );
 
18
 
19
- switch ( $instance ) :
20
- case 'the_seo_framework_general_metabox_main':
21
- $default_tabs = [
22
  'layout' => [
23
  'name' => __( 'Layout', 'autodescription' ),
24
- 'callback' => SeoSettings::class . '::_general_metabox_layout_tab',
25
  'dashicon' => 'screenoptions',
26
  ],
27
  'performance' => [
28
  'name' => __( 'Performance', 'autodescription' ),
29
- 'callback' => SeoSettings::class . '::_general_metabox_performance_tab',
30
  'dashicon' => 'performance',
31
  ],
32
  'canonical' => [
33
  'name' => __( 'Canonical', 'autodescription' ),
34
- 'callback' => SeoSettings::class . '::_general_metabox_canonical_tab',
35
  'dashicon' => 'external',
36
  ],
37
  'timestamps' => [
38
  'name' => __( 'Timestamps', 'autodescription' ),
39
- 'callback' => SeoSettings::class . '::_general_metabox_timestamps_tab',
40
  'dashicon' => 'clock',
41
  ],
42
  'exclusions' => [
43
  'name' => __( 'Exclusions', 'autodescription' ),
44
- 'callback' => SeoSettings::class . '::_general_metabox_exclusions_tab',
45
  'dashicon' => 'editor-unlink',
46
  ],
47
  ];
48
 
49
- /**
50
- * @since 2.8.0
51
- * @param array $defaults The default tabs.
52
- * @param array $args The args added on the callback.
53
- */
54
- $defaults = (array) apply_filters( 'the_seo_framework_general_settings_tabs', $default_tabs, $args );
55
-
56
- $tabs = wp_parse_args( $args, $defaults );
57
-
58
- SeoSettings::_nav_tab_wrapper( 'general', $tabs );
59
  break;
60
 
61
- case 'the_seo_framework_general_metabox_layout':
62
- Form::header_title( __( 'Administrative Layout Settings', 'autodescription' ) );
63
  HTML::description( __( 'SEO hints can be visually displayed throughout the dashboard.', 'autodescription' ) );
64
 
65
  ?>
66
  <hr>
67
  <?php
68
- Form::header_title( __( 'SEO Bar Settings', 'autodescription' ) );
69
  HTML::wrap_fields(
70
  [
71
- Form::make_checkbox( [
72
  'id' => 'display_seo_bar_tables',
73
  'label' => esc_html__( 'Display the SEO Bar in overview tables?', 'autodescription' ),
74
  'escape' => false,
75
  ] ),
76
- Form::make_checkbox( [
77
  'id' => 'display_seo_bar_metabox',
78
  'label' => esc_html__( 'Display the SEO Bar in the SEO Settings metabox?', 'autodescription' ),
79
  'escape' => false,
80
  ] ),
81
- Form::make_checkbox( [
82
  'id' => 'seo_bar_symbols',
83
  'label' => esc_html__( 'Use symbols for warnings?', 'autodescription' ) . ' ' . HTML::make_info(
84
  __( 'If you have difficulty discerning colors, this may help you spot issues more easily.', 'autodescription' ),
@@ -94,7 +91,7 @@ switch ( $instance ) :
94
  ?>
95
  <hr>
96
  <?php
97
- Form::header_title( __( 'Counter Settings', 'autodescription' ) );
98
 
99
  $pixel_info = HTML::make_info(
100
  __( 'The pixel counter computes whether the input will fit on search engine result pages.', 'autodescription' ),
@@ -110,14 +107,14 @@ switch ( $instance ) :
110
 
111
  HTML::wrap_fields(
112
  [
113
- Form::make_checkbox( [
114
  'id' => 'display_pixel_counter',
115
- 'label' => esc_html__( 'Display pixel counters?', 'autodescription' ) . ' ' . $pixel_info,
116
  'escape' => false,
117
  ] ),
118
- Form::make_checkbox( [
119
  'id' => 'display_character_counter',
120
- 'label' => esc_html__( 'Display character counters?', 'autodescription' ) . ' ' . $character_info,
121
  'escape' => false,
122
  ] ),
123
  ],
@@ -125,14 +122,14 @@ switch ( $instance ) :
125
  );
126
  break;
127
 
128
- case 'the_seo_framework_general_metabox_performance':
129
- Form::header_title( __( 'Performance Settings', 'autodescription' ) );
130
  HTML::description( __( "Depending on your server's configuration, adjusting these settings can affect performance.", 'autodescription' ) );
131
 
132
  ?>
133
  <hr>
134
  <?php
135
- Form::header_title( __( 'Query Alteration Settings', 'autodescription' ) );
136
  HTML::description_noesc(
137
  esc_html__( "Altering the query allows for more control of the site's hierarchy.", 'autodescription' )
138
  . '<br>' .
@@ -185,9 +182,9 @@ switch ( $instance ) :
185
  '<label for="%1$s">%2$s</label>
186
  <select name="%3$s" id="%1$s">%4$s</select>',
187
  [
188
- Form::get_field_id( 'alter_search_query_type' ),
189
  $perform_alteration_i18n,
190
- Form::get_field_name( 'alter_search_query_type' ),
191
  $search_query_select_options,
192
  ]
193
  );
@@ -196,16 +193,16 @@ switch ( $instance ) :
196
  '<label for="%1$s">%2$s</label>
197
  <select name="%3$s" id="%1$s">%4$s</select>',
198
  [
199
- Form::get_field_id( 'alter_archive_query_type' ),
200
  $perform_alteration_i18n,
201
- Form::get_field_name( 'alter_archive_query_type' ),
202
  $archive_query_select_options,
203
  ]
204
  );
205
 
206
  HTML::wrap_fields(
207
  [
208
- Form::make_checkbox( [
209
  'id' => 'alter_search_query',
210
  'label' => esc_html__( 'Enable search query alteration?', 'autodescription' )
211
  . ' ' . HTML::make_info( __( 'This allows you to exclude pages from on-site search results.', 'autodescription' ), '', false ),
@@ -218,7 +215,7 @@ switch ( $instance ) :
218
 
219
  HTML::wrap_fields(
220
  [
221
- Form::make_checkbox( [
222
  'id' => 'alter_archive_query',
223
  'label' => esc_html__( 'Enable archive query alteration?', 'autodescription' )
224
  . ' ' . HTML::make_info( __( 'This allows you to exclude pages from on-site archive listings.', 'autodescription' ), '', false ),
@@ -231,11 +228,11 @@ switch ( $instance ) :
231
  ?>
232
  <hr>
233
  <?php
234
- Form::header_title( __( 'Transient Cache Settings', 'autodescription' ) );
235
  HTML::description( __( 'To improve performance, generated output can be stored in the database as transient cache.', 'autodescription' ) );
236
 
237
  HTML::wrap_fields(
238
- Form::make_checkbox( [
239
  'id' => 'cache_sitemap',
240
  'label' => esc_html__( 'Enable optimized sitemap generation cache?', 'autodescription' )
241
  . ' ' . HTML::make_info( __( 'Generating the sitemap can use a lot of server resources.', 'autodescription' ), '', false ),
@@ -245,18 +242,18 @@ switch ( $instance ) :
245
  );
246
  break;
247
 
248
- case 'the_seo_framework_general_metabox_canonical':
249
- Form::header_title( __( 'Canonical URL Settings', 'autodescription' ) );
250
  HTML::description( __( 'The canonical URL meta tag urges search engines to go to the outputted URL.', 'autodescription' ) );
251
  HTML::description( __( 'If the canonical URL meta tag represents the visited page, then the search engine will crawl the visited page. Otherwise, the search engine may go to the outputted URL.', 'autodescription' ) );
252
  ?>
253
  <hr>
254
  <?php
255
- Form::header_title( __( 'Scheme Settings', 'autodescription' ) );
256
  HTML::description( __( 'If your website is accessible via both HTTP as HTTPS, you may want to set this to HTTPS if not detected automatically. Secure connections are preferred by search engines.', 'autodescription' ) );
257
  ?>
258
- <label for="<?php Form::field_id( 'canonical_scheme' ); ?>"><?php echo esc_html_x( 'Preferred canonical URL scheme:', '= Detect Automatically, HTTPS, HTTP', 'autodescription' ); ?></label>
259
- <select name="<?php Form::field_name( 'canonical_scheme' ); ?>" id="<?php Form::field_id( 'canonical_scheme' ); ?>">
260
  <?php
261
  $scheme_types = (array) apply_filters(
262
  'the_seo_framework_canonical_scheme_types',
@@ -277,11 +274,11 @@ switch ( $instance ) :
277
 
278
  <hr>
279
  <?php
280
- Form::header_title( __( 'Link Relationship Settings', 'autodescription' ) );
281
  HTML::description( __( 'Some search engines look for relations between the content of your pages. If you have pagination on a post or page, or have archives indexed, these options will help search engines look for the right page to display in the search results.', 'autodescription' ) );
282
  HTML::description( __( "It's recommended to turn these options on for better SEO consistency and to prevent duplicated content issues.", 'autodescription' ) );
283
 
284
- $prev_next_posts_checkbox = Form::make_checkbox( [
285
  'id' => 'prev_next_posts',
286
  'label' => $this->convert_markdown(
287
  /* translators: the backticks are Markdown! Preserve them as-is! */
@@ -291,7 +288,7 @@ switch ( $instance ) :
291
  'escape' => false,
292
  ] );
293
 
294
- $prev_next_archives_checkbox = Form::make_checkbox( [
295
  'id' => 'prev_next_archives',
296
  'label' => $this->convert_markdown(
297
  /* translators: the backticks are Markdown! Preserve them as-is! */
@@ -301,7 +298,7 @@ switch ( $instance ) :
301
  'escape' => false,
302
  ] );
303
 
304
- $prev_next_frontpage_checkbox = Form::make_checkbox( [
305
  'id' => 'prev_next_frontpage',
306
  'label' => $this->convert_markdown(
307
  /* translators: the backticks are Markdown! Preserve them as-is! */
@@ -314,23 +311,23 @@ switch ( $instance ) :
314
  HTML::wrap_fields( $prev_next_posts_checkbox . $prev_next_archives_checkbox . $prev_next_frontpage_checkbox, true );
315
  break;
316
 
317
- case 'the_seo_framework_general_metabox_timestamps':
318
  $timestamp_0 = gmdate( $this->get_timestamp_format( false ) );
319
  $timestamp_1 = gmdate( $this->get_timestamp_format( true ) );
320
 
321
- Form::header_title( __( 'Timestamp Settings', 'autodescription' ) );
322
  HTML::description( __( 'Timestamps help indicate when a page has been published and modified.', 'autodescription' ) );
323
  ?>
324
  <hr>
325
 
326
  <fieldset>
327
- <legend><?php Form::header_title( __( 'Timestamp Format Settings', 'autodescription' ) ); ?></legend>
328
  <?php HTML::description( __( 'This setting determines how specific the timestamp is.', 'autodescription' ) ); ?>
329
 
330
  <p id="sitemaps-timestamp-format" class="tsf-fields">
331
  <span class="tsf-toblock">
332
- <input type="radio" name="<?php Form::field_name( 'timestamps_format' ); ?>" id="<?php Form::field_id( 'timestamps_format_0' ); ?>" value="0" <?php checked( $this->get_option( 'timestamps_format' ), '0' ); ?> />
333
- <label for="<?php Form::field_id( 'timestamps_format_0' ); ?>">
334
  <?php
335
  // phpcs:ignore, WordPress.Security.EscapeOutput -- code_wrap escapes.
336
  echo HTML::code_wrap( $timestamp_0 ), ' ', HTML::make_info(
@@ -340,8 +337,8 @@ switch ( $instance ) :
340
  </label>
341
  </span>
342
  <span class="tsf-toblock">
343
- <input type="radio" name="<?php Form::field_name( 'timestamps_format' ); ?>" id="<?php Form::field_id( 'timestamps_format_1' ); ?>" value="1" <?php checked( $this->get_option( 'timestamps_format' ), '1' ); ?> />
344
- <label for="<?php Form::field_id( 'timestamps_format_1' ); ?>">
345
  <?php
346
  // phpcs:ignore, WordPress.Security.EscapeOutput -- code_wrap escapes.
347
  echo HTML::code_wrap( $timestamp_1 ), ' ', HTML::make_info(
@@ -355,11 +352,8 @@ switch ( $instance ) :
355
  <?php
356
  break;
357
 
358
- case 'the_seo_framework_general_metabox_exclusions':
359
- $default_options = $this->get_default_site_options();
360
- $warned_options = $this->get_warned_site_options();
361
-
362
- Form::header_title( __( 'Exclusion Settings', 'autodescription' ) );
363
  HTML::description( __( 'When checked, these options will remove meta optimizations, SEO suggestions, and sitemap inclusions for the selected post types and taxonomies. This will allow search engines to crawl the post type and taxonomies without advanced restrictions or directions.', 'autodescription' ) );
364
  HTML::attention_description_noesc(
365
  $this->convert_markdown(
@@ -373,15 +367,13 @@ switch ( $instance ) :
373
 
374
  <hr>
375
  <?php
376
- Form::header_title( __( 'Post Type Exclusions', 'autodescription' ) );
377
  HTML::description( __( 'Select post types which should be excluded.', 'autodescription' ) );
378
  HTML::description( __( 'These settings apply to the post type pages and their terms. When terms are shared between post types, all their post types should be checked for this to have an effect.', 'autodescription' ) );
379
 
380
  $forced_pt = $this->get_forced_supported_post_types();
381
  $boxes = [];
382
 
383
- $pt_option_id = 'disabled_post_types';
384
-
385
  foreach ( $this->get_public_post_types() as $post_type ) {
386
  $_label = $this->get_post_type_label( $post_type, false );
387
  if ( ! strlen( $_label ) ) continue;
@@ -392,15 +384,12 @@ switch ( $instance ) :
392
  esc_html( $post_type )
393
  );
394
 
395
- $boxes[] = Form::make_checkbox( [
396
- 'id' => $pt_option_id,
397
  'class' => 'tsf-excluded-post-types',
398
- 'index' => $post_type,
399
  'label' => $_label,
400
  'escape' => false,
401
  'disabled' => in_array( $post_type, $forced_pt, true ),
402
- 'default' => ! empty( $default_options[ $pt_option_id ][ $post_type ] ),
403
- 'warned' => ! empty( $warned_options[ $pt_option_id ][ $post_type ] ),
404
  ] );
405
  }
406
 
@@ -409,7 +398,7 @@ switch ( $instance ) :
409
  ?>
410
  <hr>
411
  <?php
412
- Form::header_title( __( 'Taxonomy Exclusions', 'autodescription' ) );
413
  HTML::description( __( 'Select taxonomies which should be excluded.', 'autodescription' ) );
414
  HTML::description( __( 'When taxonomies have all their bound post types excluded, they will inherit their exclusion status.', 'autodescription' ) );
415
 
@@ -428,15 +417,12 @@ switch ( $instance ) :
428
  esc_html( $taxonomy )
429
  );
430
 
431
- $boxes[] = Form::make_checkbox( [
432
- 'id' => 'disabled_taxonomies',
433
  'class' => 'tsf-excluded-taxonomies',
434
- 'index' => $taxonomy,
435
  'label' => $_label,
436
  'escape' => false,
437
  'disabled' => in_array( $taxonomy, $forced_tax, true ),
438
- 'default' => ! empty( $default_options[ $tax_option_id ][ $taxonomy ] ),
439
- 'warned' => ! empty( $warned_options[ $tax_option_id ][ $taxonomy ] ),
440
  'data' => [
441
  'postTypes' => $this->get_post_types_from_taxonomy( $taxonomy ),
442
  ],
9
 
10
  use The_SEO_Framework\Bridges\SeoSettings,
11
  The_SEO_Framework\Interpreters\HTML,
12
+ The_SEO_Framework\Interpreters\Settings_Input as Input;
13
 
14
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
15
 
16
+ switch ( $this->get_view_instance( 'general', $instance ) ) :
17
+ case 'general_main':
18
+ $_settings_class = SeoSettings::class;
19
 
20
+ $tabs = [
 
 
21
  'layout' => [
22
  'name' => __( 'Layout', 'autodescription' ),
23
+ 'callback' => [ $_settings_class, '_general_metabox_layout_tab' ],
24
  'dashicon' => 'screenoptions',
25
  ],
26
  'performance' => [
27
  'name' => __( 'Performance', 'autodescription' ),
28
+ 'callback' => [ $_settings_class, '_general_metabox_performance_tab' ],
29
  'dashicon' => 'performance',
30
  ],
31
  'canonical' => [
32
  'name' => __( 'Canonical', 'autodescription' ),
33
+ 'callback' => [ $_settings_class, '_general_metabox_canonical_tab' ],
34
  'dashicon' => 'external',
35
  ],
36
  'timestamps' => [
37
  'name' => __( 'Timestamps', 'autodescription' ),
38
+ 'callback' => [ $_settings_class, '_general_metabox_timestamps_tab' ],
39
  'dashicon' => 'clock',
40
  ],
41
  'exclusions' => [
42
  'name' => __( 'Exclusions', 'autodescription' ),
43
+ 'callback' => [ $_settings_class, '_general_metabox_exclusions_tab' ],
44
  'dashicon' => 'editor-unlink',
45
  ],
46
  ];
47
 
48
+ SeoSettings::_nav_tab_wrapper(
49
+ 'general',
50
+ /**
51
+ * @since 2.8.0
52
+ * @param array $tabs The default tabs.
53
+ */
54
+ (array) apply_filters( 'the_seo_framework_general_settings_tabs', $tabs )
55
+ );
 
 
56
  break;
57
 
58
+ case 'general_layout_tab':
59
+ HTML::header_title( __( 'Administrative Layout Settings', 'autodescription' ) );
60
  HTML::description( __( 'SEO hints can be visually displayed throughout the dashboard.', 'autodescription' ) );
61
 
62
  ?>
63
  <hr>
64
  <?php
65
+ HTML::header_title( __( 'SEO Bar Settings', 'autodescription' ) );
66
  HTML::wrap_fields(
67
  [
68
+ Input::make_checkbox( [
69
  'id' => 'display_seo_bar_tables',
70
  'label' => esc_html__( 'Display the SEO Bar in overview tables?', 'autodescription' ),
71
  'escape' => false,
72
  ] ),
73
+ Input::make_checkbox( [
74
  'id' => 'display_seo_bar_metabox',
75
  'label' => esc_html__( 'Display the SEO Bar in the SEO Settings metabox?', 'autodescription' ),
76
  'escape' => false,
77
  ] ),
78
+ Input::make_checkbox( [
79
  'id' => 'seo_bar_symbols',
80
  'label' => esc_html__( 'Use symbols for warnings?', 'autodescription' ) . ' ' . HTML::make_info(
81
  __( 'If you have difficulty discerning colors, this may help you spot issues more easily.', 'autodescription' ),
91
  ?>
92
  <hr>
93
  <?php
94
+ HTML::header_title( __( 'Counter Settings', 'autodescription' ) );
95
 
96
  $pixel_info = HTML::make_info(
97
  __( 'The pixel counter computes whether the input will fit on search engine result pages.', 'autodescription' ),
107
 
108
  HTML::wrap_fields(
109
  [
110
+ Input::make_checkbox( [
111
  'id' => 'display_pixel_counter',
112
+ 'label' => esc_html__( 'Display pixel counters?', 'autodescription' ) . " $pixel_info",
113
  'escape' => false,
114
  ] ),
115
+ Input::make_checkbox( [
116
  'id' => 'display_character_counter',
117
+ 'label' => esc_html__( 'Display character counters?', 'autodescription' ) . " $character_info",
118
  'escape' => false,
119
  ] ),
120
  ],
122
  );
123
  break;
124
 
125
+ case 'general_performance_tab':
126
+ HTML::header_title( __( 'Performance Settings', 'autodescription' ) );
127
  HTML::description( __( "Depending on your server's configuration, adjusting these settings can affect performance.", 'autodescription' ) );
128
 
129
  ?>
130
  <hr>
131
  <?php
132
+ HTML::header_title( __( 'Query Alteration Settings', 'autodescription' ) );
133
  HTML::description_noesc(
134
  esc_html__( "Altering the query allows for more control of the site's hierarchy.", 'autodescription' )
135
  . '<br>' .
182
  '<label for="%1$s">%2$s</label>
183
  <select name="%3$s" id="%1$s">%4$s</select>',
184
  [
185
+ Input::get_field_id( 'alter_search_query_type' ),
186
  $perform_alteration_i18n,
187
+ Input::get_field_name( 'alter_search_query_type' ),
188
  $search_query_select_options,
189
  ]
190
  );
193
  '<label for="%1$s">%2$s</label>
194
  <select name="%3$s" id="%1$s">%4$s</select>',
195
  [
196
+ Input::get_field_id( 'alter_archive_query_type' ),
197
  $perform_alteration_i18n,
198
+ Input::get_field_name( 'alter_archive_query_type' ),
199
  $archive_query_select_options,
200
  ]
201
  );
202
 
203
  HTML::wrap_fields(
204
  [
205
+ Input::make_checkbox( [
206
  'id' => 'alter_search_query',
207
  'label' => esc_html__( 'Enable search query alteration?', 'autodescription' )
208
  . ' ' . HTML::make_info( __( 'This allows you to exclude pages from on-site search results.', 'autodescription' ), '', false ),
215
 
216
  HTML::wrap_fields(
217
  [
218
+ Input::make_checkbox( [
219
  'id' => 'alter_archive_query',
220
  'label' => esc_html__( 'Enable archive query alteration?', 'autodescription' )
221
  . ' ' . HTML::make_info( __( 'This allows you to exclude pages from on-site archive listings.', 'autodescription' ), '', false ),
228
  ?>
229
  <hr>
230
  <?php
231
+ HTML::header_title( __( 'Transient Cache Settings', 'autodescription' ) );
232
  HTML::description( __( 'To improve performance, generated output can be stored in the database as transient cache.', 'autodescription' ) );
233
 
234
  HTML::wrap_fields(
235
+ Input::make_checkbox( [
236
  'id' => 'cache_sitemap',
237
  'label' => esc_html__( 'Enable optimized sitemap generation cache?', 'autodescription' )
238
  . ' ' . HTML::make_info( __( 'Generating the sitemap can use a lot of server resources.', 'autodescription' ), '', false ),
242
  );
243
  break;
244
 
245
+ case 'general_canonical_tab':
246
+ HTML::header_title( __( 'Canonical URL Settings', 'autodescription' ) );
247
  HTML::description( __( 'The canonical URL meta tag urges search engines to go to the outputted URL.', 'autodescription' ) );
248
  HTML::description( __( 'If the canonical URL meta tag represents the visited page, then the search engine will crawl the visited page. Otherwise, the search engine may go to the outputted URL.', 'autodescription' ) );
249
  ?>
250
  <hr>
251
  <?php
252
+ HTML::header_title( __( 'Scheme Settings', 'autodescription' ) );
253
  HTML::description( __( 'If your website is accessible via both HTTP as HTTPS, you may want to set this to HTTPS if not detected automatically. Secure connections are preferred by search engines.', 'autodescription' ) );
254
  ?>
255
+ <label for="<?php Input::field_id( 'canonical_scheme' ); ?>"><?php echo esc_html_x( 'Preferred canonical URL scheme:', '= Detect Automatically, HTTPS, HTTP', 'autodescription' ); ?></label>
256
+ <select name="<?php Input::field_name( 'canonical_scheme' ); ?>" id="<?php Input::field_id( 'canonical_scheme' ); ?>">
257
  <?php
258
  $scheme_types = (array) apply_filters(
259
  'the_seo_framework_canonical_scheme_types',
274
 
275
  <hr>
276
  <?php
277
+ HTML::header_title( __( 'Link Relationship Settings', 'autodescription' ) );
278
  HTML::description( __( 'Some search engines look for relations between the content of your pages. If you have pagination on a post or page, or have archives indexed, these options will help search engines look for the right page to display in the search results.', 'autodescription' ) );
279
  HTML::description( __( "It's recommended to turn these options on for better SEO consistency and to prevent duplicated content issues.", 'autodescription' ) );
280
 
281
+ $prev_next_posts_checkbox = Input::make_checkbox( [
282
  'id' => 'prev_next_posts',
283
  'label' => $this->convert_markdown(
284
  /* translators: the backticks are Markdown! Preserve them as-is! */
288
  'escape' => false,
289
  ] );
290
 
291
+ $prev_next_archives_checkbox = Input::make_checkbox( [
292
  'id' => 'prev_next_archives',
293
  'label' => $this->convert_markdown(
294
  /* translators: the backticks are Markdown! Preserve them as-is! */
298
  'escape' => false,
299
  ] );
300
 
301
+ $prev_next_frontpage_checkbox = Input::make_checkbox( [
302
  'id' => 'prev_next_frontpage',
303
  'label' => $this->convert_markdown(
304
  /* translators: the backticks are Markdown! Preserve them as-is! */
311
  HTML::wrap_fields( $prev_next_posts_checkbox . $prev_next_archives_checkbox . $prev_next_frontpage_checkbox, true );
312
  break;
313
 
314
+ case 'general_timestamps_tab':
315
  $timestamp_0 = gmdate( $this->get_timestamp_format( false ) );
316
  $timestamp_1 = gmdate( $this->get_timestamp_format( true ) );
317
 
318
+ HTML::header_title( __( 'Timestamp Settings', 'autodescription' ) );
319
  HTML::description( __( 'Timestamps help indicate when a page has been published and modified.', 'autodescription' ) );
320
  ?>
321
  <hr>
322
 
323
  <fieldset>
324
+ <legend><?php HTML::header_title( __( 'Timestamp Format Settings', 'autodescription' ) ); ?></legend>
325
  <?php HTML::description( __( 'This setting determines how specific the timestamp is.', 'autodescription' ) ); ?>
326
 
327
  <p id="sitemaps-timestamp-format" class="tsf-fields">
328
  <span class="tsf-toblock">
329
+ <input type="radio" name="<?php Input::field_name( 'timestamps_format' ); ?>" id="<?php Input::field_id( 'timestamps_format_0' ); ?>" value="0" <?php checked( $this->get_option( 'timestamps_format' ), '0' ); ?> />
330
+ <label for="<?php Input::field_id( 'timestamps_format_0' ); ?>">
331
  <?php
332
  // phpcs:ignore, WordPress.Security.EscapeOutput -- code_wrap escapes.
333
  echo HTML::code_wrap( $timestamp_0 ), ' ', HTML::make_info(
337
  </label>
338
  </span>
339
  <span class="tsf-toblock">
340
+ <input type="radio" name="<?php Input::field_name( 'timestamps_format' ); ?>" id="<?php Input::field_id( 'timestamps_format_1' ); ?>" value="1" <?php checked( $this->get_option( 'timestamps_format' ), '1' ); ?> />
341
+ <label for="<?php Input::field_id( 'timestamps_format_1' ); ?>">
342
  <?php
343
  // phpcs:ignore, WordPress.Security.EscapeOutput -- code_wrap escapes.
344
  echo HTML::code_wrap( $timestamp_1 ), ' ', HTML::make_info(
352
  <?php
353
  break;
354
 
355
+ case 'general_exclusions_tab':
356
+ HTML::header_title( __( 'Exclusion Settings', 'autodescription' ) );
 
 
 
357
  HTML::description( __( 'When checked, these options will remove meta optimizations, SEO suggestions, and sitemap inclusions for the selected post types and taxonomies. This will allow search engines to crawl the post type and taxonomies without advanced restrictions or directions.', 'autodescription' ) );
358
  HTML::attention_description_noesc(
359
  $this->convert_markdown(
367
 
368
  <hr>
369
  <?php
370
+ HTML::header_title( __( 'Post Type Exclusions', 'autodescription' ) );
371
  HTML::description( __( 'Select post types which should be excluded.', 'autodescription' ) );
372
  HTML::description( __( 'These settings apply to the post type pages and their terms. When terms are shared between post types, all their post types should be checked for this to have an effect.', 'autodescription' ) );
373
 
374
  $forced_pt = $this->get_forced_supported_post_types();
375
  $boxes = [];
376
 
 
 
377
  foreach ( $this->get_public_post_types() as $post_type ) {
378
  $_label = $this->get_post_type_label( $post_type, false );
379
  if ( ! strlen( $_label ) ) continue;
384
  esc_html( $post_type )
385
  );
386
 
387
+ $boxes[] = Input::make_checkbox( [
388
+ 'id' => [ 'disabled_post_types', $post_type ],
389
  'class' => 'tsf-excluded-post-types',
 
390
  'label' => $_label,
391
  'escape' => false,
392
  'disabled' => in_array( $post_type, $forced_pt, true ),
 
 
393
  ] );
394
  }
395
 
398
  ?>
399
  <hr>
400
  <?php
401
+ HTML::header_title( __( 'Taxonomy Exclusions', 'autodescription' ) );
402
  HTML::description( __( 'Select taxonomies which should be excluded.', 'autodescription' ) );
403
  HTML::description( __( 'When taxonomies have all their bound post types excluded, they will inherit their exclusion status.', 'autodescription' ) );
404
 
417
  esc_html( $taxonomy )
418
  );
419
 
420
+ $boxes[] = Input::make_checkbox( [
421
+ 'id' => [ 'disabled_taxonomies', $taxonomy ],
422
  'class' => 'tsf-excluded-taxonomies',
 
423
  'label' => $_label,
424
  'escape' => false,
425
  'disabled' => in_array( $taxonomy, $forced_tax, true ),
 
 
426
  'data' => [
427
  'postTypes' => $this->get_post_types_from_taxonomy( $taxonomy ),
428
  ],
inc/views/{admin/metaboxes/homepage-metabox.php → settings/metaboxes/homepage.php} RENAMED
@@ -9,66 +9,60 @@
9
 
10
  use The_SEO_Framework\Bridges\SeoSettings,
11
  The_SEO_Framework\Interpreters\HTML,
12
- The_SEO_Framework\Interpreters\Form;
 
13
 
14
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
15
-
16
- // Fetch the required instance within this file.
17
- $instance = $this->get_view_instance( 'the_seo_framework_homepage_metabox', $instance );
18
 
19
  $home_id = $this->get_the_front_page_ID();
20
 
21
- $_generator_args = [
22
- 'id' => $home_id,
23
- 'taxonomy' => '',
24
- ];
25
 
26
- switch ( $instance ) :
27
- case 'the_seo_framework_homepage_metabox_main':
28
  HTML::description( __( 'These settings will take precedence over the settings set within the homepage edit screen, if any.', 'autodescription' ) );
29
  ?>
30
  <hr>
31
  <?php
 
32
 
33
- $default_tabs = [
34
  'general' => [
35
  'name' => __( 'General', 'autodescription' ),
36
- 'callback' => SeoSettings::class . '::_homepage_metabox_general_tab',
37
  'dashicon' => 'admin-generic',
38
  ],
39
  'additions' => [
40
  'name' => __( 'Additions', 'autodescription' ),
41
- 'callback' => SeoSettings::class . '::_homepage_metabox_additions_tab',
42
- 'dashicon' => 'plus',
43
  ],
44
  'social' => [
45
  'name' => __( 'Social', 'autodescription' ),
46
- 'callback' => SeoSettings::class . '::_homepage_metabox_social_tab',
47
  'dashicon' => 'share',
48
  ],
49
  'robots' => [
50
  'name' => __( 'Robots', 'autodescription' ),
51
- 'callback' => SeoSettings::class . '::_homepage_metabox_robots_tab',
52
  'dashicon' => 'visibility',
53
  ],
54
  ];
55
 
56
- /**
57
- * @since 2.6.0
58
- * @param array $defaults The default tabs.
59
- * @param array $args The args added on the callback.
60
- */
61
- $defaults = (array) apply_filters( 'the_seo_framework_homepage_settings_tabs', $default_tabs, $args );
62
-
63
- $tabs = wp_parse_args( $args, $defaults );
64
-
65
- SeoSettings::_nav_tab_wrapper( 'homepage', $tabs );
66
  break;
67
 
68
- case 'the_seo_framework_homepage_metabox_general':
69
  ?>
70
  <p>
71
- <label for="<?php Form::field_id( 'homepage_title' ); ?>" class="tsf-toblock">
72
  <strong><?php esc_html_e( 'Meta Title', 'autodescription' ); ?></strong>
73
  <?php
74
  echo ' ';
@@ -81,24 +75,25 @@ switch ( $instance ) :
81
  </p>
82
  <?php
83
  // Output these unconditionally, with inline CSS attached to allow reacting on settings.
84
- Form::output_character_counter_wrap( Form::get_field_id( 'homepage_title' ), (bool) $this->get_option( 'display_character_counter' ) );
85
- Form::output_pixel_counter_wrap( Form::get_field_id( 'homepage_title' ), 'title', (bool) $this->get_option( 'display_pixel_counter' ) );
86
  ?>
87
  <p class=tsf-title-wrap>
88
- <input type="text" name="<?php Form::field_name( 'homepage_title' ); ?>" class="large-text" id="<?php Form::field_id( 'homepage_title' ); ?>" value="<?php echo $this->esc_attr_preserve_amp( $this->get_option( 'homepage_title' ) ); ?>" autocomplete=off />
89
  <?php
90
  $this->output_js_title_elements(); // legacy
91
  $this->output_js_title_data(
92
- Form::get_field_id( 'homepage_title' ),
93
  [
94
  'state' => [
95
  'refTitleLocked' => false,
96
- 'defaultTitle' =>
97
  ( $home_id ? $this->get_post_meta_item( '_genesis_title', $home_id ) : '' )
98
- ?: $this->get_filtered_raw_generated_title( $_generator_args ),
 
99
  'addAdditions' => $this->use_title_branding( $_generator_args ),
100
  'useSocialTagline' => $this->use_title_branding( $_generator_args, true ),
101
- 'additionValue' => $this->get_home_title_additions(),
102
  'additionPlacement' => 'left' === $this->get_home_title_seplocation() ? 'before' : 'after',
103
  'hasLegacy' => true,
104
  ],
@@ -109,9 +104,8 @@ switch ( $instance ) :
109
  <?php
110
  HTML::description( __( 'Note: The input value of this field may be used to describe the name of the site elsewhere.', 'autodescription' ) );
111
 
112
- if ( $home_id && $this->get_post_meta_item( '_genesis_title', $home_id ) ) {
113
  HTML::description( __( 'Note: The title placeholder is fetched from the Page SEO Settings on the homepage.', 'autodescription' ) );
114
- }
115
 
116
  /**
117
  * @since 2.8.0
@@ -124,7 +118,7 @@ switch ( $instance ) :
124
  sprintf(
125
  /* translators: %s = Homepage URL markdown */
126
  esc_html__( 'A plugin has been detected that suggests to maintain this option on the [homepage](%s).', 'autodescription' ),
127
- esc_url( admin_url( 'post.php?post=' . $home_id . '&action=edit#tsf-inpost-box' ) )
128
  ),
129
  [ 'a' ],
130
  [ 'a_internal' => false ] // opens in new tab.
@@ -135,7 +129,7 @@ switch ( $instance ) :
135
  <hr>
136
 
137
  <p>
138
- <label for="<?php Form::field_id( 'homepage_description' ); ?>" class="tsf-toblock">
139
  <strong><?php esc_html_e( 'Meta Description', 'autodescription' ); ?></strong>
140
  <?php
141
  echo ' ';
@@ -148,15 +142,15 @@ switch ( $instance ) :
148
  </p>
149
  <?php
150
  // Output these unconditionally, with inline CSS attached to allow reacting on settings.
151
- Form::output_character_counter_wrap( Form::get_field_id( 'homepage_description' ), (bool) $this->get_option( 'display_character_counter' ) );
152
- Form::output_pixel_counter_wrap( Form::get_field_id( 'homepage_description' ), 'description', (bool) $this->get_option( 'display_pixel_counter' ) );
153
  ?>
154
  <p>
155
- <textarea name="<?php Form::field_name( 'homepage_description' ); ?>" class="large-text" id="<?php Form::field_id( 'homepage_description' ); ?>" rows="3" cols="70"><?php echo esc_attr( $this->get_option( 'homepage_description' ) ); ?></textarea>
156
  <?php
157
  $this->output_js_description_elements(); // legacy
158
  $this->output_js_description_data(
159
- Form::get_field_id( 'homepage_description' ),
160
  [
161
  'state' => [
162
  'defaultDescription' =>
@@ -187,7 +181,7 @@ switch ( $instance ) :
187
  sprintf(
188
  /* translators: %s = Homepage URL markdown */
189
  esc_html__( 'A plugin has been detected that suggests to maintain this option on the [homepage](%s).', 'autodescription' ),
190
- esc_url( admin_url( 'post.php?post=' . $home_id . '&action=edit#tsf-inpost-box' ) )
191
  ),
192
  [ 'a' ],
193
  [ 'a_internal' => false ] // opens in new tab.
@@ -196,37 +190,38 @@ switch ( $instance ) :
196
  }
197
  break;
198
 
199
- case 'the_seo_framework_homepage_metabox_additions':
200
- $tagline_placeholder = $this->s_title_raw( $this->get_blogdescription() );
201
-
202
  // Fetches escaped title parts.
203
  $_example_title = $this->escape_title(
204
- $this->get_filtered_raw_custom_field_title( $_generator_args ) ?: $this->get_filtered_raw_generated_title( $_generator_args )
 
205
  );
206
  // On JS: The 'Untitled' title will disappear, this is intentional. On no-JS one will see 'Untitled'.
207
  // TODO: Deprecate no-JS support? WordPress doesn't function without JS since 5.0 anyway...
208
- $_example_blogname = $this->escape_title( $this->get_home_title_additions() ?: $this->get_static_untitled_title() );
 
 
 
209
  $_example_separator = esc_html( $this->get_separator( 'title' ) );
210
 
211
  // TODO very readable.
212
- $example_left = '<em><span class="tsf-custom-blogname-js"><span class="tsf-custom-tagline-js">' . $_example_blogname . '</span><span class="tsf-sep-js"> ' . $_example_separator . ' </span></span><span class="tsf-custom-title-js">' . $_example_title . '</span></em>';
213
- $example_right = '<em><span class="tsf-custom-title-js">' . $_example_title . '</span><span class="tsf-custom-blogname-js"><span class="tsf-sep-js"> ' . $_example_separator . ' </span><span class="tsf-custom-tagline-js">' . $_example_blogname . '</span></span></em>';
214
 
215
  ?>
216
-
217
  <p>
218
- <label for="<?php Form::field_id( 'homepage_title_tagline' ); ?>" class="tsf-toblock">
219
  <strong><?php esc_html_e( 'Meta Title Additions', 'autodescription' ); ?></strong>
220
  </label>
221
  </p>
222
  <p>
223
- <input type="text" name="<?php Form::field_name( 'homepage_title_tagline' ); ?>" class="large-text" id="<?php Form::field_id( 'homepage_title_tagline' ); ?>" placeholder="<?php echo esc_attr( $tagline_placeholder ); ?>" value="<?php echo $this->esc_attr_preserve_amp( $this->get_option( 'homepage_title_tagline' ) ); ?>" autocomplete=off />
224
  </p>
225
 
226
- <div id="tsf-title-tagline-toggle">
227
  <?php
228
  HTML::wrap_fields(
229
- Form::make_checkbox( [
230
  'id' => 'homepage_tagline',
231
  'label' => __( 'Add Meta Title Additions to the homepage title?', 'autodescription' ),
232
  ] ),
@@ -238,12 +233,12 @@ switch ( $instance ) :
238
  <hr>
239
 
240
  <fieldset>
241
- <legend><?php Form::header_title( __( 'Meta Title Additions Location', 'autodescription' ) ); ?></legend>
242
 
243
  <p id="tsf-home-title-location" class="tsf-fields">
244
  <span class="tsf-toblock">
245
- <input type="radio" name="<?php Form::field_name( 'home_title_location' ); ?>" id="<?php Form::field_id( 'home_title_location_left' ); ?>" value="left" <?php checked( $this->get_option( 'home_title_location' ), 'left' ); ?> />
246
- <label for="<?php Form::field_id( 'home_title_location_left' ); ?>">
247
  <span><?php esc_html_e( 'Left:', 'autodescription' ); ?></span>
248
  <?php
249
  // phpcs:ignore, WordPress.Security.EscapeOutput -- $example_left is already escaped.
@@ -252,8 +247,8 @@ switch ( $instance ) :
252
  </label>
253
  </span>
254
  <span class="tsf-toblock">
255
- <input type="radio" name="<?php Form::field_name( 'home_title_location' ); ?>" id="<?php Form::field_id( 'home_title_location_right' ); ?>" value="right" <?php checked( $this->get_option( 'home_title_location' ), 'right' ); ?> />
256
- <label for="<?php Form::field_id( 'home_title_location_right' ); ?>">
257
  <span><?php esc_html_e( 'Right:', 'autodescription' ); ?></span>
258
  <?php
259
  // phpcs:ignore, WordPress.Security.EscapeOutput -- $example_right is already escaped.
@@ -266,31 +261,60 @@ switch ( $instance ) :
266
  <?php
267
  break;
268
 
269
- case 'the_seo_framework_homepage_metabox_social':
 
 
 
 
 
270
  // Gets custom fields from page.
271
- $custom_og_title = $home_id ? $this->get_post_meta_item( '_open_graph_title', $home_id ) : '';
272
- $custom_og_desc = $home_id ? $this->get_post_meta_item( '_open_graph_description', $home_id ) : '';
273
- $custom_tw_title = $home_id ? $this->get_post_meta_item( '_twitter_title', $home_id ) : '';
274
- $custom_tw_desc = $home_id ? $this->get_post_meta_item( '_twitter_description', $home_id ) : '';
 
 
275
 
276
- $social_placeholders = $this->_get_social_placeholders( $_generator_args, 'settings' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
  ?>
279
  <p>
280
- <label for="<?php Form::field_id( 'homepage_og_title' ); ?>" class="tsf-toblock">
281
- <strong>
282
- <?php
283
- esc_html_e( 'Open Graph Title', 'autodescription' );
284
- ?>
285
- </strong>
286
  </label>
287
  </p>
288
  <?php
289
  // Output this unconditionally, with inline CSS attached to allow reacting on settings.
290
- Form::output_character_counter_wrap( Form::get_field_id( 'homepage_og_title' ), (bool) $this->get_option( 'display_character_counter' ) );
291
  ?>
292
  <p>
293
- <input type="text" name="<?php Form::field_name( 'homepage_og_title' ); ?>" class="large-text" id="<?php Form::field_id( 'homepage_og_title' ); ?>" placeholder="<?php echo esc_attr( $social_placeholders['title']['og'] ); ?>" value="<?php echo $this->esc_attr_preserve_amp( $this->get_option( 'homepage_og_title' ) ); ?>" autocomplete=off />
294
  </p>
295
  <?php
296
  if ( $this->has_page_on_front() && $custom_og_title ) {
@@ -301,20 +325,16 @@ switch ( $instance ) :
301
  ?>
302
 
303
  <p>
304
- <label for="<?php Form::field_id( 'homepage_og_description' ); ?>" class="tsf-toblock">
305
- <strong>
306
- <?php
307
- esc_html_e( 'Open Graph Description', 'autodescription' );
308
- ?>
309
- </strong>
310
  </label>
311
  </p>
312
  <?php
313
  // Output this unconditionally, with inline CSS attached to allow reacting on settings.
314
- Form::output_character_counter_wrap( Form::get_field_id( 'homepage_og_description' ), (bool) $this->get_option( 'display_character_counter' ) );
315
  ?>
316
  <p>
317
- <textarea name="<?php Form::field_name( 'homepage_og_description' ); ?>" class="large-text" id="<?php Form::field_id( 'homepage_og_description' ); ?>" rows="3" cols="70" placeholder="<?php echo esc_attr( $social_placeholders['description']['og'] ); ?>" autocomplete=off><?php echo esc_attr( $this->get_option( 'homepage_og_description' ) ); ?></textarea>
318
  </p>
319
  <?php
320
  if ( $this->has_page_on_front() && $custom_og_desc ) {
@@ -326,20 +346,16 @@ switch ( $instance ) :
326
  <hr>
327
 
328
  <p>
329
- <label for="<?php Form::field_id( 'homepage_twitter_title' ); ?>" class="tsf-toblock">
330
- <strong>
331
- <?php
332
- esc_html_e( 'Twitter Title', 'autodescription' );
333
- ?>
334
- </strong>
335
  </label>
336
  </p>
337
  <?php
338
  // Output this unconditionally, with inline CSS attached to allow reacting on settings.
339
- Form::output_character_counter_wrap( Form::get_field_id( 'homepage_twitter_title' ), (bool) $this->get_option( 'display_character_counter' ) );
340
  ?>
341
  <p>
342
- <input type="text" name="<?php Form::field_name( 'homepage_twitter_title' ); ?>" class="large-text" id="<?php Form::field_id( 'homepage_twitter_title' ); ?>" placeholder="<?php echo esc_attr( $social_placeholders['title']['twitter'] ); ?>" value="<?php echo $this->esc_attr_preserve_amp( $this->get_option( 'homepage_twitter_title' ) ); ?>" autocomplete=off />
343
  </p>
344
  <?php
345
  if ( $this->has_page_on_front() && ( $custom_og_title || $custom_tw_title ) ) {
@@ -350,20 +366,16 @@ switch ( $instance ) :
350
  ?>
351
 
352
  <p>
353
- <label for="<?php Form::field_id( 'homepage_twitter_description' ); ?>" class="tsf-toblock">
354
- <strong>
355
- <?php
356
- esc_html_e( 'Twitter Description', 'autodescription' );
357
- ?>
358
- </strong>
359
  </label>
360
  </p>
361
  <?php
362
  // Output this unconditionally, with inline CSS attached to allow reacting on settings.
363
- Form::output_character_counter_wrap( Form::get_field_id( 'homepage_twitter_description' ), (bool) $this->get_option( 'display_character_counter' ) );
364
  ?>
365
  <p>
366
- <textarea name="<?php Form::field_name( 'homepage_twitter_description' ); ?>" class="large-text" id="<?php Form::field_id( 'homepage_twitter_description' ); ?>" rows="3" cols="70" placeholder="<?php echo esc_attr( $social_placeholders['description']['twitter'] ); ?>" autocomplete=off><?php echo esc_attr( $this->get_option( 'homepage_twitter_description' ) ); ?></textarea>
367
  </p>
368
  <?php
369
  if ( $this->has_page_on_front() && ( $custom_og_desc || $custom_tw_desc ) ) {
@@ -374,13 +386,8 @@ switch ( $instance ) :
374
  ?>
375
  <hr>
376
  <?php
377
- Form::header_title( __( 'Social Image Settings', 'autodescription' ) );
378
  HTML::description( __( 'A social image can be displayed when your homepage is shared. It is a great way to grab attention.', 'autodescription' ) );
379
-
380
- // Fetch image placeholder.
381
- $image_details = current( $this->get_generated_image_details( $_generator_args, true, 'social', true ) );
382
- $image_placeholder = isset( $image_details['url'] ) ? $image_details['url'] : '';
383
-
384
  ?>
385
  <p>
386
  <label for="tsf_homepage_socialimage-url">
@@ -394,8 +401,8 @@ switch ( $instance ) :
394
  </label>
395
  </p>
396
  <p>
397
- <input class="large-text" type="url" name="<?php Form::field_name( 'homepage_social_image_url' ); ?>" id="tsf_homepage_socialimage-url" placeholder="<?php echo esc_url( $image_placeholder ); ?>" value="<?php echo esc_url( $this->get_option( 'homepage_social_image_url' ) ); ?>" />
398
- <input type="hidden" name="<?php Form::field_name( 'homepage_social_image_id' ); ?>" id="tsf_homepage_socialimage-id" value="<?php echo absint( $this->get_option( 'homepage_social_image_id' ) ); ?>" disabled class="tsf-enable-media-if-js" />
399
  </p>
400
  <p class="hide-if-no-tsf-js">
401
  <?php
@@ -406,7 +413,7 @@ switch ( $instance ) :
406
  <?php
407
  break;
408
 
409
- case 'the_seo_framework_homepage_metabox_robots':
410
  $noindex_post = $home_id ? $this->get_post_meta_item( '_genesis_noindex', $home_id ) : '';
411
  $nofollow_post = $home_id ? $this->get_post_meta_item( '_genesis_nofollow', $home_id ) : '';
412
  $noarchive_post = $home_id ? $this->get_post_meta_item( '_genesis_noarchive', $home_id ) : '';
@@ -424,7 +431,7 @@ switch ( $instance ) :
424
  vsprintf(
425
  '<a href="%s" title="%s" target=_blank class=attention>%s</a>',
426
  [
427
- esc_url( admin_url( 'post.php?post=' . $home_id . '&action=edit#tsf-inpost-box' ) ),
428
  esc_attr_x( 'Edit homepage page settings', 'Bear with me: the homepage can be edited globally, or via its page. Thus "homepage page".', 'autodescription' ),
429
  esc_html__( 'Overwritten by page settings', 'autodescription' ),
430
  ]
@@ -432,7 +439,7 @@ switch ( $instance ) :
432
  );
433
  }
434
 
435
- Form::header_title( __( 'Robots Meta Settings', 'autodescription' ) );
436
 
437
  $i_label = sprintf(
438
  /* translators: 1: Option label, 2: [?] option info note, 3: Optional warning */
@@ -486,17 +493,17 @@ switch ( $instance ) :
486
 
487
  HTML::wrap_fields(
488
  [
489
- Form::make_checkbox( [
490
  'id' => 'homepage_noindex',
491
  'label' => $i_label,
492
  'escape' => false,
493
  ] ),
494
- Form::make_checkbox( [
495
  'id' => 'homepage_nofollow',
496
  'label' => $f_label,
497
  'escape' => false,
498
  ] ),
499
- Form::make_checkbox( [
500
  'id' => 'homepage_noarchive',
501
  'label' => $a_label,
502
  'escape' => false,
@@ -511,7 +518,7 @@ switch ( $instance ) :
511
  sprintf(
512
  /* translators: %s = Homepage URL markdown */
513
  esc_html__( 'Note: These options may be overwritten by the [page settings](%s).', 'autodescription' ),
514
- esc_url( admin_url( 'post.php?post=' . $home_id . '&action=edit#tsf-inpost-box' ) )
515
  ),
516
  [ 'a' ],
517
  [ 'a_internal' => false ]
@@ -522,12 +529,11 @@ switch ( $instance ) :
522
 
523
  <hr>
524
  <?php
525
- Form::header_title( __( 'Homepage Pagination Robots Settings', 'autodescription' ) );
526
  HTML::description( __( "If your homepage is paginated and outputs content that's also found elsewhere on the website, enabling this option may prevent duplicate content.", 'autodescription' ) );
527
 
528
- // Echo checkbox.
529
  HTML::wrap_fields(
530
- Form::make_checkbox( [
531
  'id' => 'home_paged_noindex',
532
  'label' => $this->convert_markdown(
533
  /* translators: the backticks are Markdown! Preserve them as-is! */
9
 
10
  use The_SEO_Framework\Bridges\SeoSettings,
11
  The_SEO_Framework\Interpreters\HTML,
12
+ The_SEO_Framework\Interpreters\Form,
13
+ The_SEO_Framework\Interpreters\Settings_Input as Input;
14
 
15
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
 
 
 
16
 
17
  $home_id = $this->get_the_front_page_ID();
18
 
19
+ $_generator_args = [ 'id' => $home_id ];
 
 
 
20
 
21
+ switch ( $this->get_view_instance( 'homepage', $instance ) ) :
22
+ case 'homepage_main':
23
  HTML::description( __( 'These settings will take precedence over the settings set within the homepage edit screen, if any.', 'autodescription' ) );
24
  ?>
25
  <hr>
26
  <?php
27
+ $_settings_class = SeoSettings::class;
28
 
29
+ $tabs = [
30
  'general' => [
31
  'name' => __( 'General', 'autodescription' ),
32
+ 'callback' => [ $_settings_class, '_homepage_metabox_general_tab' ],
33
  'dashicon' => 'admin-generic',
34
  ],
35
  'additions' => [
36
  'name' => __( 'Additions', 'autodescription' ),
37
+ 'callback' => [ $_settings_class, '_homepage_metabox_additions_tab' ],
38
+ 'dashicon' => 'plus-alt2',
39
  ],
40
  'social' => [
41
  'name' => __( 'Social', 'autodescription' ),
42
+ 'callback' => [ $_settings_class, '_homepage_metabox_social_tab' ],
43
  'dashicon' => 'share',
44
  ],
45
  'robots' => [
46
  'name' => __( 'Robots', 'autodescription' ),
47
+ 'callback' => [ $_settings_class, '_homepage_metabox_robots_tab' ],
48
  'dashicon' => 'visibility',
49
  ],
50
  ];
51
 
52
+ SeoSettings::_nav_tab_wrapper(
53
+ 'homepage',
54
+ /**
55
+ * @since 2.6.0
56
+ * @param array $tabs The default tabs.
57
+ */
58
+ (array) apply_filters( 'the_seo_framework_homepage_settings_tabs', $tabs )
59
+ );
 
 
60
  break;
61
 
62
+ case 'homepage_general_tab':
63
  ?>
64
  <p>
65
+ <label for="<?php Input::field_id( 'homepage_title' ); ?>" class="tsf-toblock">
66
  <strong><?php esc_html_e( 'Meta Title', 'autodescription' ); ?></strong>
67
  <?php
68
  echo ' ';
75
  </p>
76
  <?php
77
  // Output these unconditionally, with inline CSS attached to allow reacting on settings.
78
+ Form::output_character_counter_wrap( Input::get_field_id( 'homepage_title' ), (bool) $this->get_option( 'display_character_counter' ) );
79
+ Form::output_pixel_counter_wrap( Input::get_field_id( 'homepage_title' ), 'title', (bool) $this->get_option( 'display_pixel_counter' ) );
80
  ?>
81
  <p class=tsf-title-wrap>
82
+ <input type="text" name="<?php Input::field_name( 'homepage_title' ); ?>" class="large-text" id="<?php Input::field_id( 'homepage_title' ); ?>" value="<?php echo $this->esc_attr_preserve_amp( $this->get_option( 'homepage_title' ) ); ?>" autocomplete=off />
83
  <?php
84
  $this->output_js_title_elements(); // legacy
85
  $this->output_js_title_data(
86
+ Input::get_field_id( 'homepage_title' ),
87
  [
88
  'state' => [
89
  'refTitleLocked' => false,
90
+ 'defaultTitle' => $this->s_title(
91
  ( $home_id ? $this->get_post_meta_item( '_genesis_title', $home_id ) : '' )
92
+ ?: $this->get_filtered_raw_generated_title( $_generator_args )
93
+ ),
94
  'addAdditions' => $this->use_title_branding( $_generator_args ),
95
  'useSocialTagline' => $this->use_title_branding( $_generator_args, true ),
96
+ 'additionValue' => $this->s_title( $this->get_home_title_additions() ),
97
  'additionPlacement' => 'left' === $this->get_home_title_seplocation() ? 'before' : 'after',
98
  'hasLegacy' => true,
99
  ],
104
  <?php
105
  HTML::description( __( 'Note: The input value of this field may be used to describe the name of the site elsewhere.', 'autodescription' ) );
106
 
107
+ if ( $home_id && $this->get_post_meta_item( '_genesis_title', $home_id ) )
108
  HTML::description( __( 'Note: The title placeholder is fetched from the Page SEO Settings on the homepage.', 'autodescription' ) );
 
109
 
110
  /**
111
  * @since 2.8.0
118
  sprintf(
119
  /* translators: %s = Homepage URL markdown */
120
  esc_html__( 'A plugin has been detected that suggests to maintain this option on the [homepage](%s).', 'autodescription' ),
121
+ esc_url( admin_url( "post.php?post={$home_id}&action=edit#tsf-inpost-box" ) )
122
  ),
123
  [ 'a' ],
124
  [ 'a_internal' => false ] // opens in new tab.
129
  <hr>
130
 
131
  <p>
132
+ <label for="<?php Input::field_id( 'homepage_description' ); ?>" class="tsf-toblock">
133
  <strong><?php esc_html_e( 'Meta Description', 'autodescription' ); ?></strong>
134
  <?php
135
  echo ' ';
142
  </p>
143
  <?php
144
  // Output these unconditionally, with inline CSS attached to allow reacting on settings.
145
+ Form::output_character_counter_wrap( Input::get_field_id( 'homepage_description' ), (bool) $this->get_option( 'display_character_counter' ) );
146
+ Form::output_pixel_counter_wrap( Input::get_field_id( 'homepage_description' ), 'description', (bool) $this->get_option( 'display_pixel_counter' ) );
147
  ?>
148
  <p>
149
+ <textarea name="<?php Input::field_name( 'homepage_description' ); ?>" class="large-text" id="<?php Input::field_id( 'homepage_description' ); ?>" rows="3" cols="70"><?php echo esc_attr( $this->get_option( 'homepage_description' ) ); ?></textarea>
150
  <?php
151
  $this->output_js_description_elements(); // legacy
152
  $this->output_js_description_data(
153
+ Input::get_field_id( 'homepage_description' ),
154
  [
155
  'state' => [
156
  'defaultDescription' =>
181
  sprintf(
182
  /* translators: %s = Homepage URL markdown */
183
  esc_html__( 'A plugin has been detected that suggests to maintain this option on the [homepage](%s).', 'autodescription' ),
184
+ esc_url( admin_url( "post.php?post=$home_id&action=edit#tsf-inpost-box" ) )
185
  ),
186
  [ 'a' ],
187
  [ 'a_internal' => false ] // opens in new tab.
190
  }
191
  break;
192
 
193
+ case 'homepage_additions_tab':
 
 
194
  // Fetches escaped title parts.
195
  $_example_title = $this->escape_title(
196
+ $this->get_filtered_raw_custom_field_title( $_generator_args )
197
+ ?: $this->get_filtered_raw_generated_title( $_generator_args )
198
  );
199
  // On JS: The 'Untitled' title will disappear, this is intentional. On no-JS one will see 'Untitled'.
200
  // TODO: Deprecate no-JS support? WordPress doesn't function without JS since 5.0 anyway...
201
+ $_example_blogname = $this->escape_title(
202
+ $this->get_home_title_additions()
203
+ ?: $this->get_static_untitled_title()
204
+ );
205
  $_example_separator = esc_html( $this->get_separator( 'title' ) );
206
 
207
  // TODO very readable.
208
+ $example_left = "<em><span class=tsf-custom-blogname-js><span class=tsf-custom-tagline-js>$_example_blogname</span><span class=tsf-sep-js> $_example_separator </span></span><span class=tsf-custom-title-js>$_example_title</span></em>";
209
+ $example_right = "<em><span class=tsf-custom-title-js>$_example_title</span><span class=tsf-custom-blogname-js><span class=tsf-sep-js> $_example_separator </span><span class=tsf-custom-tagline-js>$_example_blogname</span></span></em>";
210
 
211
  ?>
 
212
  <p>
213
+ <label for="<?php Input::field_id( 'homepage_title_tagline' ); ?>" class="tsf-toblock">
214
  <strong><?php esc_html_e( 'Meta Title Additions', 'autodescription' ); ?></strong>
215
  </label>
216
  </p>
217
  <p>
218
+ <input type="text" name="<?php Input::field_name( 'homepage_title_tagline' ); ?>" class="large-text" id="<?php Input::field_id( 'homepage_title_tagline' ); ?>" placeholder="<?php echo esc_attr( $this->s_title_raw( $this->get_blogdescription() ) ); ?>" value="<?php echo $this->esc_attr_preserve_amp( $this->get_option( 'homepage_title_tagline' ) ); ?>" autocomplete=off />
219
  </p>
220
 
221
+ <div class=tsf-title-tagline-toggle>
222
  <?php
223
  HTML::wrap_fields(
224
+ Input::make_checkbox( [
225
  'id' => 'homepage_tagline',
226
  'label' => __( 'Add Meta Title Additions to the homepage title?', 'autodescription' ),
227
  ] ),
233
  <hr>
234
 
235
  <fieldset>
236
+ <legend><?php HTML::header_title( __( 'Meta Title Additions Location', 'autodescription' ) ); ?></legend>
237
 
238
  <p id="tsf-home-title-location" class="tsf-fields">
239
  <span class="tsf-toblock">
240
+ <input type="radio" name="<?php Input::field_name( 'home_title_location' ); ?>" id="<?php Input::field_id( 'home_title_location_left' ); ?>" value="left" <?php checked( $this->get_option( 'home_title_location' ), 'left' ); ?> />
241
+ <label for="<?php Input::field_id( 'home_title_location_left' ); ?>">
242
  <span><?php esc_html_e( 'Left:', 'autodescription' ); ?></span>
243
  <?php
244
  // phpcs:ignore, WordPress.Security.EscapeOutput -- $example_left is already escaped.
247
  </label>
248
  </span>
249
  <span class="tsf-toblock">
250
+ <input type="radio" name="<?php Input::field_name( 'home_title_location' ); ?>" id="<?php Input::field_id( 'home_title_location_right' ); ?>" value="right" <?php checked( $this->get_option( 'home_title_location' ), 'right' ); ?> />
251
+ <label for="<?php Input::field_id( 'home_title_location_right' ); ?>">
252
  <span><?php esc_html_e( 'Right:', 'autodescription' ); ?></span>
253
  <?php
254
  // phpcs:ignore, WordPress.Security.EscapeOutput -- $example_right is already escaped.
261
  <?php
262
  break;
263
 
264
+ case 'homepage_social_tab':
265
+ $custom_og_title = '';
266
+ $custom_og_desc = '';
267
+ $custom_tw_title = '';
268
+ $custom_tw_desc = '';
269
+
270
  // Gets custom fields from page.
271
+ if ( $home_id ) {
272
+ $custom_og_title = $this->get_post_meta_item( '_open_graph_title', $home_id );
273
+ $custom_og_desc = $this->get_post_meta_item( '_open_graph_description', $home_id );
274
+ $custom_tw_title = $this->get_post_meta_item( '_twitter_title', $home_id );
275
+ $custom_tw_desc = $this->get_post_meta_item( '_twitter_description', $home_id );
276
+ }
277
 
278
+ $this->output_js_social_data(
279
+ 'homepage_social_settings',
280
+ [
281
+ 'og' => [
282
+ 'state' => [
283
+ 'defaultTitle' => $this->s_title( $custom_og_title ?: $this->get_generated_open_graph_title( $_generator_args, false ) ),
284
+ 'addAdditions' => $this->use_title_branding( $_generator_args, 'og' ),
285
+ 'defaultDesc' => $this->s_description(
286
+ $custom_og_desc ?: $this->get_generated_open_graph_description( $_generator_args, false )
287
+ ),
288
+ 'titlePhLock' => (bool) $custom_og_title,
289
+ 'descPhLock' => (bool) $custom_og_desc,
290
+ ],
291
+ ],
292
+ 'tw' => [
293
+ 'state' => [
294
+ 'defaultTitle' => $this->s_title( $custom_tw_title ?: $this->get_generated_twitter_title( $_generator_args, false ) ),
295
+ 'addAdditions' => $this->use_title_branding( $_generator_args, 'twitter' ),
296
+ 'defaultDesc' => $this->s_description(
297
+ $custom_tw_desc ?: $this->get_generated_twitter_description( $_generator_args, false )
298
+ ),
299
+ 'titlePhLock' => (bool) $custom_tw_title,
300
+ 'descPhLock' => (bool) $custom_tw_desc,
301
+ ],
302
+ ],
303
+ ]
304
+ );
305
 
306
  ?>
307
  <p>
308
+ <label for="<?php Input::field_id( 'homepage_og_title' ); ?>" class="tsf-toblock">
309
+ <strong><?php esc_html_e( 'Open Graph Title', 'autodescription' ); ?></strong>
 
 
 
 
310
  </label>
311
  </p>
312
  <?php
313
  // Output this unconditionally, with inline CSS attached to allow reacting on settings.
314
+ Form::output_character_counter_wrap( Input::get_field_id( 'homepage_og_title' ), (bool) $this->get_option( 'display_character_counter' ) );
315
  ?>
316
  <p>
317
+ <input type="text" name="<?php Input::field_name( 'homepage_og_title' ); ?>" class="large-text" id="<?php Input::field_id( 'homepage_og_title' ); ?>" value="<?php echo $this->esc_attr_preserve_amp( $this->get_option( 'homepage_og_title' ) ); ?>" autocomplete=off data-tsf-social-group=homepage_social_settings data-tsf-social-type=ogTitle />
318
  </p>
319
  <?php
320
  if ( $this->has_page_on_front() && $custom_og_title ) {
325
  ?>
326
 
327
  <p>
328
+ <label for="<?php Input::field_id( 'homepage_og_description' ); ?>" class="tsf-toblock">
329
+ <strong><?php esc_html_e( 'Open Graph Description', 'autodescription' ); ?></strong>
 
 
 
 
330
  </label>
331
  </p>
332
  <?php
333
  // Output this unconditionally, with inline CSS attached to allow reacting on settings.
334
+ Form::output_character_counter_wrap( Input::get_field_id( 'homepage_og_description' ), (bool) $this->get_option( 'display_character_counter' ) );
335
  ?>
336
  <p>
337
+ <textarea name="<?php Input::field_name( 'homepage_og_description' ); ?>" class="large-text" id="<?php Input::field_id( 'homepage_og_description' ); ?>" rows="3" cols="70" autocomplete=off data-tsf-social-group=homepage_social_settings data-tsf-social-type=ogDesc><?php echo esc_attr( $this->get_option( 'homepage_og_description' ) ); ?></textarea>
338
  </p>
339
  <?php
340
  if ( $this->has_page_on_front() && $custom_og_desc ) {
346
  <hr>
347
 
348
  <p>
349
+ <label for="<?php Input::field_id( 'homepage_twitter_title' ); ?>" class="tsf-toblock">
350
+ <strong><?php esc_html_e( 'Twitter Title', 'autodescription' ); ?></strong>
 
 
 
 
351
  </label>
352
  </p>
353
  <?php
354
  // Output this unconditionally, with inline CSS attached to allow reacting on settings.
355
+ Form::output_character_counter_wrap( Input::get_field_id( 'homepage_twitter_title' ), (bool) $this->get_option( 'display_character_counter' ) );
356
  ?>
357
  <p>
358
+ <input type="text" name="<?php Input::field_name( 'homepage_twitter_title' ); ?>" class="large-text" id="<?php Input::field_id( 'homepage_twitter_title' ); ?>" value="<?php echo $this->esc_attr_preserve_amp( $this->get_option( 'homepage_twitter_title' ) ); ?>" autocomplete=off data-tsf-social-group=homepage_social_settings data-tsf-social-type=twTitle />
359
  </p>
360
  <?php
361
  if ( $this->has_page_on_front() && ( $custom_og_title || $custom_tw_title ) ) {
366
  ?>
367
 
368
  <p>
369
+ <label for="<?php Input::field_id( 'homepage_twitter_description' ); ?>" class="tsf-toblock">
370
+ <strong><?php esc_html_e( 'Twitter Description', 'autodescription' ); ?></strong>
 
 
 
 
371
  </label>
372
  </p>
373
  <?php
374
  // Output this unconditionally, with inline CSS attached to allow reacting on settings.
375
+ Form::output_character_counter_wrap( Input::get_field_id( 'homepage_twitter_description' ), (bool) $this->get_option( 'display_character_counter' ) );
376
  ?>
377
  <p>
378
+ <textarea name="<?php Input::field_name( 'homepage_twitter_description' ); ?>" class="large-text" id="<?php Input::field_id( 'homepage_twitter_description' ); ?>" rows="3" cols="70" autocomplete=off data-tsf-social-group=homepage_social_settings data-tsf-social-type=twDesc><?php echo esc_attr( $this->get_option( 'homepage_twitter_description' ) ); ?></textarea>
379
  </p>
380
  <?php
381
  if ( $this->has_page_on_front() && ( $custom_og_desc || $custom_tw_desc ) ) {
386
  ?>
387
  <hr>
388
  <?php
389
+ HTML::header_title( __( 'Social Image Settings', 'autodescription' ) );
390
  HTML::description( __( 'A social image can be displayed when your homepage is shared. It is a great way to grab attention.', 'autodescription' ) );
 
 
 
 
 
391
  ?>
392
  <p>
393
  <label for="tsf_homepage_socialimage-url">
401
  </label>
402
  </p>
403
  <p>
404
+ <input class="large-text" type="url" name="<?php Input::field_name( 'homepage_social_image_url' ); ?>" id="tsf_homepage_socialimage-url" placeholder="<?php echo esc_url( current( $this->get_generated_image_details( $_generator_args, true, 'social', true ) )['url'] ?? '' ); ?>" value="<?php echo esc_url( $this->get_option( 'homepage_social_image_url' ) ); ?>" />
405
+ <input type="hidden" name="<?php Input::field_name( 'homepage_social_image_id' ); ?>" id="tsf_homepage_socialimage-id" value="<?php echo absint( $this->get_option( 'homepage_social_image_id' ) ); ?>" disabled class="tsf-enable-media-if-js" />
406
  </p>
407
  <p class="hide-if-no-tsf-js">
408
  <?php
413
  <?php
414
  break;
415
 
416
+ case 'homepage_robots_tab':
417
  $noindex_post = $home_id ? $this->get_post_meta_item( '_genesis_noindex', $home_id ) : '';
418
  $nofollow_post = $home_id ? $this->get_post_meta_item( '_genesis_nofollow', $home_id ) : '';
419
  $noarchive_post = $home_id ? $this->get_post_meta_item( '_genesis_noarchive', $home_id ) : '';
431
  vsprintf(
432
  '<a href="%s" title="%s" target=_blank class=attention>%s</a>',
433
  [
434
+ esc_url( admin_url( "post.php?post=$home_id&action=edit#tsf-inpost-box" ) ),
435
  esc_attr_x( 'Edit homepage page settings', 'Bear with me: the homepage can be edited globally, or via its page. Thus "homepage page".', 'autodescription' ),
436
  esc_html__( 'Overwritten by page settings', 'autodescription' ),
437
  ]
439
  );
440
  }
441
 
442
+ HTML::header_title( __( 'Robots Meta Settings', 'autodescription' ) );
443
 
444
  $i_label = sprintf(
445
  /* translators: 1: Option label, 2: [?] option info note, 3: Optional warning */
493
 
494
  HTML::wrap_fields(
495
  [
496
+ Input::make_checkbox( [
497
  'id' => 'homepage_noindex',
498
  'label' => $i_label,
499
  'escape' => false,
500
  ] ),
501
+ Input::make_checkbox( [
502
  'id' => 'homepage_nofollow',
503
  'label' => $f_label,
504
  'escape' => false,
505
  ] ),
506
+ Input::make_checkbox( [
507
  'id' => 'homepage_noarchive',
508
  'label' => $a_label,
509
  'escape' => false,
518
  sprintf(
519
  /* translators: %s = Homepage URL markdown */
520
  esc_html__( 'Note: These options may be overwritten by the [page settings](%s).', 'autodescription' ),
521
+ esc_url( admin_url( "post.php?post=$home_id&action=edit#tsf-inpost-box" ) )
522
  ),
523
  [ 'a' ],
524
  [ 'a_internal' => false ]
529
 
530
  <hr>
531
  <?php
532
+ HTML::header_title( __( 'Homepage Pagination Robots Settings', 'autodescription' ) );
533
  HTML::description( __( "If your homepage is paginated and outputs content that's also found elsewhere on the website, enabling this option may prevent duplicate content.", 'autodescription' ) );
534
 
 
535
  HTML::wrap_fields(
536
+ Input::make_checkbox( [
537
  'id' => 'home_paged_noindex',
538
  'label' => $this->convert_markdown(
539
  /* translators: the backticks are Markdown! Preserve them as-is! */
inc/views/{admin → settings}/metaboxes/index.php RENAMED
File without changes
inc/views/settings/metaboxes/post-type-archive.php ADDED
@@ -0,0 +1,469 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Views\Admin\Metaboxes
4
+ * @subpackage The_SEO_Framework\Admin\Settings
5
+ */
6
+
7
+ // phpcs:disable, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- includes.
8
+ // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
9
+
10
+ use The_SEO_Framework\Bridges\SeoSettings,
11
+ The_SEO_Framework\Interpreters\HTML,
12
+ The_SEO_Framework\Interpreters\Form,
13
+ The_SEO_Framework\Interpreters\Settings_Input as Input;
14
+
15
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
16
+
17
+ // Fetch the required instance within this file.
18
+ switch ( $this->get_view_instance( 'post_type_archive', $instance ) ) :
19
+ case 'post_type_archive_main':
20
+ $_settings_class = SeoSettings::class;
21
+ $post_types = $this->get_public_post_type_archives();
22
+
23
+ $post_types_data = [];
24
+ foreach ( $post_types as $post_type ) {
25
+ $post_types_data[ $post_type ] = [
26
+ 'label' => $this->get_post_type_label( $post_type ),
27
+ 'url' => $this->create_canonical_url( [ 'pta' => $post_type ] ),
28
+ 'hasPosts' => $this->has_posts_in_post_type_archive( $post_type ),
29
+ ];
30
+ }
31
+
32
+ printf(
33
+ '<span class=hidden id=tsf-post-type-archive-data %s></span>',
34
+ // phpcs:ignore, WordPress.Security.EscapeOutput.OutputNotEscaped -- This escapes.
35
+ HTML::make_data_attributes( [ 'postTypes' => $post_types_data ] )
36
+ );
37
+
38
+ ?>
39
+ <div id=tsf-post-type-archive-header-wrap class=tsf-fields style=display:none>
40
+ <div id=tsf-post-type-archive-select-wrap>
41
+ <label for=tsf-post-type-archive-selector><?php esc_html_e( 'Select post type:', 'autodescription' ); ?></label>
42
+ <select id=tsf-post-type-archive-selector></select>
43
+ </div>
44
+ </div>
45
+ <?php
46
+ $i = 0;
47
+ foreach ( $post_types as $post_type ) {
48
+ $_generator_args = [ 'pta' => $post_type ];
49
+
50
+ // Create `[ 'doctitle' => [ 'pta', $post_type ] ];`
51
+ $_option_map = array_fill_keys(
52
+ array_keys( $this->get_unfiltered_post_type_archive_meta_defaults() ),
53
+ [ 'pta', $post_type ]
54
+ );
55
+ // Create: `[ 'doctitle' => [ 'pta', $post_type, 'doctitle' ] ];`
56
+ array_walk(
57
+ $_option_map,
58
+ static function( &$input_id, $key ) {
59
+ $input_id = array_merge( $input_id, [ $key ] );
60
+ }
61
+ );
62
+
63
+ $tabs = [
64
+ 'general' => [
65
+ 'name' => __( 'General', 'autodescription' ),
66
+ 'callback' => [ $_settings_class, '_post_type_archive_metabox_general_tab' ],
67
+ 'dashicon' => 'admin-generic',
68
+ 'args' => compact( 'post_type', '_generator_args', '_option_map' ),
69
+ ],
70
+ 'social' => [
71
+ 'name' => __( 'Social', 'autodescription' ),
72
+ 'callback' => [ $_settings_class, '_post_type_archive_metabox_social_tab' ],
73
+ 'dashicon' => 'share',
74
+ 'args' => compact( 'post_type', '_generator_args', '_option_map' ),
75
+ ],
76
+ 'visibility' => [
77
+ 'name' => __( 'Visibility', 'autodescription' ),
78
+ 'callback' => [ $_settings_class, '_post_type_archive_metabox_visibility_tab' ],
79
+ 'dashicon' => 'visibility',
80
+ 'args' => compact( 'post_type', '_generator_args', '_option_map' ),
81
+ ],
82
+ ];
83
+
84
+ // Hide subsequent wraps to prevent layout shifts (bounce) during load: They get hidden by JS anyway.
85
+ printf(
86
+ '<div class="tsf-post-type-archive-wrap%s" %s>',
87
+ // phpcs:ignore, WordPress.Security.EscapeOutput.OutputNotEscaped -- Shut it, noob.
88
+ $i ? ' hide-if-tsf-js' : '',
89
+ // phpcs:ignore, WordPress.Security.EscapeOutput.OutputNotEscaped -- This escapes.
90
+ HTML::make_data_attributes( [ 'postType' => $post_type ] )
91
+ );
92
+ ?>
93
+ <div class=tsf-post-type-header>
94
+ <?php
95
+ // phpcs:ignore, WordPress.Security.EscapeOutput.OutputNotEscaped -- it is.
96
+ echo HTML::get_header_title(
97
+ vsprintf(
98
+ '%s &ndash; <span class=tsf-post-type-archive-details><code>%s</code> %s</span>',
99
+ [
100
+ sprintf(
101
+ /* translators: 1 = Post Type Archive name */
102
+ esc_html__( 'Archive of %s', 'autodescription' ),
103
+ esc_html( $post_types_data[ $post_type ]['label'] )
104
+ ),
105
+ esc_html( $post_type ),
106
+ sprintf(
107
+ '<span class=tsf-post-type-archive-link><a href="%s" target=_blank rel=noopener>[%s]</a></span>',
108
+ esc_url( $post_types_data[ $post_type ]['url'] ),
109
+ esc_html__( 'View archive', 'autodescription' )
110
+ ),
111
+ ]
112
+ )
113
+ );
114
+ ?>
115
+ </div>
116
+ <div class="tsf-post-type-archive-if-excluded hidden">
117
+ <?php
118
+ HTML::attention_description(
119
+ __( "This post type is excluded, so settings won't have any effect.", 'autodescription' )
120
+ )
121
+ ?>
122
+ </div>
123
+ <div class=tsf-post-type-archive-if-not-excluded>
124
+ <?php
125
+ SeoSettings::_nav_tab_wrapper(
126
+ "post_type_archive_{$post_type}",
127
+ /**
128
+ * @since 4.2.0
129
+ * @param array $tabs The default tabs.
130
+ * @param strring $post_type The post type archive's name.
131
+ */
132
+ (array) apply_filters_ref_array(
133
+ 'the_seo_framework_post_type_archive_settings_tabs',
134
+ [
135
+ $tabs,
136
+ $post_type,
137
+ ]
138
+ )
139
+ );
140
+ ?>
141
+ </div>
142
+ </div>
143
+ <?php
144
+ $i++ or print( '<hr class=hide-if-tsf-js>' );
145
+ }
146
+ break;
147
+
148
+ case 'post_type_archive_general_tab':
149
+ ?>
150
+ <p>
151
+ <label for="<?php Input::field_id( $_option_map['doctitle'] ); ?>" class=tsf-toblock>
152
+ <strong><?php esc_html_e( 'Meta Title', 'autodescription' ); ?></strong>
153
+ <?php
154
+ echo ' ';
155
+ HTML::make_info(
156
+ __( 'The meta title can be used to determine the title used on search engine result pages.', 'autodescription' ),
157
+ 'https://developers.google.com/search/docs/advanced/appearance/good-titles-snippets#page-titles'
158
+ );
159
+ ?>
160
+ </label>
161
+ </p>
162
+ <?php
163
+ // Output these unconditionally, with inline CSS attached to allow reacting on settings.
164
+ Form::output_character_counter_wrap(
165
+ Input::get_field_id( $_option_map['doctitle'] ),
166
+ (bool) $this->get_option( 'display_character_counter' )
167
+ );
168
+ Form::output_pixel_counter_wrap(
169
+ Input::get_field_id( $_option_map['doctitle'] ),
170
+ 'title',
171
+ (bool) $this->get_option( 'display_pixel_counter' )
172
+ );
173
+ ?>
174
+ <p class=tsf-title-wrap>
175
+ <input type="text" name="<?php Input::field_name( $_option_map['doctitle'] ); ?>" class="large-text" id="<?php Input::field_id( $_option_map['doctitle'] ); ?>" value="<?php echo $this->esc_attr_preserve_amp( $this->get_post_type_archive_meta_item( 'doctitle', $post_type ) ); ?>" autocomplete=off />
176
+ <?php
177
+ [ $_full_title, $_prefix_value, $_default_title ] =
178
+ $this->get_raw_generated_archive_title_items( get_post_type_object( $post_type ), 'admin' );
179
+
180
+ $this->output_js_title_data(
181
+ Input::get_field_id( $_option_map['doctitle'] ),
182
+ [
183
+ 'state' => [
184
+ 'defaultTitle' => $this->s_title( $_default_title ),
185
+ 'addAdditions' => $this->use_title_branding( $_generator_args ),
186
+ 'useSocialTagline' => $this->use_title_branding( $_generator_args, true ),
187
+ 'additionValue' => $this->s_title( $this->get_blogname() ),
188
+ 'additionPlacement' => 'left' === $this->get_title_seplocation() ? 'before' : 'after',
189
+ 'prefixValue' => $this->s_title( $_prefix_value ),
190
+ 'showPrefix' => $this->use_generated_archive_prefix( get_post_type_object( $post_type ) ),
191
+ ],
192
+ ]
193
+ );
194
+ ?>
195
+ </p>
196
+
197
+ <div class=tsf-title-tagline-toggle>
198
+ <?php
199
+ $info = HTML::make_info(
200
+ __( 'Use this when you want to rearrange the title parts manually.', 'autodescription' ),
201
+ '',
202
+ false
203
+ );
204
+
205
+ HTML::wrap_fields(
206
+ Input::make_checkbox( [
207
+ 'id' => $_option_map['title_no_blog_name'],
208
+ 'label' => esc_html__( 'Remove the site title?', 'autodescription' ) . " $info",
209
+ 'value' => $this->get_post_type_archive_meta_item( 'title_no_blog_name', $post_type ),
210
+ 'escape' => false,
211
+ ] ),
212
+ true
213
+ );
214
+ ?>
215
+ </div>
216
+
217
+ <hr>
218
+
219
+ <p>
220
+ <label for="<?php Input::field_id( $_option_map['description'] ); ?>" class="tsf-toblock">
221
+ <strong><?php esc_html_e( 'Meta Description', 'autodescription' ); ?></strong>
222
+ <?php
223
+ echo ' ';
224
+ HTML::make_info(
225
+ __( 'The meta description can be used to determine the text used under the title on search engine results pages.', 'autodescription' ),
226
+ 'https://developers.google.com/search/docs/advanced/appearance/good-titles-snippets#meta-descriptions'
227
+ );
228
+ ?>
229
+ </label>
230
+ </p>
231
+ <?php
232
+ // Output these unconditionally, with inline CSS attached to allow reacting on settings.
233
+ Form::output_character_counter_wrap( Input::get_field_id( $_option_map['description'] ), (bool) $this->get_option( 'display_character_counter' ) );
234
+ Form::output_pixel_counter_wrap( Input::get_field_id( $_option_map['description'] ), 'description', (bool) $this->get_option( 'display_pixel_counter' ) );
235
+ ?>
236
+ <p>
237
+ <textarea name="<?php Input::field_name( $_option_map['description'] ); ?>" class="large-text" id="<?php Input::field_id( $_option_map['description'] ); ?>" rows="3" cols="70"><?php echo esc_attr( $this->get_post_type_archive_meta_item( 'description', $post_type ) ); ?></textarea>
238
+ <?php
239
+ $this->output_js_description_elements(); // legacy
240
+ $this->output_js_description_data(
241
+ Input::get_field_id( $_option_map['description'] ),
242
+ [
243
+ 'state' => [
244
+ 'defaultDescription' => $this->get_generated_description( $_generator_args ),
245
+ ],
246
+ ]
247
+ );
248
+ ?>
249
+ </p>
250
+ <?php
251
+ break;
252
+ case 'post_type_archive_social_tab':
253
+ $this->output_js_social_data(
254
+ "pta_social_settings_{$post_type}",
255
+ [
256
+ 'og' => [
257
+ 'state' => [
258
+ 'defaultTitle' => $this->s_title( $this->get_generated_open_graph_title( $_generator_args, false ) ),
259
+ 'addAdditions' => $this->use_title_branding( $_generator_args, 'og' ),
260
+ 'defaultDesc' => $this->s_description( $this->get_generated_open_graph_description( $_generator_args, false ) ),
261
+ ],
262
+ ],
263
+ 'tw' => [
264
+ 'state' => [
265
+ 'defaultTitle' => $this->s_title( $this->get_generated_twitter_title( $_generator_args, false ) ),
266
+ 'addAdditions' => $this->use_title_branding( $_generator_args, 'twitter' ),
267
+ 'defaultDesc' => $this->s_description( $this->get_generated_twitter_description( $_generator_args, false ) ),
268
+ ],
269
+ ],
270
+ ]
271
+ );
272
+
273
+ ?>
274
+ <p>
275
+ <label for="<?php Input::field_id( $_option_map['og_title'] ); ?>" class="tsf-toblock">
276
+ <strong><?php esc_html_e( 'Open Graph Title', 'autodescription' ); ?></strong>
277
+ </label>
278
+ </p>
279
+ <?php
280
+ // Output this unconditionally, with inline CSS attached to allow reacting on settings.
281
+ Form::output_character_counter_wrap( Input::get_field_id( $_option_map['og_title'] ), (bool) $this->get_option( 'display_character_counter' ) );
282
+ ?>
283
+ <p>
284
+ <input type="text" name="<?php Input::field_name( $_option_map['og_title'] ); ?>" class="large-text" id="<?php Input::field_id( $_option_map['og_title'] ); ?>" value="<?php echo $this->esc_attr_preserve_amp( $this->get_post_type_archive_meta_item( 'og_title', $post_type ) ); ?>" autocomplete=off data-tsf-social-group=<?php echo esc_attr( "pta_social_settings_{$post_type}" ); ?> data-tsf-social-type=ogTitle />
285
+ </p>
286
+
287
+ <p>
288
+ <label for="<?php Input::field_id( $_option_map['og_description'] ); ?>" class="tsf-toblock">
289
+ <strong><?php esc_html_e( 'Open Graph Description', 'autodescription' ); ?></strong>
290
+ </label>
291
+ </p>
292
+ <?php
293
+ // Output this unconditionally, with inline CSS attached to allow reacting on settings.
294
+ Form::output_character_counter_wrap( Input::get_field_id( $_option_map['og_description'] ), (bool) $this->get_option( 'display_character_counter' ) );
295
+ ?>
296
+ <p>
297
+ <textarea name="<?php Input::field_name( $_option_map['og_description'] ); ?>" class="large-text" id="<?php Input::field_id( $_option_map['og_description'] ); ?>" rows="3" cols="70" autocomplete=off data-tsf-social-group=<?php echo esc_attr( "pta_social_settings_{$post_type}" ); ?> data-tsf-social-type=ogDesc><?php echo esc_attr( $this->get_post_type_archive_meta_item( 'og_description', $post_type ) ); ?></textarea>
298
+ </p>
299
+
300
+ <hr>
301
+
302
+ <p>
303
+ <label for="<?php Input::field_id( $_option_map['tw_title'] ); ?>" class="tsf-toblock">
304
+ <strong><?php esc_html_e( 'Twitter Title', 'autodescription' ); ?></strong>
305
+ </label>
306
+ </p>
307
+ <?php
308
+ // Output this unconditionally, with inline CSS attached to allow reacting on settings.
309
+ Form::output_character_counter_wrap( Input::get_field_id( $_option_map['tw_title'] ), (bool) $this->get_option( 'display_character_counter' ) );
310
+ ?>
311
+ <p>
312
+ <input type="text" name="<?php Input::field_name( $_option_map['tw_title'] ); ?>" class="large-text" id="<?php Input::field_id( $_option_map['tw_title'] ); ?>" value="<?php echo $this->esc_attr_preserve_amp( $this->get_post_type_archive_meta_item( 'tw_title', $post_type ) ); ?>" autocomplete=off data-tsf-social-group=<?php echo esc_attr( "pta_social_settings_{$post_type}" ); ?> data-tsf-social-type=twTitle />
313
+ </p>
314
+
315
+ <p>
316
+ <label for="<?php Input::field_id( $_option_map['tw_description'] ); ?>" class="tsf-toblock">
317
+ <strong><?php esc_html_e( 'Twitter Description', 'autodescription' ); ?></strong>
318
+ </label>
319
+ </p>
320
+ <?php
321
+ // Output this unconditionally, with inline CSS attached to allow reacting on settings.
322
+ Form::output_character_counter_wrap( Input::get_field_id( $_option_map['tw_description'] ), (bool) $this->get_option( 'display_character_counter' ) );
323
+ ?>
324
+ <p>
325
+ <textarea name="<?php Input::field_name( $_option_map['tw_description'] ); ?>" class="large-text" id="<?php Input::field_id( $_option_map['tw_description'] ); ?>" rows="3" cols="70" autocomplete=off data-tsf-social-group=<?php echo esc_attr( "pta_social_settings_{$post_type}" ); ?> data-tsf-social-type=twDesc><?php echo esc_attr( $this->get_post_type_archive_meta_item( 'tw_description', $post_type ) ); ?></textarea>
326
+ </p>
327
+
328
+ <hr>
329
+
330
+ <p>
331
+ <label for="<?php echo esc_attr( "tsf_pta_socialimage_{$post_type}" ); ?>-url">
332
+ <strong><?php esc_html_e( 'Social Image URL', 'autodescription' ); ?></strong>
333
+ <?php
334
+ HTML::make_info(
335
+ __( "The social image URL can be used by search engines and social networks alike. It's best to use an image with a 1.91:1 aspect ratio that is at least 1200px wide for universal support.", 'autodescription' ),
336
+ 'https://developers.facebook.com/docs/sharing/best-practices#images'
337
+ );
338
+ ?>
339
+ </label>
340
+ </p>
341
+ <p>
342
+ <input class="large-text" type="url" name="<?php Input::field_name( $_option_map['social_image_url'] ); ?>" id="<?php echo esc_attr( "tsf_pta_socialimage_{$post_type}" ); ?>-url" placeholder="<?php echo esc_url( current( $this->get_generated_image_details( $_generator_args, true, 'social', true ) )['url'] ?? '' ); ?>" value="<?php echo esc_url( $this->get_post_type_archive_meta_item( 'social_image_url', $post_type ) ); ?>" />
343
+ <input type="hidden" name="<?php Input::field_name( $_option_map['social_image_id'] ); ?>" id="<?php echo esc_attr( "tsf_pta_socialimage_{$post_type}" ); ?>-id" value="<?php echo absint( $this->get_post_type_archive_meta_item( 'social_image_id', $post_type ) ); ?>" disabled class="tsf-enable-media-if-js" />
344
+ </p>
345
+ <p class="hide-if-no-tsf-js">
346
+ <?php
347
+ // phpcs:ignore, WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped.
348
+ echo Form::get_image_uploader_form( [ 'id' => "tsf_pta_socialimage_{$post_type}" ] );
349
+ ?>
350
+ </p>
351
+ <?php
352
+ break;
353
+ case 'post_type_archive_visibility_tab':
354
+ ?>
355
+ <p>
356
+ <label for="<?php Input::field_id( $_option_map['canonical'] ); ?>" class="tsf-toblock">
357
+ <strong><?php esc_html_e( 'Canonical URL', 'autodescription' ); ?></strong>
358
+ <?php
359
+ echo ' ';
360
+ HTML::make_info(
361
+ __( 'This urges search engines to go to the outputted URL.', 'autodescription' ),
362
+ 'https://developers.google.com/search/docs/advanced/crawling/consolidate-duplicate-urls'
363
+ );
364
+ ?>
365
+ </label>
366
+ </p>
367
+ <p>
368
+ <input type="url" name="<?php Input::field_name( $_option_map['canonical'] ); ?>" class="large-text" id="<?php Input::field_id( $_option_map['canonical'] ); ?>" placeholder="<?php echo esc_url( $this->create_canonical_url( $_generator_args ) ); ?>" value="<?php echo esc_url( $this->get_post_type_archive_meta_item( 'canonical', $post_type ) ); ?>" autocomplete=off />
369
+ </p>
370
+
371
+ <hr>
372
+ <?php
373
+ $robots_settings = [
374
+ 'noindex' => [
375
+ 'force_on' => 'index',
376
+ 'force_off' => 'noindex',
377
+ 'label' => __( 'Indexing', 'autodescription' ),
378
+ '_defaultOn' => 'index',
379
+ '_defaultOff' => 'noindex',
380
+ '_value' => $this->get_post_type_archive_meta_item( 'noindex', $post_type ),
381
+ '_info' => [
382
+ __( 'This tells search engines not to show this term in their search results.', 'autodescription' ),
383
+ 'https://developers.google.com/search/docs/advanced/crawling/block-indexing',
384
+ ],
385
+ ],
386
+ 'nofollow' => [
387
+ 'force_on' => 'follow',
388
+ 'force_off' => 'nofollow',
389
+ 'label' => __( 'Link following', 'autodescription' ),
390
+ '_defaultOn' => 'follow',
391
+ '_defaultOff' => 'nofollow',
392
+ '_value' => $this->get_post_type_archive_meta_item( 'nofollow', $post_type ),
393
+ '_info' => [
394
+ __( 'This tells search engines not to follow links on this term.', 'autodescription' ),
395
+ 'https://developers.google.com/search/docs/advanced/guidelines/qualify-outbound-links',
396
+ ],
397
+ ],
398
+ 'noarchive' => [
399
+ 'force_on' => 'archive',
400
+ 'force_off' => 'noarchive',
401
+ 'label' => __( 'Archiving', 'autodescription' ),
402
+ '_defaultOn' => 'archive',
403
+ '_defaultOff' => 'noarchive',
404
+ '_value' => $this->get_post_type_archive_meta_item( 'noarchive', $post_type ),
405
+ '_info' => [
406
+ __( 'This tells search engines not to save a cached copy of this term.', 'autodescription' ),
407
+ 'https://developers.google.com/search/docs/advanced/robots/robots_meta_tag#directives',
408
+ ],
409
+ ],
410
+ ];
411
+
412
+ foreach ( $robots_settings as $_r_type => $_rs ) :
413
+ // phpcs:enable, WordPress.Security.EscapeOutput
414
+ HTML::wrap_fields(
415
+ vsprintf(
416
+ '<p><label for="%1$s"><strong>%2$s</strong> %3$s</label></p>',
417
+ [
418
+ Input::get_field_id( $_option_map[ $_r_type ] ),
419
+ esc_html( $_rs['label'] ),
420
+ HTML::make_info(
421
+ $_rs['_info'][0],
422
+ $_rs['_info'][1] ?? '',
423
+ false
424
+ ),
425
+ ]
426
+ ),
427
+ true
428
+ );
429
+ // phpcs:disable, WordPress.Security.EscapeOutput -- make_single_select_form() escapes.
430
+ echo Form::make_single_select_form( [
431
+ 'id' => Input::get_field_id( $_option_map[ $_r_type ] ),
432
+ 'class' => 'tsf-select-block',
433
+ 'name' => Input::get_field_name( $_option_map[ $_r_type ] ),
434
+ 'label' => '',
435
+ 'options' => [
436
+ 0 => __( 'Default (unknown)', 'autodescription' ),
437
+ -1 => $_rs['force_on'],
438
+ 1 => $_rs['force_off'],
439
+ ],
440
+ 'default' => $_rs['_value'],
441
+ 'data' => [
442
+ /* translators: %s = default option value */
443
+ 'defaultI18n' => __( 'Default (%s)', 'autodescription' ),
444
+ 'defaultOn' => $_rs['_defaultOn'],
445
+ 'defaultOff' => $_rs['_defaultOff'],
446
+ ],
447
+ ] );
448
+ endforeach;
449
+ ?>
450
+ <hr>
451
+
452
+ <p>
453
+ <label for="<?php Input::field_id( $_option_map['redirect'] ); ?>" class="tsf-toblock">
454
+ <strong><?php esc_html_e( '301 Redirect URL', 'autodescription' ); ?></strong>
455
+ <?php
456
+ echo ' ';
457
+ HTML::make_info(
458
+ __( 'This will force visitors to go to another URL.', 'autodescription' ),
459
+ 'https://developers.google.com/search/docs/advanced/crawling/301-redirects'
460
+ );
461
+ ?>
462
+ </label>
463
+ </p>
464
+ <p>
465
+ <input type="url" name="<?php Input::field_name( $_option_map['redirect'] ); ?>" class="large-text" id="<?php Input::field_id( $_option_map['redirect'] ); ?>" value="<?php echo esc_url( $this->get_post_type_archive_meta_item( 'redirect', $post_type ) ); ?>" autocomplete=off />
466
+ </p>
467
+ <?php
468
+ break;
469
+ endswitch;
inc/views/{admin/metaboxes/robots-metabox.php → settings/metaboxes/robots.php} RENAMED
@@ -9,15 +9,12 @@
9
 
10
  use The_SEO_Framework\Bridges\SeoSettings,
11
  The_SEO_Framework\Interpreters\HTML,
12
- The_SEO_Framework\Interpreters\Form;
13
 
14
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
15
 
16
- // Fetch the required instance within this file.
17
- $instance = $this->get_view_instance( 'the_seo_framework_robots_metabox', $instance );
18
-
19
- switch ( $instance ) :
20
- case 'the_seo_framework_robots_metabox_main':
21
  $global_types = [
22
  'author' => [
23
  'i18n' => __( 'Author pages', 'autodescription' ),
@@ -31,6 +28,7 @@ switch ( $instance ) :
31
  'i18n' => __( 'Search pages', 'autodescription' ),
32
  'i18ntype' => 'plural',
33
  ],
 
34
  'site' => [
35
  'i18n' => _x( 'the entire site', '...for the entire site', 'autodescription' ),
36
  'i18ntype' => 'singular',
@@ -56,16 +54,18 @@ switch ( $instance ) :
56
  ],
57
  ];
58
 
59
- $default_tabs = [
 
 
60
  'general' => [
61
  'name' => __( 'General', 'autodescription' ),
62
- 'callback' => SeoSettings::class . '::_robots_metabox_general_tab',
63
  'dashicon' => 'admin-generic',
64
  'args' => '',
65
  ],
66
  'index' => [
67
  'name' => __( 'Indexing', 'autodescription' ),
68
- 'callback' => SeoSettings::class . '::_robots_metabox_no_tab',
69
  'dashicon' => 'filter',
70
  'args' => [
71
  'global_types' => $global_types,
@@ -76,7 +76,7 @@ switch ( $instance ) :
76
  ],
77
  'follow' => [
78
  'name' => __( 'Following', 'autodescription' ),
79
- 'callback' => SeoSettings::class . '::_robots_metabox_no_tab',
80
  'dashicon' => 'editor-unlink',
81
  'args' => [
82
  'global_types' => $global_types,
@@ -87,7 +87,7 @@ switch ( $instance ) :
87
  ],
88
  'archive' => [
89
  'name' => __( 'Archiving', 'autodescription' ),
90
- 'callback' => SeoSettings::class . '::_robots_metabox_no_tab',
91
  'dashicon' => 'download',
92
  'args' => [
93
  'global_types' => $global_types,
@@ -98,24 +98,22 @@ switch ( $instance ) :
98
  ],
99
  ];
100
 
101
- /**
102
- * @since 2.2.4
103
- * @param array $defaults The default tabs.
104
- * @param array $args The args added on the callback.
105
- */
106
- $defaults = (array) apply_filters( 'the_seo_framework_robots_settings_tabs', $default_tabs, $args );
107
-
108
- $tabs = wp_parse_args( $args, $defaults );
109
-
110
- SeoSettings::_nav_tab_wrapper( 'robots', $tabs );
111
  break;
112
 
113
- case 'the_seo_framework_robots_metabox_general':
114
- Form::header_title( __( 'Advanced Query Protection', 'autodescription' ) );
115
  HTML::description( __( 'Some URL queries can cause WordPress to show faux archives. When search engines spot these, they will crawl and index them, which may cause a drop in ranking. Advanced query protection will prevent robots from indexing these archives.', 'autodescription' ) );
116
 
117
  HTML::wrap_fields(
118
- Form::make_checkbox( [
119
  'id' => 'advanced_query_protection',
120
  'label' => __( 'Enable advanced query protection?', 'autodescription' ),
121
  ] ),
@@ -124,11 +122,11 @@ switch ( $instance ) :
124
  ?>
125
  <hr>
126
  <?php
127
- Form::header_title( __( 'Paginated Archive Settings', 'autodescription' ) );
128
  HTML::description( __( "Indexing the second or later page of any archive might cause duplication errors. Search engines look down upon them; therefore, it's recommended to disable indexing of those pages.", 'autodescription' ) );
129
 
130
  HTML::wrap_fields(
131
- Form::make_checkbox( [
132
  'id' => 'paged_noindex',
133
  'label' => $this->convert_markdown(
134
  /* translators: the backticks are Markdown! Preserve them as-is! */
@@ -142,11 +140,11 @@ switch ( $instance ) :
142
  ?>
143
  <hr>
144
  <?php
145
- Form::header_title( __( 'Copyright Directive Settings', 'autodescription' ) );
146
  HTML::description( __( "Some search engines allow you to control copyright directives on the content they aggregate. It's best to allow some content to be taken by these aggregators, as that can improve contextualized exposure via snippets and previews. When left unspecified, regional regulations may apply. It is up to the aggregator to honor these requests.", 'autodescription' ) );
147
 
148
  HTML::wrap_fields(
149
- Form::make_checkbox( [
150
  'id' => 'set_copyright_directives',
151
  'label' => __( 'Specify aggregator copyright compliance directives?', 'autodescription' ),
152
  ] ),
@@ -165,8 +163,8 @@ switch ( $instance ) :
165
  $_current = $this->get_option( 'max_snippet_length' );
166
  foreach ( $_text_snippet_types as $_type => $_values ) {
167
  $_label = 'default' === $_type
168
- ? __( 'Standard directive', 'autodescription' )
169
- : __( 'Granular directive', 'autodescription' );
170
 
171
  $_options = '';
172
  foreach ( $_values as $_value => $_name ) {
@@ -188,9 +186,9 @@ switch ( $instance ) :
188
  <p><select name="%3$s" id="%1$s">%4$s</select></p>
189
  <p class=description>%6$s</p>',
190
  [
191
- Form::get_field_id( 'max_snippet_length' ),
192
  esc_html__( 'Maximum text snippet length', 'autodescription' ),
193
- Form::get_field_name( 'max_snippet_length' ),
194
  $text_snippet_options,
195
  HTML::make_info(
196
  __( 'This may limit the text snippet length for all pages on this site.', 'autodescription' ),
@@ -225,9 +223,9 @@ switch ( $instance ) :
225
  '<p><label for="%1$s"><strong>%2$s</strong> %5$s</label></p>
226
  <p><select name="%3$s" id="%1$s">%4$s</select></p>',
227
  [
228
- Form::get_field_id( 'max_image_preview' ),
229
  esc_html__( 'Maximum image preview size', 'autodescription' ),
230
- Form::get_field_name( 'max_image_preview' ),
231
  $image_preview_options,
232
  HTML::make_info(
233
  __( 'This may limit the image preview size for all images from this site.', 'autodescription' ),
@@ -251,8 +249,8 @@ switch ( $instance ) :
251
  $_current = $this->get_option( 'max_video_preview' );
252
  foreach ( $_video_snippet_types as $_type => $_values ) {
253
  $_label = 'default' === $_type
254
- ? __( 'Standard directive', 'autodescription' )
255
- : __( 'Granular directive', 'autodescription' );
256
 
257
  $_options = '';
258
  foreach ( $_values as $_value => $_name ) {
@@ -273,9 +271,9 @@ switch ( $instance ) :
273
  '<p><label for="%1$s"><strong>%2$s</strong> %5$s</label></p>
274
  <p><select name="%3$s" id="%1$s">%4$s</select></p>',
275
  [
276
- Form::get_field_id( 'max_video_preview' ),
277
  esc_html__( 'Maximum video preview length', 'autodescription' ),
278
- Form::get_field_name( 'max_video_preview' ),
279
  $video_preview_options,
280
  HTML::make_info(
281
  __( 'This may limit the video preview length for all videos on this site.', 'autodescription' ),
@@ -288,7 +286,7 @@ switch ( $instance ) :
288
  );
289
  break;
290
 
291
- case 'the_seo_framework_robots_metabox_no':
292
  $ro_value = $robots['value'];
293
  $ro_i18n = $robots['desc'];
294
 
@@ -299,42 +297,34 @@ switch ( $instance ) :
299
 
300
  $ro_name_wrapped = HTML::code_wrap( $ro_value );
301
 
302
- $default_options = $this->get_default_site_options();
303
- $warned_options = $this->get_warned_site_options();
304
-
305
- Form::header_title( __( 'Robots Settings', 'autodescription' ) );
306
  HTML::description( $ro_i18n );
307
  ?>
308
  <hr>
309
  <?php
310
- Form::header_title( __( 'Post Type Settings', 'autodescription' ) );
311
  HTML::description( __( 'These settings apply to the post type pages and their terms. When terms are shared between post types, all their post types should be checked for this to have an effect.', 'autodescription' ) );
312
 
313
- $pt_option_id = $this->get_robots_post_type_option_id( $ro_value );
314
-
315
  // When the post OR page post types are available, show this warning.
316
  if ( in_array( $ro_value, [ 'noindex', 'nofollow' ], true ) && array_intersect( $post_types, [ 'post', 'page' ] ) )
317
  HTML::attention_description( __( 'Warning: No site should enable these options for Posts and Pages.', 'autodescription' ) );
318
 
319
- // TODO can we assume that there's at least one post type at all times? Can WP be used in this way, albeit headless?
320
  $checkboxes = [];
321
 
 
 
322
  foreach ( $post_types as $post_type ) {
323
- $checkboxes[] = Form::make_checkbox( [
324
- 'id' => $pt_option_id,
325
- 'class' => 'tsf-robots-post-types',
326
- 'index' => $post_type,
327
- 'label' => sprintf(
328
  // RTL supported: Because the post types are Roman, browsers enforce the order.
329
  '%s &ndash; <code>%s</code>',
330
  sprintf( $apply_x_to_y_i18n_plural, $ro_name_wrapped, esc_html( $this->get_post_type_label( $post_type, false ) ) ),
331
  esc_html( $post_type )
332
  ),
333
- 'escape' => false,
334
- 'disabled' => false,
335
- 'default' => ! empty( $default_options[ $pt_option_id ][ $post_type ] ),
336
- 'warned' => ! empty( $warned_options[ $pt_option_id ][ $post_type ] ),
337
- 'data' => [
338
  'robots' => $ro_value,
339
  ],
340
  ] );
@@ -345,42 +335,38 @@ switch ( $instance ) :
345
  ?>
346
  <hr>
347
  <?php
348
- Form::header_title( __( 'Taxonomy Settings', 'autodescription' ) );
349
  HTML::description( __( "These settings apply to the taxonomies of post types. When taxonomies have all their bound post types' options checked, they will inherit their status.", 'autodescription' ) );
350
 
351
  $tax_option_id = $this->get_robots_taxonomy_option_id( $ro_value );
352
 
353
- // TODO can we assume that there's at least one taxonomy at all times? Can WP be used in this way, albeit headless?
354
  $checkboxes = [];
355
 
356
  foreach ( $taxonomies as $taxonomy ) {
357
- $checkboxes[] = Form::make_checkbox( [
358
- 'id' => $tax_option_id,
359
- 'class' => 'tsf-robots-taxonomies',
360
- 'index' => $taxonomy,
361
- 'label' => sprintf(
362
  // RTL supported: Because the post types are Roman, browsers enforce the order.
363
  '%s &ndash; <code>%s</code>',
364
  sprintf( $apply_x_to_y_i18n_plural, $ro_name_wrapped, esc_html( $this->get_tax_type_label( $taxonomy, false ) ) ),
365
  esc_html( $taxonomy )
366
  ),
367
- 'escape' => false,
368
- 'disabled' => false,
369
- 'default' => ! empty( $default_options[ $tax_option_id ][ $taxonomy ] ),
370
- 'warned' => ! empty( $warned_options[ $tax_option_id ][ $taxonomy ] ),
371
- 'data' => [
372
  'postTypes' => $this->get_post_types_from_taxonomy( $taxonomy ),
373
  'robots' => $ro_value,
374
  ],
375
  ] );
376
  }
377
 
 
378
  HTML::wrap_fields( $checkboxes, true );
379
 
380
  ?>
381
  <hr>
382
  <?php
383
- Form::header_title( __( 'Global Settings', 'autodescription' ) );
384
  HTML::description( __( 'These settings apply to other globally registered content types.', 'autodescription' ) );
385
 
386
  $checkboxes = '';
@@ -392,7 +378,8 @@ switch ( $instance ) :
392
  esc_html( $data['i18n'] )
393
  );
394
 
395
- $id = $this->s_field_id( $type . '_' . $ro_value );
 
396
 
397
  // Add warning if it's 'site'.
398
  if ( 'site' === $type ) {
@@ -405,10 +392,14 @@ switch ( $instance ) :
405
  );
406
  }
407
 
408
- $checkboxes .= Form::make_checkbox( [
409
  'id' => $id,
 
410
  'label' => $label,
411
  'escape' => false,
 
 
 
412
  ] );
413
  }
414
 
9
 
10
  use The_SEO_Framework\Bridges\SeoSettings,
11
  The_SEO_Framework\Interpreters\HTML,
12
+ The_SEO_Framework\Interpreters\Settings_Input as Input;
13
 
14
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
15
 
16
+ switch ( $this->get_view_instance( 'robots', $instance ) ) :
17
+ case 'robots_main':
 
 
 
18
  $global_types = [
19
  'author' => [
20
  'i18n' => __( 'Author pages', 'autodescription' ),
28
  'i18n' => __( 'Search pages', 'autodescription' ),
29
  'i18ntype' => 'plural',
30
  ],
31
+ // Must be last for proper <hr> styling!
32
  'site' => [
33
  'i18n' => _x( 'the entire site', '...for the entire site', 'autodescription' ),
34
  'i18ntype' => 'singular',
54
  ],
55
  ];
56
 
57
+ $_settings_class = SeoSettings::class;
58
+
59
+ $tabs = [
60
  'general' => [
61
  'name' => __( 'General', 'autodescription' ),
62
+ 'callback' => [ $_settings_class, '_robots_metabox_general_tab' ],
63
  'dashicon' => 'admin-generic',
64
  'args' => '',
65
  ],
66
  'index' => [
67
  'name' => __( 'Indexing', 'autodescription' ),
68
+ 'callback' => [ $_settings_class, '_robots_metabox_no_tab' ],
69
  'dashicon' => 'filter',
70
  'args' => [
71
  'global_types' => $global_types,
76
  ],
77
  'follow' => [
78
  'name' => __( 'Following', 'autodescription' ),
79
+ 'callback' => [ $_settings_class, '_robots_metabox_no_tab' ],
80
  'dashicon' => 'editor-unlink',
81
  'args' => [
82
  'global_types' => $global_types,
87
  ],
88
  'archive' => [
89
  'name' => __( 'Archiving', 'autodescription' ),
90
+ 'callback' => [ $_settings_class, '_robots_metabox_no_tab' ],
91
  'dashicon' => 'download',
92
  'args' => [
93
  'global_types' => $global_types,
98
  ],
99
  ];
100
 
101
+ SeoSettings::_nav_tab_wrapper(
102
+ 'robots',
103
+ /**
104
+ * @since 2.2.4
105
+ * @param array $tabs The default tabs.
106
+ */
107
+ (array) apply_filters( 'the_seo_framework_robots_settings_tabs', $tabs )
108
+ );
 
 
109
  break;
110
 
111
+ case 'robots_general_tab':
112
+ HTML::header_title( __( 'Advanced Query Protection', 'autodescription' ) );
113
  HTML::description( __( 'Some URL queries can cause WordPress to show faux archives. When search engines spot these, they will crawl and index them, which may cause a drop in ranking. Advanced query protection will prevent robots from indexing these archives.', 'autodescription' ) );
114
 
115
  HTML::wrap_fields(
116
+ Input::make_checkbox( [
117
  'id' => 'advanced_query_protection',
118
  'label' => __( 'Enable advanced query protection?', 'autodescription' ),
119
  ] ),
122
  ?>
123
  <hr>
124
  <?php
125
+ HTML::header_title( __( 'Paginated Archive Settings', 'autodescription' ) );
126
  HTML::description( __( "Indexing the second or later page of any archive might cause duplication errors. Search engines look down upon them; therefore, it's recommended to disable indexing of those pages.", 'autodescription' ) );
127
 
128
  HTML::wrap_fields(
129
+ Input::make_checkbox( [
130
  'id' => 'paged_noindex',
131
  'label' => $this->convert_markdown(
132
  /* translators: the backticks are Markdown! Preserve them as-is! */
140
  ?>
141
  <hr>
142
  <?php
143
+ HTML::header_title( __( 'Copyright Directive Settings', 'autodescription' ) );
144
  HTML::description( __( "Some search engines allow you to control copyright directives on the content they aggregate. It's best to allow some content to be taken by these aggregators, as that can improve contextualized exposure via snippets and previews. When left unspecified, regional regulations may apply. It is up to the aggregator to honor these requests.", 'autodescription' ) );
145
 
146
  HTML::wrap_fields(
147
+ Input::make_checkbox( [
148
  'id' => 'set_copyright_directives',
149
  'label' => __( 'Specify aggregator copyright compliance directives?', 'autodescription' ),
150
  ] ),
163
  $_current = $this->get_option( 'max_snippet_length' );
164
  foreach ( $_text_snippet_types as $_type => $_values ) {
165
  $_label = 'default' === $_type
166
+ ? __( 'Standard directive', 'autodescription' )
167
+ : __( 'Granular directive', 'autodescription' );
168
 
169
  $_options = '';
170
  foreach ( $_values as $_value => $_name ) {
186
  <p><select name="%3$s" id="%1$s">%4$s</select></p>
187
  <p class=description>%6$s</p>',
188
  [
189
+ Input::get_field_id( 'max_snippet_length' ),
190
  esc_html__( 'Maximum text snippet length', 'autodescription' ),
191
+ Input::get_field_name( 'max_snippet_length' ),
192
  $text_snippet_options,
193
  HTML::make_info(
194
  __( 'This may limit the text snippet length for all pages on this site.', 'autodescription' ),
223
  '<p><label for="%1$s"><strong>%2$s</strong> %5$s</label></p>
224
  <p><select name="%3$s" id="%1$s">%4$s</select></p>',
225
  [
226
+ Input::get_field_id( 'max_image_preview' ),
227
  esc_html__( 'Maximum image preview size', 'autodescription' ),
228
+ Input::get_field_name( 'max_image_preview' ),
229
  $image_preview_options,
230
  HTML::make_info(
231
  __( 'This may limit the image preview size for all images from this site.', 'autodescription' ),
249
  $_current = $this->get_option( 'max_video_preview' );
250
  foreach ( $_video_snippet_types as $_type => $_values ) {
251
  $_label = 'default' === $_type
252
+ ? __( 'Standard directive', 'autodescription' )
253
+ : __( 'Granular directive', 'autodescription' );
254
 
255
  $_options = '';
256
  foreach ( $_values as $_value => $_name ) {
271
  '<p><label for="%1$s"><strong>%2$s</strong> %5$s</label></p>
272
  <p><select name="%3$s" id="%1$s">%4$s</select></p>',
273
  [
274
+ Input::get_field_id( 'max_video_preview' ),
275
  esc_html__( 'Maximum video preview length', 'autodescription' ),
276
+ Input::get_field_name( 'max_video_preview' ),
277
  $video_preview_options,
278
  HTML::make_info(
279
  __( 'This may limit the video preview length for all videos on this site.', 'autodescription' ),
286
  );
287
  break;
288
 
289
+ case 'robots_no_tab':
290
  $ro_value = $robots['value'];
291
  $ro_i18n = $robots['desc'];
292
 
297
 
298
  $ro_name_wrapped = HTML::code_wrap( $ro_value );
299
 
300
+ HTML::header_title( __( 'Robots Settings', 'autodescription' ) );
 
 
 
301
  HTML::description( $ro_i18n );
302
  ?>
303
  <hr>
304
  <?php
305
+ HTML::header_title( __( 'Post Type Settings', 'autodescription' ) );
306
  HTML::description( __( 'These settings apply to the post type pages and their terms. When terms are shared between post types, all their post types should be checked for this to have an effect.', 'autodescription' ) );
307
 
 
 
308
  // When the post OR page post types are available, show this warning.
309
  if ( in_array( $ro_value, [ 'noindex', 'nofollow' ], true ) && array_intersect( $post_types, [ 'post', 'page' ] ) )
310
  HTML::attention_description( __( 'Warning: No site should enable these options for Posts and Pages.', 'autodescription' ) );
311
 
 
312
  $checkboxes = [];
313
 
314
+ $pt_option_id = $this->get_robots_post_type_option_id( $ro_value );
315
+
316
  foreach ( $post_types as $post_type ) {
317
+ $checkboxes[] = Input::make_checkbox( [
318
+ 'id' => [ $pt_option_id, $post_type ],
319
+ 'class' => 'tsf-robots-post-types',
320
+ 'label' => sprintf(
 
321
  // RTL supported: Because the post types are Roman, browsers enforce the order.
322
  '%s &ndash; <code>%s</code>',
323
  sprintf( $apply_x_to_y_i18n_plural, $ro_name_wrapped, esc_html( $this->get_post_type_label( $post_type, false ) ) ),
324
  esc_html( $post_type )
325
  ),
326
+ 'escape' => false,
327
+ 'data' => [
 
 
 
328
  'robots' => $ro_value,
329
  ],
330
  ] );
335
  ?>
336
  <hr>
337
  <?php
338
+ HTML::header_title( __( 'Taxonomy Settings', 'autodescription' ) );
339
  HTML::description( __( "These settings apply to the taxonomies of post types. When taxonomies have all their bound post types' options checked, they will inherit their status.", 'autodescription' ) );
340
 
341
  $tax_option_id = $this->get_robots_taxonomy_option_id( $ro_value );
342
 
 
343
  $checkboxes = [];
344
 
345
  foreach ( $taxonomies as $taxonomy ) {
346
+ $checkboxes[] = Input::make_checkbox( [
347
+ 'id' => [ $tax_option_id, $taxonomy ],
348
+ 'class' => 'tsf-robots-taxonomies',
349
+ 'label' => sprintf(
 
350
  // RTL supported: Because the post types are Roman, browsers enforce the order.
351
  '%s &ndash; <code>%s</code>',
352
  sprintf( $apply_x_to_y_i18n_plural, $ro_name_wrapped, esc_html( $this->get_tax_type_label( $taxonomy, false ) ) ),
353
  esc_html( $taxonomy )
354
  ),
355
+ 'escape' => false,
356
+ 'data' => [
 
 
 
357
  'postTypes' => $this->get_post_types_from_taxonomy( $taxonomy ),
358
  'robots' => $ro_value,
359
  ],
360
  ] );
361
  }
362
 
363
+ // TODO can we assume that there's at least one taxonomy at all times? Can WP be used in this way, albeit headless?
364
  HTML::wrap_fields( $checkboxes, true );
365
 
366
  ?>
367
  <hr>
368
  <?php
369
+ HTML::header_title( __( 'Global Settings', 'autodescription' ) );
370
  HTML::description( __( 'These settings apply to other globally registered content types.', 'autodescription' ) );
371
 
372
  $checkboxes = '';
378
  esc_html( $data['i18n'] )
379
  );
380
 
381
+ // Legacy.
382
+ $id = $this->s_field_id( "{$type}_{$ro_value}" );
383
 
384
  // Add warning if it's 'site'.
385
  if ( 'site' === $type ) {
392
  );
393
  }
394
 
395
+ $checkboxes .= Input::make_checkbox( [
396
  'id' => $id,
397
+ 'class' => 'site' === $type ? 'tsf-robots-site' : 'tsf-robots-globals',
398
  'label' => $label,
399
  'escape' => false,
400
+ 'data' => [
401
+ 'robots' => $ro_value,
402
+ ],
403
  ] );
404
  }
405
 
inc/views/{admin/metaboxes/schema-metabox.php → settings/metaboxes/schema.php} RENAMED
@@ -9,16 +9,14 @@
9
 
10
  use The_SEO_Framework\Bridges\SeoSettings,
11
  The_SEO_Framework\Interpreters\HTML,
12
- The_SEO_Framework\Interpreters\Form;
 
13
 
14
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
15
 
16
- // Fetch the required instance within this file.
17
- $instance = $this->get_view_instance( 'the_seo_framework_schema_metabox', $instance );
18
-
19
- switch ( $instance ) :
20
- case 'the_seo_framework_schema_metabox_main':
21
- Form::header_title( __( 'Schema.org Output Settings', 'autodescription' ) );
22
 
23
  if ( $this->has_json_ld_plugin() )
24
  HTML::attention_description( __( 'Another Schema.org plugin has been detected. These markup settings might conflict.', 'autodescription' ) );
@@ -27,38 +25,39 @@ switch ( $instance ) :
27
  HTML::description( __( 'When your web pages include structured data markup, search engines can use that data to index your content better, present it more prominently in search results, and use it in several different applications.', 'autodescription' ) );
28
  HTML::description( __( 'This is also known as the "Knowledge Graph" and "Structured Data", which is under heavy active development by several search engines. Therefore, the usage of the outputted markup is not guaranteed.', 'autodescription' ) );
29
 
30
- $default_tabs = [
 
 
31
  'structure' => [
32
  'name' => __( 'Structure', 'autodescription' ),
33
- 'callback' => SeoSettings::class . '::_schema_metabox_structure_tab',
34
  'dashicon' => 'admin-multisite',
35
  ],
36
  'presence' => [
37
  'name' => __( 'Presence', 'autodescription' ),
38
- 'callback' => SeoSettings::class . '::_schema_metabox_presence_tab',
39
  'dashicon' => 'networking',
40
  ],
41
  ];
42
 
43
- /**
44
- * @since 2.8.0
45
- * @param array $defaults The default tabs.
46
- * @param array $args The args added on the callback.
47
- */
48
- $defaults = (array) apply_filters( 'the_seo_framework_schema_settings_tabs', $default_tabs, $args );
49
-
50
- $tabs = wp_parse_args( $args, $defaults );
51
-
52
- SeoSettings::_nav_tab_wrapper( 'schema', $tabs );
53
  break;
54
 
55
- case 'the_seo_framework_schema_metabox_structure':
56
- Form::header_title( __( 'Site Structure Options', 'autodescription' ) );
57
  HTML::description( __( 'The site structure Schema.org output allows search engines to gain knowledge on how your website is built.', 'autodescription' ) );
58
  HTML::description( __( "For example, search engines display your pages' URLs when listed in the search results. These options allow you to enhance those URLs output.", 'autodescription' ) );
59
  ?>
60
- <hr> <?php
61
- Form::header_title( __( 'Breadcrumbs', 'autodescription' ) );
 
62
  HTML::description( __( "Breadcrumb trails indicate page positions in the site's hierarchy. Using the following option will show the hierarchy within the search results when available.", 'autodescription' ) );
63
 
64
  $info = HTML::make_info(
@@ -67,9 +66,9 @@ switch ( $instance ) :
67
  false
68
  );
69
  HTML::wrap_fields(
70
- Form::make_checkbox( [
71
  'id' => 'ld_json_breadcrumbs',
72
- 'label' => esc_html__( 'Enable Breadcrumbs?', 'autodescription' ) . ' ' . $info,
73
  'escape' => false,
74
  ] ),
75
  true
@@ -87,17 +86,17 @@ switch ( $instance ) :
87
  false
88
  );
89
  HTML::wrap_fields(
90
- Form::make_checkbox( [
91
  'id' => 'ld_json_searchbox',
92
- 'label' => esc_html_x( 'Enable Sitelinks Searchbox?', 'Sitelinks Searchbox is a Product name', 'autodescription' ) . ' ' . $info,
93
  'escape' => false,
94
  ] ),
95
  true
96
  );
97
  break;
98
 
99
- case 'the_seo_framework_schema_metabox_presence':
100
- Form::header_title( __( 'Authorized Presence Options', 'autodescription' ) );
101
  HTML::description( __( 'The authorized presence Schema.org output helps search engine users find ways to interact with this website.', 'autodescription' ) );
102
 
103
  $info = HTML::make_info(
@@ -105,11 +104,10 @@ switch ( $instance ) :
105
  'https://developers.google.com/search/docs/guides/enhance-site#add-your-sites-name-logo-and-social-links',
106
  false
107
  );
108
- // Echo checkbox.
109
  HTML::wrap_fields(
110
- Form::make_checkbox( [
111
  'id' => 'knowledge_output',
112
- 'label' => esc_html__( 'Output Authorized Presence?', 'autodescription' ) . ' ' . $info,
113
  'escape' => false,
114
  ] ),
115
  true
@@ -117,10 +115,10 @@ switch ( $instance ) :
117
  ?>
118
  <hr>
119
 
120
- <?php Form::header_title( __( 'About this website', 'autodescription' ) ); ?>
121
  <p>
122
- <label for="<?php Form::field_id( 'knowledge_type' ); ?>"><?php echo esc_html_x( 'This website represents:', '...Organization or Person.', 'autodescription' ); ?></label>
123
- <select name="<?php Form::field_name( 'knowledge_type' ); ?>" id="<?php Form::field_id( 'knowledge_type' ); ?>">
124
  <?php
125
  $knowledge_type = (array) apply_filters(
126
  'the_seo_framework_knowledge_types',
@@ -137,16 +135,16 @@ switch ( $instance ) :
137
  </p>
138
 
139
  <p>
140
- <label for="<?php Form::field_id( 'knowledge_name' ); ?>">
141
  <strong><?php esc_html_e( 'The organization or personal name', 'autodescription' ); ?></strong>
142
  </label>
143
  </p>
144
  <p>
145
- <input type="text" name="<?php Form::field_name( 'knowledge_name' ); ?>" class="large-text" id="<?php Form::field_id( 'knowledge_name' ); ?>" placeholder="<?php echo esc_attr( $this->get_blogname() ); ?>" value="<?php echo esc_attr( $this->get_option( 'knowledge_name' ) ); ?>" autocomplete=off />
146
  </p>
147
  <hr>
148
  <?php
149
- Form::header_title( __( 'Website logo', 'autodescription' ) );
150
  HTML::description( esc_html__( 'These options are used when this site represents an organization. When no logo is outputted, search engine will look elsewhere.', 'autodescription' ) );
151
  $info = HTML::make_info(
152
  __( 'Learn how this data is used.', 'autodescription' ),
@@ -154,9 +152,9 @@ switch ( $instance ) :
154
  false
155
  );
156
  HTML::wrap_fields(
157
- Form::make_checkbox( [
158
  'id' => 'knowledge_logo',
159
- 'label' => esc_html__( 'Enable logo?', 'autodescription' ) . ' ' . $info,
160
  'escape' => false,
161
  ] ),
162
  true );
@@ -168,16 +166,16 @@ switch ( $instance ) :
168
  <strong><?php esc_html_e( 'Logo URL', 'autodescription' ); ?></strong>
169
  </label>
170
  </p>
 
171
  <p>
172
- <span class="hide-if-tsf-js attention"><?php esc_html_e( 'Setting a logo requires JavaScript.', 'autodescription' ); ?></span>
173
- <input class="large-text" type="url" readonly="readonly" data-readonly="1" name="<?php Form::field_name( 'knowledge_logo_url' ); ?>" id="knowledge_logo-url" placeholder="<?php echo esc_url( $logo_placeholder ); ?>" value="<?php echo esc_url( $this->get_option( 'knowledge_logo_url' ) ); ?>" />
174
- <input type="hidden" name="<?php Form::field_name( 'knowledge_logo_id' ); ?>" id="knowledge_logo-id" value="<?php echo absint( $this->get_option( 'knowledge_logo_id' ) ); ?>" />
175
  </p>
176
  <p class="hide-if-no-tsf-js">
177
  <?php
178
  // phpcs:ignore, WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped.
179
  echo Form::get_image_uploader_form( [
180
- 'id' => 'sitemap_logo',
181
  'data' => [
182
  'inputType' => 'logo',
183
  'width' => 512,
@@ -204,14 +202,14 @@ switch ( $instance ) :
204
  'option' => 'knowledge_facebook',
205
  'dashicon' => 'dashicons-facebook',
206
  'desc' => __( 'Facebook Page', 'autodescription' ),
207
- 'placeholder' => 'https://www.facebook.com/' . $connectedi18n,
208
  'examplelink' => 'https://www.facebook.com/me',
209
  ],
210
  'twitter' => [
211
  'option' => 'knowledge_twitter',
212
  'dashicon' => 'dashicons-twitter',
213
  'desc' => __( 'Twitter Profile', 'autodescription' ),
214
- 'placeholder' => 'https://twitter.com/' . $connectedi18n,
215
  'examplelink' => 'https://twitter.com/home', // No example link available.
216
  ],
217
  'gplus' => [
@@ -225,14 +223,14 @@ switch ( $instance ) :
225
  'option' => 'knowledge_instagram',
226
  'dashicon' => 'genericon-instagram',
227
  'desc' => __( 'Instagram Profile', 'autodescription' ),
228
- 'placeholder' => 'https://instagram.com/' . $connectedi18n,
229
  'examplelink' => 'https://instagram.com/', // No example link available.
230
  ],
231
  'youtube' => [
232
  'option' => 'knowledge_youtube',
233
  'dashicon' => 'genericon-youtube',
234
  'desc' => __( 'Youtube Profile', 'autodescription' ),
235
- 'placeholder' => 'https://www.youtube.com/channel/' . $connectedi18n,
236
  'examplelink' => 'https://www.youtube.com/user/%2f', // Yes a double slash.
237
  ],
238
  'linkedin' => [
@@ -243,28 +241,28 @@ switch ( $instance ) :
243
  * TODO switch to /in/ insteadof /company/ when knowledge-type is personal?
244
  * Note that this feature is DEPRECATED. https://developers.google.com/search/docs/data-types/social-profile
245
  */
246
- 'placeholder' => 'https://www.linkedin.com/company/' . $connectedi18n . '/',
247
  'examplelink' => 'https://www.linkedin.com/profile/view',
248
  ],
249
  'pinterest' => [
250
  'option' => 'knowledge_pinterest',
251
  'dashicon' => 'genericon-pinterest-alt',
252
  'desc' => __( 'Pinterest Profile', 'autodescription' ),
253
- 'placeholder' => 'https://www.pinterest.com/' . $connectedi18n . '/',
254
  'examplelink' => 'https://www.pinterest.com/me/',
255
  ],
256
  'soundcloud' => [
257
  'option' => 'knowledge_soundcloud',
258
  'dashicon' => 'genericon-cloud', // I know, it's not the real one. D:
259
  'desc' => __( 'SoundCloud Profile', 'autodescription' ),
260
- 'placeholder' => 'https://soundcloud.com/' . $connectedi18n,
261
  'examplelink' => 'https://soundcloud.com/you',
262
  ],
263
  'tumblr' => [
264
  'option' => 'knowledge_tumblr',
265
  'dashicon' => 'genericon-tumblr',
266
  'desc' => __( 'Tumblr Blog', 'autodescription' ),
267
- 'placeholder' => 'https://www.tumblr.com/blog/' . $connectedi18n,
268
  'examplelink' => 'https://www.tumblr.com/dashboard', // No example link available.
269
  ],
270
  ];
@@ -282,7 +280,7 @@ switch ( $instance ) :
282
  ?>
283
  <hr>
284
  <?php
285
- Form::header_title( __( 'Connected Social Pages', 'autodescription' ) );
286
  HTML::description( __( "Don't have a page at a site or is the profile only privately accessible? Leave that field empty. Unsure? Fill it in anyway.", 'autodescription' ) );
287
  HTML::description( __( 'Add links that lead directly to the connected social pages of this website.', 'autodescription' ) );
288
  HTML::description( __( 'These settings do not affect sharing behavior with the social networks.', 'autodescription' ) );
@@ -304,7 +302,7 @@ switch ( $instance ) :
304
 
305
  ?>
306
  <p>
307
- <label for="<?php Form::field_id( $v['option'] ); ?>">
308
  <strong><?php echo esc_html( $v['desc'] ); ?></strong>
309
  <?php
310
  if ( $v['examplelink'] ) {
@@ -317,7 +315,7 @@ switch ( $instance ) :
317
  </label>
318
  </p>
319
  <p>
320
- <input type="url" name="<?php Form::field_name( $v['option'] ); ?>" class="large-text" id="<?php Form::field_id( $v['option'] ); ?>" placeholder="<?php echo esc_attr( $v['placeholder'] ); ?>" value="<?php echo esc_attr( $this->get_option( $v['option'] ) ); ?>" autocomplete=off />
321
  </p>
322
  <?php
323
  }
9
 
10
  use The_SEO_Framework\Bridges\SeoSettings,
11
  The_SEO_Framework\Interpreters\HTML,
12
+ The_SEO_Framework\Interpreters\Form,
13
+ The_SEO_Framework\Interpreters\Settings_Input as Input;
14
 
15
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
16
 
17
+ switch ( $this->get_view_instance( 'schema', $instance ) ) :
18
+ case 'schema_main':
19
+ HTML::header_title( __( 'Schema.org Output Settings', 'autodescription' ) );
 
 
 
20
 
21
  if ( $this->has_json_ld_plugin() )
22
  HTML::attention_description( __( 'Another Schema.org plugin has been detected. These markup settings might conflict.', 'autodescription' ) );
25
  HTML::description( __( 'When your web pages include structured data markup, search engines can use that data to index your content better, present it more prominently in search results, and use it in several different applications.', 'autodescription' ) );
26
  HTML::description( __( 'This is also known as the "Knowledge Graph" and "Structured Data", which is under heavy active development by several search engines. Therefore, the usage of the outputted markup is not guaranteed.', 'autodescription' ) );
27
 
28
+ $_settings_class = SeoSettings::class;
29
+
30
+ $tabs = [
31
  'structure' => [
32
  'name' => __( 'Structure', 'autodescription' ),
33
+ 'callback' => [ $_settings_class, '_schema_metabox_structure_tab' ],
34
  'dashicon' => 'admin-multisite',
35
  ],
36
  'presence' => [
37
  'name' => __( 'Presence', 'autodescription' ),
38
+ 'callback' => [ $_settings_class, '_schema_metabox_presence_tab' ],
39
  'dashicon' => 'networking',
40
  ],
41
  ];
42
 
43
+ SeoSettings::_nav_tab_wrapper(
44
+ 'schema',
45
+ /**
46
+ * @since 2.8.0
47
+ * @param array $defaults The default tabs.
48
+ */
49
+ (array) apply_filters( 'the_seo_framework_schema_settings_tabs', $tabs )
50
+ );
 
 
51
  break;
52
 
53
+ case 'schema_structure_tab':
54
+ HTML::header_title( __( 'Site Structure Options', 'autodescription' ) );
55
  HTML::description( __( 'The site structure Schema.org output allows search engines to gain knowledge on how your website is built.', 'autodescription' ) );
56
  HTML::description( __( "For example, search engines display your pages' URLs when listed in the search results. These options allow you to enhance those URLs output.", 'autodescription' ) );
57
  ?>
58
+ <hr>
59
+ <?php
60
+ HTML::header_title( __( 'Breadcrumbs', 'autodescription' ) );
61
  HTML::description( __( "Breadcrumb trails indicate page positions in the site's hierarchy. Using the following option will show the hierarchy within the search results when available.", 'autodescription' ) );
62
 
63
  $info = HTML::make_info(
66
  false
67
  );
68
  HTML::wrap_fields(
69
+ Input::make_checkbox( [
70
  'id' => 'ld_json_breadcrumbs',
71
+ 'label' => esc_html__( 'Enable Breadcrumbs?', 'autodescription' ) . " $info",
72
  'escape' => false,
73
  ] ),
74
  true
86
  false
87
  );
88
  HTML::wrap_fields(
89
+ Input::make_checkbox( [
90
  'id' => 'ld_json_searchbox',
91
+ 'label' => esc_html_x( 'Enable Sitelinks Searchbox?', 'Sitelinks Searchbox is a Product name', 'autodescription' ) . " $info",
92
  'escape' => false,
93
  ] ),
94
  true
95
  );
96
  break;
97
 
98
+ case 'schema_presence_tab':
99
+ HTML::header_title( __( 'Authorized Presence Options', 'autodescription' ) );
100
  HTML::description( __( 'The authorized presence Schema.org output helps search engine users find ways to interact with this website.', 'autodescription' ) );
101
 
102
  $info = HTML::make_info(
104
  'https://developers.google.com/search/docs/guides/enhance-site#add-your-sites-name-logo-and-social-links',
105
  false
106
  );
 
107
  HTML::wrap_fields(
108
+ Input::make_checkbox( [
109
  'id' => 'knowledge_output',
110
+ 'label' => esc_html__( 'Output Authorized Presence?', 'autodescription' ) . " $info",
111
  'escape' => false,
112
  ] ),
113
  true
115
  ?>
116
  <hr>
117
 
118
+ <?php HTML::header_title( __( 'About this website', 'autodescription' ) ); ?>
119
  <p>
120
+ <label for="<?php Input::field_id( 'knowledge_type' ); ?>"><?php echo esc_html_x( 'This website represents:', '...Organization or Person.', 'autodescription' ); ?></label>
121
+ <select name="<?php Input::field_name( 'knowledge_type' ); ?>" id="<?php Input::field_id( 'knowledge_type' ); ?>">
122
  <?php
123
  $knowledge_type = (array) apply_filters(
124
  'the_seo_framework_knowledge_types',
135
  </p>
136
 
137
  <p>
138
+ <label for="<?php Input::field_id( 'knowledge_name' ); ?>">
139
  <strong><?php esc_html_e( 'The organization or personal name', 'autodescription' ); ?></strong>
140
  </label>
141
  </p>
142
  <p>
143
+ <input type="text" name="<?php Input::field_name( 'knowledge_name' ); ?>" class="large-text" id="<?php Input::field_id( 'knowledge_name' ); ?>" placeholder="<?php echo esc_attr( $this->get_blogname() ); ?>" value="<?php echo esc_attr( $this->get_option( 'knowledge_name' ) ); ?>" autocomplete=off />
144
  </p>
145
  <hr>
146
  <?php
147
+ HTML::header_title( __( 'Website logo', 'autodescription' ) );
148
  HTML::description( esc_html__( 'These options are used when this site represents an organization. When no logo is outputted, search engine will look elsewhere.', 'autodescription' ) );
149
  $info = HTML::make_info(
150
  __( 'Learn how this data is used.', 'autodescription' ),
152
  false
153
  );
154
  HTML::wrap_fields(
155
+ Input::make_checkbox( [
156
  'id' => 'knowledge_logo',
157
+ 'label' => esc_html__( 'Enable logo?', 'autodescription' ) . " $info",
158
  'escape' => false,
159
  ] ),
160
  true );
166
  <strong><?php esc_html_e( 'Logo URL', 'autodescription' ); ?></strong>
167
  </label>
168
  </p>
169
+ <p class="hide-if-tsf-js attention"><?php esc_html_e( 'Setting a logo requires JavaScript.', 'autodescription' ); ?></p>
170
  <p>
171
+ <input class="large-text" type="url" readonly="readonly" data-readonly="1" name="<?php Input::field_name( 'knowledge_logo_url' ); ?>" id="knowledge_logo-url" placeholder="<?php echo esc_url( $logo_placeholder ); ?>" value="<?php echo esc_url( $this->get_option( 'knowledge_logo_url' ) ); ?>" />
172
+ <input type="hidden" name="<?php Input::field_name( 'knowledge_logo_id' ); ?>" id="knowledge_logo-id" value="<?php echo absint( $this->get_option( 'knowledge_logo_id' ) ); ?>" />
 
173
  </p>
174
  <p class="hide-if-no-tsf-js">
175
  <?php
176
  // phpcs:ignore, WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped.
177
  echo Form::get_image_uploader_form( [
178
+ 'id' => 'knowledge_logo',
179
  'data' => [
180
  'inputType' => 'logo',
181
  'width' => 512,
202
  'option' => 'knowledge_facebook',
203
  'dashicon' => 'dashicons-facebook',
204
  'desc' => __( 'Facebook Page', 'autodescription' ),
205
+ 'placeholder' => "https://www.facebook.com/$connectedi18n",
206
  'examplelink' => 'https://www.facebook.com/me',
207
  ],
208
  'twitter' => [
209
  'option' => 'knowledge_twitter',
210
  'dashicon' => 'dashicons-twitter',
211
  'desc' => __( 'Twitter Profile', 'autodescription' ),
212
+ 'placeholder' => "https://twitter.com/$connectedi18n",
213
  'examplelink' => 'https://twitter.com/home', // No example link available.
214
  ],
215
  'gplus' => [
223
  'option' => 'knowledge_instagram',
224
  'dashicon' => 'genericon-instagram',
225
  'desc' => __( 'Instagram Profile', 'autodescription' ),
226
+ 'placeholder' => "https://instagram.com/$connectedi18n",
227
  'examplelink' => 'https://instagram.com/', // No example link available.
228
  ],
229
  'youtube' => [
230
  'option' => 'knowledge_youtube',
231
  'dashicon' => 'genericon-youtube',
232
  'desc' => __( 'Youtube Profile', 'autodescription' ),
233
+ 'placeholder' => "https://www.youtube.com/channel/$connectedi18n",
234
  'examplelink' => 'https://www.youtube.com/user/%2f', // Yes a double slash.
235
  ],
236
  'linkedin' => [
241
  * TODO switch to /in/ insteadof /company/ when knowledge-type is personal?
242
  * Note that this feature is DEPRECATED. https://developers.google.com/search/docs/data-types/social-profile
243
  */
244
+ 'placeholder' => "https://www.linkedin.com/company/$connectedi18n/",
245
  'examplelink' => 'https://www.linkedin.com/profile/view',
246
  ],
247
  'pinterest' => [
248
  'option' => 'knowledge_pinterest',
249
  'dashicon' => 'genericon-pinterest-alt',
250
  'desc' => __( 'Pinterest Profile', 'autodescription' ),
251
+ 'placeholder' => "https://www.pinterest.com/$connectedi18n/",
252
  'examplelink' => 'https://www.pinterest.com/me/',
253
  ],
254
  'soundcloud' => [
255
  'option' => 'knowledge_soundcloud',
256
  'dashicon' => 'genericon-cloud', // I know, it's not the real one. D:
257
  'desc' => __( 'SoundCloud Profile', 'autodescription' ),
258
+ 'placeholder' => "https://soundcloud.com/$connectedi18n",
259
  'examplelink' => 'https://soundcloud.com/you',
260
  ],
261
  'tumblr' => [
262
  'option' => 'knowledge_tumblr',
263
  'dashicon' => 'genericon-tumblr',
264
  'desc' => __( 'Tumblr Blog', 'autodescription' ),
265
+ 'placeholder' => "https://www.tumblr.com/blog/$connectedi18n",
266
  'examplelink' => 'https://www.tumblr.com/dashboard', // No example link available.
267
  ],
268
  ];
280
  ?>
281
  <hr>
282
  <?php
283
+ HTML::header_title( __( 'Connected Social Pages', 'autodescription' ) );
284
  HTML::description( __( "Don't have a page at a site or is the profile only privately accessible? Leave that field empty. Unsure? Fill it in anyway.", 'autodescription' ) );
285
  HTML::description( __( 'Add links that lead directly to the connected social pages of this website.', 'autodescription' ) );
286
  HTML::description( __( 'These settings do not affect sharing behavior with the social networks.', 'autodescription' ) );
302
 
303
  ?>
304
  <p>
305
+ <label for="<?php Input::field_id( $v['option'] ); ?>">
306
  <strong><?php echo esc_html( $v['desc'] ); ?></strong>
307
  <?php
308
  if ( $v['examplelink'] ) {
315
  </label>
316
  </p>
317
  <p>
318
+ <input type="url" name="<?php Input::field_name( $v['option'] ); ?>" class="large-text" id="<?php Input::field_id( $v['option'] ); ?>" placeholder="<?php echo esc_attr( $v['placeholder'] ); ?>" value="<?php echo esc_attr( $this->get_option( $v['option'] ) ); ?>" autocomplete=off />
319
  </p>
320
  <?php
321
  }
inc/views/{admin/metaboxes/sitemaps-metabox.php → settings/metaboxes/sitemaps.php} RENAMED
@@ -9,61 +9,60 @@
9
 
10
  use The_SEO_Framework\Bridges\SeoSettings,
11
  The_SEO_Framework\Interpreters\HTML,
12
- The_SEO_Framework\Interpreters\Form;
 
13
 
14
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
15
 
16
- // Fetch the required instance within this file.
17
- $instance = $this->get_view_instance( 'the_seo_framework_sitemaps_metabox', $instance );
 
18
 
19
- switch ( $instance ) :
20
- case 'the_seo_framework_sitemaps_metabox_main':
21
- $default_tabs = [
22
  'general' => [
23
  'name' => __( 'General', 'autodescription' ),
24
- 'callback' => SeoSettings::class . '::_sitemaps_metabox_general_tab',
25
  'dashicon' => 'admin-generic',
26
  ],
27
  'robots' => [
28
  'name' => 'Robots.txt',
29
- 'callback' => SeoSettings::class . '::_sitemaps_metabox_robots_tab',
30
  'dashicon' => 'share-alt2',
31
  ],
32
  'metadata' => [
33
  'name' => __( 'Metadata', 'autodescription' ),
34
- 'callback' => SeoSettings::class . '::_sitemaps_metabox_metadata_tab',
35
  'dashicon' => 'index-card',
36
  ],
37
  'notify' => [
38
  'name' => _x( 'Ping', 'Ping or notify search engine', 'autodescription' ),
39
- 'callback' => SeoSettings::class . '::_sitemaps_metabox_notify_tab',
40
  'dashicon' => 'megaphone',
41
  ],
42
  'style' => [
43
  'name' => __( 'Style', 'autodescription' ),
44
- 'callback' => SeoSettings::class . '::_sitemaps_metabox_style_tab',
45
  'dashicon' => 'art',
46
  ],
47
  ];
48
 
49
- /**
50
- * @param array $defaults The default tabs.
51
- * @param array $args The args added on the callback.
52
- */
53
- $defaults = (array) apply_filters( 'the_seo_framework_sitemaps_settings_tabs', $default_tabs, $args );
54
-
55
- $tabs = wp_parse_args( $args, $defaults );
56
-
57
- SeoSettings::_nav_tab_wrapper( 'sitemaps', $tabs );
58
  break;
59
 
60
- case 'the_seo_framework_sitemaps_metabox_general':
61
  $sitemap_url = The_SEO_Framework\Bridges\Sitemap::get_instance()->get_expected_sitemap_endpoint_url();
62
  $has_sitemap_plugin = $this->detect_sitemap_plugin();
63
  $use_core_sitemaps = $this->use_core_sitemaps();
64
  $sitemap_detected = $this->has_sitemap_xml();
65
 
66
- Form::header_title( __( 'Sitemap Integration Settings', 'autodescription' ) );
67
  HTML::description( __( 'The sitemap is an XML file that lists indexable pages of your website along with optional metadata. It helps search engines find new and updated content quickly.', 'autodescription' ) );
68
 
69
  HTML::description_noesc(
@@ -88,11 +87,10 @@ switch ( $instance ) :
88
  ?>
89
  <hr>
90
  <?php
91
- Form::header_title( __( 'Sitemap Output', 'autodescription' ) );
92
 
93
- // Echo checkbox.
94
  HTML::wrap_fields(
95
- Form::make_checkbox( [
96
  'id' => 'sitemaps_output',
97
  'label' => esc_html__( 'Output optimized sitemap?', 'autodescription' )
98
  . ' ' . HTML::make_info(
@@ -127,13 +125,31 @@ switch ( $instance ) :
127
  )
128
  );
129
  }
130
- }
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  ?>
133
  <hr>
134
 
135
  <p>
136
- <label for="<?php Form::field_id( 'sitemap_query_limit' ); ?>">
137
  <strong><?php esc_html_e( 'Sitemap Query Limit', 'autodescription' ); ?></strong>
138
  </label>
139
  </p>
@@ -142,26 +158,26 @@ switch ( $instance ) :
142
 
143
  if ( has_filter( 'the_seo_framework_sitemap_post_limit' ) ) :
144
  ?>
145
- <input type=hidden name="<?php Form::field_name( 'sitemap_query_limit' ); ?>" value="<?php echo absint( $this->get_sitemap_post_limit() ); ?>">
146
  <p>
147
- <input type="number" id="<?php Form::field_id( 'sitemap_query_limit' ); ?>" value="<?php echo absint( $this->get_sitemap_post_limit() ); ?>" disabled />
148
  </p>
149
  <?php
150
  else :
151
  ?>
152
  <p>
153
- <input type="number" min=1 max=50000 name="<?php Form::field_name( 'sitemap_query_limit' ); ?>" id="<?php Form::field_id( 'sitemap_query_limit' ); ?>" placeholder="<?php echo absint( $this->get_default_option( 'sitemap_query_limit' ) ); ?>" value="<?php echo absint( $this->get_option( 'sitemap_query_limit' ) ); ?>" />
154
  </p>
155
  <?php
156
  endif;
157
  HTML::description( __( 'Consider lowering this value when the sitemap shows a white screen or notifies you of memory exhaustion.', 'autodescription' ) );
158
  break;
159
 
160
- case 'the_seo_framework_sitemaps_metabox_robots':
161
  $show_settings = true;
162
  $robots_url = $this->get_robots_txt_url();
163
 
164
- Form::header_title( __( 'Robots.txt Settings', 'autodescription' ) );
165
 
166
  if ( $this->has_robots_txt() ) :
167
  HTML::attention_description(
@@ -200,12 +216,9 @@ switch ( $instance ) :
200
  echo '<hr>';
201
 
202
  if ( $show_settings ) :
203
- printf(
204
- '<h4>%s</h4>',
205
- esc_html__( 'Sitemap Hinting', 'autodescription' )
206
- );
207
  HTML::wrap_fields(
208
- Form::make_checkbox( [
209
  'id' => 'sitemaps_robots',
210
  'label' => __( 'Add sitemap location to robots.txt?', 'autodescription' ),
211
  ] ),
@@ -226,13 +239,12 @@ switch ( $instance ) :
226
  }
227
  break;
228
 
229
- case 'the_seo_framework_sitemaps_metabox_metadata':
230
- Form::header_title( __( 'Timestamps Settings', 'autodescription' ) );
231
  HTML::description( __( 'The modified time suggests to search engines where to look for content changes first.', 'autodescription' ) );
232
 
233
- // Echo checkbox.
234
  HTML::wrap_fields(
235
- Form::make_checkbox( [
236
  'id' => 'sitemaps_modified',
237
  'label' => $this->convert_markdown(
238
  /* translators: the backticks are Markdown! Preserve them as-is! */
@@ -243,38 +255,16 @@ switch ( $instance ) :
243
  ] ),
244
  true
245
  );
246
-
247
- if ( $this->get_option( 'sitemaps_priority' ) ) :
248
- ?>
249
- <hr>
250
- <?php
251
- Form::header_title( __( 'Priority Settings', 'autodescription' ) );
252
- HTML::description( __( 'The priority index suggests to search engines which pages are deemed more important. It has no known impact on the SEO value and it is generally ignored.', 'autodescription' ) );
253
-
254
- // Echo checkbox.
255
- HTML::wrap_fields(
256
- Form::make_checkbox( [
257
- 'id' => 'sitemaps_priority',
258
- 'label' => $this->convert_markdown(
259
- /* translators: the backticks are Markdown! Preserve them as-is! */
260
- esc_html__( 'Add `<priority>` to the optimized sitemap?', 'autodescription' ),
261
- [ 'code' ]
262
- ),
263
- 'escape' => false,
264
- ] ),
265
- true
266
- );
267
- endif;
268
  break;
269
 
270
- case 'the_seo_framework_sitemaps_metabox_notify':
271
- Form::header_title( __( 'Ping Settings', 'autodescription' ) );
272
  HTML::description( __( 'Notifying search engines of a sitemap change is helpful to get your content indexed as soon as possible.', 'autodescription' ) );
273
  HTML::description( __( 'By default this will happen at most once an hour.', 'autodescription' ) );
274
 
275
  HTML::wrap_fields(
276
  [
277
- Form::make_checkbox( [
278
  'id' => 'ping_use_cron',
279
  'label' => esc_html__( 'Use cron for pinging?', 'autodescription' )
280
  . ' ' . HTML::make_info(
@@ -284,7 +274,7 @@ switch ( $instance ) :
284
  ),
285
  'escape' => false,
286
  ] ),
287
- Form::make_checkbox( [
288
  'id' => 'ping_use_cron_prerender',
289
  'label' => esc_html__( 'Prerender optimized sitemap before pinging via cron?', 'autodescription' )
290
  . ' ' . HTML::make_info(
@@ -302,7 +292,7 @@ switch ( $instance ) :
302
  ?>
303
  <hr>
304
  <?php
305
- Form::header_title( __( 'Notify Search Engines', 'autodescription' ) );
306
 
307
  $engines = [
308
  'ping_google' => 'Google',
@@ -314,27 +304,26 @@ switch ( $instance ) :
314
  foreach ( $engines as $option => $engine ) {
315
  /* translators: %s = Google */
316
  $ping_label = sprintf( __( 'Notify %s about sitemap changes?', 'autodescription' ), $engine );
317
- $ping_checkbox .= Form::make_checkbox( [
318
  'id' => $option,
319
  'label' => $ping_label,
320
  ] );
321
  }
322
 
323
- // Echo checkbox.
324
  HTML::wrap_fields( $ping_checkbox, true );
325
  break;
326
 
327
- case 'the_seo_framework_sitemaps_metabox_style':
328
- Form::header_title( __( 'Optimized Sitemap Styling Settings', 'autodescription' ) );
329
  HTML::description( __( 'You can style the optimized sitemap to give it a more personal look for your visitors. Search engines do not use these styles.', 'autodescription' ) );
330
  HTML::description( __( 'Note: Changes may not appear to have an effect directly because the stylesheet is cached in the browser for 30 minutes.', 'autodescription' ) );
331
  ?>
332
  <hr>
333
  <?php
334
- Form::header_title( __( 'Enable Styling', 'autodescription' ) );
335
 
336
  HTML::wrap_fields(
337
- Form::make_checkbox( [
338
  'id' => 'sitemap_styles',
339
  'label' => esc_html__( 'Style sitemap?', 'autodescription' ) . ' ' . HTML::make_info( __( 'This makes the sitemap more readable for humans.', 'autodescription' ), '', false ),
340
  'escape' => false,
@@ -351,29 +340,29 @@ switch ( $instance ) :
351
 
352
  ?>
353
  <p>
354
- <label for="<?php Form::field_id( 'sitemap_color_main' ); ?>">
355
  <strong><?php esc_html_e( 'Sitemap Header Background Color', 'autodescription' ); ?></strong>
356
  </label>
357
  </p>
358
  <p>
359
- <input type="text" name="<?php Form::field_name( 'sitemap_color_main' ); ?>" class="tsf-color-picker" id="<?php Form::field_id( 'sitemap_color_main' ); ?>" placeholder="<?php echo esc_attr( $default_colors['main'] ); ?>" value="<?php echo esc_attr( $current_colors['main'] ); ?>" data-tsf-default-color="<?php echo esc_attr( $default_colors['main'] ); ?>" />
360
  </p>
361
 
362
  <p>
363
- <label for="<?php Form::field_id( 'sitemap_color_accent' ); ?>">
364
  <strong><?php esc_html_e( 'Sitemap Title and Lines Color', 'autodescription' ); ?></strong>
365
  </label>
366
  </p>
367
  <p>
368
- <input type="text" name="<?php Form::field_name( 'sitemap_color_accent' ); ?>" class="tsf-color-picker" id="<?php Form::field_id( 'sitemap_color_accent' ); ?>" placeholder="<?php echo esc_attr( $default_colors['accent'] ); ?>" value="<?php echo esc_attr( $current_colors['accent'] ); ?>" data-tsf-default-color="<?php echo esc_attr( $default_colors['accent'] ); ?>" />
369
  </p>
370
 
371
  <hr>
372
  <?php
373
- Form::header_title( __( 'Header Title Logo', 'autodescription' ) );
374
 
375
  HTML::wrap_fields(
376
- Form::make_checkbox( [
377
  'id' => 'sitemap_logo',
378
  'label' => __( 'Show logo next to sitemap header title?', 'autodescription' ),
379
  ] ),
@@ -391,10 +380,10 @@ switch ( $instance ) :
391
  <strong><?php esc_html_e( 'Logo URL', 'autodescription' ); ?></strong>
392
  </label>
393
  </p>
 
394
  <p>
395
- <span class="hide-if-tsf-js attention"><?php esc_html_e( 'Setting a logo requires JavaScript.', 'autodescription' ); ?></span>
396
- <input class="large-text" type="url" readonly="readonly" data-readonly="1" name="<?php Form::field_name( 'sitemap_logo_url' ); ?>" id="sitemap_logo-url" placeholder="<?php echo esc_url( $logo_placeholder ); ?>" value="<?php echo esc_url( $this->get_option( 'sitemap_logo_url' ) ); ?>" />
397
- <input type="hidden" name="<?php Form::field_name( 'sitemap_logo_id' ); ?>" id="sitemap_logo-id" value="<?php echo absint( $this->get_option( 'sitemap_logo_id' ) ); ?>" />
398
  </p>
399
  <p class="hide-if-no-tsf-js">
400
  <?php
9
 
10
  use The_SEO_Framework\Bridges\SeoSettings,
11
  The_SEO_Framework\Interpreters\HTML,
12
+ The_SEO_Framework\Interpreters\Form,
13
+ The_SEO_Framework\Interpreters\Settings_Input as Input;
14
 
15
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
16
 
17
+ switch ( $this->get_view_instance( 'sitemaps', $instance ) ) :
18
+ case 'sitemaps_main':
19
+ $_settings_class = SeoSettings::class;
20
 
21
+ $tabs = [
 
 
22
  'general' => [
23
  'name' => __( 'General', 'autodescription' ),
24
+ 'callback' => [ $_settings_class, '_sitemaps_metabox_general_tab' ],
25
  'dashicon' => 'admin-generic',
26
  ],
27
  'robots' => [
28
  'name' => 'Robots.txt',
29
+ 'callback' => [ $_settings_class, '_sitemaps_metabox_robots_tab' ],
30
  'dashicon' => 'share-alt2',
31
  ],
32
  'metadata' => [
33
  'name' => __( 'Metadata', 'autodescription' ),
34
+ 'callback' => [ $_settings_class, '_sitemaps_metabox_metadata_tab' ],
35
  'dashicon' => 'index-card',
36
  ],
37
  'notify' => [
38
  'name' => _x( 'Ping', 'Ping or notify search engine', 'autodescription' ),
39
+ 'callback' => [ $_settings_class, '_sitemaps_metabox_notify_tab' ],
40
  'dashicon' => 'megaphone',
41
  ],
42
  'style' => [
43
  'name' => __( 'Style', 'autodescription' ),
44
+ 'callback' => [ $_settings_class, '_sitemaps_metabox_style_tab' ],
45
  'dashicon' => 'art',
46
  ],
47
  ];
48
 
49
+ SeoSettings::_nav_tab_wrapper(
50
+ 'sitemaps',
51
+ /**
52
+ * @since 2.6.0
53
+ * @param array $tabs The default tabs.
54
+ */
55
+ (array) apply_filters( 'the_seo_framework_sitemaps_settings_tabs', $tabs )
56
+ );
 
57
  break;
58
 
59
+ case 'sitemaps_general_tab':
60
  $sitemap_url = The_SEO_Framework\Bridges\Sitemap::get_instance()->get_expected_sitemap_endpoint_url();
61
  $has_sitemap_plugin = $this->detect_sitemap_plugin();
62
  $use_core_sitemaps = $this->use_core_sitemaps();
63
  $sitemap_detected = $this->has_sitemap_xml();
64
 
65
+ HTML::header_title( __( 'Sitemap Integration Settings', 'autodescription' ) );
66
  HTML::description( __( 'The sitemap is an XML file that lists indexable pages of your website along with optional metadata. It helps search engines find new and updated content quickly.', 'autodescription' ) );
67
 
68
  HTML::description_noesc(
87
  ?>
88
  <hr>
89
  <?php
90
+ HTML::header_title( __( 'Sitemap Output', 'autodescription' ) );
91
 
 
92
  HTML::wrap_fields(
93
+ Input::make_checkbox( [
94
  'id' => 'sitemaps_output',
95
  'label' => esc_html__( 'Output optimized sitemap?', 'autodescription' )
96
  . ' ' . HTML::make_info(
125
  )
126
  );
127
  }
 
128
 
129
+ /**
130
+ * @since 4.2.0
131
+ * @param bool $tell Whether to tell that there's a plugin active that can use multiple sitemaps.
132
+ */
133
+ if ( apply_filters( 'the_seo_framework_tell_multilingual_sitemap', false ) ) {
134
+ HTML::description_noesc(
135
+ // Markdown escapes.
136
+ $this->convert_markdown(
137
+ sprintf(
138
+ /* translators: %s = Documentation URL in markdown */
139
+ esc_html__( 'A multilingual plugin has been detected, so your site may have multiple sitemaps. [Learn more](%s).', 'autodescription' ),
140
+ 'https://kb.theseoframework.com/?p=104#same-site-sitemaps'
141
+ ),
142
+ [ 'a' ],
143
+ [ 'a_internal' => false ] // opens in new tab.
144
+ )
145
+ );
146
+ }
147
+ }
148
  ?>
149
  <hr>
150
 
151
  <p>
152
+ <label for="<?php Input::field_id( 'sitemap_query_limit' ); ?>">
153
  <strong><?php esc_html_e( 'Sitemap Query Limit', 'autodescription' ); ?></strong>
154
  </label>
155
  </p>
158
 
159
  if ( has_filter( 'the_seo_framework_sitemap_post_limit' ) ) :
160
  ?>
161
+ <input type=hidden name="<?php Input::field_name( 'sitemap_query_limit' ); ?>" value="<?php echo absint( $this->get_sitemap_post_limit() ); ?>">
162
  <p>
163
+ <input type="number" id="<?php Input::field_id( 'sitemap_query_limit' ); ?>" value="<?php echo absint( $this->get_sitemap_post_limit() ); ?>" disabled />
164
  </p>
165
  <?php
166
  else :
167
  ?>
168
  <p>
169
+ <input type="number" min=1 max=50000 name="<?php Input::field_name( 'sitemap_query_limit' ); ?>" id="<?php Input::field_id( 'sitemap_query_limit' ); ?>" placeholder="<?php echo absint( $this->get_default_option( 'sitemap_query_limit' ) ); ?>" value="<?php echo absint( $this->get_option( 'sitemap_query_limit' ) ); ?>" />
170
  </p>
171
  <?php
172
  endif;
173
  HTML::description( __( 'Consider lowering this value when the sitemap shows a white screen or notifies you of memory exhaustion.', 'autodescription' ) );
174
  break;
175
 
176
+ case 'sitemaps_robots_tab':
177
  $show_settings = true;
178
  $robots_url = $this->get_robots_txt_url();
179
 
180
+ HTML::header_title( __( 'Robots.txt Settings', 'autodescription' ) );
181
 
182
  if ( $this->has_robots_txt() ) :
183
  HTML::attention_description(
216
  echo '<hr>';
217
 
218
  if ( $show_settings ) :
219
+ HTML::header_title( __( 'Sitemap Hinting', 'autodescription' ) );
 
 
 
220
  HTML::wrap_fields(
221
+ Input::make_checkbox( [
222
  'id' => 'sitemaps_robots',
223
  'label' => __( 'Add sitemap location to robots.txt?', 'autodescription' ),
224
  ] ),
239
  }
240
  break;
241
 
242
+ case 'sitemaps_metadata_tab':
243
+ HTML::header_title( __( 'Timestamps Settings', 'autodescription' ) );
244
  HTML::description( __( 'The modified time suggests to search engines where to look for content changes first.', 'autodescription' ) );
245
 
 
246
  HTML::wrap_fields(
247
+ Input::make_checkbox( [
248
  'id' => 'sitemaps_modified',
249
  'label' => $this->convert_markdown(
250
  /* translators: the backticks are Markdown! Preserve them as-is! */
255
  ] ),
256
  true
257
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  break;
259
 
260
+ case 'sitemaps_notify_tab':
261
+ HTML::header_title( __( 'Ping Settings', 'autodescription' ) );
262
  HTML::description( __( 'Notifying search engines of a sitemap change is helpful to get your content indexed as soon as possible.', 'autodescription' ) );
263
  HTML::description( __( 'By default this will happen at most once an hour.', 'autodescription' ) );
264
 
265
  HTML::wrap_fields(
266
  [
267
+ Input::make_checkbox( [
268
  'id' => 'ping_use_cron',
269
  'label' => esc_html__( 'Use cron for pinging?', 'autodescription' )
270
  . ' ' . HTML::make_info(
274
  ),
275
  'escape' => false,
276
  ] ),
277
+ Input::make_checkbox( [
278
  'id' => 'ping_use_cron_prerender',
279
  'label' => esc_html__( 'Prerender optimized sitemap before pinging via cron?', 'autodescription' )
280
  . ' ' . HTML::make_info(
292
  ?>
293
  <hr>
294
  <?php
295
+ HTML::header_title( __( 'Notify Search Engines', 'autodescription' ) );
296
 
297
  $engines = [
298
  'ping_google' => 'Google',
304
  foreach ( $engines as $option => $engine ) {
305
  /* translators: %s = Google */
306
  $ping_label = sprintf( __( 'Notify %s about sitemap changes?', 'autodescription' ), $engine );
307
+ $ping_checkbox .= Input::make_checkbox( [
308
  'id' => $option,
309
  'label' => $ping_label,
310
  ] );
311
  }
312
 
 
313
  HTML::wrap_fields( $ping_checkbox, true );
314
  break;
315
 
316
+ case 'sitemaps_style_tab':
317
+ HTML::header_title( __( 'Optimized Sitemap Styling Settings', 'autodescription' ) );
318
  HTML::description( __( 'You can style the optimized sitemap to give it a more personal look for your visitors. Search engines do not use these styles.', 'autodescription' ) );
319
  HTML::description( __( 'Note: Changes may not appear to have an effect directly because the stylesheet is cached in the browser for 30 minutes.', 'autodescription' ) );
320
  ?>
321
  <hr>
322
  <?php
323
+ HTML::header_title( __( 'Enable Styling', 'autodescription' ) );
324
 
325
  HTML::wrap_fields(
326
+ Input::make_checkbox( [
327
  'id' => 'sitemap_styles',
328
  'label' => esc_html__( 'Style sitemap?', 'autodescription' ) . ' ' . HTML::make_info( __( 'This makes the sitemap more readable for humans.', 'autodescription' ), '', false ),
329
  'escape' => false,
340
 
341
  ?>
342
  <p>
343
+ <label for="<?php Input::field_id( 'sitemap_color_main' ); ?>">
344
  <strong><?php esc_html_e( 'Sitemap Header Background Color', 'autodescription' ); ?></strong>
345
  </label>
346
  </p>
347
  <p>
348
+ <input type="text" name="<?php Input::field_name( 'sitemap_color_main' ); ?>" class="tsf-color-picker" id="<?php Input::field_id( 'sitemap_color_main' ); ?>" placeholder="<?php echo esc_attr( $default_colors['main'] ); ?>" value="<?php echo esc_attr( $current_colors['main'] ); ?>" data-tsf-default-color="<?php echo esc_attr( $default_colors['main'] ); ?>" />
349
  </p>
350
 
351
  <p>
352
+ <label for="<?php Input::field_id( 'sitemap_color_accent' ); ?>">
353
  <strong><?php esc_html_e( 'Sitemap Title and Lines Color', 'autodescription' ); ?></strong>
354
  </label>
355
  </p>
356
  <p>
357
+ <input type="text" name="<?php Input::field_name( 'sitemap_color_accent' ); ?>" class="tsf-color-picker" id="<?php Input::field_id( 'sitemap_color_accent' ); ?>" placeholder="<?php echo esc_attr( $default_colors['accent'] ); ?>" value="<?php echo esc_attr( $current_colors['accent'] ); ?>" data-tsf-default-color="<?php echo esc_attr( $default_colors['accent'] ); ?>" />
358
  </p>
359
 
360
  <hr>
361
  <?php
362
+ HTML::header_title( __( 'Header Title Logo', 'autodescription' ) );
363
 
364
  HTML::wrap_fields(
365
+ Input::make_checkbox( [
366
  'id' => 'sitemap_logo',
367
  'label' => __( 'Show logo next to sitemap header title?', 'autodescription' ),
368
  ] ),
380
  <strong><?php esc_html_e( 'Logo URL', 'autodescription' ); ?></strong>
381
  </label>
382
  </p>
383
+ <p class="hide-if-tsf-js attention"><?php esc_html_e( 'Setting a logo requires JavaScript.', 'autodescription' ); ?></p>
384
  <p>
385
+ <input class="large-text" type="url" readonly="readonly" data-readonly="1" name="<?php Input::field_name( 'sitemap_logo_url' ); ?>" id="sitemap_logo-url" placeholder="<?php echo esc_url( $logo_placeholder ); ?>" value="<?php echo esc_url( $this->get_option( 'sitemap_logo_url' ) ); ?>" />
386
+ <input type="hidden" name="<?php Input::field_name( 'sitemap_logo_id' ); ?>" id="sitemap_logo-id" value="<?php echo absint( $this->get_option( 'sitemap_logo_id' ) ); ?>" />
 
387
  </p>
388
  <p class="hide-if-no-tsf-js">
389
  <?php
inc/views/{admin/metaboxes/social-metabox.php → settings/metaboxes/social.php} RENAMED
@@ -9,57 +9,55 @@
9
 
10
  use The_SEO_Framework\Bridges\SeoSettings,
11
  The_SEO_Framework\Interpreters\HTML,
12
- The_SEO_Framework\Interpreters\Form;
 
13
 
14
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
15
 
16
- // Fetch the required instance within this file.
17
- $instance = $this->get_view_instance( 'the_seo_framework_social_metabox', $instance );
 
18
 
19
- switch ( $instance ) :
20
- case 'the_seo_framework_social_metabox_main':
21
- $default_tabs = [
22
  'general' => [
23
  'name' => __( 'General', 'autodescription' ),
24
- 'callback' => SeoSettings::class . '::_social_metabox_general_tab',
25
  'dashicon' => 'admin-generic',
26
  ],
27
  'facebook' => [
28
  'name' => 'Facebook',
29
- 'callback' => SeoSettings::class . '::_social_metabox_facebook_tab',
30
  'dashicon' => 'facebook-alt',
31
  ],
32
  'twitter' => [
33
  'name' => 'Twitter',
34
- 'callback' => SeoSettings::class . '::_social_metabox_twitter_tab',
35
  'dashicon' => 'twitter',
36
  ],
37
  'oembed' => [
38
  'name' => 'oEmbed',
39
- 'callback' => SeoSettings::class . '::_social_metabox_oembed_tab',
40
  'dashicon' => 'share-alt2',
41
  ],
42
  'postdates' => [
43
  'name' => __( 'Post Dates', 'autodescription' ),
44
- 'callback' => SeoSettings::class . '::_social_metabox_postdates_tab',
45
  'dashicon' => 'backup',
46
  ],
47
  ];
48
 
49
- /**
50
- * @since 2.2.2
51
- * @param array $defaults The default tabs.
52
- * @param array $args The args added on the callback.
53
- */
54
- $defaults = (array) apply_filters( 'the_seo_framework_social_settings_tabs', $default_tabs, $args );
55
-
56
- $tabs = wp_parse_args( $args, $defaults );
57
-
58
- SeoSettings::_nav_tab_wrapper( 'social', $tabs );
59
  break;
60
 
61
- case 'the_seo_framework_social_metabox_general':
62
- Form::header_title( __( 'Social Meta Tags Settings', 'autodescription' ) );
63
  HTML::description( __( 'Output various meta tags for social site integration, among other third-party services.', 'autodescription' ) );
64
 
65
  ?>
@@ -68,7 +66,7 @@ switch ( $instance ) :
68
 
69
  // Echo Open Graph Tags checkboxes.
70
  HTML::wrap_fields(
71
- Form::make_checkbox( [
72
  'id' => 'og_tags',
73
  'label' => __( 'Output Open Graph meta tags?', 'autodescription' ),
74
  'description' => __( 'Facebook, Twitter, Pinterest and many other social sites make use of these meta tags.', 'autodescription' ),
@@ -80,7 +78,7 @@ switch ( $instance ) :
80
 
81
  // Echo Facebook Tags checkbox.
82
  HTML::wrap_fields(
83
- Form::make_checkbox( [
84
  'id' => 'facebook_tags',
85
  'label' => __( 'Output Facebook meta tags?', 'autodescription' ),
86
  'description' => __( 'Output various meta tags targeted at Facebook.', 'autodescription' ),
@@ -90,7 +88,7 @@ switch ( $instance ) :
90
 
91
  // Echo Twitter Tags checkboxes.
92
  HTML::wrap_fields(
93
- Form::make_checkbox( [
94
  'id' => 'twitter_tags',
95
  'label' => __( 'Output Twitter meta tags?', 'autodescription' ),
96
  'description' => __( 'Output various meta tags targeted at Twitter.', 'autodescription' ),
@@ -102,7 +100,7 @@ switch ( $instance ) :
102
 
103
  // Echo oEmbed scripts checkboxes.
104
  HTML::wrap_fields(
105
- Form::make_checkbox( [
106
  'id' => 'oembed_scripts',
107
  'label' => __( 'Output oEmbed scripts?', 'autodescription' ),
108
  'description' => __( 'WordPress, Discord, Drupal, Squarespace, and many other clients can make use of these scripts.', 'autodescription' ),
@@ -112,7 +110,7 @@ switch ( $instance ) :
112
  ?>
113
  <hr>
114
  <?php
115
- Form::header_title( __( 'Social Title Settings', 'autodescription' ) );
116
  HTML::description( __( 'Most social sites and third-party services automatically include the website URL inside their embeds. When the site title is described well in the site URL, including it in the social title will be redundant.', 'autodescription' ) );
117
 
118
  $info = HTML::make_info(
@@ -122,9 +120,9 @@ switch ( $instance ) :
122
  );
123
 
124
  HTML::wrap_fields(
125
- Form::make_checkbox( [
126
  'id' => 'social_title_rem_additions',
127
- 'label' => esc_html__( 'Remove site title from generated social titles?', 'autodescription' ) . ' ' . $info,
128
  'escape' => false,
129
  ] ),
130
  true
@@ -132,11 +130,11 @@ switch ( $instance ) :
132
  ?>
133
  <hr>
134
  <?php
135
- Form::header_title( __( 'Social Image Settings', 'autodescription' ) );
136
  HTML::description( __( 'A social image can be displayed when your website is shared. It is a great way to grab attention.', 'autodescription' ) );
137
 
138
  HTML::wrap_fields(
139
- Form::make_checkbox( [
140
  'id' => 'multi_og_image',
141
  'label' => __( 'Output multiple Open Graph image tags?', 'autodescription' ),
142
  'description' => __( 'This enables users to select any image attached to the page shared on social networks, like Facebook.', 'autodescription' ),
@@ -151,8 +149,8 @@ switch ( $instance ) :
151
  </label>
152
  </p>
153
  <p>
154
- <input class="large-text" type="url" name="<?php Form::field_name( 'social_image_fb_url' ); ?>" id="tsf_fb_socialimage-url" value="<?php echo esc_url( $this->get_option( 'social_image_fb_url' ) ); ?>" />
155
- <input type="hidden" name="<?php Form::field_name( 'social_image_fb_id' ); ?>" id="tsf_fb_socialimage-id" value="<?php echo absint( $this->get_option( 'social_image_fb_id' ) ); ?>" disabled class="tsf-enable-media-if-js" />
156
  </p>
157
  <p class="hide-if-no-tsf-js">
158
  <?php
@@ -162,24 +160,24 @@ switch ( $instance ) :
162
  </p>
163
  <hr>
164
  <?php
165
- Form::header_title( __( 'Theme Color Settings', 'autodescription' ) );
166
  HTML::description( __( 'Discord styles embeds with the theme color. The theme color can also affect the tab-color in some browsers.', 'autodescription' ) );
167
  ?>
168
  <p>
169
- <label for="<?php Form::field_id( 'theme_color' ); ?>">
170
  <strong><?php esc_html_e( 'Theme Color', 'autodescription' ); ?></strong>
171
  </label>
172
  </p>
173
  <p>
174
- <input type="text" name="<?php Form::field_name( 'theme_color' ); ?>" class="tsf-color-picker" id="<?php Form::field_id( 'theme_color' ); ?>" value="<?php echo esc_attr( $this->get_option( 'theme_color' ) ); ?>" data-tsf-default-color="" />
175
  </p>
176
  <hr>
177
  <?php
178
- Form::header_title( __( 'Site Shortlink Settings', 'autodescription' ) );
179
  HTML::description( __( 'The shortlink tag can be manually used for microblogging services like Twitter, but it has no SEO value whatsoever.', 'autodescription' ) );
180
 
181
  HTML::wrap_fields(
182
- Form::make_checkbox( [
183
  'id' => 'shortlink_tag',
184
  'label' => __( 'Output shortlink tag?', 'autodescription' ),
185
  ] ),
@@ -187,7 +185,7 @@ switch ( $instance ) :
187
  );
188
  break;
189
 
190
- case 'the_seo_framework_social_metabox_facebook':
191
  $fb_author = $this->get_option( 'facebook_author' );
192
  $fb_author_placeholder = _x( 'https://www.facebook.com/YourPersonalProfile', 'Example Facebook Personal URL', 'autodescription' );
193
 
@@ -197,14 +195,14 @@ switch ( $instance ) :
197
  $fb_appid = $this->get_option( 'facebook_appid' );
198
  $fb_appid_placeholder = '123456789012345';
199
 
200
- Form::header_title( __( 'Facebook Integration Settings', 'autodescription' ) );
201
  HTML::description( __( 'Facebook post sharing works mostly through Open Graph. However, you can also link your Business and Personal Facebook pages, among various other options.', 'autodescription' ) );
202
  HTML::description( __( 'When these options are filled in, Facebook might link the Facebook profile to be followed and liked when your post or page is shared.', 'autodescription' ) );
203
  ?>
204
  <hr>
205
 
206
  <p>
207
- <label for="<?php Form::field_id( 'facebook_appid' ); ?>">
208
  <strong><?php esc_html_e( 'Facebook App ID', 'autodescription' ); ?></strong>
209
  <?php
210
  echo ' ';
@@ -216,11 +214,11 @@ switch ( $instance ) :
216
  </label>
217
  </p>
218
  <p>
219
- <input type="text" name="<?php Form::field_name( 'facebook_appid' ); ?>" class="large-text ltr" id="<?php Form::field_id( 'facebook_appid' ); ?>" placeholder="<?php echo esc_attr( $fb_appid_placeholder ); ?>" value="<?php echo esc_attr( $fb_appid ); ?>" />
220
  </p>
221
 
222
  <p>
223
- <label for="<?php Form::field_id( 'facebook_publisher' ); ?>">
224
  <strong><?php esc_html_e( 'Facebook Publisher page', 'autodescription' ); ?></strong>
225
  <?php
226
  echo ' ';
@@ -232,11 +230,11 @@ switch ( $instance ) :
232
  </label>
233
  </p>
234
  <p>
235
- <input type="url" name="<?php Form::field_name( 'facebook_publisher' ); ?>" class="large-text" id="<?php Form::field_id( 'facebook_publisher' ); ?>" placeholder="<?php echo esc_attr( $fb_publisher_placeholder ); ?>" value="<?php echo esc_attr( $fb_publisher ); ?>" />
236
  </p>
237
 
238
  <p>
239
- <label for="<?php Form::field_id( 'facebook_author' ); ?>">
240
  <strong><?php esc_html_e( 'Facebook Author Fallback Page', 'autodescription' ); ?></strong>
241
  <?php
242
  echo ' ';
@@ -249,12 +247,12 @@ switch ( $instance ) :
249
  </p>
250
  <?php HTML::description( __( 'Authors can override this option on their profile page.', 'autodescription' ) ); ?>
251
  <p>
252
- <input type="url" name="<?php Form::field_name( 'facebook_author' ); ?>" class="large-text" id="<?php Form::field_id( 'facebook_author' ); ?>" placeholder="<?php echo esc_attr( $fb_author_placeholder ); ?>" value="<?php echo esc_attr( $fb_author ); ?>" />
253
  </p>
254
  <?php
255
  break;
256
 
257
- case 'the_seo_framework_social_metabox_twitter':
258
  $tw_site = $this->get_option( 'twitter_site' );
259
  $tw_site_placeholder = _x( '@your-site-username', 'Twitter @username', 'autodescription' );
260
 
@@ -263,14 +261,14 @@ switch ( $instance ) :
263
 
264
  $twitter_card = $this->get_twitter_card_types();
265
 
266
- Form::header_title( __( 'Twitter Integration Settings', 'autodescription' ) );
267
  HTML::description( __( 'Twitter post sharing works mostly through Twitter Cards, and may fall back to use Open Graph. However, you can also link your Business and Personal Twitter pages, among various other options.', 'autodescription' ) );
268
 
269
  ?>
270
  <hr>
271
 
272
  <fieldset id="tsf-twitter-cards">
273
- <legend><?php Form::header_title( __( 'Twitter Card Type', 'autodescription' ) ); ?></legend>
274
  <?php
275
  HTML::description(
276
  __( 'The Twitter Card type may have the image highlighted, either small at the side or large above.', 'autodescription' )
@@ -282,15 +280,15 @@ switch ( $instance ) :
282
  foreach ( $twitter_card as $type => $name ) {
283
  ?>
284
  <span class="tsf-toblock">
285
- <input type="radio" name="<?php Form::field_name( 'twitter_card' ); ?>" id="<?php Form::field_id( 'twitter_card_' . $type ); ?>" value="<?php echo esc_attr( $type ); ?>" <?php checked( $this->get_option( 'twitter_card' ), $type ); ?> />
286
- <label for="<?php Form::field_id( 'twitter_card_' . $type ); ?>">
287
  <span>
288
  <?php
289
  echo HTML::code_wrap( $name ); // phpcs:ignore, WordPress.Security.EscapeOutput
290
  echo ' ';
291
  HTML::make_info(
292
  __( 'Learn more about this card.', 'autodescription' ),
293
- 'https://dev.twitter.com/cards/types/' . $name
294
  );
295
  ?>
296
  </span>
@@ -304,14 +302,14 @@ switch ( $instance ) :
304
 
305
  <hr>
306
  <?php
307
- Form::header_title( __( 'Card and Content Attribution', 'autodescription' ) );
308
  /* source: https://developer.twitter.com/en/docs/tweets/optimize-with-cards/guides/getting-started#attribution */
309
  HTML::description( __( 'Twitter claims users will be able to follow and view the profiles of attributed accounts directly from the card when these fields are filled in.', 'autodescription' ) );
310
  HTML::description( __( 'However, for now, these fields seem to have no discernible effect.', 'autodescription' ) );
311
  ?>
312
 
313
  <p>
314
- <label for="<?php Form::field_id( 'twitter_site' ); ?>" class="tsf-toblock">
315
  <strong><?php esc_html_e( 'Website Twitter Profile', 'autodescription' ); ?></strong>
316
  <?php
317
  echo ' ';
@@ -323,11 +321,11 @@ switch ( $instance ) :
323
  </label>
324
  </p>
325
  <p>
326
- <input type="text" name="<?php Form::field_name( 'twitter_site' ); ?>" class="large-text ltr" id="<?php Form::field_id( 'twitter_site' ); ?>" placeholder="<?php echo esc_attr( $tw_site_placeholder ); ?>" value="<?php echo esc_attr( $tw_site ); ?>" />
327
  </p>
328
 
329
  <p>
330
- <label for="<?php Form::field_id( 'twitter_creator' ); ?>" class="tsf-toblock">
331
  <strong><?php esc_html_e( 'Twitter Author Fallback Profile', 'autodescription' ); ?></strong>
332
  <?php
333
  echo ' ';
@@ -340,13 +338,13 @@ switch ( $instance ) :
340
  </p>
341
  <?php HTML::description( __( 'Authors can override this option on their profile page.', 'autodescription' ) ); ?>
342
  <p>
343
- <input type="text" name="<?php Form::field_name( 'twitter_creator' ); ?>" class="large-text ltr" id="<?php Form::field_id( 'twitter_creator' ); ?>" placeholder="<?php echo esc_attr( $tw_creator_placeholder ); ?>" value="<?php echo esc_attr( $tw_creator ); ?>" />
344
  </p>
345
  <?php
346
  break;
347
 
348
- case 'the_seo_framework_social_metabox_oembed':
349
- Form::header_title( __( 'oEmbed Settings', 'autodescription' ) );
350
  HTML::description( __( 'Some social sharing services and clients, like WordPress, LinkedIn, and Discord, obtain the linked page information via oEmbed.', 'autodescription' ) );
351
  ?>
352
  <hr>
@@ -354,7 +352,7 @@ switch ( $instance ) :
354
 
355
  // Split the wraps--the informational messages make for bad legibility otherwise.
356
  HTML::wrap_fields(
357
- Form::make_checkbox( [
358
  'id' => 'oembed_use_og_title',
359
  'label' => __( 'Use Open Graph title?', 'autodescription' ),
360
  'description' => __( 'Check this option if you want to replace page titles with Open Graph titles in embeds.', 'autodescription' ),
@@ -367,16 +365,16 @@ switch ( $instance ) :
367
  false
368
  );
369
  HTML::wrap_fields(
370
- Form::make_checkbox( [
371
  'id' => 'oembed_use_social_image',
372
- 'label' => esc_html__( 'Use social image?', 'autodescription' ) . ' ' . $_info,
373
  'description' => esc_html__( "LinkedIn displays the post's featured image in embeds. Check this option if you want to replace it with the social image.", 'autodescription' ),
374
  'escape' => false,
375
  ] ),
376
  true
377
  );
378
  HTML::wrap_fields(
379
- Form::make_checkbox( [
380
  'id' => 'oembed_remove_author',
381
  'label' => __( 'Remove author name?', 'autodescription' ),
382
  'description' => __( "Discord shows the page author's name above the sharing embed. Check this option if you find this undesirable.", 'autodescription' ),
@@ -385,10 +383,10 @@ switch ( $instance ) :
385
  );
386
 
387
  break;
388
- case 'the_seo_framework_social_metabox_postdates':
389
  $posts_i18n = esc_html__( 'Posts', 'autodescription' );
390
 
391
- Form::header_title( __( 'Post Date Settings', 'autodescription' ) );
392
  HTML::description( __( "Some social sites output the shared post's publishing and modified data in the sharing snippet.", 'autodescription' ) );
393
  ?>
394
  <hr>
@@ -396,7 +394,7 @@ switch ( $instance ) :
396
 
397
  HTML::wrap_fields(
398
  [
399
- Form::make_checkbox( [
400
  'id' => 'post_publish_time',
401
  'label' => $this->convert_markdown(
402
  /* translators: the backticks are Markdown! Preserve them as-is! */
@@ -405,7 +403,7 @@ switch ( $instance ) :
405
  ),
406
  'escape' => false,
407
  ] ),
408
- Form::make_checkbox( [
409
  'id' => 'post_modify_time',
410
  'label' => $this->convert_markdown(
411
  /* translators: the backticks are Markdown! Preserve them as-is! */
9
 
10
  use The_SEO_Framework\Bridges\SeoSettings,
11
  The_SEO_Framework\Interpreters\HTML,
12
+ The_SEO_Framework\Interpreters\Form,
13
+ The_SEO_Framework\Interpreters\Settings_Input as Input;
14
 
15
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
16
 
17
+ switch ( $this->get_view_instance( 'social', $instance ) ) :
18
+ case 'social_main':
19
+ $_settings_class = SeoSettings::class;
20
 
21
+ $tabs = [
 
 
22
  'general' => [
23
  'name' => __( 'General', 'autodescription' ),
24
+ 'callback' => [ $_settings_class, '_social_metabox_general_tab' ],
25
  'dashicon' => 'admin-generic',
26
  ],
27
  'facebook' => [
28
  'name' => 'Facebook',
29
+ 'callback' => [ $_settings_class, '_social_metabox_facebook_tab' ],
30
  'dashicon' => 'facebook-alt',
31
  ],
32
  'twitter' => [
33
  'name' => 'Twitter',
34
+ 'callback' => [ $_settings_class, '_social_metabox_twitter_tab' ],
35
  'dashicon' => 'twitter',
36
  ],
37
  'oembed' => [
38
  'name' => 'oEmbed',
39
+ 'callback' => [ $_settings_class, '_social_metabox_oembed_tab' ],
40
  'dashicon' => 'share-alt2',
41
  ],
42
  'postdates' => [
43
  'name' => __( 'Post Dates', 'autodescription' ),
44
+ 'callback' => [ $_settings_class, '_social_metabox_postdates_tab' ],
45
  'dashicon' => 'backup',
46
  ],
47
  ];
48
 
49
+ SeoSettings::_nav_tab_wrapper(
50
+ 'social',
51
+ /**
52
+ * @since 2.2.2
53
+ * @param array $defaults The default tabs.
54
+ */
55
+ (array) apply_filters( 'the_seo_framework_social_settings_tabs', $tabs )
56
+ );
 
 
57
  break;
58
 
59
+ case 'social_general_tab':
60
+ HTML::header_title( __( 'Social Meta Tags Settings', 'autodescription' ) );
61
  HTML::description( __( 'Output various meta tags for social site integration, among other third-party services.', 'autodescription' ) );
62
 
63
  ?>
66
 
67
  // Echo Open Graph Tags checkboxes.
68
  HTML::wrap_fields(
69
+ Input::make_checkbox( [
70
  'id' => 'og_tags',
71
  'label' => __( 'Output Open Graph meta tags?', 'autodescription' ),
72
  'description' => __( 'Facebook, Twitter, Pinterest and many other social sites make use of these meta tags.', 'autodescription' ),
78
 
79
  // Echo Facebook Tags checkbox.
80
  HTML::wrap_fields(
81
+ Input::make_checkbox( [
82
  'id' => 'facebook_tags',
83
  'label' => __( 'Output Facebook meta tags?', 'autodescription' ),
84
  'description' => __( 'Output various meta tags targeted at Facebook.', 'autodescription' ),
88
 
89
  // Echo Twitter Tags checkboxes.
90
  HTML::wrap_fields(
91
+ Input::make_checkbox( [
92
  'id' => 'twitter_tags',
93
  'label' => __( 'Output Twitter meta tags?', 'autodescription' ),
94
  'description' => __( 'Output various meta tags targeted at Twitter.', 'autodescription' ),
100
 
101
  // Echo oEmbed scripts checkboxes.
102
  HTML::wrap_fields(
103
+ Input::make_checkbox( [
104
  'id' => 'oembed_scripts',
105
  'label' => __( 'Output oEmbed scripts?', 'autodescription' ),
106
  'description' => __( 'WordPress, Discord, Drupal, Squarespace, and many other clients can make use of these scripts.', 'autodescription' ),
110
  ?>
111
  <hr>
112
  <?php
113
+ HTML::header_title( __( 'Social Title Settings', 'autodescription' ) );
114
  HTML::description( __( 'Most social sites and third-party services automatically include the website URL inside their embeds. When the site title is described well in the site URL, including it in the social title will be redundant.', 'autodescription' ) );
115
 
116
  $info = HTML::make_info(
120
  );
121
 
122
  HTML::wrap_fields(
123
+ Input::make_checkbox( [
124
  'id' => 'social_title_rem_additions',
125
+ 'label' => esc_html__( 'Remove site title from generated social titles?', 'autodescription' ) . " $info",
126
  'escape' => false,
127
  ] ),
128
  true
130
  ?>
131
  <hr>
132
  <?php
133
+ HTML::header_title( __( 'Social Image Settings', 'autodescription' ) );
134
  HTML::description( __( 'A social image can be displayed when your website is shared. It is a great way to grab attention.', 'autodescription' ) );
135
 
136
  HTML::wrap_fields(
137
+ Input::make_checkbox( [
138
  'id' => 'multi_og_image',
139
  'label' => __( 'Output multiple Open Graph image tags?', 'autodescription' ),
140
  'description' => __( 'This enables users to select any image attached to the page shared on social networks, like Facebook.', 'autodescription' ),
149
  </label>
150
  </p>
151
  <p>
152
+ <input class="large-text" type="url" name="<?php Input::field_name( 'social_image_fb_url' ); ?>" id="tsf_fb_socialimage-url" value="<?php echo esc_url( $this->get_option( 'social_image_fb_url' ) ); ?>" />
153
+ <input type="hidden" name="<?php Input::field_name( 'social_image_fb_id' ); ?>" id="tsf_fb_socialimage-id" value="<?php echo absint( $this->get_option( 'social_image_fb_id' ) ); ?>" disabled class="tsf-enable-media-if-js" />
154
  </p>
155
  <p class="hide-if-no-tsf-js">
156
  <?php
160
  </p>
161
  <hr>
162
  <?php
163
+ HTML::header_title( __( 'Theme Color Settings', 'autodescription' ) );
164
  HTML::description( __( 'Discord styles embeds with the theme color. The theme color can also affect the tab-color in some browsers.', 'autodescription' ) );
165
  ?>
166
  <p>
167
+ <label for="<?php Input::field_id( 'theme_color' ); ?>">
168
  <strong><?php esc_html_e( 'Theme Color', 'autodescription' ); ?></strong>
169
  </label>
170
  </p>
171
  <p>
172
+ <input type="text" name="<?php Input::field_name( 'theme_color' ); ?>" class="tsf-color-picker" id="<?php Input::field_id( 'theme_color' ); ?>" value="<?php echo esc_attr( $this->get_option( 'theme_color' ) ); ?>" data-tsf-default-color="" />
173
  </p>
174
  <hr>
175
  <?php
176
+ HTML::header_title( __( 'Site Shortlink Settings', 'autodescription' ) );
177
  HTML::description( __( 'The shortlink tag can be manually used for microblogging services like Twitter, but it has no SEO value whatsoever.', 'autodescription' ) );
178
 
179
  HTML::wrap_fields(
180
+ Input::make_checkbox( [
181
  'id' => 'shortlink_tag',
182
  'label' => __( 'Output shortlink tag?', 'autodescription' ),
183
  ] ),
185
  );
186
  break;
187
 
188
+ case 'social_facebook_tab':
189
  $fb_author = $this->get_option( 'facebook_author' );
190
  $fb_author_placeholder = _x( 'https://www.facebook.com/YourPersonalProfile', 'Example Facebook Personal URL', 'autodescription' );
191
 
195
  $fb_appid = $this->get_option( 'facebook_appid' );
196
  $fb_appid_placeholder = '123456789012345';
197
 
198
+ HTML::header_title( __( 'Facebook Integration Settings', 'autodescription' ) );
199
  HTML::description( __( 'Facebook post sharing works mostly through Open Graph. However, you can also link your Business and Personal Facebook pages, among various other options.', 'autodescription' ) );
200
  HTML::description( __( 'When these options are filled in, Facebook might link the Facebook profile to be followed and liked when your post or page is shared.', 'autodescription' ) );
201
  ?>
202
  <hr>
203
 
204
  <p>
205
+ <label for="<?php Input::field_id( 'facebook_appid' ); ?>">
206
  <strong><?php esc_html_e( 'Facebook App ID', 'autodescription' ); ?></strong>
207
  <?php
208
  echo ' ';
214
  </label>
215
  </p>
216
  <p>
217
+ <input type="text" name="<?php Input::field_name( 'facebook_appid' ); ?>" class="large-text ltr" id="<?php Input::field_id( 'facebook_appid' ); ?>" placeholder="<?php echo esc_attr( $fb_appid_placeholder ); ?>" value="<?php echo esc_attr( $fb_appid ); ?>" />
218
  </p>
219
 
220
  <p>
221
+ <label for="<?php Input::field_id( 'facebook_publisher' ); ?>">
222
  <strong><?php esc_html_e( 'Facebook Publisher page', 'autodescription' ); ?></strong>
223
  <?php
224
  echo ' ';
230
  </label>
231
  </p>
232
  <p>
233
+ <input type="url" name="<?php Input::field_name( 'facebook_publisher' ); ?>" class="large-text" id="<?php Input::field_id( 'facebook_publisher' ); ?>" placeholder="<?php echo esc_attr( $fb_publisher_placeholder ); ?>" value="<?php echo esc_attr( $fb_publisher ); ?>" />
234
  </p>
235
 
236
  <p>
237
+ <label for="<?php Input::field_id( 'facebook_author' ); ?>">
238
  <strong><?php esc_html_e( 'Facebook Author Fallback Page', 'autodescription' ); ?></strong>
239
  <?php
240
  echo ' ';
247
  </p>
248
  <?php HTML::description( __( 'Authors can override this option on their profile page.', 'autodescription' ) ); ?>
249
  <p>
250
+ <input type="url" name="<?php Input::field_name( 'facebook_author' ); ?>" class="large-text" id="<?php Input::field_id( 'facebook_author' ); ?>" placeholder="<?php echo esc_attr( $fb_author_placeholder ); ?>" value="<?php echo esc_attr( $fb_author ); ?>" />
251
  </p>
252
  <?php
253
  break;
254
 
255
+ case 'social_twitter_tab':
256
  $tw_site = $this->get_option( 'twitter_site' );
257
  $tw_site_placeholder = _x( '@your-site-username', 'Twitter @username', 'autodescription' );
258
 
261
 
262
  $twitter_card = $this->get_twitter_card_types();
263
 
264
+ HTML::header_title( __( 'Twitter Integration Settings', 'autodescription' ) );
265
  HTML::description( __( 'Twitter post sharing works mostly through Twitter Cards, and may fall back to use Open Graph. However, you can also link your Business and Personal Twitter pages, among various other options.', 'autodescription' ) );
266
 
267
  ?>
268
  <hr>
269
 
270
  <fieldset id="tsf-twitter-cards">
271
+ <legend><?php HTML::header_title( __( 'Twitter Card Type', 'autodescription' ) ); ?></legend>
272
  <?php
273
  HTML::description(
274
  __( 'The Twitter Card type may have the image highlighted, either small at the side or large above.', 'autodescription' )
280
  foreach ( $twitter_card as $type => $name ) {
281
  ?>
282
  <span class="tsf-toblock">
283
+ <input type="radio" name="<?php Input::field_name( 'twitter_card' ); ?>" id="<?php Input::field_id( "twitter_card_{$type}" ); ?>" value="<?php echo esc_attr( $type ); ?>" <?php checked( $this->get_option( 'twitter_card' ), $type ); ?> />
284
+ <label for="<?php Input::field_id( "twitter_card_{$type}" ); ?>">
285
  <span>
286
  <?php
287
  echo HTML::code_wrap( $name ); // phpcs:ignore, WordPress.Security.EscapeOutput
288
  echo ' ';
289
  HTML::make_info(
290
  __( 'Learn more about this card.', 'autodescription' ),
291
+ "https://dev.twitter.com/cards/types/$name"
292
  );
293
  ?>
294
  </span>
302
 
303
  <hr>
304
  <?php
305
+ HTML::header_title( __( 'Card and Content Attribution', 'autodescription' ) );
306
  /* source: https://developer.twitter.com/en/docs/tweets/optimize-with-cards/guides/getting-started#attribution */
307
  HTML::description( __( 'Twitter claims users will be able to follow and view the profiles of attributed accounts directly from the card when these fields are filled in.', 'autodescription' ) );
308
  HTML::description( __( 'However, for now, these fields seem to have no discernible effect.', 'autodescription' ) );
309
  ?>
310
 
311
  <p>
312
+ <label for="<?php Input::field_id( 'twitter_site' ); ?>" class="tsf-toblock">
313
  <strong><?php esc_html_e( 'Website Twitter Profile', 'autodescription' ); ?></strong>
314
  <?php
315
  echo ' ';
321
  </label>
322
  </p>
323
  <p>
324
+ <input type="text" name="<?php Input::field_name( 'twitter_site' ); ?>" class="large-text ltr" id="<?php Input::field_id( 'twitter_site' ); ?>" placeholder="<?php echo esc_attr( $tw_site_placeholder ); ?>" value="<?php echo esc_attr( $tw_site ); ?>" />
325
  </p>
326
 
327
  <p>
328
+ <label for="<?php Input::field_id( 'twitter_creator' ); ?>" class="tsf-toblock">
329
  <strong><?php esc_html_e( 'Twitter Author Fallback Profile', 'autodescription' ); ?></strong>
330
  <?php
331
  echo ' ';
338
  </p>
339
  <?php HTML::description( __( 'Authors can override this option on their profile page.', 'autodescription' ) ); ?>
340
  <p>
341
+ <input type="text" name="<?php Input::field_name( 'twitter_creator' ); ?>" class="large-text ltr" id="<?php Input::field_id( 'twitter_creator' ); ?>" placeholder="<?php echo esc_attr( $tw_creator_placeholder ); ?>" value="<?php echo esc_attr( $tw_creator ); ?>" />
342
  </p>
343
  <?php
344
  break;
345
 
346
+ case 'social_oembed_tab':
347
+ HTML::header_title( __( 'oEmbed Settings', 'autodescription' ) );
348
  HTML::description( __( 'Some social sharing services and clients, like WordPress, LinkedIn, and Discord, obtain the linked page information via oEmbed.', 'autodescription' ) );
349
  ?>
350
  <hr>
352
 
353
  // Split the wraps--the informational messages make for bad legibility otherwise.
354
  HTML::wrap_fields(
355
+ Input::make_checkbox( [
356
  'id' => 'oembed_use_og_title',
357
  'label' => __( 'Use Open Graph title?', 'autodescription' ),
358
  'description' => __( 'Check this option if you want to replace page titles with Open Graph titles in embeds.', 'autodescription' ),
365
  false
366
  );
367
  HTML::wrap_fields(
368
+ Input::make_checkbox( [
369
  'id' => 'oembed_use_social_image',
370
+ 'label' => esc_html__( 'Use social image?', 'autodescription' ) . " $_info",
371
  'description' => esc_html__( "LinkedIn displays the post's featured image in embeds. Check this option if you want to replace it with the social image.", 'autodescription' ),
372
  'escape' => false,
373
  ] ),
374
  true
375
  );
376
  HTML::wrap_fields(
377
+ Input::make_checkbox( [
378
  'id' => 'oembed_remove_author',
379
  'label' => __( 'Remove author name?', 'autodescription' ),
380
  'description' => __( "Discord shows the page author's name above the sharing embed. Check this option if you find this undesirable.", 'autodescription' ),
383
  );
384
 
385
  break;
386
+ case 'social_postdates_tab':
387
  $posts_i18n = esc_html__( 'Posts', 'autodescription' );
388
 
389
+ HTML::header_title( __( 'Post Date Settings', 'autodescription' ) );
390
  HTML::description( __( "Some social sites output the shared post's publishing and modified data in the sharing snippet.", 'autodescription' ) );
391
  ?>
392
  <hr>
394
 
395
  HTML::wrap_fields(
396
  [
397
+ Input::make_checkbox( [
398
  'id' => 'post_publish_time',
399
  'label' => $this->convert_markdown(
400
  /* translators: the backticks are Markdown! Preserve them as-is! */
403
  ),
404
  'escape' => false,
405
  ] ),
406
+ Input::make_checkbox( [
407
  'id' => 'post_modify_time',
408
  'label' => $this->convert_markdown(
409
  /* translators: the backticks are Markdown! Preserve them as-is! */
inc/views/{admin/metaboxes/title-metabox.php → settings/metaboxes/title.php} RENAMED
@@ -9,21 +9,17 @@
9
 
10
  use The_SEO_Framework\Bridges\SeoSettings,
11
  The_SEO_Framework\Interpreters\HTML,
12
- The_SEO_Framework\Interpreters\Form;
13
 
14
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and the_seo_framework()->_verify_include_secret( $_secret ) or die;
15
 
16
- // Fetch the required instance within this file.
17
- $instance = $this->get_view_instance( 'the_seo_framework_title_metabox', $instance );
18
-
19
- switch ( $instance ) :
20
- case 'the_seo_framework_title_metabox_main':
21
- $blogname = $this->get_blogname();
22
  $sep = esc_html( $this->get_separator( 'title' ) );
23
- $showleft = 'left' === $this->get_option( 'title_location' );
24
 
25
- $additions_left = '<span class=tsf-title-additions-js>' . $blogname . '<span class=tsf-sep-js>' . " $sep " . '</span></span>';
26
- $additions_right = '<span class=tsf-title-additions-js><span class=tsf-sep-js>' . " $sep " . '</span>' . $blogname . '</span>';
27
 
28
  $latest_post_id = $this->get_latest_post_id();
29
  $latest_cat_id = $this->get_latest_category_id();
@@ -32,56 +28,78 @@ switch ( $instance ) :
32
  $post_name = strip_tags( get_the_title( $latest_post_id ) ) ?: __( 'Example Post', 'autodescription' );
33
  $post_title = $this->s_title( $this->hellip_if_over( $post_name, 60 ) );
34
 
35
- // phpcs:ignore, WordPress.WP.AlternativeFunctions.strip_tags_strip_tags -- We don't expect users to set scripts in titles.
36
- $cat_name = strip_tags( get_cat_name( $latest_cat_id ) ?: __( 'Example Category', 'autodescription' ) );
37
- $cat_prefix = $this->s_title( $this->get_tax_type_label( 'category', true ) ?: __( 'Category', 'default' ) );
38
- $tax_title = sprintf(
39
- '<span class=tsf-title-prefix-example style=display:%s>%s: </span> %s', // TODO RTL?
40
- $this->get_option( 'title_rem_prefixes' ) ? 'none' : 'inline',
 
 
 
41
  $cat_prefix,
42
- $this->s_title( $this->hellip_if_over( $cat_name, 60 - strlen( $cat_prefix ) ) )
43
  );
44
 
45
- $example_post_left = '<em>' . $additions_left . $post_name . '</em>';
46
- $example_post_right = '<em>' . $post_name . $additions_right . '</em>';
47
- $example_tax_left = '<em>' . $additions_left . $tax_title . '</em>';
48
- $example_tax_right = '<em>' . $tax_title . $additions_right . '</em>';
 
 
49
 
50
- Form::header_title( __( 'Automated Title Settings', 'autodescription' ) );
9
 
10
  use The_SEO_Framework\Bridges\SeoSettings,
11
  The_SEO_Framework\Interpreters\HTML,
12
+ The_SEO_Framework\Interpreters\Settings_Input as Input;
13
 
14
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secret ) or die;
15
 
16
+ switch ( $this->get_view_instance( 'title', $instance ) ) :
17
+ case 'title_main':
18
+ $blogname = esc_html( $this->get_blogname() );
 
 
 
19
  $sep = esc_html( $this->get_separator( 'title' ) );
 
20
 
21
+ $additions_left = "<span class=tsf-title-additions-js><span class=tsf-site-title-js>$blogname</span><span class=tsf-sep-js> $sep </span></span>";
22
+ $additions_right = "<span class=tsf-title-additions-js><span class=tsf-sep-js> $sep </span><span class=tsf-site-title-js>$blogname</span></span>";
23
 
24
  $latest_post_id = $this->get_latest_post_id();
25
  $latest_cat_id = $this->get_latest_category_id();
28
  $post_name = strip_tags( get_the_title( $latest_post_id ) ) ?: __( 'Example Post', 'autodescription' );
29
  $post_title = $this->s_title( $this->hellip_if_over( $post_name, 60 ) );
30
 
31
+ $cat_prefix = $this->s_title( _x( 'Category:', 'category archive title prefix', 'default' ) );
32
+ $cat_title = $this->s_title( $this->hellip_if_over(
33
+ // phpcs:ignore, WordPress.WP.AlternativeFunctions.strip_tags_strip_tags -- We don't expect users to set scripts in titles.
34
+ strip_tags( get_cat_name( $latest_cat_id ) ?: __( 'Example Category', 'autodescription' ) ),
35
+ 60 - strlen( $cat_prefix )
36
+ ) );
37
+ $cat_title_full = sprintf(
38
+ /* translators: 1: Title prefix. 2: Title. */
39
+ _x( '%1$s %2$s', 'archive title', 'default' ),
40
  $cat_prefix,
41
+ $cat_title
42
  );
43
 
44
+ $example_post_left = "<em>{$additions_left}{$post_name}</em>";
45
+ $example_post_right = "<em>{$post_name}{$additions_right}</em>";
46
+ $example_tax_left_full = "<em>{$additions_left}{$cat_title_full}</em>";
47
+ $example_tax_right_full = "<em>{$cat_title_full}{$additions_right}</em>";
48
+ $example_tax_left = "<em>{$additions_left}{$cat_title}</em>";
49
+ $example_tax_right = "<em>{$cat_title}{$additions_right}</em>";
50
 
51
+ HTML::header_title( __( 'Automated Title Settings', 'autodescription' ) )